diff options
| author | Lars Amsel <lars.amsel@ni.com> | 2021-06-04 08:27:50 +0200 | 
|---|---|---|
| committer | Aaron Rossetto <aaron.rossetto@ni.com> | 2021-06-10 12:01:53 -0500 | 
| commit | 2a575bf9b5a4942f60e979161764b9e942699e1e (patch) | |
| tree | 2f0535625c30025559ebd7494a4b9e7122550a73 /mpm/python/usrp_mpm/dboard_manager | |
| parent | e17916220cc955fa219ae37f607626ba88c4afe3 (diff) | |
| download | uhd-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')
| -rw-r--r-- | mpm/python/usrp_mpm/dboard_manager/CMakeLists.txt | 5 | ||||
| -rw-r--r-- | mpm/python/usrp_mpm/dboard_manager/__init__.py | 5 | ||||
| -rw-r--r-- | mpm/python/usrp_mpm/dboard_manager/base.py | 37 | ||||
| -rw-r--r--[-rwxr-xr-x] | mpm/python/usrp_mpm/dboard_manager/dboard_iface.py | 68 | ||||
| -rw-r--r-- | mpm/python/usrp_mpm/dboard_manager/empty_slot.py | 37 | ||||
| -rw-r--r-- | mpm/python/usrp_mpm/dboard_manager/x4xx_db_iface.py | 144 | ||||
| -rw-r--r-- | mpm/python/usrp_mpm/dboard_manager/x4xx_debug_db.py | 152 | ||||
| -rw-r--r-- | mpm/python/usrp_mpm/dboard_manager/x4xx_if_test_cca.py | 167 | ||||
| -rw-r--r-- | mpm/python/usrp_mpm/dboard_manager/zbx.py | 461 | ||||
| -rw-r--r-- | mpm/python/usrp_mpm/dboard_manager/zbx_update_cpld.py | 218 | 
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()) | 
