1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167
|
#
# Copyright 2022 Ettus Research, a National Instruments Brand
#
# SPDX-License-Identifier: GPL-3.0-or-later
#
"""
Mixin class for daughterboard classes that live on a X4xx motherboard
"""
from usrp_mpm.mpmlog import get_logger
from usrp_mpm import tlv_eeprom
from usrp_mpm.sys_utils.udev import get_eeprom_paths_by_symbol
from usrp_mpm.rpc_utils import no_rpc
# 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']),
}
class X4xxDbMixin:
"""
Mixin class for daughterboards that live in an X4xx motherboard.
"""
# Make sure to populated this in the base class
DBOARD_SUPPORTED_COMPAT_REVS = ()
def __init__(self, product_name, slot_idx, **kwargs):
self.log = get_logger(f"{product_name}-{slot_idx}")
self.log.trace("Initializing %s daughterboard, slot index %d",
product_name, slot_idx)
super().__init__(slot_idx, **kwargs)
self.eeprom_symbol = f"db{slot_idx}_eeprom"
self.product_name = product_name
# Get MB interface
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.enable_base_power()
assert self.DBOARD_SUPPORTED_COMPAT_REVS
# This allows overriding the rev_compat from the EEPROM
rev_compat = kwargs.get('rev_compat') if 'rev_compat' in kwargs else \
self.get_eeprom()['rev_compat']
self._assert_rev_compatibility(rev_compat)
self._assert_mb_cpld_compatibility(self.db_iface.mboard.cpld_control)
###########################################################################
# Init helpers
###########################################################################
@no_rpc
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 _assert_rev_compatibility(self, rev_compat):
"""
Check the daughterboard hardware revision compatibility with this driver.
Throws a RuntimeError() if this version of MPM does not recognize the
hardware.
Note: The CPLD image version is checked separately.
"""
if rev_compat not in self.DBOARD_SUPPORTED_COMPAT_REVS:
err = \
f"This MPM version is not compatible with this {self.product_name}" \
f"daughterboard. Found rev_compat value: 0x{rev_compat:02x}. " \
"Please update your MPM version to support this daughterboard revision."
self.log.error(err)
raise RuntimeError(err)
def _assert_mb_cpld_compatibility(self, mb_cpld_ctrl):
"""
Verify that the MB CPLD is compatible with this daughterboard.
Throws a RuntimeError() if that's not the case.
"""
if not any(pid in self.pids for pid in mb_cpld_ctrl.COMPATIBLE_DB_PIDS):
err = \
f"This {self.product_name} daughterboard is not compatible with " \
f"the motherboard CPLD image (CPLD signature: {mb_cpld_ctrl.SIGNATURE:X}). " \
f"Please update the motherboard CPLD image."
self.log.error(err)
raise RuntimeError(err)
@no_rpc
def enable_base_power(self, enable=True):
"""
Enables or disables power to the DB.
"""
if enable:
self.db_iface.enable_daughterboard(enable=True)
if not self.db_iface.check_enable_daughterboard():
self.db_iface.enable_daughterboard(enable=False)
err_msg = f"{self.product_name} {self.slot_idx} power up failed"
self.log.error(err_msg)
raise RuntimeError(err_msg)
else: # disable
# Removing power from the CPLD will set all 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():
err_msg = f"{self.product_name} {self.slot_idx} power down failed"
self.log.error(err_msg)
###########################################################################
# Clocking and RFDC interface
###########################################################################
def get_rfdc_rate_sensor(self, _):
"""
Return the RFDC rate (the ADC/DAC converter rate) of this daughterboard's
converters as a sensor value.
"""
return {
'name': 'rfdc_rate',
'type': 'REALNUM',
'unit': 'Hz',
'value': str(self.get_dboard_sample_rate()),
}
###########################################################################
# Clocking and RFDC interface
###########################################################################
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. For ZBX, 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 get_master_clock_rate(self):
"""
Return the master clock rate. This is the rate that UHD cares about,
in the 125-500 MHz range for X410/ZBX.
"""
return self.db_iface.mboard.clk_mgr.get_master_clock_rate(self.slot_idx)
|