mirror of
https://github.com/saymrwulf/uhd.git
synced 2026-05-15 21:01:26 +00:00
The pca953x driver introduced a change for how the "label" property
populates. Instead of using the device model, it gives a device specific
name. As a replacement, use device/name. This affects the tca6424
and tca6408.
For the kernel change that causes this see:
5128f8d445
378 lines
13 KiB
Python
378 lines
13 KiB
Python
#
|
|
# Copyright 2018 Ettus Research, a National Instruments Company
|
|
# Copyright 2019 Ettus Research, a National Instruments Brand
|
|
#
|
|
# SPDX-License-Identifier: GPL-3.0-or-later
|
|
#
|
|
"""
|
|
N3xx peripherals
|
|
"""
|
|
|
|
from usrp_mpm import lib
|
|
from usrp_mpm.sys_utils.sysfs_gpio import SysFSGPIO, GPIOBank
|
|
from usrp_mpm.sys_utils import i2c_dev
|
|
from usrp_mpm.chips.ds125df410 import DS125DF410
|
|
from usrp_mpm.periph_manager.common import MboardRegsCommon
|
|
|
|
# Map register values to SFP transport types
|
|
N3XX_SFP_TYPES = {
|
|
0: "", # Port not connected
|
|
1: "1G",
|
|
2: "10G",
|
|
3: "A", # Aurora
|
|
4: "W" # White Rabbit
|
|
}
|
|
|
|
N3XX_FPGA_TYPES_BY_SFP = {
|
|
("", ""): "",
|
|
("1G", "10G"): "HG",
|
|
("10G", "10G"): "XG",
|
|
("10G", "A"): "XA",
|
|
("A", "A"): "AA",
|
|
("W", "10G"): "WX",
|
|
}
|
|
|
|
class TCA6424(object):
|
|
"""
|
|
Abstraction layer for the port/gpio expander
|
|
pins_list is an array of different version of TCA6424 pins map.
|
|
First element of this array corresponding to revC, second is revD etc...
|
|
"""
|
|
pins_list = [
|
|
(
|
|
'PWREN-CLK-MGT156MHz',
|
|
'NETCLK-CE', #revC name: 'PWREN-CLK-WB-CDCM',
|
|
'NETCLK-RESETn', #revC name: 'WB-CDCM-RESETn',
|
|
'NETCLK-PR0', #revC name: 'WB-CDCM-PR0',
|
|
'NETCLK-PR1', #revC name: 'WB-CDCM-PR1',
|
|
'NETCLK-OD0', #revC name: 'WB-CDCM-OD0',
|
|
'NETCLK-OD1', #revC name: 'WB-CDCM-OD1',
|
|
'NETCLK-OD2', #revC name: 'WB-CDCM-OD2',
|
|
'PWREN-CLK-MAINREF',
|
|
'CLK-MAINSEL-25MHz', #revC name: 'CLK-MAINREF-SEL1',
|
|
'CLK-MAINSEL-EX_B', #revC name: 'CLK-MAINREF-SEL0',
|
|
'12',
|
|
'CLK-MAINSEL-GPS', #revC name: '13',
|
|
'FPGA-GPIO-EN',
|
|
'PWREN-CLK-WB-20MHz',
|
|
'PWREN-CLK-WB-25MHz',
|
|
'GPS-PHASELOCK',
|
|
'GPS-nINITSURV',
|
|
'GPS-nRESET',
|
|
'GPS-WARMUP',
|
|
'GPS-SURVEY',
|
|
'GPS-LOCKOK',
|
|
'GPS-ALARM',
|
|
'PWREN-GPS',
|
|
),
|
|
(
|
|
'NETCLK-PR1',
|
|
'NETCLK-PR0',
|
|
'NETCLK-CE',
|
|
'NETCLK-RESETn',
|
|
'NETCLK-OD2',
|
|
'NETCLK-OD1',
|
|
'NETCLK-OD0',
|
|
'PWREN-CLK-MGT156MHz',
|
|
'PWREN-CLK-MAINREF',
|
|
'CLK-MAINSEL-25MHz',
|
|
'CLK-MAINSEL-EX_B',
|
|
'12',
|
|
'CLK-MAINSEL-GPS',
|
|
'FPGA-GPIO-EN',
|
|
'PWREN-CLK-WB-20MHz',
|
|
'PWREN-CLK-WB-25MHz',
|
|
'GPS-PHASELOCK',
|
|
'GPS-nINITSURV',
|
|
'GPS-nRESET',
|
|
'GPS-WARMUP',
|
|
'GPS-SURVEY',
|
|
'GPS-LOCKOK',
|
|
'GPS-ALARM',
|
|
'PWREN-GPS',
|
|
)]
|
|
|
|
def __init__(self, rev):
|
|
# Default state: Turn on GPS power, take GPS out of reset or
|
|
# init-survey, turn on 156.25 MHz clock
|
|
# min Support from revC or rev = 2
|
|
if rev == 2:
|
|
self.pins = self.pins_list[0]
|
|
else:
|
|
self.pins = self.pins_list[1]
|
|
|
|
default_val = 0x860101 if rev == 2 else 0x860780
|
|
self._gpios = SysFSGPIO({'device/name': 'tca6424', 'device/of_node/name': 'gpio'}, 0xFFF7FF, 0x86F7FF, default_val)
|
|
|
|
def set(self, name, value=None):
|
|
"""
|
|
Assert a pin by name
|
|
"""
|
|
assert name in self.pins
|
|
self._gpios.set(self.pins.index(name), value=value)
|
|
|
|
def reset(self, name):
|
|
"""
|
|
Deassert a pin by name
|
|
"""
|
|
self.set(name, value=0)
|
|
|
|
def get(self, name):
|
|
"""
|
|
Read back a pin by name
|
|
"""
|
|
assert name in self.pins
|
|
return self._gpios.get(self.pins.index(name))
|
|
|
|
|
|
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,
|
|
0xFFF, # use_mask
|
|
ddr
|
|
)
|
|
|
|
class BackpanelGPIO(GPIOBank):
|
|
"""
|
|
Abstraction layer for the back panel GPIO
|
|
"""
|
|
EMIO_BASE = 54
|
|
BP_GPIO_OFFSET = 45
|
|
LED_LINK = 0
|
|
LED_REF = 1
|
|
LED_GPS = 2
|
|
|
|
def __init__(self):
|
|
GPIOBank.__init__(
|
|
self,
|
|
{'label': 'zynq_gpio'},
|
|
self.BP_GPIO_OFFSET + self.EMIO_BASE,
|
|
0x7, # use_mask
|
|
0x7, # ddr
|
|
)
|
|
|
|
class MboardRegsControl(MboardRegsCommon):
|
|
"""
|
|
Control the FPGA Motherboard registers
|
|
"""
|
|
# pylint: disable=bad-whitespace
|
|
# Motherboard registers (on top of the ones in MboardRegsCommon)
|
|
MB_CLOCK_CTRL = 0x0018
|
|
MB_XADC_RB = 0x001C
|
|
MB_BUS_CLK_RATE = 0x0020
|
|
MB_BUS_COUNTER = 0x0024
|
|
MB_SFP0_INFO = 0x0028
|
|
MB_SFP1_INFO = 0x002C
|
|
MB_GPIO_MASTER = 0x0030
|
|
MB_GPIO_RADIO_SRC = 0x0034
|
|
|
|
# Bitfield locations for the MB_CLOCK_CTRL register.
|
|
MB_CLOCK_CTRL_PPS_SEL_INT_10 = 0 # pps_sel is one-hot encoded!
|
|
MB_CLOCK_CTRL_PPS_SEL_INT_25 = 1
|
|
MB_CLOCK_CTRL_PPS_SEL_EXT = 2
|
|
MB_CLOCK_CTRL_PPS_SEL_GPSDO = 3
|
|
MB_CLOCK_CTRL_PPS_SEL_SFP0 = 5
|
|
MB_CLOCK_CTRL_PPS_SEL_SFP1 = 6
|
|
MB_CLOCK_CTRL_PPS_OUT_EN = 4 # output enabled = 1
|
|
MB_CLOCK_CTRL_MEAS_CLK_RESET = 12 # set to 1 to reset mmcm, default is 0
|
|
MB_CLOCK_CTRL_MEAS_CLK_LOCKED = 13 # locked indication for meas_clk mmcm
|
|
MB_CLOCK_CTRL_DISABLE_REF_CLK = 16 # to disable the ref_clk, write a '1'
|
|
# pylint: enable=bad-whitespace
|
|
|
|
def __init__(self, label, log):
|
|
MboardRegsCommon.__init__(self, label, log)
|
|
|
|
def set_fp_gpio_master(self, value):
|
|
"""set driver for front panel GPIO
|
|
Arguments:
|
|
value {unsigned} -- value is a single bit bit mask of 12 pins GPIO
|
|
0: means the pin is driven by PL
|
|
1: means the pin is driven by PS
|
|
"""
|
|
with self.regs:
|
|
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 12 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) & 0xfff
|
|
|
|
def set_fp_gpio_radio_src(self, value):
|
|
"""set driver for front panel GPIO
|
|
Arguments:
|
|
value {unsigned} -- value is 2x12 bits, two bits per GPIO pin
|
|
00: means the pin is driven by radio 0
|
|
01: means the pin is driven by radio 1
|
|
10: means the pin is driven by radio 2
|
|
11: means the pin is driven by radio 3
|
|
"""
|
|
with self.regs:
|
|
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 12 pins GPIO.
|
|
00: means the pin is driven by radio 0
|
|
01: means the pin is driven by radio 1
|
|
10: means the pin is driven by radio 2
|
|
11: means the pin is driven by radio 3
|
|
"""
|
|
with self.regs:
|
|
return self.peek32(self.MB_GPIO_RADIO_SRC) & 0xffffff
|
|
|
|
def set_time_source(self, time_source, ref_clk_freq):
|
|
"""
|
|
Set time source
|
|
"""
|
|
pps_sel_val = 0x0
|
|
if time_source == 'internal':
|
|
assert ref_clk_freq in (10e6, 25e6), \
|
|
"Invalid reference frequency for time source 'internal'. Must " \
|
|
"be either 10 MHz or 25 MHz. Check clock and time source match."
|
|
if ref_clk_freq == 10e6:
|
|
self.log.debug("Setting time source to internal "
|
|
"(10 MHz reference)...")
|
|
pps_sel_val = 0b1 << self.MB_CLOCK_CTRL_PPS_SEL_INT_10
|
|
elif ref_clk_freq == 25e6:
|
|
self.log.debug("Setting time source to internal "
|
|
"(25 MHz reference)...")
|
|
pps_sel_val = 0b1 << self.MB_CLOCK_CTRL_PPS_SEL_INT_25
|
|
elif time_source == 'external':
|
|
self.log.debug("Setting time source to external...")
|
|
pps_sel_val = 0b1 << self.MB_CLOCK_CTRL_PPS_SEL_EXT
|
|
elif time_source == 'gpsdo':
|
|
self.log.debug("Setting time source to gpsdo...")
|
|
pps_sel_val = 0b1 << self.MB_CLOCK_CTRL_PPS_SEL_GPSDO
|
|
elif time_source == 'sfp0':
|
|
self.log.debug("Setting time source to sfp0...")
|
|
pps_sel_val = 0b1 << self.MB_CLOCK_CTRL_PPS_SEL_SFP0
|
|
elif time_source == 'sfp1':
|
|
self.log.debug("Setting time source to sfp1...")
|
|
pps_sel_val = 0b1 << self.MB_CLOCK_CTRL_PPS_SEL_SFP1
|
|
else:
|
|
raise RuntimeError("Invalid time source: {}".format(time_source))
|
|
|
|
with self.regs:
|
|
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 enable_pps_out(self, enable):
|
|
"""
|
|
Enables the PPS/Trig output on the back panel
|
|
"""
|
|
self.log.trace("%s PPS/Trig output!",
|
|
"Enabling" if enable else "Disabling")
|
|
mask = 0xFFFFFFFF ^ (0b1 << self.MB_CLOCK_CTRL_PPS_OUT_EN)
|
|
with self.regs:
|
|
# mask the bit to clear it:
|
|
reg_val = self.peek32(self.MB_CLOCK_CTRL) & mask
|
|
if enable:
|
|
# set the bit if desired:
|
|
reg_val = reg_val | (0b1 << self.MB_CLOCK_CTRL_PPS_OUT_EN)
|
|
self.log.trace("Writing MB_CLOCK_CTRL to 0x{:08X}".format(reg_val))
|
|
self.poke32(self.MB_CLOCK_CTRL, reg_val)
|
|
|
|
def enable_ref_clk(self, enable):
|
|
"""
|
|
Enables the reference clock internal to the FPGA
|
|
"""
|
|
self.log.trace("%s the Reference Clock!",
|
|
"Enabling" if enable else "Disabling")
|
|
mask = 0xFFFFFFFF ^ (0b1 << self.MB_CLOCK_CTRL_DISABLE_REF_CLK)
|
|
with self.regs:
|
|
# mask the bit to clear it and therefore enable the clock:
|
|
reg_val = self.peek32(self.MB_CLOCK_CTRL) & mask
|
|
if not enable:
|
|
# set the bit if not enabled (note this is a DISABLE bit when = 1):
|
|
reg_val = reg_val | (0b1 << self.MB_CLOCK_CTRL_DISABLE_REF_CLK)
|
|
self.log.trace("Writing MB_CLOCK_CTRL to 0x{:08X}".format(reg_val))
|
|
self.poke32(self.MB_CLOCK_CTRL, reg_val)
|
|
|
|
def reset_meas_clk_mmcm(self, reset=True):
|
|
"""
|
|
Reset or unreset the MMCM for the measurement clock in the FPGA TDC.
|
|
"""
|
|
self.log.trace("%s measurement clock MMCM reset...",
|
|
"Asserting" if reset else "Clearing")
|
|
mask = 0xFFFFFFFF ^ (0b1 << self.MB_CLOCK_CTRL_MEAS_CLK_RESET)
|
|
with self.regs:
|
|
# mask the bit to clear it
|
|
reg_val = self.peek32(self.MB_CLOCK_CTRL) & mask
|
|
if reset:
|
|
# set the bit if desired
|
|
reg_val = reg_val | (0b1 << self.MB_CLOCK_CTRL_MEAS_CLK_RESET)
|
|
self.log.trace("Writing MB_CLOCK_CTRL to 0x{:08X}".format(reg_val))
|
|
self.poke32(self.MB_CLOCK_CTRL, reg_val)
|
|
|
|
def get_meas_clock_mmcm_lock(self):
|
|
"""
|
|
Check the status of the MMCM for the measurement clock in the FPGA TDC.
|
|
"""
|
|
mask = 0b1 << self.MB_CLOCK_CTRL_MEAS_CLK_LOCKED
|
|
with self.regs:
|
|
reg_val = self.peek32(self.MB_CLOCK_CTRL)
|
|
locked = (reg_val & mask) > 0
|
|
if not locked:
|
|
self.log.warning("Measurement clock MMCM reporting unlocked. "
|
|
"MB_CLOCK_CTRL reg: 0x{:08X}".format(reg_val))
|
|
else:
|
|
self.log.trace("Measurement clock MMCM locked!")
|
|
return locked
|
|
|
|
def get_fpga_type(self):
|
|
"""
|
|
Reads the type of the FPGA image currently loaded
|
|
Returns a string with the type (ie HG, XG, AA, etc.)
|
|
"""
|
|
with self.regs:
|
|
sfp0_info_rb = self.peek32(self.MB_SFP0_INFO)
|
|
sfp1_info_rb = self.peek32(self.MB_SFP1_INFO)
|
|
# Print the registers values as 32-bit hex values
|
|
self.log.trace("SFP0 Info: 0x{0:0{1}X}".format(sfp0_info_rb, 8))
|
|
self.log.trace("SFP1 Info: 0x{0:0{1}X}".format(sfp1_info_rb, 8))
|
|
sfp0_type = N3XX_SFP_TYPES.get((sfp0_info_rb & 0x0000FF00) >> 8, "")
|
|
sfp1_type = N3XX_SFP_TYPES.get((sfp1_info_rb & 0x0000FF00) >> 8, "")
|
|
self.log.trace("SFP types: ({}, {})".format(sfp0_type, sfp1_type))
|
|
try:
|
|
return N3XX_FPGA_TYPES_BY_SFP[(sfp0_type, sfp1_type)]
|
|
except KeyError:
|
|
self.log.warning("Unrecognized SFP type combination: ({}, {})"
|
|
.format(sfp0_type, sfp1_type))
|
|
return ""
|
|
|
|
|
|
class RetimerQSFP(DS125DF410):
|
|
"""
|
|
Thin wrapper around an I2C device that controls the QSFP retimer
|
|
"""
|
|
# (deemphasis, swing)
|
|
DRIVER_PRESETS = {
|
|
'1m': (0x00, 0x07), '3m': (0x41, 0x06), 'Optical': (0x41, 0x04)
|
|
}
|
|
|
|
def __init__(self, i2c_bus):
|
|
regs_iface = lib.i2c.make_i2cdev_regs_iface(
|
|
i2c_bus,
|
|
0x18,
|
|
False,
|
|
100,
|
|
1
|
|
)
|
|
super(RetimerQSFP, self).__init__(regs_iface)
|