1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395
|
#
# Copyright 2018 Ettus Research, a National Instruments Company
#
# SPDX-License-Identifier: GPL-3.0-or-later
#
"""
E320 peripherals
"""
import math
import time
from usrp_mpm.sys_utils.sysfs_gpio import SysFSGPIO, GPIOBank
from usrp_mpm.periph_manager.common import MboardRegsCommon
# 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,
{'label': 'zynq_gpio'},
self.FP_GPIO_OFFSET + self.EMIO_BASE,
0xFF, # use_mask
ddr
)
class MboardRegsControl(MboardRegsCommon):
"""
Control the FPGA Motherboard registers
"""
# pylint: disable=bad-whitespace
# Motherboard registers
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
# pylint: enable=bad-whitespace
# 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
# This is connected to pin 23 (manual reset) of the GPS module.
# Section 2.3.9 (System Reset) of the data sheet says:
# This pin [...] can be connected to a reset switch. Initiating a system
# reset will cause the GPS receiver to not generate GPS fixes for up to 35
# seconds if power had been off for prolonged periods of time, and down to
# 3 seconds typically if the unit had been powered-on recently.
#
# Note that in this use case, we typically don't use the system reset during
# normal operations. Instead, we always fully power the GPS down when it's
# not needed. Standard operation is to power-up the GPS when the device boots
# and leave it running. GPS can also be powered-up or -down for the duration
# of a single UHD session, and the default state can be overriden in mpm.conf.
MB_GPS_CTRL_RST_N = 1
# This pin is for auto-survey mode, which we do not use. This pin is kept
# low at all times, as this mode is not intended for stationary use.
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
# Bitfield locations for the MB_DBOARD_STATUS register.
MB_DBOARD_STATUS_RX_LOCK = 6
MB_DBOARD_STATUS_TX_LOCK = 7
def __init__(self, label, log):
MboardRegsCommon.__init__(self, label, log)
def enable_fp_gpio(self, enable):
""" Enable front panel GPIO buffers and power supply
and set voltage 3.3 V
"""
self.set_fp_gpio_voltage(3.3)
mask = 0xFFFFFFFF ^ ((0b1 << self.MB_GPIO_CTRL_BUFFER_OE_N) | \
(0b1 << self.MB_GPIO_CTRL_EN_VAR_SUPPLY))
with self.regs:
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 : 3.3
"""
assert any([math.isclose(value, nn, abs_tol=0.1) for nn in (3.3,)]),\
"FP GPIO currently only supports 3.3V"
if math.isclose(value, 1.8, abs_tol=0.1):
voltage_reg = 0
elif math.isclose(value, 2.5, abs_tol=0.1):
voltage_reg = 1
elif math.isclose(value, 3.3, abs_tol=0.1):
voltage_reg = 2
mask = 0xFFFFFFFF ^ ((0b1 << self.MB_GPIO_CTRL_EN_3V3) | \
(0b1 << self.MB_GPIO_CTRL_EN_2V5))
with self.regs:
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:
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:
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:
return self.peek32(self.MB_GPIO_MASTER) & 0xff
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:
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:
return self.peek32(self.MB_GPIO_RADIO_SRC) & 0xffff
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)
pps_sel_mask = ((0b1 << self.MB_CLOCK_CTRL_PPS_SEL_INT) |
(0b1 << self.MB_CLOCK_CTRL_PPS_SEL_EXT))
with self.regs:
# prevent glitches by writing a cleared value first, then the final value.
reg_val = self.peek32(self.MB_CLOCK_CTRL) & ~pps_sel_mask
self.log.trace("Writing MB_CLOCK_CTRL to 0x{:08X}".format(reg_val))
self.poke32(self.MB_CLOCK_CTRL, reg_val)
reg_val = reg_val | pps_sel_val
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:
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:
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))
sfp_type = E320_SFP_TYPES.get((sfp_info_rb & 0x0000FF00) >> 8, "")
self.log.trace("SFP type: {}".format(sfp_type))
try:
return E320_FPGA_TYPES_BY_SFP[(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:
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:
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.
When enabling the power, we sequence the power such that we first power
the chip, then bring it out of reset with a short delay.
When powering down the GPS, we make sure that all pins are low.
Power signal is GPS_3V3.
Note: This logic keeps MB_GPS_CTRL_INITSURV_N low at all times. Should
a different behaviour be desired, we need to change this to also
factor in the desired state of INITSURV_N. However, we are intentionally
keeping INITSURV mode off, as it's not intended for stationary use.
"""
self.log.trace("{} power to GPS".format(
"Enabling" if enable else "Disabling"
))
pwr_en_mask = (0b1 << self.MB_GPS_CTRL_PWR_EN)
rstn_mask = (0b1 << self.MB_GPS_CTRL_RST_N)
enabled_mask = pwr_en_mask | rstn_mask
with self.regs:
cur_reg_val = self.peek32(self.MB_GPS_CTRL)
if enable and (cur_reg_val & enabled_mask != enabled_mask):
# First bring up supply, then bring out of reset
self.log.trace(f"Setting MB_GPS_CTRL to 0x{pwr_en_mask:08X} (power enable)")
self.poke32(self.MB_GPS_CTRL, pwr_en_mask)
# Jackson Labs data sheet says: "Pull to ground for >50ms to
# initiate a system reset." Technically, we're powering the device
# on and pulling it out of reset, so the full reset time is not
# necessary, but we like sticking to values in data sheets and
# this is a safe number.
time.sleep(0.060)
self.log.trace(f"Setting MB_GPS_CTRL to 0x{enabled_mask:08X} (out of reset)")
return self.poke32(self.MB_GPS_CTRL, enabled_mask)
elif not enable:
# All controls pins low to avoid backfeeding I/O pins when unpowered
self.log.trace("Setting MB_GPS_CTRL to 0x00000000")
return self.poke32(self.MB_GPS_CTRL, 0)
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:
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:
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: %s",
"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)
def get_ad9361_tx_lo_lock(self):
"""
Check the status of TX LO lock from CTRL_OUT pins from Catalina
"""
mask = 0b1 << self.MB_DBOARD_STATUS_TX_LOCK
with self.regs:
reg_val = self.peek32(self.MB_DBOARD_STATUS)
locked = (reg_val & mask) > 0
if not locked:
self.log.warning("TX RF PLL reporting unlocked. ")
else:
self.log.trace("TX RF PLL locked")
return locked
def get_ad9361_rx_lo_lock(self):
"""
Check the status of RX LO lock from CTRL_OUT pins from Catalina
"""
mask = 0b1 << self.MB_DBOARD_STATUS_RX_LOCK
with self.regs:
reg_val = self.peek32(self.MB_DBOARD_STATUS)
locked = (reg_val & mask) > 0
if not locked:
self.log.warning("RX RF PLL reporting unlocked. ")
else:
self.log.trace("RX RF PLL locked")
return locked
|