aboutsummaryrefslogtreecommitdiffstats
path: root/mpm/python/usrp_mpm/dboard_manager
diff options
context:
space:
mode:
Diffstat (limited to 'mpm/python/usrp_mpm/dboard_manager')
-rw-r--r--mpm/python/usrp_mpm/dboard_manager/CMakeLists.txt5
-rw-r--r--mpm/python/usrp_mpm/dboard_manager/__init__.py5
-rw-r--r--mpm/python/usrp_mpm/dboard_manager/base.py37
-rw-r--r--[-rwxr-xr-x]mpm/python/usrp_mpm/dboard_manager/dboard_iface.py68
-rw-r--r--mpm/python/usrp_mpm/dboard_manager/empty_slot.py37
-rw-r--r--mpm/python/usrp_mpm/dboard_manager/x4xx_db_iface.py144
-rw-r--r--mpm/python/usrp_mpm/dboard_manager/x4xx_debug_db.py152
-rw-r--r--mpm/python/usrp_mpm/dboard_manager/x4xx_if_test_cca.py167
-rw-r--r--mpm/python/usrp_mpm/dboard_manager/zbx.py461
-rw-r--r--mpm/python/usrp_mpm/dboard_manager/zbx_update_cpld.py218
10 files changed, 1271 insertions, 23 deletions
diff --git a/mpm/python/usrp_mpm/dboard_manager/CMakeLists.txt b/mpm/python/usrp_mpm/dboard_manager/CMakeLists.txt
index dfac467d0..b01d220df 100644
--- a/mpm/python/usrp_mpm/dboard_manager/CMakeLists.txt
+++ b/mpm/python/usrp_mpm/dboard_manager/CMakeLists.txt
@@ -28,9 +28,14 @@ set(USRP_MPM_DBMGR_FILES
${CMAKE_CURRENT_SOURCE_DIR}/magnesium_update_cpld.py
${CMAKE_CURRENT_SOURCE_DIR}/mg_init.py
${CMAKE_CURRENT_SOURCE_DIR}/mg_periphs.py
+ ${CMAKE_CURRENT_SOURCE_DIR}/zbx.py
${CMAKE_CURRENT_SOURCE_DIR}/test.py
${CMAKE_CURRENT_SOURCE_DIR}/unknown.py
${CMAKE_CURRENT_SOURCE_DIR}/dboard_iface.py
+ ${CMAKE_CURRENT_SOURCE_DIR}/x4xx_db_iface.py
+ ${CMAKE_CURRENT_SOURCE_DIR}/zbx_update_cpld.py
+ ${CMAKE_CURRENT_SOURCE_DIR}/x4xx_debug_db.py
+ ${CMAKE_CURRENT_SOURCE_DIR}/x4xx_if_test_cca.py
)
list(APPEND USRP_MPM_FILES ${USRP_MPM_DBMGR_FILES})
set(USRP_MPM_FILES ${USRP_MPM_FILES} PARENT_SCOPE)
diff --git a/mpm/python/usrp_mpm/dboard_manager/__init__.py b/mpm/python/usrp_mpm/dboard_manager/__init__.py
index 58262025e..28a8ef80b 100644
--- a/mpm/python/usrp_mpm/dboard_manager/__init__.py
+++ b/mpm/python/usrp_mpm/dboard_manager/__init__.py
@@ -14,6 +14,11 @@ if not __simulated__:
from .neon import Neon
from .e31x_db import E31x_db
from .eiscat import EISCAT
+ from .empty_slot import EmptySlot
+ from .zbx import ZBX
from .test import test
from .unknown import unknown
from .dboard_iface import DboardIface
+ from .x4xx_db_iface import X4xxDboardIface
+ from .x4xx_debug_db import X4xxDebugDboard
+ from .x4xx_if_test_cca import X4xxIfTestCCA
diff --git a/mpm/python/usrp_mpm/dboard_manager/base.py b/mpm/python/usrp_mpm/dboard_manager/base.py
index be37a6264..978f1c5ae 100644
--- a/mpm/python/usrp_mpm/dboard_manager/base.py
+++ b/mpm/python/usrp_mpm/dboard_manager/base.py
@@ -24,6 +24,9 @@ class DboardManagerBase(object):
# Very important: A list of PIDs that apply to the current device. Must be
# list, even if there's only one entry.
pids = []
+ # tuple of id and name of the first revision,
+ # id and name of revisions are consecutive (2, B), (3, C), ...
+ first_revision = (1, 'A')
# See PeriphManager.mboard_sensor_callback_map for a description.
rx_sensor_callback_map = {}
# See PeriphManager.mboard_sensor_callback_map for a description.
@@ -91,6 +94,13 @@ class DboardManagerBase(object):
"""
self.log.debug("deinit() called, but not implemented.")
+ def tear_down(self):
+ """
+ Tear down all members that need to be specially handled before
+ deconstruction.
+ """
+ pass
+
def get_serial(self):
"""
Return this daughterboard's serial number as a string. Will return an
@@ -98,6 +108,33 @@ class DboardManagerBase(object):
"""
return self.device_info.get("serial", "")
+ def get_revision(self):
+ """
+ Return this daughterboard's revision number as integer. Will return
+ -1 if no revision can be found or revision is not an integer
+ """
+ try:
+ return int(self.device_info.get('rev', '-1'))
+ except ValueError:
+ return -1
+
+ def get_revision_string(self):
+ """
+ Converts revision number to string.
+ """
+ return chr(ord(self.first_revision[1])
+ + self.get_revision()
+ - self.first_revision[0])
+
+ ##########################################################################
+ # Clocking
+ ##########################################################################
+ def reset_clock(self, value):
+ """
+ Called when the motherboard is reconfiguring its clocks.
+ """
+ pass
+
def update_ref_clock_freq(self, freq, **kwargs):
"""
Call this function if the frequency of the reference clock changes.
diff --git a/mpm/python/usrp_mpm/dboard_manager/dboard_iface.py b/mpm/python/usrp_mpm/dboard_manager/dboard_iface.py
index 87bff846b..e100b02a2 100755..100644
--- a/mpm/python/usrp_mpm/dboard_manager/dboard_iface.py
+++ b/mpm/python/usrp_mpm/dboard_manager/dboard_iface.py
@@ -28,6 +28,32 @@ class DboardIface(object):
if hasattr(self.mboard, 'log'):
self.log = self.mboard.log.getChild("DboardIface")
+ def tear_down(self):
+ """
+ Tear down all members that need to be specially handled before
+ deconstruction.
+ """
+ # The mboard object is the periph_manager that has the dboard
+ # that in turn has this DboardIface. Breaking the reference
+ # cycle will make garbage collection easier.
+ self.mboard = None
+
+ ####################################################################
+ # Power
+ # Enable and disable the DB's power rails
+ ####################################################################
+ def enable_daughterboard(self, enable = True):
+ """
+ Enable or disable the daughterboard.
+ """
+ raise NotImplementedError('DboardIface::enable_daughterboard() not supported!')
+
+ def check_enable_daughterboard(self):
+ """
+ Return the enable state of the daughterboard.
+ """
+ raise NotImplementedError('DboardIface::check_enable_daughterboard() not supported!')
+
####################################################################
# CTRL SPI
# CTRL SPI lines are connected to the CPLD of the DB if it exists
@@ -42,18 +68,6 @@ class DboardIface(object):
raise NotImplementedError('DboardIface::ctrl_spi_reset() not supported!')
####################################################################
- # GPIO
- # GPIO lines are used for high speed control of the DB
- ####################################################################
- def get_high_speed_gpio_ctrl_core(self):
- """
- Return a GpioAtrCore4000 instance that controls the GPIO lines
- interfacing the MB and DB
- """
- raise NotImplementedError('DboardIface::get_high_speed_gpio_ctrl_core()'
- ' not supported!')
-
- ####################################################################
# Management Bus
####################################################################
@@ -82,20 +96,28 @@ class DboardIface(object):
"""
raise NotImplementedError('DboardIface::set_if_freq() not supported!')
+ def get_if_freq(self, direction, channel):
+ """
+ Gets the IF frequency of the ADC/DAC corresponding
+ to the specified channel of the DB.
+ """
+ raise NotImplementedError('DboardIface::get_if_freq() not supported!')
+
+ def enable_iq_swap(self, enable, direction, channel):
+ """
+ Enable or disable swap of I and Q samples from the RFDCs.
+ """
+ raise NotImplementedError('DboardIface::enable_iq_swap() not supported!')
+
+ def get_sample_rate(self):
+ """
+ Gets the sample rate of the RFDCs.
+ """
+ raise NotImplementedError('DboardIface::get_sample_rate() not supported!')
+
def get_prc_rate(self):
"""
Returns the rate of the PLL Reference Clock (PRC) which is
routed to the daughterboard.
"""
raise NotImplementedError('DboardIface::get_pll_ref_clock() not supported!')
-
- ####################################################################
- # SPCC MPCC Control
- ####################################################################
- def get_protocol_cores(self):
- """
- Returns all discovered protocols in SPCC and MPCC blocks on the
- Daughterboard's CPLD in the form of SpiCore4000, I2cCore4000,
- UartCore4000, and GpioAtrCore4000
- """
- raise NotImplementedError('DboardIface::get_protocol_cores() not supported!')
diff --git a/mpm/python/usrp_mpm/dboard_manager/empty_slot.py b/mpm/python/usrp_mpm/dboard_manager/empty_slot.py
new file mode 100644
index 000000000..997d3ac6a
--- /dev/null
+++ b/mpm/python/usrp_mpm/dboard_manager/empty_slot.py
@@ -0,0 +1,37 @@
+#
+# Copyright 2021 Ettus Research, a National Instruments Brand
+#
+# SPDX-License-Identifier: GPL-3.0-or-later
+#
+"""
+Dummy daughterboard class for empty slots
+"""
+from usrp_mpm.dboard_manager import DboardManagerBase
+from usrp_mpm.mpmlog import get_logger
+
+class EmptySlot(DboardManagerBase):
+ """
+ DboardManager class for when a slot is empty
+ """
+ #########################################################################
+ # Overridables
+ #
+ # See DboardManagerBase for documentation on these fields
+ #########################################################################
+ pids = [0x0]
+ ### End of overridables #################################################
+
+ def __init__(self, slot_idx, **kwargs):
+ DboardManagerBase.__init__(self, slot_idx, **kwargs)
+ self.log = get_logger("EmptyDB-{}".format(slot_idx))
+ self.log.trace("Initializing Empty dboard, slot index %d",
+ self.slot_idx)
+
+ def init(self, args):
+ """
+ Execute necessary init dance to bring up dboard
+ """
+ self.log.debug("init() called with args `{}'".format(
+ ",".join(['{}={}'.format(x, args[x]) for x in args])
+ ))
+ return True
diff --git a/mpm/python/usrp_mpm/dboard_manager/x4xx_db_iface.py b/mpm/python/usrp_mpm/dboard_manager/x4xx_db_iface.py
new file mode 100644
index 000000000..d89236859
--- /dev/null
+++ b/mpm/python/usrp_mpm/dboard_manager/x4xx_db_iface.py
@@ -0,0 +1,144 @@
+#
+# Copyright 2019 Ettus Research, a National Instruments Brand
+#
+# SPDX-License-Identifier: GPL-3.0-or-later
+#
+
+from usrp_mpm.sys_utils.db_flash import DBFlash
+from usrp_mpm.sys_utils.gpio import Gpio
+from usrp_mpm.dboard_manager import DboardIface
+from usrp_mpm import lib # Pulls in everything from C++-land
+
+class X4xxDboardIface(DboardIface):
+ """
+ X4xx DboardIface implementation
+
+ slot_idx - The numerical ID of the daughterboard slot using this
+ interface (e.g. 0, 1)
+ motherboard - The instance of the motherboard class which implements
+ these controls
+ """
+ # The device tree label for the bus to the DB's Management EEPROM
+ MGMT_EEPROM_DEVICE_LABEL = "e0004000.i2c"
+
+ def __init__(self, slot_idx, motherboard):
+ super().__init__(slot_idx, motherboard)
+ self.db_cpld_iface = motherboard.ctrlport_regs.get_db_cpld_iface(self.slot_idx)
+ self._power_enable = Gpio('DB{}_PWR_EN'.format(slot_idx), Gpio.OUTPUT)
+ self._power_status = Gpio('DB{}_PWR_STATUS'.format(slot_idx), Gpio.INPUT)
+
+ self.db_flash = DBFlash(slot_idx, log=self.log)
+
+ def tear_down(self):
+ self.log.trace("Tearing down X4xx daughterboard...")
+ if self.db_flash:
+ self.db_flash.deinit()
+ super().tear_down()
+
+ ####################################################################
+ # Power
+ # Enable and disable the DB's power rails
+ ####################################################################
+ def enable_daughterboard(self, enable=True):
+ """
+ Enable or disable the daughterboard.
+ """
+ if self.db_flash and not enable:
+ self.db_flash.deinit()
+ self._power_enable.set(enable)
+ self.mboard.cpld_control.enable_daughterboard(self.slot_idx, enable)
+ if self.db_flash and enable:
+ self.db_flash.init()
+
+ def check_enable_daughterboard(self):
+ """
+ Return the enable state of the daughterboard.
+ """
+ return self._power_status.get()
+
+ ####################################################################
+ # CTRL SPI
+ # CTRL SPI lines are connected to the CPLD of the DB if it exists
+ ####################################################################
+ def peek_db_cpld(self, addr):
+ return self.db_cpld_iface.peek32(addr)
+
+ def poke_db_cpld(self, addr, val):
+ self.db_cpld_iface.poke32(addr, val)
+
+ ####################################################################
+ # Management Bus
+ ####################################################################
+
+ ####################################################################
+ # Calibration SPI
+ # The SPI/QSPI node used to interact with the DB
+ # Calibration EEPROM if it exists
+ ####################################################################
+ def get_cal_eeprom_spi_node(self, addr):
+ """
+ Returns the QSPI node leading to the calibration EEPROM of the
+ given DB.
+ """
+ chip_select = self.mboard.qspi_cs.get(self.db_name, None)
+ if chip_select is None:
+ raise RuntimeError('No QSPI chip select corresponds ' \
+ 'with daughterboard {}'.format(self.db_name))
+ return self.mboard.qspi_nodes[chip_select]
+
+ ####################################################################
+ # MB Control
+ # Some of the MB settings may be controlled from the DB Driver
+ ####################################################################
+ def _find_converters(self, direction='both', channel='both'):
+ """
+ Returns a list of (tile_id, block_id, is_dac) tuples describing
+ the data converters associated with a given channel and direction.
+ """
+ return self.mboard.rfdc._find_converters(self.slot_idx, direction, channel)
+
+ def set_if_freq(self, freq, direction='both', channel='both'):
+ """
+ Use the rfdc_ctrl object to set the IF frequency of the ADCs and
+ DACs corresponding to the specified channels of the DB.
+ By default, all channels and directions will be set.
+ Returns True if the IF frequency was successfully set.
+ """
+ for tile_id, block_id, is_dac in self._find_converters(direction, channel):
+ if not self.mboard.rfdc._rfdc_ctrl.set_if(tile_id, block_id, is_dac, freq):
+ return False
+ return True
+
+ def get_if_freq(self, direction, channel):
+ """
+ Gets the IF frequency of the ADC/DAC corresponding
+ to the specified channel of the DB.
+ """
+ converters = self._find_converters(direction, channel)
+ assert len(converters) == 1, \
+ 'Expected a single RFDC associated with {}{}. Instead found {}.' \
+ .format(direction, channel, len(converters))
+ (tile_id, block_id, is_dac) = converters[0]
+ return self.mboard.rfdc._rfdc_ctrl.get_nco_freq(tile_id, block_id, is_dac)
+
+ def enable_iq_swap(self, enable, direction, channel):
+ """
+ Enable or disable swap of I and Q samples from the RFDCs.
+ """
+ for tile_id, block_id, is_dac in self._find_converters(direction, channel):
+ self.mboard.rfdc._rfdc_regs.enable_iq_swap(enable, self.slot_idx, block_id, is_dac)
+
+ def get_sample_rate(self):
+ """
+ Gets the sample rate of the RFDCs.
+ """
+ return self.mboard.get_spll_freq()
+
+ def get_prc_rate(self):
+ """
+ Returns the rate of the PLL Reference Clock (PRC) which is
+ routed to the daughterboard.
+ Note: The ref clock will change if the sample clock frequency
+ is modified.
+ """
+ return self.mboard.get_prc_rate()
diff --git a/mpm/python/usrp_mpm/dboard_manager/x4xx_debug_db.py b/mpm/python/usrp_mpm/dboard_manager/x4xx_debug_db.py
new file mode 100644
index 000000000..f5e023229
--- /dev/null
+++ b/mpm/python/usrp_mpm/dboard_manager/x4xx_debug_db.py
@@ -0,0 +1,152 @@
+#
+# Copyright 2020 Ettus Research, a National Instruments Brand
+#
+# SPDX-License-Identifier: GPL-3.0-or-later
+#
+"""
+Debug dboard implementation module
+"""
+
+from usrp_mpm.dboard_manager import DboardManagerBase
+from usrp_mpm.mpmlog import get_logger
+from usrp_mpm.sys_utils.gpio import Gpio
+
+class DebugDboardSignalPath:
+ def __init__(self, slot_idx, path, adc_indexes, dac_indexes, loopback):
+ self.log = get_logger("X4xxDebugDboard-{}-path-{}".format(slot_idx, path))
+
+ self.rxa2_led = Gpio("DB{}_RX{}2_LED".format(slot_idx, path), Gpio.OUTPUT, 0)
+ self.rxa_led = Gpio("DB{}_RX{}_LED".format(slot_idx, path), Gpio.OUTPUT, 0)
+ self.txa_led = Gpio("DB{}_TX{}_LED".format(slot_idx, path), Gpio.OUTPUT, 0)
+
+ self.trx_ctrl = Gpio("DB{}_TRX{}_CTRL".format(slot_idx, path), Gpio.OUTPUT, 0)
+ self.rx_mux_ctrl = Gpio("DB{}_RX{}_MUX_CTRL".format(slot_idx, path), Gpio.OUTPUT, 0)
+ self.tx_mux_ctrl = Gpio("DB{}_TX{}_MUX_CTRL".format(slot_idx, path), Gpio.OUTPUT, 0)
+
+ self._adc_indices = adc_indexes
+ self._dac_indices = dac_indexes
+ self._loopback = loopback
+ self._path = path
+
+ def configure(self, adc, dac, loopback):
+ """
+ Configure this path with the appropriate settings
+ """
+ if adc.lower() not in self._adc_indices:
+ error_msg = "Could not find ADC {} on path {}. Possible ADCs: {}".format(
+ adc, self._path, ", ".join(self._adc_indices.keys())
+ )
+ self.log.error(error_msg)
+ raise RuntimeError(error_msg)
+
+ if dac.lower() not in self._dac_indices:
+ error_msg = "Could not find DAC {} on path {}. Possible DACs: {}".format(
+ dac, self._path, ", ".join(self._dac_indices.keys())
+ )
+ self.log.error(error_msg)
+ raise RuntimeError(error_msg)
+
+ self.rx_mux_ctrl.set(self._adc_indices[adc.lower()])
+ self.tx_mux_ctrl.set(self._dac_indices[dac.lower()])
+ self.trx_ctrl.set(self._loopback if loopback else not self._loopback)
+
+
+class X4xxDebugDboard(DboardManagerBase):
+ """
+ Holds all dboard specific information and methods of the X4xx debug dboard
+ """
+ #########################################################################
+ # Overridables
+ #
+ # See DboardManagerBase for documentation on these fields
+ #########################################################################
+ pids = [0x4001]
+ ### End of overridables #################################################
+
+ def __init__(self, slot_idx, **kwargs):
+ DboardManagerBase.__init__(self, slot_idx, **kwargs)
+ self.log = get_logger("X4xxDebugDboard-{}".format(slot_idx))
+ self.log.trace("Initializing X4xxDebug daughterboard, slot index %d",
+ self.slot_idx)
+
+ # Interface with MB HW
+ if 'db_iface' not in kwargs:
+ self.log.error("Required DB Iface was not provided!")
+ raise RuntimeError("Required DB Iface was not provided!")
+ self.db_iface = kwargs['db_iface']
+
+ # Power on the card
+ self.db_iface.enable_daughterboard(enable=True)
+ if not self.db_iface.check_enable_daughterboard():
+ self.db_iface.enable_daughterboard(enable=False)
+ self.log.error('Debug dboard {} power up failed'.format(self.slot_idx))
+ raise RuntimeError('Debug dboard {} power up failed'.format(self.slot_idx))
+
+ self._paths = {
+ "a": DebugDboardSignalPath(
+ slot_idx,
+ "A",
+ {
+ "adc0": 1,
+ "adc2": 0,
+ },
+ {
+ "dac0": 1,
+ "dac2": 0,
+ },
+ 1 # TRXA_CTRL=1 enables loopback
+ ),
+ "b": DebugDboardSignalPath(
+ slot_idx,
+ "B",
+ {
+ "adc3": 1,
+ "adc1": 0,
+ },
+ {
+ "dac3": 1,
+ "dac1": 0,
+ },
+ 0 # TRXB_CTRL=0 enables loopback
+ ),
+ }
+
+
+ # TODO: Configure the correct RFDC settings for this board
+ #if not self.db_iface.disable_mixer():
+ # raise RuntimeError("Received an error disabling the mixer for slot_idx={}".format(slot_idx))
+
+ def init(self, args):
+ """
+ Execute necessary init dance to bring up dboard
+ """
+ self.log.debug("init() called with args `{}'".format(
+ ",".join(['{}={}'.format(x, args[x]) for x in args])
+ ))
+ self.config_path("a", "adc0", "dac0", 0)
+ self.config_path("b", "adc1", "dac1", 0)
+ return True
+
+ def deinit(self):
+ pass
+
+ def tear_down(self):
+ self.db_iface.tear_down()
+
+ def config_path(self, path, adc, dac, loopback):
+ """
+ Configure the signal paths on the daughterboard.
+ path - Select between front panel connectors A or B. The two paths are unconnected.
+ adc - Select which ADC to connect to the path (adc0 or adc2 on A, adc1 or adc3 on B)
+ dac - Select which DAC to connect to the path (dac0 or dac2 on A, dac1 or dac3 on B)
+ loopback - Whether to enable loopback (1) or route the ADC/DACs to the front panel (0)
+
+ Example MPM shell usage:
+ > db_0_config_path a adc0 dac2 1
+ """
+ if path.lower() not in self._paths:
+ self.log.error("Tried to configure path {} which does not exist!".format(path))
+ raise RuntimeError("Tried to configure path {} which does not exist!".format(path))
+
+ path = self._paths[path.lower()]
+ path.configure(adc, dac, int(loopback))
diff --git a/mpm/python/usrp_mpm/dboard_manager/x4xx_if_test_cca.py b/mpm/python/usrp_mpm/dboard_manager/x4xx_if_test_cca.py
new file mode 100644
index 000000000..c435ddb1c
--- /dev/null
+++ b/mpm/python/usrp_mpm/dboard_manager/x4xx_if_test_cca.py
@@ -0,0 +1,167 @@
+#
+# Copyright 2020 Ettus Research, a National Instruments Brand
+#
+# SPDX-License-Identifier: GPL-3.0-or-later
+#
+"""
+IF Test CCA implementation module
+"""
+from usrp_mpm.dboard_manager import DboardManagerBase
+from usrp_mpm.mpmlog import get_logger
+from usrp_mpm.sys_utils.gpio import Gpio
+
+class X4xxIfTestCCA(DboardManagerBase):
+ """
+ Holds all dboard specific information and methods of the X4xx IF Test CCA
+ """
+ #########################################################################
+ # Overridables
+ #
+ # See DboardManagerBase for documentation on these fields
+ #########################################################################
+ pids = [0x4006]
+ ### End of overridables #################################################
+
+ def __init__(self, slot_idx, **kwargs):
+ DboardManagerBase.__init__(self, slot_idx, **kwargs)
+ self.log = get_logger("X4xxIfTestCCA-{}".format(slot_idx))
+ self.log.trace("Initializing X4xxIfTestCCA, slot index %d",
+ self.slot_idx)
+
+ # Interface with MB HW
+ if 'db_iface' not in kwargs:
+ self.log.error("Required DB Iface was not provided!")
+ raise RuntimeError("Required DB Iface was not provided!")
+ self.db_iface = kwargs['db_iface']
+
+ # Power on the card
+ self.db_iface.enable_daughterboard(enable=True)
+ if not self.db_iface.check_enable_daughterboard():
+ self.db_iface.enable_daughterboard(enable=False)
+ self.log.error('IF Test CCA {} power up failed'.format(self.slot_idx))
+ raise RuntimeError('IF Test CCA {} power up failed'.format(self.slot_idx))
+
+ # [boolean for stage 1 mux , boolean for stage 2 mux]
+ self._adc_mux_settings = {
+ "adc0" : [0, 0],
+ "adc1" : [1, 1],
+ "adc2" : [1, 0],
+ "adc3" : [0, 1],
+ }
+
+ self._dac_mux_settings = {
+ "dac0" : [1, 0],
+ "dac1" : [1, 1],
+ "dac2" : [0, 0],
+ "dac3" : [0, 1],
+ }
+
+ # There are 4 possible Tx (DAC) streams that are available to choose
+ # to export to the SMA TX port using a 2-stage hardware mux.
+
+ # Choose between 0 and 2 OR 1 and 3
+ self.tx_0_2_1_3_mux_ctrl = Gpio("DB{}_TX0_2p_1_3n".format(slot_idx), Gpio.OUTPUT, 0)
+ # Choose between 0 OR 2
+ self.tx_0_2_mux_ctrl = Gpio("DB{}_TX_MUX_0p_2n".format(slot_idx), Gpio.OUTPUT, 0)
+ # Choose between 1 OR 3
+ self.tx_1_3_mux_ctrl = Gpio("DB{}_TX_MUX_1p_3n".format(slot_idx), Gpio.OUTPUT, 0)
+
+ # The signal from the SMA RX port can be directed to one of the 4
+ # available Rx (ADC) streams using a 2-stage hardware mux.
+
+ # Choose between 0 and 2 OR 1 and 3
+ self.rx_0_2_1_3_mux_ctrl = Gpio("DB{}_RX0_2p_1_3n".format(slot_idx), Gpio.OUTPUT, 0)
+ # Choose between 0 OR 2
+ self.rx_0_2_mux_ctrl = Gpio("DB{}_RX_MUX_0p_2n".format(slot_idx), Gpio.OUTPUT, 0)
+ # Choose between 1 OR 3
+ self.rx_1_3_mux_ctrl = Gpio("DB{}_RX_MUX_1p_3n".format(slot_idx), Gpio.OUTPUT, 0)
+
+ self._tx_path = ""
+ self._rx_path = ""
+
+ # Controls to load the power supplies on the daughterboard. Enabling
+ # these will increase the power draw of the daughterboard.
+ self.enable_1v8_load = Gpio("DB{}_1V8_LOAD".format(slot_idx), Gpio.OUTPUT, 0)
+ self.enable_2v5_load = Gpio("DB{}_2V5_LOAD".format(slot_idx), Gpio.OUTPUT, 0)
+ self.enable_3v3_load = Gpio("DB{}_3V3_LOAD".format(slot_idx), Gpio.OUTPUT, 0)
+ self.enable_3v3_mcu_load = Gpio("DB{}_3V3_MCU_LOAD".format(slot_idx), Gpio.OUTPUT, 0)
+ self.enable_3v7_load = Gpio("DB{}_3V7_LOAD".format(slot_idx), Gpio.OUTPUT, 0)
+ self.enable_12v_load = Gpio("DB{}_12V_LOAD".format(slot_idx), Gpio.OUTPUT, 0)
+
+ # Control to choose between DAC output or MB VCM signals as the VCM
+ # signal to use on board.
+ self.disable_vcm_dac = Gpio("DB{}_VCM_MB_nDAC".format(slot_idx), Gpio.OUTPUT, 0)
+
+ # Control to choose which MB clock to output to the SMA Clock port.
+ # Choices are BaseRefClk and PllRefClk
+ self.disable_vcm_dac = Gpio("DB{}_REF_CLK_SEL_USR".format(slot_idx), Gpio.OUTPUT, 0)
+
+
+ def init(self, args):
+ """
+ Execute necessary init dance to bring up dboard
+ """
+ self.log.debug("init() called with args `{}'".format(
+ ",".join(['{}={}'.format(x, args[x]) for x in args])
+ ))
+ self.config_tx_path("dac0")
+ self.config_rx_path("adc0")
+ return True
+
+ def deinit(self):
+ pass
+
+ def tear_down(self):
+ self.db_iface.tear_down()
+
+ def config_tx_path(self, dac):
+ """
+ Configure the tx signal path on the daughterboard.
+ dac - Select which DAC to connect to the Tx path (dac0 through dac3)
+
+ Example MPM shell usage:
+ > db_0_config_tx_path dac2
+ """
+
+ if dac.lower() not in self._dac_mux_settings:
+ error_msg = "Could not find DAC {}. Possible DACs: {}".format(
+ dac, ", ".join(self._dac_mux_settings.keys())
+ )
+ self.log.error(error_msg)
+ raise RuntimeError(error_msg)
+
+ # Only one of the following setting really matters; simplify logic
+ # by toggling both since the stage 2 decides what gets connected.
+ self.tx_0_2_mux_ctrl.set(self._dac_mux_settings[dac.lower()][0])
+ self.tx_1_3_mux_ctrl.set(self._dac_mux_settings[dac.lower()][0])
+ self.tx_0_2_1_3_mux_ctrl.set(self._dac_mux_settings[dac.lower()][1])
+ self._tx_path = dac.upper()
+
+ def get_tx_path(self):
+ return self._tx_path
+
+ def config_rx_path(self, adc):
+ """
+ Configure the rx signal path on the daughterboard.
+ adc - Select which ADC to connect to the Rx path (adc0 through adc3)
+
+ Example MPM shell usage:
+ > db_0_config_rx_path adc0
+ """
+
+ if adc.lower() not in self._adc_mux_settings:
+ error_msg = "Could not find ADC {}. Possible ADCs: {}".format(
+ adc, ", ".join(self._adc_mux_settings.keys())
+ )
+ self.log.error(error_msg)
+ raise RuntimeError(error_msg)
+
+ self.rx_0_2_1_3_mux_ctrl.set(self._adc_mux_settings[adc.lower()][1])
+ # Only one of the following setting really matters; simplify logic
+ # by toggling both
+ self.rx_0_2_mux_ctrl.set(self._adc_mux_settings[adc.lower()][0])
+ self.rx_1_3_mux_ctrl.set(self._adc_mux_settings[adc.lower()][0])
+ self._rx_path = adc.upper()
+
+ def get_rx_path(self):
+ return self._rx_path
diff --git a/mpm/python/usrp_mpm/dboard_manager/zbx.py b/mpm/python/usrp_mpm/dboard_manager/zbx.py
new file mode 100644
index 000000000..8343119c8
--- /dev/null
+++ b/mpm/python/usrp_mpm/dboard_manager/zbx.py
@@ -0,0 +1,461 @@
+#
+# Copyright 2019-2020 Ettus Research, a National Instruments Brand
+#
+# SPDX-License-Identifier: GPL-3.0-or-later
+#
+"""
+ZBX dboard implementation module
+"""
+
+import time
+from usrp_mpm import tlv_eeprom
+from usrp_mpm.dboard_manager import DboardManagerBase
+from usrp_mpm.mpmlog import get_logger
+from usrp_mpm.chips.ic_reg_maps import zbx_cpld_regs_t
+from usrp_mpm.periph_manager.x4xx_periphs import get_temp_sensor
+from usrp_mpm.sys_utils.udev import get_eeprom_paths_by_symbol
+
+###############################################################################
+# Helpers
+###############################################################################
+def parse_encoded_git_hash(encoded):
+ """
+ Helper function: Unpacks the git hash encoded in the ZBX CPLD image into
+ the git hash and a dirty flag.
+ """
+ git_hash = encoded & 0x0FFFFFFF
+ tree_dirty = ((encoded & 0xF0000000) > 0)
+ dirtiness_qualifier = 'dirty' if tree_dirty else 'clean'
+ return (git_hash, dirtiness_qualifier)
+
+
+# pylint: disable=too-few-public-methods
+class EepromTagMap:
+ """
+ Defines the tagmap for EEPROMs matching this magic.
+ The tagmap is a dictionary mapping an 8-bit tag to a NamedStruct instance.
+ The canonical list of tags and the binary layout of the associated structs
+ is defined in mpm/tools/tlv_eeprom/usrp_eeprom.h. Only the subset relevant
+ to MPM are included below.
+ """
+ magic = 0x55535250
+ tagmap = {
+ # 0x10: usrp_eeprom_board_info
+ 0x10: tlv_eeprom.NamedStruct('< H H H 7s 1x',
+ ['pid', 'rev', 'rev_compat', 'serial']),
+ }
+
+
+###############################################################################
+# Main dboard control class
+###############################################################################
+class ZBX(DboardManagerBase):
+ """
+ Holds all dboard specific information and methods of the ZBX dboard
+ """
+ #########################################################################
+ # Overridables
+ #
+ # See DboardManagerBase for documentation on these fields
+ #########################################################################
+ pids = [0x4002]
+ rx_sensor_callback_map = {
+ 'temperature': 'get_rf_temp_sensor',
+ }
+ tx_sensor_callback_map = {
+ 'temperature': 'get_rf_temp_sensor',
+ }
+ ### End of overridables #################################################
+
+ # Daughterboard required rev_compat value, this is compared against
+ # rev_compat in the eeprom
+ # Change only on breaking changes
+ DBOARD_REQUIRED_COMPAT_REV = 0x1
+
+ # CPLD compatibility revision
+ # Change this revision only on breaking changes.
+ REQ_OLDEST_COMPAT_REV = 0x20110611
+ REQ_COMPAT_REV = 0x20110611
+
+ #########################################################################
+ # MPM Initialization
+ #########################################################################
+ def __init__(self, slot_idx, **kwargs):
+ DboardManagerBase.__init__(self, slot_idx, **kwargs)
+ self.log = get_logger("ZBX-{}".format(slot_idx))
+ self.log.trace("Initializing ZBX daughterboard, slot index %d",
+ self.slot_idx)
+
+ # local variable to track if PLL ref clock is enabled for the CPLD logic
+ self._clock_enabled = False
+
+ # Interface with MB HW
+ if 'db_iface' not in kwargs:
+ self.log.error("Required DB Iface was not provided!")
+ raise RuntimeError("Required DB Iface was not provided!")
+ self.db_iface = kwargs['db_iface']
+
+ self.eeprom_symbol = f"db{slot_idx}_eeprom"
+ eeprom = self._get_eeprom()
+ if eeprom["rev_compat"] != self.DBOARD_REQUIRED_COMPAT_REV:
+ err = f"Found ZBX rev_compat 0x{eeprom['rev_compat']:02x}," \
+ f" required is 0x{self.DBOARD_REQUIRED_COMPAT_REV:02x}"
+ self.log.error(err)
+ raise RuntimeError(err)
+
+ # Initialize daughterboard CPLD control
+ self.poke_cpld = self.db_iface.poke_db_cpld
+ self.peek_cpld = self.db_iface.peek_db_cpld
+ self.regs = zbx_cpld_regs_t()
+ self._spi_addr = self.regs.SPI_READY_addr
+ self._enable_base_power()
+ # Check register map compatibility
+ self._check_compat_version()
+ self.log.debug("ZBX CPLD build git hash: %s", self._get_cpld_git_hash())
+ # Power up the DB
+ self._enable_power()
+ # enable PLL reference clock
+ self.reset_clock(False)
+ self._cpld_set_safe_defaults()
+
+ def _get_eeprom(self):
+ """
+ Return the eeprom data.
+ """
+ path = get_eeprom_paths_by_symbol(self.eeprom_symbol)[self.eeprom_symbol]
+ eeprom, _ = tlv_eeprom.read_eeprom(path, EepromTagMap.tagmap, EepromTagMap.magic, None)
+ return eeprom
+
+ def _enable_base_power(self, enable=True):
+ """
+ Enables or disables power to the DB which enables communication to DB CPLD
+ """
+ if enable:
+ self.db_iface.enable_daughterboard(enable=True)
+ if not self.db_iface.check_enable_daughterboard():
+ self.db_iface.enable_daughterboard(enable=False)
+ self.log.error('ZBX {} power up failed'.format(self.slot_idx))
+ raise RuntimeError('ZBX {} power up failed'.format(self.slot_idx))
+ else: # disable
+ # Removing power from the CPLD will set all the the output pins to open and the
+ # supplies default to disabled on power up.
+ self.db_iface.enable_daughterboard(enable=False)
+ if self.db_iface.check_enable_daughterboard():
+ self.log.error('ZBX {} power down failed'.format(self.slot_idx))
+
+ def _enable_power(self, enable=True):
+ """ Enables or disables power switches internal to the DB CPLD """
+ self.regs.ENABLE_TX_POS_7V0 = self.regs.ENABLE_TX_POS_7V0_t(int(enable))
+ self.regs.ENABLE_RX_POS_7V0 = self.regs.ENABLE_RX_POS_7V0_t(int(enable))
+ self.regs.ENABLE_POS_3V3 = self.regs.ENABLE_POS_3V3_t(int(enable))
+ self.poke_cpld(
+ self.regs.ENABLE_POS_3V3_addr,
+ self.regs.get_reg(self.regs.ENABLE_POS_3V3_addr))
+
+ def _check_compat_version(self):
+ """ Check compatibility of DB CPLD image and SW regmap """
+ compat_revision_addr = self.regs.OLDEST_COMPAT_REVISION_addr
+ cpld_oldest_compat_revision = self.peek_cpld(compat_revision_addr)
+ if cpld_oldest_compat_revision < self.REQ_OLDEST_COMPAT_REV:
+ err_msg = (
+ f'DB CPLD oldest compatible revision 0x{cpld_oldest_compat_revision:x}'
+ f' is out of date, the required revision is 0x{self.REQ_OLDEST_COMPAT_REV:x}. '
+ f'Update your CPLD image.')
+ self.log.error(err_msg)
+ raise RuntimeError(err_msg)
+ if cpld_oldest_compat_revision > self.REQ_OLDEST_COMPAT_REV:
+ err_msg = (
+ f'DB CPLD oldest compatible revision 0x{cpld_oldest_compat_revision:x}'
+ f' is newer than the expected revision 0x{self.REQ_OLDEST_COMPAT_REV:x}.'
+ ' Downgrade your CPLD image or update MPM.')
+ self.log.error(err_msg)
+ raise RuntimeError(err_msg)
+
+ if not self.has_compat_version(self.REQ_COMPAT_REV):
+ err_msg = (
+ "ZBX DB CPLD revision is too old. Update your"
+ f" CPLD image to at least 0x{self.REQ_COMPAT_REV:08x}.")
+ self.log.error(err_msg)
+ raise RuntimeError(err_msg)
+
+ def has_compat_version(self, min_required_version):
+ """
+ Check for a minimum required version.
+ """
+ cpld_image_compat_revision = self.peek_cpld(self.regs.REVISION_addr)
+ return cpld_image_compat_revision >= min_required_version
+
+ # pylint: disable=too-many-statements
+ def _cpld_set_safe_defaults(self):
+ """
+ Set the CPLD into a safe state.
+ """
+ cpld_regs = zbx_cpld_regs_t()
+ # We un-configure some registers to force a change later. None of these
+ # values get written to the CPLD!
+ cpld_regs.RF0_OPTION = cpld_regs.RF0_OPTION.RF0_OPTION_FPGA_STATE
+ cpld_regs.RF1_OPTION = cpld_regs.RF1_OPTION.RF1_OPTION_FPGA_STATE
+ cpld_regs.SW_RF0_CONFIG = 255
+ cpld_regs.SW_RF1_CONFIG = 255
+ cpld_regs.TX0_DSA1[0] = 0
+ cpld_regs.TX0_DSA2[0] = 0
+ cpld_regs.RX0_DSA1[0] = 0
+ cpld_regs.RX0_DSA2[0] = 0
+ cpld_regs.RX0_DSA3_A[0] = 0
+ cpld_regs.RX0_DSA3_B[0] = 0
+ cpld_regs.save_state()
+ # Now all the registers we touch will be enumerated by get_changed_addrs()
+ # Everything below *will* get written to the CPLD:
+ # ATR control
+ cpld_regs.RF0_OPTION = cpld_regs.RF0_OPTION.RF0_OPTION_SW_DEFINED
+ cpld_regs.RF1_OPTION = cpld_regs.RF1_OPTION.RF1_OPTION_SW_DEFINED
+ # Back to state 0 and sw-defined. That means nothing will get configured
+ # until UHD boots again.
+ cpld_regs.SW_RF0_CONFIG = 0
+ cpld_regs.SW_RF1_CONFIG = 0
+ # TX0 path control
+ cpld_regs.TX0_IF2_1_2[0] = cpld_regs.TX0_IF2_1_2[0].TX0_IF2_1_2_FILTER_2
+ cpld_regs.TX0_IF1_3[0] = cpld_regs.TX0_IF1_3[0].TX0_IF1_3_FILTER_0_3
+ cpld_regs.TX0_IF1_4[0] = cpld_regs.TX0_IF1_4[0].TX0_IF1_4_TERMINATION
+ cpld_regs.TX0_IF1_5[0] = cpld_regs.TX0_IF1_5[0].TX0_IF1_5_TERMINATION
+ cpld_regs.TX0_IF1_6[0] = cpld_regs.TX0_IF1_6[0].TX0_IF1_6_FILTER_0_3
+ cpld_regs.TX0_7[0] = cpld_regs.TX0_7[0].TX0_7_TERMINATION
+ cpld_regs.TX0_RF_8[0] = cpld_regs.TX0_RF_8[0].TX0_RF_8_RF_1
+ cpld_regs.TX0_RF_9[0] = cpld_regs.TX0_RF_9[0].TX0_RF_9_RF_1
+ cpld_regs.TX0_ANT_10[0] = cpld_regs.TX0_ANT_10[0].TX0_ANT_10_BYPASS_AMP
+ cpld_regs.TX0_ANT_11[0] = cpld_regs.TX0_ANT_11[0].TX0_ANT_11_BYPASS_AMP
+ cpld_regs.TX0_LO_13[0] = cpld_regs.TX0_LO_13[0].TX0_LO_13_INTERNAL
+ cpld_regs.TX0_LO_14[0] = cpld_regs.TX0_LO_14[0].TX0_LO_14_INTERNAL
+ # TX1 path control
+ cpld_regs.TX1_IF2_1_2[0] = cpld_regs.TX1_IF2_1_2[0].TX1_IF2_1_2_FILTER_2
+ cpld_regs.TX1_IF1_3[0] = cpld_regs.TX1_IF1_3[0].TX1_IF1_3_FILTER_0_3
+ cpld_regs.TX1_IF1_4[0] = cpld_regs.TX1_IF1_4[0].TX1_IF1_4_TERMINATION
+ cpld_regs.TX1_IF1_5[0] = cpld_regs.TX1_IF1_5[0].TX1_IF1_5_TERMINATION
+ cpld_regs.TX1_IF1_6[0] = cpld_regs.TX1_IF1_6[0].TX1_IF1_6_FILTER_0_3
+ cpld_regs.TX1_7[0] = cpld_regs.TX1_7[0].TX1_7_TERMINATION
+ cpld_regs.TX1_RF_8[0] = cpld_regs.TX1_RF_8[0].TX1_RF_8_RF_1
+ cpld_regs.TX1_RF_9[0] = cpld_regs.TX1_RF_9[0].TX1_RF_9_RF_1
+ cpld_regs.TX1_ANT_10[0] = cpld_regs.TX1_ANT_10[0].TX1_ANT_10_BYPASS_AMP
+ cpld_regs.TX1_ANT_11[0] = cpld_regs.TX1_ANT_11[0].TX1_ANT_11_BYPASS_AMP
+ cpld_regs.TX1_LO_13[0] = cpld_regs.TX1_LO_13[0].TX1_LO_13_INTERNAL
+ cpld_regs.TX1_LO_14[0] = cpld_regs.TX1_LO_14[0].TX1_LO_14_INTERNAL
+ # RX0 path control
+ cpld_regs.RX0_ANT_1[0] = cpld_regs.RX0_ANT_1[0].RX0_ANT_1_TERMINATION
+ cpld_regs.RX0_2[0] = cpld_regs.RX0_2[0].RX0_2_LOWBAND
+ cpld_regs.RX0_RF_3[0] = cpld_regs.RX0_RF_3[0].RX0_RF_3_RF_1
+ cpld_regs.RX0_4[0] = cpld_regs.RX0_4[0].RX0_4_LOWBAND
+ cpld_regs.RX0_IF1_5[0] = cpld_regs.RX0_IF1_5[0].RX0_IF1_5_FILTER_1
+ cpld_regs.RX0_IF1_6[0] = cpld_regs.RX0_IF1_6[0].RX0_IF1_6_FILTER_1
+ cpld_regs.RX0_LO_9[0] = cpld_regs.RX0_LO_9[0].RX0_LO_9_INTERNAL
+ cpld_regs.RX0_LO_10[0] = cpld_regs.RX0_LO_10[0].RX0_LO_10_INTERNAL
+ cpld_regs.RX0_RF_11[0] = cpld_regs.RX0_RF_11[0].RX0_RF_11_RF_3
+ # RX1 path control
+ cpld_regs.RX1_ANT_1[0] = cpld_regs.RX1_ANT_1[0].RX1_ANT_1_TERMINATION
+ cpld_regs.RX1_2[0] = cpld_regs.RX1_2[0].RX1_2_LOWBAND
+ cpld_regs.RX1_RF_3[0] = cpld_regs.RX1_RF_3[0].RX1_RF_3_RF_1
+ cpld_regs.RX1_4[0] = cpld_regs.RX1_4[0].RX1_4_LOWBAND
+ cpld_regs.RX1_IF1_5[0] = cpld_regs.RX1_IF1_5[0].RX1_IF1_5_FILTER_1
+ cpld_regs.RX1_IF1_6[0] = cpld_regs.RX1_IF1_6[0].RX1_IF1_6_FILTER_1
+ cpld_regs.RX1_LO_9[0] = cpld_regs.RX1_LO_9[0].RX1_LO_9_INTERNAL
+ cpld_regs.RX1_LO_10[0] = cpld_regs.RX1_LO_10[0].RX1_LO_10_INTERNAL
+ cpld_regs.RX1_RF_11[0] = cpld_regs.RX1_RF_11[0].RX1_RF_11_RF_3
+ # TX DSA
+ cpld_regs.TX0_DSA1[0] = 31
+ cpld_regs.TX0_DSA2[0] = 31
+ # RX DSA
+ cpld_regs.RX0_DSA1[0] = 15
+ cpld_regs.RX0_DSA2[0] = 15
+ cpld_regs.RX0_DSA3_A[0] = 15
+ cpld_regs.RX0_DSA3_B[0] = 15
+ for addr in cpld_regs.get_changed_addrs():
+ self.poke_cpld(addr, cpld_regs.get_reg(addr))
+ # pylint: enable=too-many-statements
+
+ #########################################################################
+ # UHD (De-)Initialization
+ #########################################################################
+ def init(self, args):
+ """
+ Execute necessary init dance to bring up dboard. This happens when a UHD
+ session starts.
+ """
+ self.log.debug("init() called with args `{}'".format(
+ ",".join(['{}={}'.format(x, args[x]) for x in args])
+ ))
+ return True
+
+ def deinit(self):
+ """
+ De-initialize after UHD session completes
+ """
+ self.log.debug("Setting CPLD back to safe defaults after UHD session.")
+ self._cpld_set_safe_defaults()
+
+ def tear_down(self):
+ self.db_iface.tear_down()
+
+ #########################################################################
+ # API calls needed by the zbx_dboard driver
+ #########################################################################
+ def enable_iq_swap(self, enable, trx, channel):
+ """
+ Turn on IQ swapping in the RFDC
+ """
+ self.db_iface.enable_iq_swap(enable, trx, channel)
+
+ def get_dboard_sample_rate(self):
+ """
+ Return the RFDC rate. This is usually a big number in the 3 GHz range.
+ """
+ return self.db_iface.get_sample_rate()
+
+ def get_dboard_prc_rate(self):
+ """
+ Return the PRC rate. The CPLD and LOs are clocked with this.
+ """
+ return self.db_iface.get_prc_rate()
+
+ def _has_compat_version(self, min_required_version):
+ """
+ Check for a minimum required version.
+ """
+ cpld_image_compat_revision = self.peek_cpld(self.regs.REVISION_addr)
+ return cpld_image_compat_revision >= min_required_version
+
+ def _get_cpld_git_hash(self):
+ """
+ Trace build of MB CPLD
+ """
+ git_hash_rb = self.peek_cpld(self.regs.GIT_HASH_addr)
+ (git_hash, dirtiness_qualifier) = parse_encoded_git_hash(git_hash_rb)
+ return "{:07x} ({})".format(git_hash, dirtiness_qualifier)
+
+ def reset_clock(self, value):
+ """
+ Disable PLL reference clock to enable SPLL reconfiguration
+
+ Puts the clock into reset if value is True, takes it out of reset
+ otherwise.
+ """
+ if self._clock_enabled != bool(value):
+ return
+ addr = self.regs.get_addr("PLL_REF_CLOCK_ENABLE")
+ enum = self.regs.PLL_REF_CLOCK_ENABLE_t
+ if value:
+ reg_value = enum.PLL_REF_CLOCK_ENABLE_DISABLE.value
+ else:
+ reg_value = enum.PLL_REF_CLOCK_ENABLE_ENABLE.value
+ self.poke_cpld(addr, reg_value)
+ self._clock_enabled = not bool(value)
+
+ #########################################################################
+ # LO SPI API
+ #
+ # We keep a LO peek/poke interface for debugging purposes.
+ #########################################################################
+ def _wait_for_spi_ready(self, timeout):
+ """ Returns False if a timeout occurred. timeout is in ms """
+ for _ in range(timeout):
+ if (self.peek_cpld(self._spi_addr) >> self.regs.SPI_READY_shift) \
+ & self.regs.SPI_READY_mask:
+ return True
+ time.sleep(0.001)
+ return False
+
+ def _lo_spi_send_tx(self, lo_name, write, addr, data=None):
+ """ Wait for SPI Ready and setup the TX data for a LO SPI transaction """
+ if not self._wait_for_spi_ready(timeout=100):
+ self.log.error('Timeout before LO SPI transaction waiting for SPI Ready')
+ raise RuntimeError('Timeout before LO SPI transaction waiting for SPI Ready')
+ lo_enum_name = 'LO_SELECT_' + lo_name.upper()
+ assert hasattr(self.regs.LO_SELECT_t, lo_enum_name), \
+ "Invalid LO name: {}".format(lo_name)
+ self.regs.LO_SELECT = getattr(self.regs.LO_SELECT_t, lo_enum_name)
+ if write:
+ self.regs.READ_FLAG = self.regs.READ_FLAG_t.READ_FLAG_WRITE
+ else:
+ self.regs.READ_FLAG = self.regs.READ_FLAG_t.READ_FLAG_READ
+ if data is not None:
+ self.regs.DATA = data
+ else:
+ self.regs.DATA = 0
+ self.regs.ADDRESS = addr
+ self.regs.START_TRANSACTION = \
+ self.regs.START_TRANSACTION_t.START_TRANSACTION_ENABLE
+ self.poke_cpld(self._spi_addr, self.regs.get_reg(self._spi_addr))
+
+ def _lo_spi_check_status(self, lo_name, addr, write=False):
+ """ Wait for SPI Ready and check the success of the LO SPI transaction """
+ # SPI Ready indicates that the previous transaction has completed
+ # and the RX data is ready to be consumed
+ if not write and not self._wait_for_spi_ready(timeout=100):
+ self.log.error('Timeout after LO SPI transaction waiting for SPI Ready')
+ raise RuntimeError('Timeout after LO SPI transaction waiting for SPI Ready')
+ # If the address or CS are not the same as what we set, there
+ # was interference during the SPI transaction
+ lo_select = self.regs.LO_SELECT.name[len('LO_SELECT_'):]
+ if self.regs.ADDRESS != addr or lo_select != lo_name.upper():
+ self.log.error('SPI transaction to LO failed!')
+ raise RuntimeError('SPI transaction to LO failed!')
+
+ def _lo_spi_get_rx(self):
+ """ Return RX data read from the LO SPI transaction """
+ spi_reg = self.peek_cpld(self._spi_addr)
+ return (spi_reg >> self.regs.DATA_shift) & self.regs.DATA_mask
+
+ def peek_lo_spi(self, lo_name, addr):
+ """ Perform a register read access to an LO via SPI """
+ self._lo_spi_send_tx(lo_name=lo_name, write=False, addr=addr)
+ self._lo_spi_check_status(lo_name, addr)
+ return self._lo_spi_get_rx()
+
+ def poke_lo_spi(self, lo_name, addr, val):
+ """ Perform a register write access to an LO via SPI """
+ self._lo_spi_send_tx(lo_name=lo_name, write=True, addr=addr, data=val)
+ self._lo_spi_check_status(lo_name, addr, write=True)
+
+ ###########################################################################
+ # LEDs
+ ###########################################################################
+ def set_leds(self, channel, rx, trx_rx, trx_tx):
+ """ Set the frontpanel LEDs """
+ assert channel in (0, 1)
+
+ self.regs.save_state()
+ if channel == 0:
+ # ensure to be in SW controlled mode
+ self.regs.RF0_OPTION = self.regs.RF0_OPTION.RF0_OPTION_SW_DEFINED
+ self.regs.SW_RF0_CONFIG = 0
+ self.regs.RX0_RX_LED[0] = self.regs.RX0_RX_LED[0].RX0_RX_LED_ENABLE \
+ if bool(rx) else self.regs.RX0_RX_LED[0].RX0_RX_LED_DISABLE
+ self.regs.RX0_TRX_LED[0] = self.regs.RX0_TRX_LED[0].RX0_TRX_LED_ENABLE \
+ if bool(trx_rx) else self.regs.RX0_TRX_LED[0].RX0_TRX_LED_DISABLE
+ self.regs.TX0_TRX_LED[0] = self.regs.TX0_TRX_LED[0].TX0_TRX_LED_ENABLE \
+ if bool(trx_tx) else self.regs.TX0_TRX_LED[0].TX0_TRX_LED_DISABLE
+ else:
+ # ensure to be in SW controlled mode
+ self.regs.RF1_OPTION = self.regs.RF1_OPTION.RF1_OPTION_SW_DEFINED
+ self.regs.SW_RF1_CONFIG = 0
+ self.regs.RX1_RX_LED[0] = self.regs.RX1_RX_LED[0].RX1_RX_LED_ENABLE \
+ if bool(rx) else self.regs.RX1_RX_LED[0].RX1_RX_LED_DISABLE
+ self.regs.RX1_TRX_LED[0] = self.regs.RX1_TRX_LED[0].RX1_TRX_LED_ENABLE \
+ if bool(trx_rx) else self.regs.RX1_TRX_LED[0].RX1_TRX_LED_DISABLE
+ self.regs.TX1_TRX_LED[0] = self.regs.TX1_TRX_LED[0].TX1_TRX_LED_ENABLE \
+ if bool(trx_tx) else self.regs.TX1_TRX_LED[0].TX1_TRX_LED_DISABLE
+
+ for addr in self.regs.get_changed_addrs():
+ self.poke_cpld(addr, self.regs.get_reg(addr))
+
+ ###########################################################################
+ # Sensors
+ ###########################################################################
+ def get_rf_temp_sensor(self, _):
+ """
+ Return the RF temperature sensor value
+ """
+ self.log.trace("Reading RF daughterboard temperature.")
+ sensor_names = [
+ f"TMP112 DB{self.slot_idx} Top",
+ f"TMP112 DB{self.slot_idx} Bottom",
+ ]
+ return get_temp_sensor(sensor_names, log=self.log)
diff --git a/mpm/python/usrp_mpm/dboard_manager/zbx_update_cpld.py b/mpm/python/usrp_mpm/dboard_manager/zbx_update_cpld.py
new file mode 100644
index 000000000..851cfe997
--- /dev/null
+++ b/mpm/python/usrp_mpm/dboard_manager/zbx_update_cpld.py
@@ -0,0 +1,218 @@
+#!/usr/bin/env python3
+#
+# Copyright 2019 Ettus Research, a National Instruments Brand
+#
+# SPDX-License-Identifier: GPL-3.0-or-later
+#
+"""
+Update the CPLD image for a ZBX daughterboard
+"""
+
+import sys
+import os
+import argparse
+import subprocess
+import pyudev
+from usrp_mpm.mpmlog import get_logger
+from usrp_mpm.mpmutils import check_fpga_state
+from usrp_mpm.sys_utils.sysfs_gpio import GPIOBank
+from usrp_mpm.periph_manager.x4xx_periphs import CtrlportRegs
+from usrp_mpm.periph_manager.x4xx_mb_cpld import MboardCPLD
+from usrp_mpm.chips.max10_cpld_flash_ctrl import Max10CpldFlashCtrl
+from usrp_mpm.sys_utils.udev import dt_symbol_get_spidev
+
+OPENOCD_DIR = "/usr/share/openocd/scripts"
+CONFIGS = {
+ 'axi_bitq' : {
+ 'files' : ["fpga/altera-10m50.cfg"],
+ 'cmd' : ["interface axi_bitq; axi_bitq_config %u %u %u; adapter_khz %u",
+ "init; svf -tap 10m50.tap %s -progress -quiet;exit"]
+ }
+}
+
+AXI_BITQ_ADAPTER_SPEED = 5000
+AXI_BITQ_BUS_CLK = 50000000
+
+#The offsets are for JTAG_DB0 and JTAG_DB1 on the motherboard CPLD
+DAUGHTERBOARD0_OFFSET = CtrlportRegs.MB_PL_CPLD + 0x60
+DAUGHTERBOARD1_OFFSET = CtrlportRegs.MB_PL_CPLD + 0x80
+
+# ZBX flash reconfiguration engine specific offsets
+RECONFIG_ENGINE_OFFSET = 0x20
+CPLD_MIN_REVISION = 0x20052016
+
+def check_openocd_files(files, logger=None):
+ """
+ Check if all file required by OpenOCD exist
+ :param logger: logger object
+ """
+ for ocd_file in files:
+ if not os.path.exists(os.path.join(OPENOCD_DIR, ocd_file)):
+ if logger is not None:
+ logger.error("Missing file %s" % os.path.join(OPENOCD_DIR, ocd_file))
+ return False
+ return True
+
+def find_offset(dboard):
+ """
+ Find the AXI Bitq UIO device
+ :param dboard: the dboard, can be either 0 or 1
+ """
+ assert dboard in (0, 1)
+ return DAUGHTERBOARD0_OFFSET if dboard == 0 else DAUGHTERBOARD1_OFFSET
+
+def find_axi_bitq_uio():
+ """
+ Find the AXI Bitq UIO device
+ """
+ label = 'ctrlport-mboard-regs'
+
+ logger = get_logger('update_cpld')
+
+ try:
+ context = pyudev.Context()
+ for uio in context.list_devices(subsystem="uio"):
+ uio_label = uio.attributes.asstring('maps/map0/name')
+ logger.trace("UIO label: {}, match: {} number: {}".format(
+ uio_label, uio_label == label, uio.sys_number))
+ if uio_label == label:
+ return int(uio.sys_number)
+ return None
+ except OSError as ex:
+ logger.error("Error while looking for axi_bitq uio nodes: {}".format(ex))
+ return None
+
+def do_update_cpld(filename, daughterboards, updater_mode):
+ """
+ Carry out update process for the CPLD
+ :param filename: path (on device) to the new CPLD image
+ :param daughterboards: iterable containing dboard numbers to update
+ :param updater_mode: the updater method to use- Either flash or legacy (JTAG)
+ :return: True on success, False otherwise
+ """
+ assert updater_mode in ('flash', 'legacy'), \
+ f"Invalid updater method {updater_mode} given"
+ logger = get_logger('update_cpld')
+ logger.info("Programming CPLD of dboards {} with image {} using {} mode"
+ .format(daughterboards, filename, updater_mode))
+
+ if not daughterboards:
+ logger.error("Invalid daughterboard selection.")
+ return False
+
+ if not os.path.exists(filename):
+ logger.error("CPLD image file {} not found".format(filename))
+ return False
+
+ if not check_fpga_state(logger=logger):
+ logger.error("CPLD lines are routed through fabric, FPGA is not programmed, giving up")
+ return False
+
+ if updater_mode == 'legacy':
+ return jtag_cpld_update(filename, daughterboards, logger)
+ # updater_mode == flash:
+ for dboard in daughterboards:
+ dboard = int(dboard, 10)
+ logger.info("Updating daughterboard slot {}...".format(dboard))
+ # enable required daughterboard clock
+ cpld_spi_node = dt_symbol_get_spidev('mb_cpld')
+ cpld_control = MboardCPLD(cpld_spi_node, logger)
+ cpld_control.enable_daughterboard_support_clock(dboard, enable=True)
+ # setup flash configuration engine and required register access
+ label = "ctrlport-mboard-regs"
+ ctrlport_regs = CtrlportRegs(label, logger)
+ regs = ctrlport_regs.get_db_cpld_iface(dboard)
+ flash_control = Max10CpldFlashCtrl(
+ logger, regs, RECONFIG_ENGINE_OFFSET, CPLD_MIN_REVISION)
+ success = flash_control.update(filename)
+ # disable clock
+ cpld_control.enable_daughterboard_support_clock(dboard, enable=False)
+ if not success:
+ return success
+ return True
+
+def jtag_cpld_update(filename, daughterboards, logger=None):
+ """
+ Carry out update process for the CPLD
+ :param filename: path (on device) to the new CPLD image
+ :param daughterboards: iterable containing dboard numbers to update
+ :return: True on success, False otherwise
+ """
+ mode = 'axi_bitq'
+ config = CONFIGS[mode]
+
+ if check_openocd_files(config['files'], logger=logger):
+ logger.trace("Found required OpenOCD files.")
+ else:
+ # check_openocd_files logs errors
+ return False
+
+ for dboard in daughterboards:
+ logger.info("Updating daughterboard slot {}...".format(dboard))
+
+ uio_id = find_axi_bitq_uio()
+ offset = find_offset(int(dboard, 10))
+ if uio_id is None or uio_id < 0:
+ logger.error('Failed to find axi_bitq uio devices. '\
+ 'Make sure overlays are up to date')
+ return False
+
+ cmd = [
+ "openocd",
+ "-c", config['cmd'][0] % (uio_id, AXI_BITQ_BUS_CLK, offset, AXI_BITQ_ADAPTER_SPEED),
+ "-f", (config['files'][0]).strip(),
+ "-c", config['cmd'][1] % filename]
+
+ logger.trace("Update CPLD CMD: {}".format(" ".join(cmd)))
+ subprocess.call(cmd)
+
+ logger.trace("Done programming CPLD...")
+ return True
+
+def main():
+ """
+ Go, go, go!
+ """
+ # Do some setup
+ def parse_args():
+ """Parse the command-line arguments"""
+ parser = argparse.ArgumentParser(description='Update the CPLD image on ZBX daughterboard')
+ parser.add_argument("--file", help="Filename of CPLD image",
+ default="/lib/firmware/ni/cpld-zbx.rpd")
+ parser.add_argument("--dboards", help="Slot name to program", default="0,1")
+ parser.add_argument("--updater",
+ help="The image updater method to use, either "
+ " 'legacy' (uses openocd) or 'flash'",
+ default="flash")
+ parser.add_argument(
+ '-v',
+ '--verbose',
+ help="Increase verbosity level",
+ action="count",
+ default=1
+ )
+ parser.add_argument(
+ '-q',
+ '--quiet',
+ help="Decrease verbosity level",
+ action="count",
+ default=0
+ )
+ return parser.parse_args()
+
+ args = parse_args()
+
+ # We need to make a logger if we're running stand-alone
+ from usrp_mpm.mpmlog import get_main_logger
+ log = get_main_logger(log_default_delta=args.verbose-args.quiet)
+
+ dboards = args.dboards.split(",")
+ if any([x not in ('0', '1') for x in dboards]):
+ log.error("Unsupported dboards requested: %s", dboards)
+ return False
+
+ return do_update_cpld(args.file, dboards, args.updater)
+
+
+if __name__ == "__main__":
+ sys.exit(not main())