diff options
author | Brent Stapleton <brent.stapleton@ettus.com> | 2018-04-17 19:33:23 -0700 |
---|---|---|
committer | Brent Stapleton <bstapleton@g.hmc.edu> | 2018-07-18 15:37:27 -0700 |
commit | 300a5e3f6e5e845b4b8d093222e1c51ca4640f79 (patch) | |
tree | 62f8bb7bc7d847b8f32f1fe5b4c9c06ef608080e /mpm/python/usrp_mpm/periph_manager/e320_periphs.py | |
parent | a1d6530ce50ca9590739ffa40464747d3db968eb (diff) | |
download | uhd-300a5e3f6e5e845b4b8d093222e1c51ca4640f79.tar.gz uhd-300a5e3f6e5e845b4b8d093222e1c51ca4640f79.tar.bz2 uhd-300a5e3f6e5e845b4b8d093222e1c51ca4640f79.zip |
mpm: initial commit of E320 code
Co-authored-by: Sugandha Gupta <sugandha.gupta@ettus.com>
Diffstat (limited to 'mpm/python/usrp_mpm/periph_manager/e320_periphs.py')
-rw-r--r-- | mpm/python/usrp_mpm/periph_manager/e320_periphs.py | 383 |
1 files changed, 383 insertions, 0 deletions
diff --git a/mpm/python/usrp_mpm/periph_manager/e320_periphs.py b/mpm/python/usrp_mpm/periph_manager/e320_periphs.py new file mode 100644 index 000000000..d98c5a0e5 --- /dev/null +++ b/mpm/python/usrp_mpm/periph_manager/e320_periphs.py @@ -0,0 +1,383 @@ +# +# Copyright 2018 Ettus Research, a National Instruments Company +# +# SPDX-License-Identifier: GPL-3.0-or-later +# +""" +E320 peripherals +""" + +import datetime +from usrp_mpm.sys_utils.sysfs_gpio import SysFSGPIO, GPIOBank +from usrp_mpm.sys_utils.uio import UIO + +# Map register values to SFP transport types +E320_SFP_TYPES = { + 0: "", # Port not connected + 1: "1G", + 2: "10G", + 3: "A", # Aurora +} + +E320_FPGA_TYPES_BY_SFP = { + (""): "", + ("1G"): "1G", + ("10G"): "XG", + ("A"): "AA", +} + +class FrontpanelGPIO(GPIOBank): + """ + Abstraction layer for the front panel GPIO + """ + EMIO_BASE = 54 + FP_GPIO_OFFSET = 32 # Bit offset within the ps_gpio_* pins + + def __init__(self, ddr): + GPIOBank.__init__( + self, + 'zynq_gpio', + self.FP_GPIO_OFFSET + self.EMIO_BASE, + 0xFF, # use_mask + ddr + ) + +class MboardRegsControl(object): + """ + Control the FPGA Motherboard registers + """ + # Motherboard registers + MB_COMPAT_NUM = 0x0000 + MB_DATESTAMP = 0x0004 + MB_GIT_HASH = 0x0008 + MB_SCRATCH = 0x000C + MB_NUM_CE = 0x0010 + MB_NUM_IO_CE = 0x0014 + MB_CLOCK_CTRL = 0x0018 + MB_XADC_RB = 0x001C + MB_BUS_CLK_RATE = 0x0020 + MB_BUS_COUNTER = 0x0024 + MB_SFP_PORT_INFO = 0x0028 + MB_GPIO_CTRL = 0x002C + MB_GPIO_MASTER = 0x0030 + MB_GPIO_RADIO_SRC = 0x0034 + MB_GPS_CTRL = 0x0038 + MB_GPS_STATUS = 0x003C + MB_DBOARD_CTRL = 0x0040 + MB_DBOARD_STATUS = 0x0044 + + # Bitfield locations for the MB_CLOCK_CTRL register. + MB_CLOCK_CTRL_PPS_SEL_INT = 0 + MB_CLOCK_CTRL_PPS_SEL_EXT = 1 + MB_CLOCK_CTRL_REF_SEL = 2 + MB_CLOCK_CTRL_REF_CLK_LOCKED = 3 + + # Bitfield locations for the MB_GPIO_CTRL register. + MB_GPIO_CTRL_BUFFER_OE_N = 0 + MB_GPIO_CTRL_EN_VAR_SUPPLY = 1 + MB_GPIO_CTRL_EN_2V5 = 2 + MB_GPIO_CTRL_EN_3V3 = 3 + + # Bitfield locations for the MB_GPS_CTRL register. + MB_GPS_CTRL_PWR_EN = 0 + MB_GPS_CTRL_RST_N = 1 + MB_GPS_CTRL_INITSURV_N = 2 + + # Bitfield locations for the MB_GPS_STATUS register. + MB_GPS_STATUS_LOCK = 0 + MB_GPS_STATUS_ALARM = 1 + MB_GPS_STATUS_PHASELOCK = 2 + MB_GPS_STATUS_SURVEY = 3 + MB_GPS_STATUS_WARMUP = 4 + + # Bitfield locations for the MB_DBOARD_CTRL register. + MB_DBOARD_CTRL_MIMO = 0 + MB_DBOARD_CTRL_TX_CHAN_SEL = 1 + + def __init__(self, label, log): + self.log = log + self.regs = UIO( + label=label, + read_only=False + ) + self.poke32 = self.regs.poke32 + self.peek32 = self.regs.peek32 + + def get_compat_number(self): + """get FPGA compat number + + This function reads back FPGA compat number. + The return is a tuple of + 2 numbers: (major compat number, minor compat number ) + """ + with self.regs.open(): + compat_number = self.peek32(self.MB_COMPAT_NUM) + minor = compat_number & 0xff + major = (compat_number>>16) & 0xff + return (major, minor) + + def enable_fp_gpio(self, value): + """ Enable front panel GPIO buffers and power supply + and set voltage 1.8, 2.5 or 3.3 V + Setting value to 0 would disable gpio + """ + if value == 0: + enable = False + else: + enable = True + self.set_fp_gpio_voltage(value) + mask = 0xFFFFFFFF ^ ((0b1 << self.MB_GPIO_CTRL_BUFFER_OE_N) | \ + (0b1 << self.MB_GPIO_CTRL_EN_VAR_SUPPLY)) + with self.regs.open(): + reg_val = self.peek32(self.MB_GPIO_CTRL) & mask + reg_val = reg_val | (not enable << self.MB_GPIO_CTRL_BUFFER_OE_N) | \ + (enable << self.MB_GPIO_CTRL_EN_VAR_SUPPLY) + self.log.trace("Writing MB_GPIO_CTRL to 0x{:08X}".format(reg_val)) + return self.poke32(self.MB_GPIO_CTRL, reg_val) + + def set_fp_gpio_voltage(self, value): + """ Set Front Panel GPIO voltage (in volts) + 3V3 2V5 | Voltage + ----------------- + 0 0 | 1.8 V + 0 1 | 2.5 V + 1 0 | 3.3 V + Arguments: + value : 1.8, 2.5 or 3.3 + """ + assert value in (1.8, 2.5, 3.3) + if value == 1.8: + voltage_reg = 0 + elif value == 2.5: + voltage_reg = 1 + elif value == 3.3: + voltage_reg = 2 + mask = 0xFFFFFFFF ^ ((0b1 << self.MB_GPIO_CTRL_EN_3V3) | \ + (0b1 << self.MB_GPIO_CTRL_EN_2V5)) + with self.regs.open(): + reg_val = self.peek32(self.MB_GPIO_CTRL) & mask + reg_val = reg_val | (voltage_reg << self.MB_GPIO_CTRL_EN_2V5) + self.log.trace("Writing MB_GPIO_CTRL to 0x{:08X}".format(reg_val)) + return self.poke32(self.MB_GPIO_CTRL, reg_val) + + def get_fp_gpio_voltage(self): + """ + Get Front Panel GPIO voltage (in volts) + """ + mask = 0x3 << self.MB_GPIO_CTRL_EN_2V5 + voltage = [1.8, 2.5, 3.3] + with self.regs.open(): + reg_val = (self.peek32(self.MB_GPIO_CTRL) & mask) >> self.MB_GPIO_CTRL_EN_2V5 + return voltage[reg_val] + + def set_fp_gpio_master(self, value): + """set driver for front panel GPIO + Arguments: + value {unsigned} -- value is a single bit bit mask of 8 pins GPIO + """ + with self.regs.open(): + return self.poke32(self.MB_GPIO_MASTER, value) + + def get_fp_gpio_master(self): + """get "who" is driving front panel gpio + The return value is a bit mask of 8 pins GPIO. + 0: means the pin is driven by PL + 1: means the pin is driven by PS + """ + with self.regs.open(): + return self.peek32(self.MB_GPIO_MASTER) & 0xfff + + def set_fp_gpio_radio_src(self, value): + """set driver for front panel GPIO + Arguments: + value {unsigned} -- value is 2-bit bit mask of 8 pins GPIO + 00: means the pin is driven by radio 0 + 01: means the pin is driven by radio 1 + """ + with self.regs.open(): + return self.poke32(self.MB_GPIO_RADIO_SRC, value) + + def get_fp_gpio_radio_src(self): + """get which radio is driving front panel gpio + The return value is 2-bit bit mask of 8 pins GPIO. + 00: means the pin is driven by radio 0 + 01: means the pin is driven by radio 1 + """ + with self.regs.open(): + return self.peek32(self.MB_GPIO_RADIO_SRC) & 0xffffff + + def get_build_timestamp(self): + """ + Returns the build date/time for the FPGA image. + The return is datetime string with the ISO 8601 format + (YYYY-MM-DD HH:MM:SS.mmmmmm) + """ + with self.regs.open(): + datestamp_rb = self.peek32(self.MB_DATESTAMP) + if datestamp_rb > 0: + dt_str = datetime.datetime( + year=((datestamp_rb>>17)&0x3F)+2000, + month=(datestamp_rb>>23)&0x0F, + day=(datestamp_rb>>27)&0x1F, + hour=(datestamp_rb>>12)&0x1F, + minute=(datestamp_rb>>6)&0x3F, + second=((datestamp_rb>>0)&0x3F)) + self.log.trace("FPGA build timestamp: {}".format(str(dt_str))) + return str(dt_str) + else: + # Compatibility with FPGAs without datestamp capability + return '' + + def get_git_hash(self): + """ + Returns the GIT hash for the FPGA build. + The return is a tuple of + 2 numbers: (short git hash, bool: is the tree dirty?) + """ + with self.regs.open(): + git_hash_rb = self.peek32(self.MB_GIT_HASH) + git_hash = git_hash_rb & 0x0FFFFFFF + tree_dirty = ((git_hash_rb & 0xF0000000) > 0) + dirtiness_qualifier = 'dirty' if tree_dirty else 'clean' + self.log.trace("FPGA build GIT Hash: {:07x} ({})".format( + git_hash, dirtiness_qualifier)) + return (git_hash, dirtiness_qualifier) + + def set_time_source(self, time_source, ref_clk_freq): + """ + Set time source + """ + pps_sel_val = 0x0 + if time_source == 'internal' or time_source == 'gpsdo': + self.log.trace("Setting time source to internal (GPSDO)" + "({:.1f} MHz reference)...".format(ref_clk_freq)) + pps_sel_val = 0b1 << self.MB_CLOCK_CTRL_PPS_SEL_INT + elif time_source == 'external': + self.log.debug("Setting time source to external...") + pps_sel_val = 0b1 << self.MB_CLOCK_CTRL_PPS_SEL_EXT + else: + assert False, "Cannot set to invalid time source: {}".format(time_source) + with self.regs.open(): + reg_val = self.peek32(self.MB_CLOCK_CTRL) & 0xFFFFFF90 + # prevent glitches by writing a cleared value first, then the final value. + self.poke32(self.MB_CLOCK_CTRL, reg_val) + reg_val = reg_val | (pps_sel_val & 0x6F) + self.log.trace("Writing MB_CLOCK_CTRL to 0x{:08X}".format(reg_val)) + self.poke32(self.MB_CLOCK_CTRL, reg_val) + + def set_clock_source(self, clock_source, ref_clk_freq): + """ + Set clock source + """ + if clock_source == 'internal' or clock_source == 'gpsdo': + self.log.trace("Setting clock source to internal (GPSDO)" + "({:.1f} MHz reference)...".format(ref_clk_freq)) + ref_sel_val = 0b0 + elif clock_source == 'external': + self.log.debug("Setting clock source to external..." + "({:.1f} MHz reference)...".format(ref_clk_freq)) + ref_sel_val = 0b1 + else: + assert False, "Cannot set to invalid clock source: {}".format(clock_source) + mask = 0xFFFFFFFF ^ (0b1 << self.MB_CLOCK_CTRL_REF_SEL) + with self.regs.open(): + reg_val = self.peek32(self.MB_CLOCK_CTRL) & mask + reg_val = reg_val | (ref_sel_val << self.MB_CLOCK_CTRL_REF_SEL) + self.log.trace("Writing MB_CLOCK_CTRL to 0x{:08X}".format(reg_val)) + self.poke32(self.MB_CLOCK_CTRL, reg_val) + + def get_fpga_type(self): + """ + Reads the type of the FPGA image currently loaded + Returns a string with the type (ie 1G, XG, AU, etc.) + """ + with self.regs.open(): + sfp_info_rb = self.peek32(self.MB_SFP_PORT_INFO) + # Print the registers values as 32-bit hex values + self.log.trace("SFP Info: 0x{0:0{1}X}".format(sfp_info_rb, 8)) + try: + sfp_type = E320_SFP_TYPES.get((sfp_info_rb & 0x0000FF00) >> 8, "") + self.log.trace("SFP type: {}".format(sfp_type)) + return sfp_type + except KeyError: + self.log.warning("Unrecognized SFP type: {}" + .format(sfp_type)) + return "" + + def get_gps_locked_val(self): + """ + Get GPS LOCK status + """ + mask = 0b1 << self.MB_GPS_STATUS_LOCK + with self.regs.open(): + reg_val = self.peek32(self.MB_GPS_STATUS) & mask + gps_locked = reg_val & 0x1 #FIXME + if gps_locked: + self.log.trace("GPS locked!") + # Can return this value because the gps_locked value is on the LSB + return gps_locked + + def get_gps_status(self): + """ + Get GPS status + """ + mask = 0x1F + with self.regs.open(): + gps_status = self.peek32(self.MB_GPS_STATUS) & mask + return gps_status + + def enable_gps(self, enable): + """ + Turn power to the GPS (CLK_GPS_PWR_EN) off or on. + Power signal is GPS_3V3. + """ + self.log.trace("{} power to GPS".format( + "Enabling" if enable else "Disabling" + )) + mask = 0xFFFFFFFF ^ (0b1 << self.MB_GPS_CTRL_PWR_EN) + with self.regs.open(): + reg_val = self.peek32(self.MB_GPS_CTRL) & mask + reg_val = reg_val | (enable << self.MB_GPS_CTRL_PWR_EN) + self.log.trace("Writing MB_GPS_CTRL to 0x{:08X}".format(reg_val)) + return self.poke32(self.MB_GPS_CTRL, reg_val) + + def get_refclk_lock(self): + """ + Check the status of the reference clock (adf4002) in FPGA. + """ + mask = 0b1 << self.MB_CLOCK_CTRL_REF_CLK_LOCKED + with self.regs.open(): + reg_val = self.peek32(self.MB_CLOCK_CTRL) + locked = (reg_val & mask) > 0 + if not locked: + self.log.warning("Reference Clock reporting unlocked. " + "MB_CLOCK_CTRL reg: 0x{:08X}".format(reg_val)) + else: + self.log.trace("Reference Clock locked!") + return locked + + def set_channel_mode(self, channel_mode): + """ + Set channel mode in FPGA and select which tx channel to use + channel mode = "MIMO" for mimo + channel mode = "SISO_TX1", "SISO_TX0" for siso tx1, tx0 respectively. + """ + with self.regs.open(): + reg_val = self.peek32(self.MB_DBOARD_CTRL) + if channel_mode == "MIMO": + reg_val = (0b1 << self.MB_DBOARD_CTRL_MIMO) + self.log.trace("Setting channel mode in AD9361 interface: {}".format("2R2T" if channel_mode == 2 else "1R1T")) + else: + # Warn if user tries to set either tx0/tx1 in mimo mode + # as both will be set automatically + if channel_mode == "SISO_TX1": + # in SISO mode, Channel 1 + reg_val = (0b1 << self.MB_DBOARD_CTRL_TX_CHAN_SEL) | (0b0 << self.MB_DBOARD_CTRL_MIMO) + self.log.trace("Setting TX channel in AD9361 interface to: TX1") + elif channel_mode == "SISO_TX0": + # in SISO mode, Channel 0 + reg_val = (0b0 << self.MB_DBOARD_CTRL_TX_CHAN_SEL) | (0b0 << self.MB_DBOARD_CTRL_MIMO) + self.log.trace("Setting TX channel in AD9361 interface to: TX0") + self.log.trace("Writing MB_DBOARD_CTRL to 0x{:08X}".format(reg_val)) + self.poke32(self.MB_DBOARD_CTRL, reg_val) + |