aboutsummaryrefslogtreecommitdiffstats
path: root/mpm/python
diff options
context:
space:
mode:
authorMartin Braun <martin.braun@ettus.com>2021-12-06 10:30:29 +0100
committerAaron Rossetto <aaron.rossetto@ni.com>2021-12-09 13:09:28 -0800
commitdb5ce31b0e60f4409afba2fa42602a404d8e122f (patch)
treea881b55fb00b220ab21f1cc72af6b79a5627d6b9 /mpm/python
parent370e8766daddec469244e4b5c9bac6dac7da3621 (diff)
downloaduhd-db5ce31b0e60f4409afba2fa42602a404d8e122f.tar.gz
uhd-db5ce31b0e60f4409afba2fa42602a404d8e122f.tar.bz2
uhd-db5ce31b0e60f4409afba2fa42602a404d8e122f.zip
mpm: x4xx: Move DioControl into its own module
No functional changes whatsoever, only: - Move DioControl to its own module (x4xx_dio_control.py) - Where PyLint was complaining about whitespace issues, fix those - Fix import list in x4xx_periphs.py after removing DioControl - Fix import list in x4xx.py to import DioControl from the correct location
Diffstat (limited to 'mpm/python')
-rw-r--r--mpm/python/usrp_mpm/periph_manager/CMakeLists.txt1
-rw-r--r--mpm/python/usrp_mpm/periph_manager/x4xx.py2
-rw-r--r--mpm/python/usrp_mpm/periph_manager/x4xx_dio_control.py912
-rw-r--r--mpm/python/usrp_mpm/periph_manager/x4xx_periphs.py881
4 files changed, 914 insertions, 882 deletions
diff --git a/mpm/python/usrp_mpm/periph_manager/CMakeLists.txt b/mpm/python/usrp_mpm/periph_manager/CMakeLists.txt
index 1ca96f53e..07f079fee 100644
--- a/mpm/python/usrp_mpm/periph_manager/CMakeLists.txt
+++ b/mpm/python/usrp_mpm/periph_manager/CMakeLists.txt
@@ -22,6 +22,7 @@ set(USRP_MPM_PERIPHMGR_FILES
${CMAKE_CURRENT_SOURCE_DIR}/x4xx_periphs.py
${CMAKE_CURRENT_SOURCE_DIR}/x4xx_clk_aux.py
${CMAKE_CURRENT_SOURCE_DIR}/x4xx_clk_mgr.py
+ ${CMAKE_CURRENT_SOURCE_DIR}/x4xx_dio_control.py
${CMAKE_CURRENT_SOURCE_DIR}/x4xx_sample_pll.py
${CMAKE_CURRENT_SOURCE_DIR}/x4xx_reference_pll.py
${CMAKE_CURRENT_SOURCE_DIR}/x4xx_update_cpld.py
diff --git a/mpm/python/usrp_mpm/periph_manager/x4xx.py b/mpm/python/usrp_mpm/periph_manager/x4xx.py
index 445d905df..469ce50a0 100644
--- a/mpm/python/usrp_mpm/periph_manager/x4xx.py
+++ b/mpm/python/usrp_mpm/periph_manager/x4xx.py
@@ -27,7 +27,7 @@ from usrp_mpm.periph_manager import PeriphManagerBase
from usrp_mpm.xports import XportMgrUDP
from usrp_mpm.periph_manager.x4xx_periphs import MboardRegsControl
from usrp_mpm.periph_manager.x4xx_periphs import CtrlportRegs
-from usrp_mpm.periph_manager.x4xx_periphs import DioControl
+from usrp_mpm.periph_manager.x4xx_dio_control import DioControl
from usrp_mpm.periph_manager.x4xx_periphs import QSFPModule
from usrp_mpm.periph_manager.x4xx_periphs import get_temp_sensor
from usrp_mpm.periph_manager.x4xx_mb_cpld import MboardCPLD
diff --git a/mpm/python/usrp_mpm/periph_manager/x4xx_dio_control.py b/mpm/python/usrp_mpm/periph_manager/x4xx_dio_control.py
new file mode 100644
index 000000000..08d211bd6
--- /dev/null
+++ b/mpm/python/usrp_mpm/periph_manager/x4xx_dio_control.py
@@ -0,0 +1,912 @@
+#
+# Copyright 2021 Ettus Research, a National Instruments Brand
+#
+# SPDX-License-Identifier: GPL-3.0-or-later
+#
+"""
+X4xx DIO Control
+"""
+
+import re
+import signal
+from multiprocessing import Process, Event, Value
+from usrp_mpm.sys_utils.gpio import Gpio
+from usrp_mpm.mpmutils import poll_with_timeout
+
+class DioControl:
+ """
+ DioControl acts as front end for DIO AUX BOARD
+
+ The DioControl class uses three hardware resources to control the behavior
+ of the board, which are
+ * I2C extender
+ * MB registers
+ * MB cpld registers
+
+ DioControl supports arbitrary methods of addressing the pins on the
+ frontend. The current implementation supports two ways of pin addressing:
+ HDMI and DIO. Use set_port_mapping to switch between both of them.
+
+ When using HDMI as pin addressing scheme you have to give the real pin
+ number of the HDMI adapter like this::
+ ┌───────────────────────────────┐
+ └┐19 17 15 13 11 09 07 05 03 01┌┘
+ └┐ 18 16 14 12 10 08 06 04 02┌┘
+ └───────────────────────────┘
+ Be aware that not all pins are accessible. The DioControl class will warn
+ about the usage of unassigned pins.
+
+ The second option is the DIO addressing scheme. Here all user accessible
+ pins are numbered along the HDMI pin numbers which gives a pin table like
+ this::
+ ┌───────────────────────────────┐
+ └┐11 -- 09 08 -- 05 04 -- 01 00┌┘
+ └┐ -- 10 -- 07 06 -- 03 02 --┌┘
+ └───────────────────────────┘
+
+ Within the MPM shell one can query the state of the DIO board using
+ dio_status. This gives an output like this::
+ HDMI mapping | PORT A | PORT B
+ --------------+-------------------------+-------------------------
+ voltage | OFF - PG:NO - EXT:OFF | 1V8 - PG:YES - EXT:OFF
+ --------------+-------------------------+-------------------------
+ master | 0.. 00.0 0.00 .00. 00.1 | 0.. 00.0 0.00 .00. 00.1
+ direction | 0.. 00.0 0.00 .00. 00.1 | 0.. 00.0 0.00 .00. 00.0
+ --------------+-------------------------+-------------------------
+ output | 0.. 00.0 0.00 .00. 00.1 | 0.. 00.0 0.00 .00. 00.0
+ input | 0.. 00.0 0.00 .00. 00.1 | 0.. 00.0 0.00 .00. 00.0
+ The table displays the current state of HDMI port A and B provided by the
+ DIO board as well as the state of the corresponding register maps and GPIO
+ pins in a user readable form.
+
+ The header shows the active mapping and the port names. Change the
+ mapping with set_port_mapping.
+
+ The first row shows the voltage state of each port. Voltage can be one of
+ the states in (OFF, 1V8, 2V5, 3V3). Change the power state by using
+ set_voltage_level. When the voltage level is set to OFF, the corresponding
+ GPIO pin EN_PORT is set low, high otherwise.
+ When voltage is set to one of the other states EN_PORT<x> is set to high and
+ EN_PORT<x>_<?V?> is set accordingly where 1V8 corresponds to 2V5 and 3V3
+ being both low. PG shows whether the PG (power good) pin corresponding to
+ the port (PORT<x>_PG) is high. This is NO if power is OFF and YES otherwise.
+ EXT shows whether EN_EXT_PWR_<x> is enabled for the port. Change the
+ external power using set_external_power.
+ Note: A port must have a reasonable voltage level assigned to it before
+ changes to the output register takes effect on the HDMI port pins or
+ pin states of the HDMI port can be read in input register.
+
+ The master row shows the pin assignments for the master register which
+ decides whether PS (1) or FPGA (0) drives output register pins. Change
+ values using set_master_pin(s).
+
+ The direction row shows the pin assignments for the direction register which
+ decides whether the pin is written (1) or read (0) by the FPGA. Change
+ values using set_direction_pin(s).
+
+ The output and input rows shows the pin assignments for the FPGAs input and
+ output registers. Only the output register pins can be changed. Change
+ values using set_output_pin(s).
+
+ """
+ # For this, we want our own formatting rules
+ # pylint: disable=bad-whitespace
+
+ # Available DIO ports
+ DIO_PORTS = ("PORTA", "PORTB")
+ # Available voltage levels
+ DIO_VOLTAGE_LEVELS = ("OFF", "1V8", "2V5", "3V3")
+
+ # For each mapping supported by DioControl the class needs the following
+ # information
+ # * map_name: name of the mapping (in uppercase)
+ # * pin_names: names of the pins starting with smallest. Unassignable PINS
+ # must be named as well
+ # * port_map: mapping table from FPGA register indices to pin indices. A
+ # mapping of (4, 2) means pin 4 is mapped to register bit 0 and
+ # pin2 is mapped to register bit 1. Only assignable pins
+ # may appear in the mapping.
+ # * first_pin: index of the first pin for the mapping
+
+ # HDMI mapping constants
+ HDMI_MAP_NAME = "HDMI"
+ HDMI_PIN_NAMES = ("Data2+", "Data2_SHD", "Data2-", "Data1+", "Data1_SHD",
+ "Data1-", "Data0+", "Data0_SHD", "Data0-", "CLK+",
+ "CLK_SHD", "CLK-", "RESERVED", "HEC_Data-", "SCL",
+ "SDA", "HEC_GND", "V+", "HEC_Data+")
+ HDMI_PORT_MAP = {
+ DIO_PORTS[0]: ( 3, 1, 4, 6, 9, 7, 10, 12, 15, 13, 16, 19),
+ DIO_PORTS[1]: (16, 19, 15, 13, 10, 12, 9, 7, 4, 6, 3, 1)
+ }
+ HDMI_FIRST_PIN = 1
+
+ # DIO mapping constants
+ DIO_MAP_NAME = "DIO"
+ DIO_PIN_NAMES = ("DIO0", "DIO1", "DIO2", "DIO3", "DIO4", "DIO5",
+ "DIO6", "DIO7", "DIO8", "DIO9", "DIO10", "DIO11")
+ DIO_PORT_MAP = {
+ DIO_PORTS[0]: (1, 0, 2, 3, 5, 4, 6, 7, 9, 8, 10, 11),
+ DIO_PORTS[1]: (10, 11, 9, 8, 6, 7, 5, 4, 2, 3, 1, 0)
+ }
+ DIO_FIRST_PIN = 0
+
+ # Register layout/size constants
+ PORT_BIT_SIZE = 16 # number of bits used in register per port
+ PORT_USED_BITS_MASK = 0xFFF # masks out lower 12 of 16 bits used per port
+
+ # DIO registers addresses in FPGA
+ FPGA_DIO_REGISTER_BASE = 0x2000
+ FPGA_DIO_MASTER_REGISTER = FPGA_DIO_REGISTER_BASE
+ FPGA_DIO_DIRECTION_REGISTER = FPGA_DIO_REGISTER_BASE + 0x4
+ FPGA_DIO_INPUT_REGISTER = FPGA_DIO_REGISTER_BASE + 0x8
+ FPGA_DIO_OUTPUT_REGISTER = FPGA_DIO_REGISTER_BASE + 0xC
+ FPGA_DIO_SOURCE_REGISTER = FPGA_DIO_REGISTER_BASE + 0x10
+ FPGA_DIO_RADIO_SOURCE_REGISTER = FPGA_DIO_REGISTER_BASE + 0x14
+ FPGA_DIO_INTERFACE_DIO_SELECT_REGISTER = FPGA_DIO_REGISTER_BASE + 0x18
+ FPGA_DIO_OVERRIDE_REGISTER = FPGA_DIO_REGISTER_BASE + 0x1C
+ FPGA_DIO_SW_DIO_CONTROL_REGISTER = FPGA_DIO_REGISTER_BASE + 0x20
+
+ FULL_DIO_FPGA_COMPAT = (7, 5)
+
+ # DIO register addresses
+ RADIO_DIO_REGISTER_BASE = 0x8C000
+ RADIO_DIO_CLASSIC_ATR_CONFIG_REGISTER = RADIO_DIO_REGISTER_BASE + 0x40
+
+ # DIO registers addresses in CPLD
+ CPLD_DIO_DIRECTION_REGISTER = 0x30
+
+ # GPIO attributes
+ X4XX_GPIO_BANKS = ["GPIO0", "GPIO1"]
+ X4XX_GPIO_SRC_PS = "PS"
+ X4XX_GPIO_SRC_MPM = "MPM"
+ X4XX_GPIO_SRC_USER_APP = "USER_APP"
+ X4XX_GPIO_SRC_RADIO = [
+ ["DB0_RF0", "DB0_RF1", "DB0_SPI"],
+ ["DB1_RF0", "DB1_RF1", "DB1_SPI"]
+ ]
+ X4XX_GPIO_WIDTH = 12
+ # pylint: enable=bad-whitespace
+
+ class _PortMapDescriptor:
+ """
+ Helper class to hold port mapping relevant information
+ """
+ def __init__(self, name, pin_names, pin_map, first_pin):
+ self.name = name
+ self.pin_names = pin_names
+ self.map = pin_map
+ self.first_pin = first_pin
+
+
+ class _PortControl:
+ """
+ Helper class for controlling ports on the I2C expander
+ """
+ def __init__(self, port):
+ assert port in DioControl.DIO_PORTS
+ prefix = "DIOAUX_%s" % port
+
+ self.enable = Gpio('%s_ENABLE' % prefix, Gpio.OUTPUT)
+ self.en_3v3 = Gpio('%s_3V3' % prefix, Gpio.OUTPUT)
+ self.en_2v5 = Gpio('%s_2V5' % prefix, Gpio.OUTPUT)
+ self.ext_pwr = Gpio('%s_ENABLE_EXT_PWR' % prefix, Gpio.OUTPUT)
+ self.power_good = Gpio('%s_PWR_GOOD' % prefix, Gpio.INPUT)
+
+
+ class _GpioReg:
+ """
+ Helper class for manipulating GPIO source configuration registers of this form:
+ [31..28]: Reserved
+ [27..16]: Port B
+ [15..12]: Reserved
+ [11..0]: Port A
+ """
+
+ def __init__(self, dio_control, bank, offset):
+ self.offset = offset
+ self.value = dio_control.mboard_regs.peek32(offset)
+ self.mboard_regs = dio_control.mboard_regs
+ self.bank_offset = 0 if bank == dio_control.X4XX_GPIO_BANKS[0] else 16
+
+ def set_pin(self, pin_index, value):
+ self.value &= ~(1 << (pin_index + self.bank_offset))
+ self.value |= (value << (pin_index + self.bank_offset))
+
+ def get_pin(self, pin_index):
+ return bool((self.value >> (pin_index + self.bank_offset)) & 0x1)
+
+ def save(self):
+ self.mboard_regs.poke32(self.offset, self.value)
+
+
+ def __init__(self, mboard_regs, mboard_cpld, log, dboards):
+ """
+ Initializes access to hardware components as well as creating known
+ port mappings
+ :param log: logger to be used for output
+ """
+ self.log = log.getChild(self.__class__.__name__)
+ self.port_control = {port: self._PortControl(port) for port in self.DIO_PORTS}
+ self.mboard_regs = mboard_regs
+ self.mboard_cpld = mboard_cpld
+
+ # initialize port mapping for HDMI and DIO
+ self.port_mappings = {}
+ self.mapping = None
+ self.port_mappings[self.HDMI_MAP_NAME] = self._PortMapDescriptor(
+ self.HDMI_MAP_NAME, self.HDMI_PIN_NAMES,
+ self.HDMI_PORT_MAP, self.HDMI_FIRST_PIN)
+ self.port_mappings[self.DIO_MAP_NAME] = self._PortMapDescriptor(
+ self.DIO_MAP_NAME, self.DIO_PIN_NAMES,
+ self.DIO_PORT_MAP, self.DIO_FIRST_PIN)
+ self.set_port_mapping(self.HDMI_MAP_NAME)
+ self.log.trace("Spawning DIO fault monitors...")
+ self._tear_down_monitor = Event()
+ self._dio_fault = {
+ "PORTA": Value('b', 0),
+ "PORTB": Value('b', 0),
+ }
+ self._dio0_fault_monitor = Process(
+ target=self._monitor_dio_fault,
+ args=('A', "DIO_INT0", self._tear_down_monitor, self._dio_fault["PORTA"])
+ )
+ self._dio1_fault_monitor = Process(
+ target=self._monitor_dio_fault,
+ args=('B', "DIO_INT1", self._tear_down_monitor, self._dio_fault["PORTB"])
+ )
+ signal.signal(signal.SIGINT, self._monitor_int_handler)
+ self._dio0_fault_monitor.start()
+ self._dio1_fault_monitor.start()
+
+ # Init GPIO sources
+ gpio_srcs = [
+ self.X4XX_GPIO_SRC_PS,
+ self.X4XX_GPIO_SRC_MPM,
+ self.X4XX_GPIO_SRC_USER_APP
+ ]
+ for dboard in dboards:
+ gpio_srcs.extend(self.X4XX_GPIO_SRC_RADIO[dboard.slot_idx])
+
+ self._gpio_srcs = {
+ gpio_bank : gpio_srcs for gpio_bank in self.X4XX_GPIO_BANKS
+ }
+
+ self.log.debug(f"Found the following GPIO sources: {', '.join(gpio_srcs)}")
+
+ self._current_voltage_level = {
+ "PORTA": "3V3",
+ "PORTB": "3V3",
+ }
+ self.set_voltage_level("PORTA", "3V3")
+ self.set_voltage_level("PORTB", "3V3")
+
+ def _monitor_dio_fault(self, dio_port, fault, tear_down, fault_state):
+ """
+ Monitor the DIO_INT lines to detect an external power fault.
+ If there is a fault, turn off external power.
+ """
+ self.log.trace("Launching monitor loop...")
+ fault_line = Gpio(fault, Gpio.FALLING_EDGE)
+ while True:
+ try:
+ if fault_line.event_wait():
+ # If we saw a fault, disable the external power
+ self.log.warning(
+ f"DIO fault occurred on port {dio_port} - "
+ "turning off external power")
+ self.set_external_power(dio_port, 0)
+ fault_state.value = 1
+ # If the event wait gets interrupted because we are trying to tear
+ # down then stop the monitoring process. If not, keep monitoring
+ except InterruptedError:
+ pass
+ if tear_down.is_set():
+ break
+
+ def _monitor_int_handler(self, _signum, _frame):
+ """
+ If we see an expected interrupt signal, mark the DIO fault monitors
+ for tear down.
+ """
+ self._tear_down_monitor.set()
+
+ # --------------------------------------------------------------------------
+ # Helper methods
+ # --------------------------------------------------------------------------
+ def _map_to_register_bit(self, port, pin):
+ """
+ Maps a pin denoted in current mapping scheme to a corresponding bit in
+ the register map.
+ :param port: port to do the mapping on
+ :param pin: pin (in current mapping scheme)
+ :return: bit position in register map
+ :raises RuntimeError: pin is not in range of current mapping scheme
+ or not user assignable.
+ """
+ assert isinstance(pin, int)
+ port = self._normalize_port_name(port)
+ first_pin = self.mapping.first_pin
+ last_pin = first_pin + len(self.mapping.pin_names) - 1
+ port_map = self.mapping.map[port]
+
+ if not first_pin <= pin <= last_pin:
+ raise RuntimeError("Pin must be in range [%d,%d]. Given pin: %d" %
+ (first_pin, last_pin, pin))
+ if pin not in port_map:
+ raise RuntimeError("Pin %d (%s) is not a user assignable pin." %
+ (pin,
+ self.mapping.pin_names[pin - first_pin]))
+
+ # map pin back to register bit
+ bit = port_map.index(pin)
+ # lift register bit up by PORT_BIT_SIZE for port b
+ bit = bit if port == self.DIO_PORTS[0] else bit + self.PORT_BIT_SIZE
+ return bit
+
+ def _calc_register_value(self, register, port, pin, value):
+ """
+ Recalculates register value.
+
+ Current register state is read and the bit that corresponds to the
+ values given by port and pin is determined. The register content is
+ changed at position of bit to what is given by value.
+
+ Note: This routine only reads the current and calculates the new
+ register value. It is up to the callee to set the register value.
+ :param register: Address of the register value to recalculate
+ :param port: port associated with pin
+ :param pin: pin to change (will be mapped to bit according to
+ current mapping scheme an given port)
+ :param value: new bit value to set
+ :return: new register value.
+ """
+ assert value in [0, 1]
+
+ content = self.mboard_regs.peek32(register)
+ bit = self._map_to_register_bit(port, pin)
+ content = (content | 1 << bit) if value == 1 else (content & ~(1 << bit))
+ return content
+
+ def _set_pin_values(self, port, values, set_method):
+ """
+ Helper method to assign multiple pins in one call.
+ :param port: Port to set pins on
+ :param values: New pin assignment represented by an integer. Each bit of
+ values corresponds to a pin on board according to current
+ mapping scheme. Bits that do not correspond to a pin in
+ the current mapping scheme are skipped.
+ :param set_method: method to be used to set/unset a pin. Signature of
+ set_method is (port, pin).
+ """
+ first_pin = self.mapping.first_pin
+ port = self._normalize_port_name(port)
+ for i, _ in enumerate(self.mapping.pin_names):
+ if i + first_pin in self.mapping.map[port]:
+ set_method(port, i + first_pin, int(values & 1 << i != 0))
+
+ # --------------------------------------------------------------------------
+ # Helper to convert abbreviations to constants defined in DioControl
+ # --------------------------------------------------------------------------
+
+ def _normalize_mapping(self, mapping):
+ """
+ Map name to one of the key in self.port_mappings.
+ :param mapping: mapping name or any abbreviation by removing letters
+ from the end of the name
+ :return: Key found for mapping name
+ :raises RuntimeError: no matching mapping could be found
+ """
+ assert isinstance(mapping, str)
+ mapping = mapping.upper()
+ mapping_names = self.port_mappings.keys()
+ try:
+ # search for abbr of mapping in mapping names
+ index = [
+ re.match("^%s" % mapping, name) is not None for name in mapping_names
+ ].index(True)
+ return list(self.port_mappings.keys())[index]
+ except ValueError:
+ raise RuntimeError("Mapping %s not found in %s" % (mapping, mapping_names))
+
+ def _normalize_port_name(self, name):
+ """
+ Map port name to the normalized form of self.DIO_PORTS
+ :param name: port name or abbreviation with A or B, case insensitive
+ :return: normalized port name
+ :raises RuntimeError: name could not be normalized
+ """
+ assert isinstance(name, str)
+ gpio0_names = (self.DIO_PORTS[0], self.X4XX_GPIO_BANKS[0], "A")
+ gpio1_names = (self.DIO_PORTS[1], self.X4XX_GPIO_BANKS[1], "B")
+ if name.upper() not in gpio0_names + gpio1_names:
+ raise RuntimeError("Could not map %s to port name" % name)
+ return self.DIO_PORTS[0] if name.upper() in gpio0_names \
+ else self.DIO_PORTS[1]
+
+ # --------------------------------------------------------------------------
+ # Helper to format status output
+ # --------------------------------------------------------------------------
+
+ def _get_port_voltage(self, port):
+ """
+ Format voltage table cell value.
+ """
+ port_control = self.port_control[port]
+ result = ""
+ if port_control.enable.get() == 0:
+ result += self.DIO_VOLTAGE_LEVELS[0]
+ elif port_control.en_2v5.get() == 1:
+ result += self.DIO_VOLTAGE_LEVELS[2]
+ elif port_control.en_3v3.get() == 1:
+ result += self.DIO_VOLTAGE_LEVELS[3]
+ else:
+ result += self.DIO_VOLTAGE_LEVELS[1]
+ result += " - PG:"
+ result += "YES" if port_control.power_good.get() else "NO"
+ result += " - EXT:"
+ result += "ON" if port_control.ext_pwr.get() else "OFF"
+ return result
+
+ def _get_voltage(self):
+ """
+ Format voltage table cells
+ """
+ return [self._get_port_voltage(port) for port in self.DIO_PORTS]
+
+ def _format_register(self, port, content):
+ """
+ Format a port value according to current mapping scheme. Pins are
+ grouped by 4. Pins which are not user assignable are marked with a dot.
+ :param content: register content
+ :return: register content as pin assignment according to current
+ mapping scheme
+ """
+ result = ""
+ first_pin = self.mapping.first_pin
+ pin_names = self.mapping.pin_names
+ mapping = self.mapping.map[port]
+ for i, _ in enumerate(pin_names):
+ if i % 4 == 0 and i > 0:
+ result = " " + result
+ if i + first_pin in mapping:
+ result = str(int(content & (1 << mapping.index(i + first_pin)) != 0)) + result
+ else:
+ result = "." + result
+ return result
+
+ def _format_registers(self, content):
+ """
+ Formats register content for port A and B
+ :param content:
+ :return:
+ """
+ port_a = content & self.PORT_USED_BITS_MASK
+ port_b = (content >> self.PORT_BIT_SIZE) & self.PORT_USED_BITS_MASK
+ return [self._format_register(self.DIO_PORTS[0], port_a),
+ self._format_register(self.DIO_PORTS[1], port_b)]
+
+ def _format_row(self, values, fill=" ", delim="|"):
+ """
+ Format a table row with fix colums widths. Generates row spaces using
+ value list with empty strings and "-" as fill and "+" as delim.
+ :param values: cell values (list of three elements)
+ :param fill: fill character to use (space by default)
+ :param delim: delimiter character between columns
+ :return: formated row
+ """
+ col_widths = [14, 25, 25]
+ return delim.join([
+ fill + values[i].ljust(width - len(fill), fill)
+ for i, width in enumerate(col_widths)
+ ]) + "\n"
+
+ def get_gpio_banks(self):
+ """
+ Returns a list of GPIO banks over which MPM has any control
+ """
+ if self.mboard_regs.get_compat_number() < self.FULL_DIO_FPGA_COMPAT:
+ return []
+
+ return self.X4XX_GPIO_BANKS
+
+ def get_gpio_srcs(self, bank: str):
+ """
+ Return a list of valid GPIO sources for a given bank
+ """
+ if self.mboard_regs.get_compat_number() < self.FULL_DIO_FPGA_COMPAT:
+ return ["MPM"]
+
+ assert bank in self.get_gpio_banks(), f"Invalid GPIO bank: {bank}"
+ return self._gpio_srcs[bank]
+
+ def get_gpio_src(self, bank: str):
+ """
+ Return the currently selected GPIO source for a given bank. The return
+ value is a list of strings. The length of the vector is identical to
+ the number of controllable GPIO pins on this bank. USER_APP is a GPIO
+ source that can be used in custom FPGA designs (e.g. LabView binary uses
+ this pin source).
+ """
+ if self.mboard_regs.get_compat_number() < self.FULL_DIO_FPGA_COMPAT:
+ return []
+
+ assert bank in self.get_gpio_banks(), f"Invalid GPIO bank: {bank}"
+
+ master_reg = self._GpioReg(self, bank, self.FPGA_DIO_MASTER_REGISTER)
+ source_reg = self._GpioReg(self, bank, self.FPGA_DIO_SOURCE_REGISTER)
+ radio_source_reg = self._GpioReg(self, bank, self.FPGA_DIO_RADIO_SOURCE_REGISTER)
+ interface_select_reg = self._GpioReg(
+ self, bank, self.FPGA_DIO_INTERFACE_DIO_SELECT_REGISTER)
+ override_reg = self._GpioReg(self, bank, self.FPGA_DIO_OVERRIDE_REGISTER)
+ sw_control_reg = self._GpioReg(self, bank, self.FPGA_DIO_SW_DIO_CONTROL_REGISTER)
+ classic_atr_config_reg = self._GpioReg(
+ self, bank, self.RADIO_DIO_CLASSIC_ATR_CONFIG_REGISTER)
+
+ def get_gpio_src_i(gpio_pin_index):
+ """
+ Return the current source given a pin index.
+ """
+ if source_reg.get_pin(gpio_pin_index):
+ if override_reg.get_pin(gpio_pin_index):
+ db = int(interface_select_reg.get_pin(gpio_pin_index))
+ return f"DB{db}_SPI"
+ else:
+ db = int(radio_source_reg.get_pin(gpio_pin_index))
+ ch = int(classic_atr_config_reg.get_pin(gpio_pin_index))
+ return f"DB{db}_RF{ch}"
+ else:
+ if master_reg.get_pin(gpio_pin_index):
+ if sw_control_reg.get_pin(gpio_pin_index):
+ return self.X4XX_GPIO_SRC_PS
+ else:
+ return self.X4XX_GPIO_SRC_MPM
+ else:
+ return self.X4XX_GPIO_SRC_USER_APP
+
+ return [get_gpio_src_i(i) for i in range(self.X4XX_GPIO_WIDTH)]
+
+ def set_gpio_src(self, bank: str, src):
+ """
+ Set the GPIO source for a given bank.
+ src input is big-endian
+ Usage:
+ > set_gpio_src <bank> <srcs>
+ > set_gpio_src GPIO0 PS DB1_RF0 PS PS MPM PS PS PS MPM USER_APP PS
+ """
+ if self.mboard_regs.get_compat_number() < self.FULL_DIO_FPGA_COMPAT:
+ # Older FPGAs only have a single GPIO source selector
+ master_reg = self._GpioReg(self, bank, self.FPGA_DIO_MASTER_REGISTER)
+ for pin_index, src_name in enumerate(src):
+ if src_name == "MPM":
+ master_reg.set_pin(pin_index, 1)
+ else:
+ master_reg.set_pin(pin_index, 0)
+ master_reg.save()
+ return
+
+ assert bank in self.get_gpio_banks(), f"Invalid GPIO bank: {bank}"
+ assert len(src) == self.X4XX_GPIO_WIDTH, \
+ f"Invalid number of GPIO sources! Expecting {self.X4XX_GPIO_WIDTH}, but got {len(src)}."
+
+ for pin_index, src_name in enumerate(src):
+ if src_name not in self.get_gpio_srcs(bank):
+ raise RuntimeError(
+ f"Invalid GPIO source name `{src_name}' at bit position {pin_index}!")
+
+ master_reg = self._GpioReg(self, bank, self.FPGA_DIO_MASTER_REGISTER)
+ source_reg = self._GpioReg(self, bank, self.FPGA_DIO_SOURCE_REGISTER)
+ radio_source_reg = self._GpioReg(self, bank, self.FPGA_DIO_RADIO_SOURCE_REGISTER)
+ interface_select_reg = self._GpioReg(
+ self, bank, self.FPGA_DIO_INTERFACE_DIO_SELECT_REGISTER)
+ override_reg = self._GpioReg(self, bank, self.FPGA_DIO_OVERRIDE_REGISTER)
+ sw_control_reg = self._GpioReg(self, bank, self.FPGA_DIO_SW_DIO_CONTROL_REGISTER)
+ classic_atr_config_reg = self._GpioReg(
+ self, bank, self.RADIO_DIO_CLASSIC_ATR_CONFIG_REGISTER)
+
+ for pin_index, src_name in enumerate(src):
+ radio_srcs = [
+ item for sublist in self.X4XX_GPIO_SRC_RADIO for item in sublist]
+ if src_name in radio_srcs:
+ source_reg.set_pin(pin_index, 1)
+ slot = int(src_name[2])
+ if src_name.endswith("_SPI"):
+ override_reg.set_pin(pin_index, 1)
+ interface_select_reg.set_pin(pin_index, slot)
+ else:
+ channel = int(src_name[6])
+ override_reg.set_pin(pin_index, 0)
+ radio_source_reg.set_pin(pin_index, slot)
+ classic_atr_config_reg.set_pin(pin_index, channel)
+ else:
+ source_reg.set_pin(pin_index, 0)
+ if src_name in (self.X4XX_GPIO_SRC_PS, self.X4XX_GPIO_SRC_MPM):
+ master_reg.set_pin(pin_index, 1)
+ sw_control_reg.set_pin(
+ pin_index, int(src_name == self.X4XX_GPIO_SRC_PS))
+ else:
+ master_reg.set_pin(pin_index, 0)
+
+ master_reg.save()
+ source_reg.save()
+ radio_source_reg.save()
+ interface_select_reg.save()
+ override_reg.save()
+ sw_control_reg.save()
+ classic_atr_config_reg.save()
+
+ # --------------------------------------------------------------------------
+ # Public API
+ # --------------------------------------------------------------------------
+
+ def tear_down(self):
+ """
+ Mark the DIO monitoring processes for tear down and terminate the processes
+ """
+ self._tear_down_monitor.set()
+ self._dio0_fault_monitor.terminate()
+ self._dio1_fault_monitor.terminate()
+ self._dio0_fault_monitor.join(3)
+ self._dio1_fault_monitor.join(3)
+ if self._dio0_fault_monitor.is_alive() or \
+ self._dio1_fault_monitor.is_alive():
+ self.log.warning("DIO monitor didn't exit properly")
+
+ def set_port_mapping(self, mapping):
+ """
+ Change the port mapping to mapping. Mapping must denote a mapping found
+ in this.port_mappings.keys() or any abbreviation allowed by
+ _normalize_port_mapping. The mapping does not change the status of the
+ FPGA registers. It only changes the status display and the way calls
+ to set_pin_<register_name>(s) are interpreted.
+ :param mapping: new mapping to be used
+ :raises RuntimeError: mapping could not be found
+ """
+ assert isinstance(mapping, str)
+ map_name = self._normalize_mapping(mapping)
+ if not map_name in self.port_mappings.keys():
+ raise RuntimeError("Could not map %s to port mapping" % mapping)
+ self.mapping = self.port_mappings[map_name]
+
+ def set_pin_direction(self, port, pin, value=1):
+ """
+ Set direction pin of a port. The direction pin decides whether the DIO
+ external pin is used as an output (write - value is 1) or input (read -
+ value is 0). To change the pin value the current register content is
+ read first and modified before it is written back, so the register must
+ be readable.
+ Besides the FPGA register map, the CPLD register map is also written. To
+ prevent the internal line to be driven by FGPA and DIO board at the same
+ time the CPLD register is written first if the direction will become an
+ output. If direction will become an input the FPGA register is written
+ first.
+ :param port: port to change direction assignment on
+ :param pin: pin to change
+ :param value: desired pin value
+ """
+ content = self._calc_register_value(self.FPGA_DIO_DIRECTION_REGISTER,
+ port, pin, value)
+ # When setting direction pin, order matters. Always switch the component
+ # first that will get the driver disabled.
+ # This ensures that there wont be two drivers active at a time.
+ if value == 1: # FPGA is driver => write DIO register first
+ self.mboard_cpld.poke32(self.CPLD_DIO_DIRECTION_REGISTER, content)
+ self.mboard_regs.poke32(self.FPGA_DIO_DIRECTION_REGISTER, content)
+ else: # DIO is driver => write FPGA register first
+ self.mboard_regs.poke32(self.FPGA_DIO_DIRECTION_REGISTER, content)
+ self.mboard_cpld.poke32(self.CPLD_DIO_DIRECTION_REGISTER, content)
+ # Read back values to ensure registers are in sync
+ cpld_content = self.mboard_cpld.peek32(self.CPLD_DIO_DIRECTION_REGISTER)
+ mbrd_content = self.mboard_regs.peek32(self.FPGA_DIO_DIRECTION_REGISTER)
+ if not ((cpld_content == content) and (mbrd_content == content)):
+ raise RuntimeError("Direction register content mismatch. Expected:"
+ "0x%0.8X, CPLD: 0x%0.8X, FPGA: 0x%0.8X." %
+ (content, cpld_content, mbrd_content))
+
+ def set_pin_directions(self, port, values):
+ """
+ Set all direction pins of a port at once using a bit mask.
+ :param port: port to change direction pin assignment
+ :param values: New pin assignment represented by an integer. Each bit of
+ values corresponds to a pin on board according to current
+ mapping scheme. Bits that do not correspond to a pin in
+ the current mapping scheme are skipped.
+ """
+ self._set_pin_values(port, values, self.set_pin_direction)
+
+ def set_pin_output(self, port, pin, value=1):
+ """
+ Set output value of a pin on a port. Setting this value only takes
+ effect if the direction of the corresponding pin of this port is set
+ accordingly. To change the pin value the current register content is
+ read first and modified before it is written back, so the register must
+ be readable.
+ :param port: port to change output assignment on
+ :param pin: pin to change
+ :param value: desired pin value
+ """
+ content = self._calc_register_value(self.FPGA_DIO_OUTPUT_REGISTER,
+ port, pin, value)
+ self.mboard_regs.poke32(self.FPGA_DIO_OUTPUT_REGISTER, content)
+
+ def set_pin_outputs(self, port, values):
+ """
+ Set all output pins of a port at once using a bit mask.
+ :param port: port to change direction pin assignment
+ :param values: New pin assignment represented by an integer. Each bit of
+ values corresponds to a pin on board according to current
+ mapping scheme. Bits that do not correspond to a pin in
+ the current mapping scheme are skipped.
+ """
+ self._set_pin_values(port, values, self.set_pin_output)
+
+ def get_pin_input(self, port, pin):
+ """
+ Returns the input pin value of a port.
+ If the pin is not assignable in the current mapping None is returned.
+
+ :param port: port to read pin value from
+ :param pin: pin value to read
+ :returns: actual pin value or None if pin is not assignable
+ """
+ port = self._normalize_port_name(port)
+
+ register = self.mboard_regs.peek32(self.FPGA_DIO_INPUT_REGISTER)
+ if port == self.DIO_PORTS[1]:
+ register = register >> self.PORT_BIT_SIZE
+ register &= self.PORT_USED_BITS_MASK
+
+ mapping = self.mapping.map[port]
+ if not pin in mapping:
+ raise RuntimeError("Pin %d (%s) is not a user readable pin." %
+ (pin,
+ self.mapping.pin_names[pin - self.mapping.first_pin]))
+ return 0 if (register & (1 << mapping.index(pin)) == 0) else 1
+
+ def get_pin_inputs(self, port):
+ """
+ Returns a bit mask of all pins for the given port.
+
+ :param port: port to read input pins from
+ :returns: Bit map of input pins, each bit of pins corresponds to a pin
+ on board according to current mapping scheme. Unused pins
+ stay zero
+ """
+ result = 0
+ first_pin = self.mapping.first_pin
+ pin_names = self.mapping.pin_names
+ port = self._normalize_port_name(port)
+ mapping = self.mapping.map[port]
+ for i, _name in enumerate(pin_names):
+ if i + first_pin in mapping:
+ if self.get_pin_input(port, i + first_pin):
+ result |= 1 << i
+ return result
+
+ def set_voltage_level(self, port, level):
+ """
+ Change voltage level of a port. This is how EN_<port>, EN_<port>_2V5 and
+ EN_<port>_3V3 are set according to level::
+ level EN_<port> EN_<port>_2V5 EN_<port>_3V3
+ off 0 0 0
+ 1V8 1 0 0
+ 2V5 1 1 0
+ 3V3 1 0 1
+ If level is set to anything other than off this method waits for
+ <port>_PG to go high. Waiting stops as soon as <port>_PG goes high or
+ a timeout of 1s occurs.
+ Note: All pins are set to zero first before the new level is applied.
+ :param port: port to change power level for
+ :param level: new power level
+ :raises RuntimeError: power good pin did not go high
+ """
+ port = self._normalize_port_name(port)
+ level = level.upper()
+ assert port in self.DIO_PORTS
+ assert level in self.DIO_VOLTAGE_LEVELS
+ port_control = self.port_control[port]
+
+ self._current_voltage_level[port] = level
+
+ port_control.enable.set(0)
+ port_control.en_2v5.set(0)
+ port_control.en_3v3.set(0)
+ if level == self.DIO_VOLTAGE_LEVELS[2]:
+ port_control.en_2v5.set(1)
+ elif level == self.DIO_VOLTAGE_LEVELS[3]:
+ port_control.en_3v3.set(1)
+
+ # wait for <port>_PG to go high
+ if not level == self.DIO_VOLTAGE_LEVELS[0]: # off
+ port_control.enable.set(1)
+ if not poll_with_timeout(
+ lambda: port_control.power_good.get() == 1, 1000, 10):
+ raise RuntimeError(
+ "Power good pin did not go high after power up")
+
+ def get_voltage_level(self, port):
+ """
+ Returns the current GPIO voltage level as set by set_voltage_level
+ """
+ port = self._normalize_port_name(port)
+ assert port in self.DIO_PORTS
+ return self._current_voltage_level[port]
+
+ def set_external_power(self, port, value):
+ """
+ Change EN_EXT_PWR_<port> to value.
+ :param port: port to change external power level for
+ :param value: 1 to enable external power, 0 to disable
+ :raise RuntimeError: port or pin value could not be mapped
+ """
+ port = self._normalize_port_name(port)
+ value = int(value)
+ assert value in (0, 1)
+ assert port in self.DIO_PORTS
+ self.port_control[port].ext_pwr.set(value)
+ self._dio_fault[port].value = 0
+
+ def get_external_power_state(self, port):
+ """
+ Returns the current state of the external power supply.
+
+ Usage:
+ > get_external_power_state PORTA
+ """
+ port = self._normalize_port_name(port)
+ if self._dio_fault[port].value == 1:
+ return "FAULT"
+ if self.port_control[port].ext_pwr.get() == 1:
+ return "ON"
+ return "OFF"
+
+ def get_supported_voltage_levels(self, port):
+ """
+ Returns the list of all supported voltage levels for the given port.
+ Note that, currently, all ports support all voltage levels, so we
+ simply validate that the given port name is valid.
+ """
+ _ = self._normalize_port_name(port)
+ return ["OFF", "1V8", "2V5", "3V3"]
+
+ def status(self):
+ """
+ Build a full status string for the DIO AUX board, including
+ I2C pin states and register content in a human readable form.
+ :return: board status
+ """
+ result = "\n" \
+ + self._format_row(["%s mapping" % self.mapping.name, self.DIO_PORTS[0], self.DIO_PORTS[1]]) \
+ + self._format_row(["", "", ""], "-", "+") \
+ + self._format_row(["voltage"] + self._get_voltage()) \
+ + self._format_row(["", "", ""], "-", "+")
+
+ register = self.mboard_regs.peek32(self.FPGA_DIO_MASTER_REGISTER)
+ result += self._format_row(["master"] + self._format_registers(register))
+
+ register = self.mboard_regs.peek32(self.FPGA_DIO_DIRECTION_REGISTER)
+ result += self._format_row(["direction"] + self._format_registers(register))
+
+ result += self._format_row(["", "", ""], "-", "+")
+
+ register = self.mboard_regs.peek32(self.FPGA_DIO_OUTPUT_REGISTER)
+ result += self._format_row(["output"] + self._format_registers(register))
+
+ register = self.mboard_regs.peek32(self.FPGA_DIO_INPUT_REGISTER)
+ result += self._format_row(["input"] + self._format_registers(register))
+ return result
+
+ def debug(self):
+ """
+ Create a debug string containing the FPGA register maps. The CPLD
+ direction register is not part of the string as the DioControl maintains
+ it in sync with the FPGA direction register.
+ :return: register states for debug purpose in human readable form.
+ """
+ master = format(self.mboard_regs.peek32(self.FPGA_DIO_MASTER_REGISTER), "032b")
+ direction = format(self.mboard_regs.peek32(self.FPGA_DIO_DIRECTION_REGISTER), "032b")
+ output = format(self.mboard_regs.peek32(self.FPGA_DIO_OUTPUT_REGISTER), "032b")
+ input_reg = format(self.mboard_regs.peek32(self.FPGA_DIO_INPUT_REGISTER), "032b")
+ return "\nmaster: " + " ".join(re.findall('....', master)) + "\n" + \
+ "direction: " + " ".join(re.findall('....', direction)) + "\n" + \
+ "output: " + " ".join(re.findall('....', output)) + "\n" + \
+ "input: " + " ".join(re.findall('....', input_reg))
diff --git a/mpm/python/usrp_mpm/periph_manager/x4xx_periphs.py b/mpm/python/usrp_mpm/periph_manager/x4xx_periphs.py
index 036300372..1bea76615 100644
--- a/mpm/python/usrp_mpm/periph_manager/x4xx_periphs.py
+++ b/mpm/python/usrp_mpm/periph_manager/x4xx_periphs.py
@@ -7,11 +7,8 @@
X4xx peripherals
"""
-import re
import time
-import signal
import struct
-from multiprocessing import Process, Event, Value
from statistics import mean
from usrp_mpm import lib # Pulls in everything from C++-land
from usrp_mpm.sys_utils import i2c_dev
@@ -21,884 +18,6 @@ from usrp_mpm.mpmutils import poll_with_timeout
from usrp_mpm.sys_utils.sysfs_thermal import read_thermal_sensor_value
from usrp_mpm.periph_manager.common import MboardRegsCommon
-class DioControl:
- """
- DioControl acts as front end for DIO AUX BOARD
-
- The DioControl class uses three hardware resources to control the behavior
- of the board, which are
- * I2C extender
- * MB registers
- * MB cpld registers
-
- DioControl supports arbitrary methods of addressing the pins on the
- frontend. The current implementation supports two ways of pin addressing:
- HDMI and DIO. Use set_port_mapping to switch between both of them.
-
- When using HDMI as pin addressing scheme you have to give the real pin
- number of the HDMI adapter like this::
- ┌───────────────────────────────┐
- └┐19 17 15 13 11 09 07 05 03 01┌┘
- └┐ 18 16 14 12 10 08 06 04 02┌┘
- └───────────────────────────┘
- Be aware that not all pins are accessible. The DioControl class will warn
- about the usage of unassigned pins.
-
- The second option is the DIO addressing scheme. Here all user accessible
- pins are numbered along the HDMI pin numbers which gives a pin table like
- this::
- ┌───────────────────────────────┐
- └┐11 -- 09 08 -- 05 04 -- 01 00┌┘
- └┐ -- 10 -- 07 06 -- 03 02 --┌┘
- └───────────────────────────┘
-
- Within the MPM shell one can query the state of the DIO board using
- dio_status. This gives an output like this::
- HDMI mapping | PORT A | PORT B
- --------------+-------------------------+-------------------------
- voltage | OFF - PG:NO - EXT:OFF | 1V8 - PG:YES - EXT:OFF
- --------------+-------------------------+-------------------------
- master | 0.. 00.0 0.00 .00. 00.1 | 0.. 00.0 0.00 .00. 00.1
- direction | 0.. 00.0 0.00 .00. 00.1 | 0.. 00.0 0.00 .00. 00.0
- --------------+-------------------------+-------------------------
- output | 0.. 00.0 0.00 .00. 00.1 | 0.. 00.0 0.00 .00. 00.0
- input | 0.. 00.0 0.00 .00. 00.1 | 0.. 00.0 0.00 .00. 00.0
- The table displays the current state of HDMI port A and B provided by the
- DIO board as well as the state of the corresponding register maps and GPIO
- pins in a user readable form.
-
- The header shows the active mapping and the port names. Change the
- mapping with set_port_mapping.
-
- The first row shows the voltage state of each port. Voltage can be one of
- the states in (OFF, 1V8, 2V5, 3V3). Change the power state by using
- set_voltage_level. When the voltage level is set to OFF, the corresponding
- GPIO pin EN_PORT is set low, high otherwise.
- When voltage is set to one of the other states EN_PORT<x> is set to high and
- EN_PORT<x>_<?V?> is set accordingly where 1V8 corresponds to 2V5 and 3V3
- being both low. PG shows whether the PG (power good) pin corresponding to
- the port (PORT<x>_PG) is high. This is NO if power is OFF and YES otherwise.
- EXT shows whether EN_EXT_PWR_<x> is enabled for the port. Change the
- external power using set_external_power.
- Note: A port must have a reasonable voltage level assigned to it before
- changes to the output register takes effect on the HDMI port pins or
- pin states of the HDMI port can be read in input register.
-
- The master row shows the pin assignments for the master register which
- decides whether PS (1) or FPGA (0) drives output register pins. Change
- values using set_master_pin(s).
-
- The direction row shows the pin assignments for the direction register which
- decides whether the pin is written (1) or read (0) by the FPGA. Change
- values using set_direction_pin(s).
-
- The output and input rows shows the pin assignments for the FPGAs input and
- output registers. Only the output register pins can be changed. Change
- values using set_output_pin(s).
-
- """
-
- # Available DIO ports
- DIO_PORTS = ("PORTA", "PORTB")
- # Available voltage levels
- DIO_VOLTAGE_LEVELS = ("OFF", "1V8", "2V5", "3V3")
-
- # For each mapping supported by DioControl the class needs the following
- # information
- # * map_name: name of the mapping (in uppercase)
- # * pin_names: names of the pins starting with smallest. Unassignable PINS
- # must be named as well
- # * port_map: mapping table from FPGA register indices to pin indices. A
- # mapping of (4, 2) means pin 4 is mapped to register bit 0 and
- # pin2 is mapped to register bit 1. Only assignable pins
- # may appear in the mapping.
- # * first_pin: index of the first pin for the mapping
-
- # HDMI mapping constants
- HDMI_MAP_NAME = "HDMI"
- HDMI_PIN_NAMES = ("Data2+", "Data2_SHD", "Data2-", "Data1+", "Data1_SHD",
- "Data1-", "Data0+", "Data0_SHD", "Data0-", "CLK+",
- "CLK_SHD", "CLK-", "RESERVED", "HEC_Data-", "SCL",
- "SDA", "HEC_GND", "V+", "HEC_Data+")
- HDMI_PORT_MAP = {DIO_PORTS[0]: (3, 1, 4, 6, 9, 7, 10, 12, 15, 13, 16, 19),
- DIO_PORTS[1]: (16, 19, 15, 13, 10, 12, 9, 7, 4, 6, 3, 1)}
- HDMI_FIRST_PIN = 1
-
- # DIO mapping constants
- DIO_MAP_NAME = "DIO"
- DIO_PIN_NAMES = ("DIO0", "DIO1", "DIO2", "DIO3", "DIO4", "DIO5",
- "DIO6", "DIO7", "DIO8", "DIO9", "DIO10", "DIO11")
- DIO_PORT_MAP = {DIO_PORTS[0]: (1, 0, 2, 3, 5, 4, 6, 7, 9, 8, 10, 11),
- DIO_PORTS[1]: (10, 11, 9, 8, 6, 7, 5, 4, 2, 3, 1, 0)}
- DIO_FIRST_PIN = 0
-
- # Register layout/size constants
- PORT_BIT_SIZE = 16 # number of bits used in register per port
- PORT_USED_BITS_MASK = 0xFFF # masks out lower 12 of 16 bits used per port
-
- # DIO registers addresses in FPGA
- FPGA_DIO_REGISTER_BASE = 0x2000
- FPGA_DIO_MASTER_REGISTER = FPGA_DIO_REGISTER_BASE
- FPGA_DIO_DIRECTION_REGISTER = FPGA_DIO_REGISTER_BASE + 0x4
- FPGA_DIO_INPUT_REGISTER = FPGA_DIO_REGISTER_BASE + 0x8
- FPGA_DIO_OUTPUT_REGISTER = FPGA_DIO_REGISTER_BASE + 0xC
- FPGA_DIO_SOURCE_REGISTER = FPGA_DIO_REGISTER_BASE + 0x10
- FPGA_DIO_RADIO_SOURCE_REGISTER = FPGA_DIO_REGISTER_BASE + 0x14
- FPGA_DIO_INTERFACE_DIO_SELECT_REGISTER = FPGA_DIO_REGISTER_BASE + 0x18
- FPGA_DIO_OVERRIDE_REGISTER = FPGA_DIO_REGISTER_BASE + 0x1C
- FPGA_DIO_SW_DIO_CONTROL_REGISTER = FPGA_DIO_REGISTER_BASE + 0x20
-
- FULL_DIO_FPGA_COMPAT = (7, 5)
-
- # DIO register addresses
- RADIO_DIO_REGISTER_BASE = 0x8C000
- RADIO_DIO_CLASSIC_ATR_CONFIG_REGISTER = RADIO_DIO_REGISTER_BASE + 0x40
-
- # DIO registers addresses in CPLD
- CPLD_DIO_DIRECTION_REGISTER = 0x30
-
- # GPIO attributes
- X4XX_GPIO_BANKS = ["GPIO0", "GPIO1"]
- X4XX_GPIO_SRC_PS = "PS"
- X4XX_GPIO_SRC_MPM = "MPM"
- X4XX_GPIO_SRC_USER_APP = "USER_APP"
- X4XX_GPIO_SRC_RADIO = [
- ["DB0_RF0", "DB0_RF1", "DB0_SPI"],
- ["DB1_RF0", "DB1_RF1", "DB1_SPI"]
- ]
- X4XX_GPIO_WIDTH = 12
-
- class _PortMapDescriptor:
- """
- Helper class to hold port mapping relevant information
- """
- def __init__(self, name, pin_names, map, first_pin):
- self.name = name
- self.pin_names = pin_names
- self.map = map
- self.first_pin = first_pin
-
-
- class _PortControl:
- """
- Helper class for controlling ports on the I2C expander
- """
- def __init__(self, port):
- assert port in DioControl.DIO_PORTS
- prefix = "DIOAUX_%s" % port
-
- self.enable = Gpio('%s_ENABLE' % prefix, Gpio.OUTPUT)
- self.en_3v3 = Gpio('%s_3V3' % prefix, Gpio.OUTPUT)
- self.en_2v5 = Gpio('%s_2V5' % prefix, Gpio.OUTPUT)
- self.ext_pwr = Gpio('%s_ENABLE_EXT_PWR' % prefix, Gpio.OUTPUT)
- self.power_good = Gpio('%s_PWR_GOOD' % prefix, Gpio.INPUT)
-
-
- class _GpioReg:
- """
- Helper class for manipulating GPIO source configuration registers of this form:
- [31..28]: Reserved
- [27..16]: Port B
- [15..12]: Reserved
- [11..0]: Port A
- """
-
- def __init__(self, dio_control, bank, offset):
- self.offset = offset
- self.value = dio_control.mboard_regs.peek32(offset)
- self.mboard_regs = dio_control.mboard_regs
- self.bank_offset = 0 if bank == dio_control.X4XX_GPIO_BANKS[0] else 16
-
- def set_pin(self, pin_index, value):
- self.value &= ~(1 << (pin_index + self.bank_offset))
- self.value |= (value << (pin_index + self.bank_offset))
-
- def get_pin(self, pin_index):
- return bool((self.value >> (pin_index + self.bank_offset)) & 0x1)
-
- def save(self):
- self.mboard_regs.poke32(self.offset, self.value)
-
-
- def __init__(self, mboard_regs, mboard_cpld, log, dboards):
- """
- Initializes access to hardware components as well as creating known
- port mappings
- :param log: logger to be used for output
- """
- self.log = log.getChild(self.__class__.__name__)
- self.port_control = {port: self._PortControl(port) for port in self.DIO_PORTS}
- self.mboard_regs = mboard_regs
- self.mboard_cpld = mboard_cpld
-
- # initialize port mapping for HDMI and DIO
- self.port_mappings = {}
- self.mapping = None
- self.port_mappings[self.HDMI_MAP_NAME] = self._PortMapDescriptor(
- self.HDMI_MAP_NAME, self.HDMI_PIN_NAMES,
- self.HDMI_PORT_MAP, self.HDMI_FIRST_PIN)
- self.port_mappings[self.DIO_MAP_NAME] = self._PortMapDescriptor(
- self.DIO_MAP_NAME, self.DIO_PIN_NAMES,
- self.DIO_PORT_MAP, self.DIO_FIRST_PIN)
- self.set_port_mapping(self.HDMI_MAP_NAME)
- self.log.trace("Spawning DIO fault monitors...")
- self._tear_down_monitor = Event()
- self._dio_fault = {
- "PORTA": Value('b', 0),
- "PORTB": Value('b', 0),
- }
- self._dio0_fault_monitor = Process(
- target=self._monitor_dio_fault,
- args=('A', "DIO_INT0", self._tear_down_monitor, self._dio_fault["PORTA"])
- )
- self._dio1_fault_monitor = Process(
- target=self._monitor_dio_fault,
- args=('B', "DIO_INT1", self._tear_down_monitor, self._dio_fault["PORTB"])
- )
- signal.signal(signal.SIGINT, self._monitor_int_handler)
- self._dio0_fault_monitor.start()
- self._dio1_fault_monitor.start()
-
- # Init GPIO sources
- gpio_srcs = [
- self.X4XX_GPIO_SRC_PS,
- self.X4XX_GPIO_SRC_MPM,
- self.X4XX_GPIO_SRC_USER_APP
- ]
- for dboard in dboards:
- gpio_srcs.extend(self.X4XX_GPIO_SRC_RADIO[dboard.slot_idx])
-
- self._gpio_srcs = { gpio_bank : gpio_srcs for gpio_bank in self.X4XX_GPIO_BANKS }
-
- self.log.debug(f"Found the following GPIO sources: {', '.join(gpio_srcs)}")
-
- self._current_voltage_level = {
- "PORTA": "3V3",
- "PORTB": "3V3",
- }
- self.set_voltage_level("PORTA", "3V3")
- self.set_voltage_level("PORTB", "3V3")
-
- def _monitor_dio_fault(self, dio_port, fault, tear_down, fault_state):
- """
- Monitor the DIO_INT lines to detect an external power fault.
- If there is a fault, turn off external power.
- """
- self.log.trace("Launching monitor loop...")
- fault_line = Gpio(fault, Gpio.FALLING_EDGE)
- while True:
- try:
- if fault_line.event_wait():
- # If we saw a fault, disable the external power
- self.log.warning("DIO fault occurred on port {} - turning off external power"
- .format(dio_port))
- self.set_external_power(dio_port, 0)
- fault_state.value = 1
- # If the event wait gets interrupted because we are trying to tear down then stop
- # the monitoring process. If not, keep monitoring
- except InterruptedError:
- pass
- if tear_down.is_set():
- break
-
- def _monitor_int_handler(self, signum, frame):
- """
- If we see an expected interrupt signal, mark the DIO fault monitors
- for tear down.
- """
- self._tear_down_monitor.set()
-
- # --------------------------------------------------------------------------
- # Helper methods
- # --------------------------------------------------------------------------
- def _map_to_register_bit(self, port, pin):
- """
- Maps a pin denoted in current mapping scheme to a corresponding bit in
- the register map.
- :param port: port to do the mapping on
- :param pin: pin (in current mapping scheme)
- :return: bit position in register map
- :raises RuntimeError: pin is not in range of current mapping scheme
- or not user assignable.
- """
- assert isinstance(pin, int)
- port = self._normalize_port_name(port)
- first_pin = self.mapping.first_pin
- last_pin = first_pin + len(self.mapping.pin_names) - 1
- port_map = self.mapping.map[port]
-
- if not (first_pin <= pin <= last_pin):
- raise RuntimeError("Pin must be in range [%d,%d]. Given pin: %d" %
- (first_pin, last_pin, pin))
- if pin not in port_map:
- raise RuntimeError("Pin %d (%s) is not a user assignable pin." %
- (pin,
- self.mapping.pin_names[pin - first_pin]))
-
- # map pin back to register bit
- bit = port_map.index(pin)
- # lift register bit up by PORT_BIT_SIZE for port b
- bit = bit if port == self.DIO_PORTS[0] else bit + self.PORT_BIT_SIZE
- return bit
-
- def _calc_register_value(self, register, port, pin, value):
- """
- Recalculates register value.
-
- Current register state is read and the bit that corresponds to the
- values given by port and pin is determined. The register content is
- changed at position of bit to what is given by value.
-
- Note: This routine only reads the current and calculates the new
- register value. It is up to the callee to set the register value.
- :param register: Address of the register value to recalculate
- :param port: port associated with pin
- :param pin: pin to change (will be mapped to bit according to
- current mapping scheme an given port)
- :param value: new bit value to set
- :return: new register value.
- """
- assert value in [0, 1]
-
- content = self.mboard_regs.peek32(register)
- bit = self._map_to_register_bit(port, pin)
- content = (content | 1 << bit) if value == 1 else (content & ~(1 << bit))
- return content
-
- def _set_pin_values(self, port, values, set_method):
- """
- Helper method to assign multiple pins in one call.
- :param port: Port to set pins on
- :param values: New pin assignment represented by an integer. Each bit of
- values corresponds to a pin on board according to current
- mapping scheme. Bits that do not correspond to a pin in
- the current mapping scheme are skipped.
- :param set_method: method to be used to set/unset a pin. Signature of
- set_method is (port, pin).
- """
- first_pin = self.mapping.first_pin
- port = self._normalize_port_name(port)
- for i, pin_name in enumerate(self.mapping.pin_names):
- if i + first_pin in self.mapping.map[port]:
- set_method(port, i + first_pin, int(values & 1 << i != 0))
-
- # --------------------------------------------------------------------------
- # Helper to convert abbreviations to constants defined in DioControl
- # --------------------------------------------------------------------------
-
- def _normalize_mapping(self, mapping):
- """
- Map name to one of the key in self.port_mappings.
- :param mapping: mapping name or any abbreviation by removing letters
- from the end of the name
- :return: Key found for mapping name
- :raises RuntimeError: no matching mapping could be found
- """
- assert isinstance(mapping, str)
- mapping = mapping.upper()
- mapping_names = self.port_mappings.keys()
- try:
- # search for abbr of mapping in mapping names
- index = [re.match("^%s" % mapping, name) is not None for name in mapping_names].index(True)
- return list(self.port_mappings.keys())[index]
- except ValueError:
- raise RuntimeError("Mapping %s not found in %s" % (mapping, mapping_names))
-
- def _normalize_port_name(self, name):
- """
- Map port name to the normalized form of self.DIO_PORTS
- :param name: port name or abbreviation with A or B, case insensitive
- :return: normalized port name
- :raises RuntimeError: name could not be normalized
- """
- assert isinstance(name, str)
- gpio0_names = (self.DIO_PORTS[0], self.X4XX_GPIO_BANKS[0], "A")
- gpio1_names = (self.DIO_PORTS[1], self.X4XX_GPIO_BANKS[1], "B")
- if name.upper() not in gpio0_names + gpio1_names:
- raise RuntimeError("Could not map %s to port name" % name)
- return self.DIO_PORTS[0] if name.upper() in gpio0_names \
- else self.DIO_PORTS[1]
-
- # --------------------------------------------------------------------------
- # Helper to format status output
- # --------------------------------------------------------------------------
-
- def _get_port_voltage(self, port):
- """
- Format voltage table cell value.
- """
- port_control = self.port_control[port]
- result = ""
- if port_control.enable.get() == 0:
- result += self.DIO_VOLTAGE_LEVELS[0]
- elif port_control.en_2v5.get() == 1:
- result += self.DIO_VOLTAGE_LEVELS[2]
- elif port_control.en_3v3.get() == 1:
- result += self.DIO_VOLTAGE_LEVELS[3]
- else:
- result += self.DIO_VOLTAGE_LEVELS[1]
- result += " - PG:"
- result += "YES" if port_control.power_good.get() else "NO"
- result += " - EXT:"
- result += "ON" if port_control.ext_pwr.get() else "OFF"
- return result
-
- def _get_voltage(self):
- """
- Format voltage table cells
- """
- return [self._get_port_voltage(port) for port in self.DIO_PORTS]
-
- def _format_register(self, port, content):
- """
- Format a port value according to current mapping scheme. Pins are
- grouped by 4. Pins which are not user assignable are marked with a dot.
- :param content: register content
- :return: register content as pin assignment according to current
- mapping scheme
- """
- result = ""
- first_pin = self.mapping.first_pin
- pin_names = self.mapping.pin_names
- mapping = self.mapping.map[port]
- for i, _ in enumerate(pin_names):
- if i % 4 == 0 and i > 0:
- result = " " + result
- if i + first_pin in mapping:
- result = str(int(content & (1 << mapping.index(i + first_pin)) != 0)) + result
- else:
- result = "." + result
- return result
-
- def _format_registers(self, content):
- """
- Formats register content for port A and B
- :param content:
- :return:
- """
- port_a = content & self.PORT_USED_BITS_MASK
- port_b = (content >> self.PORT_BIT_SIZE) & self.PORT_USED_BITS_MASK
- return [self._format_register(self.DIO_PORTS[0], port_a),
- self._format_register(self.DIO_PORTS[1], port_b)]
-
- def _format_row(self, values, fill=" ", delim="|"):
- """
- Format a table row with fix colums widths. Generates row spaces using
- value list with empty strings and "-" as fill and "+" as delim.
- :param values: cell values (list of three elements)
- :param fill: fill character to use (space by default)
- :param delim: delimiter character between columns
- :return: formated row
- """
- col_widths = [14, 25, 25]
- return delim.join([
- fill + values[i].ljust(width - len(fill), fill)
- for i, width in enumerate(col_widths)
- ]) + "\n"
-
- def get_gpio_banks(self):
- """
- Returns a list of GPIO banks over which MPM has any control
- """
- if self.mboard_regs.get_compat_number() < self.FULL_DIO_FPGA_COMPAT:
- return []
-
- return self.X4XX_GPIO_BANKS
-
- def get_gpio_srcs(self, bank: str):
- """
- Return a list of valid GPIO sources for a given bank
- """
- if self.mboard_regs.get_compat_number() < self.FULL_DIO_FPGA_COMPAT:
- return ["MPM"]
-
- assert bank in self.get_gpio_banks(), f"Invalid GPIO bank: {bank}"
- return self._gpio_srcs[bank]
-
- def get_gpio_src(self, bank: str):
- """
- Return the currently selected GPIO source for a given bank. The return
- value is a list of strings. The length of the vector is identical to
- the number of controllable GPIO pins on this bank. USER_APP is a GPIO
- source that can be used in custom FPGA designs (e.g. LabView binary uses
- this pin source).
- """
- if self.mboard_regs.get_compat_number() < self.FULL_DIO_FPGA_COMPAT:
- return []
-
- assert bank in self.get_gpio_banks(), f"Invalid GPIO bank: {bank}"
-
- master_reg = self._GpioReg(self, bank, self.FPGA_DIO_MASTER_REGISTER)
- source_reg = self._GpioReg(self, bank, self.FPGA_DIO_SOURCE_REGISTER)
- radio_source_reg = self._GpioReg(self, bank, self.FPGA_DIO_RADIO_SOURCE_REGISTER)
- interface_select_reg = self._GpioReg(self, bank, self.FPGA_DIO_INTERFACE_DIO_SELECT_REGISTER)
- override_reg = self._GpioReg(self, bank, self.FPGA_DIO_OVERRIDE_REGISTER)
- sw_control_reg = self._GpioReg(self, bank, self.FPGA_DIO_SW_DIO_CONTROL_REGISTER)
- classic_atr_config_reg = self._GpioReg(self, bank, self.RADIO_DIO_CLASSIC_ATR_CONFIG_REGISTER)
-
- def get_gpio_src_i(gpio_pin_index):
- """
- Return the current source given a pin index.
- """
- if source_reg.get_pin(gpio_pin_index):
- if override_reg.get_pin(gpio_pin_index):
- db = int(interface_select_reg.get_pin(gpio_pin_index))
- return f"DB{db}_SPI"
- else:
- db = int(radio_source_reg.get_pin(gpio_pin_index))
- ch = int(classic_atr_config_reg.get_pin(gpio_pin_index))
- return f"DB{db}_RF{ch}"
- else:
- if master_reg.get_pin(gpio_pin_index):
- if sw_control_reg.get_pin(gpio_pin_index):
- return self.X4XX_GPIO_SRC_PS
- else:
- return self.X4XX_GPIO_SRC_MPM
- else:
- return self.X4XX_GPIO_SRC_USER_APP
-
- return [get_gpio_src_i(i) for i in range(self.X4XX_GPIO_WIDTH)]
-
- def set_gpio_src(self, bank: str, src):
- """
- Set the GPIO source for a given bank.
- src input is big-endian
- Usage:
- > set_gpio_src <bank> <srcs>
- > set_gpio_src GPIO0 PS DB1_RF0 PS PS MPM PS PS PS MPM USER_APP PS
- """
- if self.mboard_regs.get_compat_number() < self.FULL_DIO_FPGA_COMPAT:
- # Older FPGAs only have a single GPIO source selector
- master_reg = self._GpioReg(self, bank, self.FPGA_DIO_MASTER_REGISTER)
- for pin_index, src_name in enumerate(src):
- if src_name == "MPM":
- master_reg.set_pin(pin_index, 1)
- else:
- master_reg.set_pin(pin_index, 0)
- master_reg.save()
- return
-
- assert bank in self.get_gpio_banks(), f"Invalid GPIO bank: {bank}"
- assert len(src) == self.X4XX_GPIO_WIDTH, f"Invalid number of GPIO sources! Expecting {self.X4XX_GPIO_WIDTH}, but got {len(src)}."
-
- for pin_index, src_name in enumerate(src):
- if src_name not in self.get_gpio_srcs(bank):
- raise RuntimeError(f"Invalid GPIO source name `{src_name}' at bit position {pin_index}!")
-
- master_reg = self._GpioReg(self, bank, self.FPGA_DIO_MASTER_REGISTER)
- source_reg = self._GpioReg(self, bank, self.FPGA_DIO_SOURCE_REGISTER)
- radio_source_reg = self._GpioReg(self, bank, self.FPGA_DIO_RADIO_SOURCE_REGISTER)
- interface_select_reg = self._GpioReg(self, bank, self.FPGA_DIO_INTERFACE_DIO_SELECT_REGISTER)
- override_reg = self._GpioReg(self, bank, self.FPGA_DIO_OVERRIDE_REGISTER)
- sw_control_reg = self._GpioReg(self, bank, self.FPGA_DIO_SW_DIO_CONTROL_REGISTER)
- classic_atr_config_reg = self._GpioReg(self, bank, self.RADIO_DIO_CLASSIC_ATR_CONFIG_REGISTER)
-
- for pin_index, src_name in enumerate(src):
- radio_srcs = [item for sublist in self.X4XX_GPIO_SRC_RADIO for item in sublist]
- if src_name in radio_srcs:
- source_reg.set_pin(pin_index, 1)
- slot = int(src_name[2])
- if src_name.endswith("_SPI"):
- override_reg.set_pin(pin_index, 1)
- interface_select_reg.set_pin(pin_index, slot)
- else:
- channel = int(src_name[6])
- override_reg.set_pin(pin_index, 0)
- radio_source_reg.set_pin(pin_index, slot)
- classic_atr_config_reg.set_pin(pin_index, channel)
- else:
- source_reg.set_pin(pin_index, 0)
- if src_name in (self.X4XX_GPIO_SRC_PS, self.X4XX_GPIO_SRC_MPM):
- master_reg.set_pin(pin_index, 1)
- sw_control_reg.set_pin(pin_index, int(src_name == self.X4XX_GPIO_SRC_PS))
- else:
- master_reg.set_pin(pin_index, 0)
-
- master_reg.save()
- source_reg.save()
- radio_source_reg.save()
- interface_select_reg.save()
- override_reg.save()
- sw_control_reg.save()
- classic_atr_config_reg.save()
-
- # --------------------------------------------------------------------------
- # Public API
- # --------------------------------------------------------------------------
-
- def tear_down(self):
- """
- Mark the DIO monitoring processes for tear down and terminate the processes
- """
- self._tear_down_monitor.set()
- self._dio0_fault_monitor.terminate()
- self._dio1_fault_monitor.terminate()
- self._dio0_fault_monitor.join(3)
- self._dio1_fault_monitor.join(3)
- if self._dio0_fault_monitor.is_alive() or \
- self._dio1_fault_monitor.is_alive():
- self.log.warning("DIO monitor didn't exit properly")
-
- def set_port_mapping(self, mapping):
- """
- Change the port mapping to mapping. Mapping must denote a mapping found
- in this.port_mappings.keys() or any abbreviation allowed by
- _normalize_port_mapping. The mapping does not change the status of the
- FPGA registers. It only changes the status display and the way calls
- to set_pin_<register_name>(s) are interpreted.
- :param mapping: new mapping to be used
- :raises RuntimeError: mapping could not be found
- """
- assert isinstance(mapping, str)
- map_name = self._normalize_mapping(mapping)
- if not map_name in self.port_mappings.keys():
- raise RuntimeError("Could not map %s to port mapping" % mapping)
- self.mapping = self.port_mappings[map_name]
-
- def set_pin_direction(self, port, pin, value=1):
- """
- Set direction pin of a port. The direction pin decides whether the DIO
- external pin is used as an output (write - value is 1) or input (read -
- value is 0). To change the pin value the current register content is
- read first and modified before it is written back, so the register must
- be readable.
- Besides the FPGA register map, the CPLD register map is also written. To
- prevent the internal line to be driven by FGPA and DIO board at the same
- time the CPLD register is written first if the direction will become an
- output. If direction will become an input the FPGA register is written
- first.
- :param port: port to change direction assignment on
- :param pin: pin to change
- :param value: desired pin value
- """
- content = self._calc_register_value(self.FPGA_DIO_DIRECTION_REGISTER,
- port, pin, value)
- # When setting direction pin, order matters. Always switch the component
- # first that will get the driver disabled.
- # This ensures that there wont be two drivers active at a time.
- if value == 1: # FPGA is driver => write DIO register first
- self.mboard_cpld.poke32(self.CPLD_DIO_DIRECTION_REGISTER, content)
- self.mboard_regs.poke32(self.FPGA_DIO_DIRECTION_REGISTER, content)
- else: # DIO is driver => write FPGA register first
- self.mboard_regs.poke32(self.FPGA_DIO_DIRECTION_REGISTER, content)
- self.mboard_cpld.poke32(self.CPLD_DIO_DIRECTION_REGISTER, content)
- # Read back values to ensure registers are in sync
- cpld_content = self.mboard_cpld.peek32(self.CPLD_DIO_DIRECTION_REGISTER)
- mbrd_content = self.mboard_regs.peek32(self.FPGA_DIO_DIRECTION_REGISTER)
- if not ((cpld_content == content) and (mbrd_content == content)):
- raise RuntimeError("Direction register content mismatch. Expected:"
- "0x%0.8X, CPLD: 0x%0.8X, FPGA: 0x%0.8X." %
- (content, cpld_content, mbrd_content))
-
- def set_pin_directions(self, port, values):
- """
- Set all direction pins of a port at once using a bit mask.
- :param port: port to change direction pin assignment
- :param values: New pin assignment represented by an integer. Each bit of
- values corresponds to a pin on board according to current
- mapping scheme. Bits that do not correspond to a pin in
- the current mapping scheme are skipped.
- """
- self._set_pin_values(port, values, self.set_pin_direction)
-
- def set_pin_output(self, port, pin, value=1):
- """
- Set output value of a pin on a port. Setting this value only takes
- effect if the direction of the corresponding pin of this port is set
- accordingly. To change the pin value the current register content is
- read first and modified before it is written back, so the register must
- be readable.
- :param port: port to change output assignment on
- :param pin: pin to change
- :param value: desired pin value
- """
- content = self._calc_register_value(self.FPGA_DIO_OUTPUT_REGISTER,
- port, pin, value)
- self.mboard_regs.poke32(self.FPGA_DIO_OUTPUT_REGISTER, content)
-
- def set_pin_outputs(self, port, values):
- """
- Set all output pins of a port at once using a bit mask.
- :param port: port to change direction pin assignment
- :param values: New pin assignment represented by an integer. Each bit of
- values corresponds to a pin on board according to current
- mapping scheme. Bits that do not correspond to a pin in
- the current mapping scheme are skipped.
- """
- self._set_pin_values(port, values, self.set_pin_output)
-
- def get_pin_input(self, port, pin):
- """
- Returns the input pin value of a port.
- If the pin is not assignable in the current mapping None is returned.
-
- :param port: port to read pin value from
- :param pin: pin value to read
- :returns: actual pin value or None if pin is not assignable
- """
- port = self._normalize_port_name(port)
-
- register = self.mboard_regs.peek32(self.FPGA_DIO_INPUT_REGISTER)
- if port == self.DIO_PORTS[1]:
- register = register >> self.PORT_BIT_SIZE
- register &= self.PORT_USED_BITS_MASK
-
- mapping = self.mapping.map[port]
- if not pin in mapping:
- raise RuntimeError("Pin %d (%s) is not a user readable pin." %
- (pin,
- self.mapping.pin_names[pin - self.mapping.first_pin]))
- return 0 if (register & (1 << mapping.index(pin)) == 0) else 1
-
- def get_pin_inputs(self, port):
- """
- Returns a bit mask of all pins for the given port.
-
- :param port: port to read input pins from
- :returns: Bit map of input pins, each bit of pins corresponds to a pin
- on board according to current mapping scheme. Unused pins
- stay zero
- """
- result = 0
- first_pin = self.mapping.first_pin
- pin_names = self.mapping.pin_names
- port = self._normalize_port_name(port)
- mapping = self.mapping.map[port]
- for i, name in enumerate(pin_names):
- if i + first_pin in mapping:
- if self.get_pin_input(port, i + first_pin):
- result |= 1 << i
- return result
-
- def set_voltage_level(self, port, level):
- """
- Change voltage level of a port. This is how EN_<port>, EN_<port>_2V5 and
- EN_<port>_3V3 are set according to level::
- level EN_<port> EN_<port>_2V5 EN_<port>_3V3
- off 0 0 0
- 1V8 1 0 0
- 2V5 1 1 0
- 3V3 1 0 1
- If level is set to anything other than off this method waits for
- <port>_PG to go high. Waiting stops as soon as <port>_PG goes high or
- a timeout of 1s occurs.
- Note: All pins are set to zero first before the new level is applied.
- :param port: port to change power level for
- :param level: new power level
- :raises RuntimeError: power good pin did not go high
- """
- port = self._normalize_port_name(port)
- level = level.upper()
- assert port in self.DIO_PORTS
- assert level in self.DIO_VOLTAGE_LEVELS
- port_control = self.port_control[port]
-
- self._current_voltage_level[port] = level
-
- port_control.enable.set(0)
- port_control.en_2v5.set(0)
- port_control.en_3v3.set(0)
- if level == self.DIO_VOLTAGE_LEVELS[2]:
- port_control.en_2v5.set(1)
- elif level == self.DIO_VOLTAGE_LEVELS[3]:
- port_control.en_3v3.set(1)
-
- # wait for <port>_PG to go high
- if not level == self.DIO_VOLTAGE_LEVELS[0]: # off
- port_control.enable.set(1)
- if not poll_with_timeout(
- lambda: port_control.power_good.get() == 1, 1000, 10):
- raise RuntimeError(
- "Power good pin did not go high after power up")
-
- def get_voltage_level(self, port):
- """
- Returns the current GPIO voltage level as set by set_voltage_level
- """
- port = self._normalize_port_name(port)
- assert port in self.DIO_PORTS
- return self._current_voltage_level[port]
-
- def set_external_power(self, port, value):
- """
- Change EN_EXT_PWR_<port> to value.
- :param port: port to change external power level for
- :param value: 1 to enable external power, 0 to disable
- :raise RuntimeError: port or pin value could not be mapped
- """
- port = self._normalize_port_name(port)
- value = int(value)
- assert value in (0, 1)
- assert port in self.DIO_PORTS
- self.port_control[port].ext_pwr.set(value)
- self._dio_fault[port].value = 0
-
- def get_external_power_state(self, port):
- """
- Returns the current state of the external power supply.
-
- Usage:
- > get_external_power_state PORTA
- """
- port = self._normalize_port_name(port)
- if self._dio_fault[port].value == 1:
- return "FAULT"
- if self.port_control[port].ext_pwr.get() == 1:
- return "ON"
- return "OFF"
-
- def get_supported_voltage_levels(self, port):
- """
- Returns the list of all supported voltage levels for the given port.
- Note that, currently, all ports support all voltage levels, so we
- simply validate that the given port name is valid.
- """
- _ = self._normalize_port_name(port)
- return ["OFF", "1V8", "2V5", "3V3"]
-
- def status(self):
- """
- Build a full status string for the DIO AUX board, including
- I2C pin states and register content in a human readable form.
- :return: board status
- """
- result = "\n" \
- + self._format_row(["%s mapping" % self.mapping.name, self.DIO_PORTS[0], self.DIO_PORTS[1]]) \
- + self._format_row(["", "", ""], "-", "+") \
- + self._format_row(["voltage"] + self._get_voltage()) \
- + self._format_row(["", "", ""], "-", "+")
-
- register = self.mboard_regs.peek32(self.FPGA_DIO_MASTER_REGISTER)
- result += self._format_row(["master"] + self._format_registers(register))
-
- register = self.mboard_regs.peek32(self.FPGA_DIO_DIRECTION_REGISTER)
- result += self._format_row(["direction"] + self._format_registers(register))
-
- result += self._format_row(["", "", ""], "-", "+")
-
- register = self.mboard_regs.peek32(self.FPGA_DIO_OUTPUT_REGISTER)
- result += self._format_row(["output"] + self._format_registers(register))
-
- register = self.mboard_regs.peek32(self.FPGA_DIO_INPUT_REGISTER)
- result += self._format_row(["input"] + self._format_registers(register))
- return result
-
- def debug(self):
- """
- Create a debug string containing the FPGA register maps. The CPLD
- direction register is not part of the string as the DioControl maintains
- it in sync with the FPGA direction register.
- :return: register states for debug purpose in human readable form.
- """
- master = format(self.mboard_regs.peek32(self.FPGA_DIO_MASTER_REGISTER), "032b")
- direction = format(self.mboard_regs.peek32(self.FPGA_DIO_DIRECTION_REGISTER), "032b")
- output = format(self.mboard_regs.peek32(self.FPGA_DIO_OUTPUT_REGISTER), "032b")
- input = format(self.mboard_regs.peek32(self.FPGA_DIO_INPUT_REGISTER), "032b")
- return "\nmaster: " + " ".join(re.findall('....', master)) + "\n" + \
- "direction: " + " ".join(re.findall('....', direction)) + "\n" + \
- "output: " + " ".join(re.findall('....', output)) + "\n" + \
- "input: " + " ".join(re.findall('....', input))
-
class MboardRegsControl(MboardRegsCommon):
"""