aboutsummaryrefslogtreecommitdiffstats
path: root/mpm/python/usrp_mpm/dboard_manager/zbx.py
diff options
context:
space:
mode:
authorLars Amsel <lars.amsel@ni.com>2021-06-04 08:27:50 +0200
committerAaron Rossetto <aaron.rossetto@ni.com>2021-06-10 12:01:53 -0500
commit2a575bf9b5a4942f60e979161764b9e942699e1e (patch)
tree2f0535625c30025559ebd7494a4b9e7122550a73 /mpm/python/usrp_mpm/dboard_manager/zbx.py
parente17916220cc955fa219ae37f607626ba88c4afe3 (diff)
downloaduhd-2a575bf9b5a4942f60e979161764b9e942699e1e.tar.gz
uhd-2a575bf9b5a4942f60e979161764b9e942699e1e.tar.bz2
uhd-2a575bf9b5a4942f60e979161764b9e942699e1e.zip
uhd: Add support for the USRP X410
Co-authored-by: Lars Amsel <lars.amsel@ni.com> Co-authored-by: Michael Auchter <michael.auchter@ni.com> Co-authored-by: Martin Braun <martin.braun@ettus.com> Co-authored-by: Paul Butler <paul.butler@ni.com> Co-authored-by: Cristina Fuentes <cristina.fuentes-curiel@ni.com> Co-authored-by: Humberto Jimenez <humberto.jimenez@ni.com> Co-authored-by: Virendra Kakade <virendra.kakade@ni.com> Co-authored-by: Lane Kolbly <lane.kolbly@ni.com> Co-authored-by: Max Köhler <max.koehler@ni.com> Co-authored-by: Andrew Lynch <andrew.lynch@ni.com> Co-authored-by: Grant Meyerhoff <grant.meyerhoff@ni.com> Co-authored-by: Ciro Nishiguchi <ciro.nishiguchi@ni.com> Co-authored-by: Thomas Vogel <thomas.vogel@ni.com>
Diffstat (limited to 'mpm/python/usrp_mpm/dboard_manager/zbx.py')
-rw-r--r--mpm/python/usrp_mpm/dboard_manager/zbx.py461
1 files changed, 461 insertions, 0 deletions
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)