mirror of
https://github.com/saymrwulf/uhd.git
synced 2026-05-16 21:10:10 +00:00
mpm: x4xx: add DIO GPIO API configuration methods
These methods allow for reconfiguration of GPIO masters for x4xx. The method names are get_gpio_banks, get_gpio_srcs, get_gpio_src, and set_gpio_src.
This commit is contained in:
parent
426f078ba0
commit
c36fc5f6c0
3 changed files with 227 additions and 39 deletions
|
|
@ -302,6 +302,9 @@ class x4xx(ZynqComponents, PeriphManagerBase):
|
|||
try:
|
||||
self._init_peripherals(args)
|
||||
self.init_dboards(args)
|
||||
# We need to init dio_control separately from peripherals
|
||||
# since it needs information about available dboards
|
||||
self._init_dio_control(args)
|
||||
self._clk_mgr.set_dboard_reset_cb(
|
||||
lambda enable: [db.reset_clock(enable) for db in self.dboards])
|
||||
except Exception as ex:
|
||||
|
|
@ -535,13 +538,6 @@ class x4xx(ZynqComponents, PeriphManagerBase):
|
|||
# is possible to always enable the iPass cable present forwarding.
|
||||
self.ctrlport_regs.enable_cable_present_forwarding(True)
|
||||
|
||||
# Init DIO
|
||||
if self._check_compat_aux_board(DIOAUX_EEPROM, DIOAUX_PID):
|
||||
self.dio_control = DioControl(self.mboard_regs_control,
|
||||
self.cpld_control, self.log)
|
||||
# add dio_control public methods to MPM API
|
||||
self._add_public_methods(self.dio_control, "dio")
|
||||
|
||||
# Init QSFP modules
|
||||
for idx, config in enumerate(X400_QSFP_I2C_CONFIGS):
|
||||
attr = QSFPModule(
|
||||
|
|
@ -566,6 +562,18 @@ class x4xx(ZynqComponents, PeriphManagerBase):
|
|||
self._status_monitor_thread.start()
|
||||
# Init complete.
|
||||
self.log.debug("Device info: {}".format(self.device_info))
|
||||
|
||||
def _init_dio_control(self, _):
|
||||
"""
|
||||
Turn on gpio peripherals. This may throw an error on failure, so make
|
||||
sure to catch it.
|
||||
"""
|
||||
if self._check_compat_aux_board(DIOAUX_EEPROM, DIOAUX_PID):
|
||||
self.dio_control = DioControl(self.mboard_regs_control,
|
||||
self.cpld_control, self.log,
|
||||
self.dboards)
|
||||
# add dio_control public methods to MPM API
|
||||
self._add_public_methods(self.dio_control, "dio")
|
||||
|
||||
def _check_compat_aux_board(self, name, pid):
|
||||
"""
|
||||
|
|
@ -1018,6 +1026,40 @@ class x4xx(ZynqComponents, PeriphManagerBase):
|
|||
}
|
||||
]
|
||||
|
||||
###########################################################################
|
||||
# GPIO API
|
||||
###########################################################################
|
||||
|
||||
def get_gpio_banks(self):
|
||||
"""
|
||||
Returns a list of GPIO banks over which MPM has any control
|
||||
"""
|
||||
return self.dio_control.get_gpio_banks()
|
||||
|
||||
def get_gpio_srcs(self, bank: str):
|
||||
"""
|
||||
Return a list of valid GPIO sources for a given bank
|
||||
"""
|
||||
return self.dio_control.get_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. CUSTOM is for
|
||||
miscellaneous pin source, and USER_APP is for LabView pin source.
|
||||
"""
|
||||
return self.dio_control.get_gpio_src(bank)
|
||||
|
||||
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_RF1 PS PS MPM PS PS PS MPM USER_APP PS
|
||||
"""
|
||||
self.dio_control.set_gpio_src(bank, *src)
|
||||
|
||||
###########################################################################
|
||||
# Utility for validating RPU core number
|
||||
|
|
|
|||
|
|
@ -142,9 +142,30 @@ class DioControl:
|
|||
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
|
||||
|
||||
# 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
|
||||
|
|
@ -171,7 +192,33 @@ class DioControl:
|
|||
self.power_good = Gpio('%s_PWR_GOOD' % prefix, Gpio.INPUT)
|
||||
|
||||
|
||||
def __init__(self, mboard_regs, mboard_cpld, log):
|
||||
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
|
||||
|
|
@ -206,10 +253,23 @@ class DioControl:
|
|||
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)}")
|
||||
|
||||
def _monitor_dio_fault(self, dio_port, fault, tear_down):
|
||||
"""
|
||||
Monitor the DIO_INT lines to detect an external power fault.
|
||||
If there is a fault, turn off external power.
|
||||
If there is a fault, turn off external power.
|
||||
"""
|
||||
self.log.trace("Launching monitor loop...")
|
||||
fault_line = Gpio(fault, Gpio.FALLING_EDGE)
|
||||
|
|
@ -218,7 +278,7 @@ class DioControl:
|
|||
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))
|
||||
.format(dio_port))
|
||||
self.set_external_power(dio_port, 0)
|
||||
# If the event wait gets interrupted because we are trying to tear down then stop
|
||||
# the monitoring process. If not, keep monitoring
|
||||
|
|
@ -420,6 +480,112 @@ class DioControl:
|
|||
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
|
||||
"""
|
||||
return self.X4XX_GPIO_BANKS
|
||||
|
||||
def get_gpio_srcs(self, bank: str):
|
||||
"""
|
||||
Return a list of valid GPIO sources for a given bank
|
||||
"""
|
||||
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).
|
||||
"""
|
||||
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
|
||||
"""
|
||||
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
|
||||
# --------------------------------------------------------------------------
|
||||
|
|
@ -453,31 +619,6 @@ class DioControl:
|
|||
raise RuntimeError("Could not map %s to port mapping" % mapping)
|
||||
self.mapping = self.port_mappings[map_name]
|
||||
|
||||
def set_pin_master(self, port, pin, value=1):
|
||||
"""
|
||||
Set master pin of a port. The master pin decides whether the DIO board
|
||||
pin is driven by the PS (1) or FPGA (0) register interface. 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 master assignment on
|
||||
:param pin: pin to change
|
||||
:param value: desired pin value
|
||||
"""
|
||||
content = self._calc_register_value(self.FPGA_DIO_MASTER_REGISTER,
|
||||
port, pin, value)
|
||||
self.mboard_regs.poke32(self.FPGA_DIO_MASTER_REGISTER, content)
|
||||
|
||||
def set_pin_masters(self, port, values):
|
||||
"""
|
||||
Set all master pins of a port at once using a bit mask.
|
||||
:param port: port to change master 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_master)
|
||||
|
||||
def set_pin_direction(self, port, pin, value=1):
|
||||
"""
|
||||
Set direction pin of a port. The direction pin decides whether the DIO
|
||||
|
|
|
|||
|
|
@ -659,8 +659,13 @@ class X4XXBIST(bist.UsrpBIST):
|
|||
mask = 0xDB6D
|
||||
else:
|
||||
mask = 0xFFF
|
||||
mpm_c.dio_set_pin_masters(inport, mask)
|
||||
mpm_c.dio_set_pin_masters(outport, mask)
|
||||
bank_convert = {
|
||||
"PORTA": "GPIO0",
|
||||
"PORTB": "GPIO1",
|
||||
}
|
||||
ps_control_args = ["MPM"] * 12
|
||||
mpm_c.dio_set_gpio_src(bank_convert[inport], ps_control_args)
|
||||
mpm_c.dio_set_gpio_src(bank_convert[outport], ps_control_args)
|
||||
mpm_c.dio_set_voltage_level(inport, voltage)
|
||||
mpm_c.dio_set_voltage_level(outport, voltage)
|
||||
mpm_c.dio_set_pin_directions(inport, 0x00000)
|
||||
|
|
@ -677,13 +682,13 @@ class X4XXBIST(bist.UsrpBIST):
|
|||
for voltage in ["1V8", "2V5", "3V3"]:
|
||||
for mode in ["DIO", "HDMI"]:
|
||||
for pattern in [0xFFFF, 0xA5A5, 0x5A5A, 0x0000]:
|
||||
sys.stderr.write("test: PortA -> PortB, {}, {}, 0x{:04X}"
|
||||
sys.stderr.write("test: PortA -> PortB, {}, {}, 0x{:04X}\n"
|
||||
.format(voltage, mode, pattern))
|
||||
status, data = _run_sub_test(
|
||||
"PORTB", "PORTA", mode, voltage, pattern)
|
||||
if not status:
|
||||
return status, data
|
||||
sys.stderr.write("test: PortB -> PortA, {}, {}, 0x{:04X}"
|
||||
sys.stderr.write("test: PortB -> PortA, {}, {}, 0x{:04X}\n"
|
||||
.format(voltage, mode, pattern))
|
||||
status, data = _run_sub_test(
|
||||
"PORTA", "PORTB", mode, voltage, pattern)
|
||||
|
|
|
|||
Loading…
Reference in a new issue