diff options
-rw-r--r-- | mpm/python/usrp_mpm/periph_manager/x4xx.py | 56 | ||||
-rw-r--r-- | mpm/python/usrp_mpm/periph_manager/x4xx_periphs.py | 197 | ||||
-rw-r--r-- | mpm/python/x4xx_bist | 13 |
3 files changed, 227 insertions, 39 deletions
diff --git a/mpm/python/usrp_mpm/periph_manager/x4xx.py b/mpm/python/usrp_mpm/periph_manager/x4xx.py index 30938a6b2..ad8f2fdcc 100644 --- a/mpm/python/usrp_mpm/periph_manager/x4xx.py +++ b/mpm/python/usrp_mpm/periph_manager/x4xx.py @@ -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 diff --git a/mpm/python/usrp_mpm/periph_manager/x4xx_periphs.py b/mpm/python/usrp_mpm/periph_manager/x4xx_periphs.py index be0487872..3cfd1eda8 100644 --- a/mpm/python/usrp_mpm/periph_manager/x4xx_periphs.py +++ b/mpm/python/usrp_mpm/periph_manager/x4xx_periphs.py @@ -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 diff --git a/mpm/python/x4xx_bist b/mpm/python/x4xx_bist index 30a9cee48..0c6fb1155 100644 --- a/mpm/python/x4xx_bist +++ b/mpm/python/x4xx_bist @@ -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) |