aboutsummaryrefslogtreecommitdiffstats
path: root/mpm
diff options
context:
space:
mode:
authorAlex Williams <alex.williams@ni.com>2018-10-15 12:16:16 -0700
committerBrent Stapleton <bstapleton@g.hmc.edu>2018-11-07 18:26:13 -0800
commit5c991d82f718c2f51c2f4b2cb183aeb3c910de3e (patch)
treef9223bab1644b3756539296c63891b03d03e1b95 /mpm
parent738b9ec102cdb307413cab23fdcf7fb2522f9895 (diff)
downloaduhd-5c991d82f718c2f51c2f4b2cb183aeb3c910de3e.tar.gz
uhd-5c991d82f718c2f51c2f4b2cb183aeb3c910de3e.tar.bz2
uhd-5c991d82f718c2f51c2f4b2cb183aeb3c910de3e.zip
mpm: Add basic driver for QSFP board's retimer
Diffstat (limited to 'mpm')
-rw-r--r--mpm/python/usrp_mpm/chips/CMakeLists.txt1
-rw-r--r--mpm/python/usrp_mpm/chips/ds125df410.py107
-rw-r--r--mpm/python/usrp_mpm/periph_manager/n3xx.py17
-rw-r--r--mpm/python/usrp_mpm/periph_manager/n3xx_periphs.py17
4 files changed, 142 insertions, 0 deletions
diff --git a/mpm/python/usrp_mpm/chips/CMakeLists.txt b/mpm/python/usrp_mpm/chips/CMakeLists.txt
index 94b62f33d..ff4f61bdd 100644
--- a/mpm/python/usrp_mpm/chips/CMakeLists.txt
+++ b/mpm/python/usrp_mpm/chips/CMakeLists.txt
@@ -9,6 +9,7 @@ SET(USRP_MPM_CHIP_FILES
${CMAKE_CURRENT_SOURCE_DIR}/__init__.py
${CMAKE_CURRENT_SOURCE_DIR}/lmk04828.py
${CMAKE_CURRENT_SOURCE_DIR}/adf400x.py
+ ${CMAKE_CURRENT_SOURCE_DIR}/ds125df410.py
)
LIST(APPEND USRP_MPM_FILES ${USRP_MPM_CHIP_FILES})
SET(USRP_MPM_FILES ${USRP_MPM_FILES} PARENT_SCOPE)
diff --git a/mpm/python/usrp_mpm/chips/ds125df410.py b/mpm/python/usrp_mpm/chips/ds125df410.py
new file mode 100644
index 000000000..5cea7e67a
--- /dev/null
+++ b/mpm/python/usrp_mpm/chips/ds125df410.py
@@ -0,0 +1,107 @@
+#
+# Copyright 2018 Ettus Research, a National Instruments Company
+#
+# SPDX-License-Identifier: GPL-3.0-or-later
+#
+"""
+DS125DF410 driver class
+
+For use with TI's retimer chip
+"""
+
+import math
+from builtins import object
+from usrp_mpm.mpmlog import get_logger
+
+LINE_RATE_PRESETS = {'Ethernet': 0xF6, 'CPRI1': 0x36, 'CPRI2': 0x46}
+ALL_CHANS = range(4)
+
+
+class DS125DF410(object):
+ """
+ Driver class for DS125DF410 access.
+
+ Inputs:
+ regs_iface : regs_iface bus driver to access device
+ parent_log : logger of parent
+ """
+
+ # (deemphasis, swing)
+ DRIVER_PRESETS = {}
+
+ def __init__(self, regs_iface, parent_log=None):
+ self.log = \
+ parent_log.getChild("DS125DF410") if parent_log is not None \
+ else get_logger("DS125DF410")
+ self.regs_iface = regs_iface
+ # Set channel select register to control set
+ self.regs_iface.poke8(0xFF, 0)
+ # Probe chip ID
+ chip_id = self.regs_iface.peek8(0x01)
+ assert chip_id == 0xd1
+ self.log.debug("Probed DS125DF410 retimer")
+ for chan in ALL_CHANS:
+ self._set_chan_select(chan)
+ # Reset channel registers
+ self.regs_iface.poke8(0x00, 0x04)
+ # Enable DFE mode
+ self.regs_iface.poke8(0x1E, 0xE1)
+ self.regs_iface.poke8(0x31, 0x40)
+
+ def _rmw(self, addr, data, mask):
+ """ Read, modify, write """
+ base = self.regs_iface.peek8(addr) & ~mask
+ data = (data & mask) | base
+ self.regs_iface.poke8(addr, data)
+
+ def _set_chan_select(self, chan):
+ """
+ Channel select
+ """
+ assert chan in ALL_CHANS
+ self.regs_iface.poke8(0xFF, chan + 4)
+
+ def set_rate_preset(self, preset, channels=None):
+ """
+ Set rate preset
+ """
+ channels = channels or ALL_CHANS
+ assert preset in LINE_RATE_PRESETS.keys()
+ for chan in channels:
+ self._set_chan_select(chan)
+ self.regs_iface.poke8(0x2F, LINE_RATE_PRESETS[preset])
+
+ def set_rate(self, rate, channels=None):
+ """
+ Set rate
+ """
+ channels = channels or ALL_CHANS
+ self.log.trace("Writing custom line rate {}".format(rate))
+ ppm_val = int(math.ceil(rate*1280.0))
+ assert ppm_val <= 0x7FFF
+ for chan in channels:
+ assert chan in ALL_CHANS
+ self._set_chan_select(chan)
+ # Set VCO divider to 1
+ self.regs_iface.poke8(0x2F, 0xC6)
+ # Set frequency range detection
+ self.regs_iface.poke8(0x60, (ppm_val & 0x00FF))
+ self.regs_iface.poke8(0x62, (ppm_val & 0x00FF))
+ self.regs_iface.poke8(0x61, 0x80 | ((ppm_val >> 8) & 0x00FF))
+ self.regs_iface.poke8(0x63, 0x80 | ((ppm_val >> 8) & 0x00FF))
+ # Set VCO tolerance range to max
+ self.regs_iface.poke8(0x64, 0xFF)
+
+ def set_driver_preset(self, preset, channels=None):
+ """
+ Set driver preset
+ """
+ channels = channels or ALL_CHANS
+ assert preset in self.DRIVER_PRESETS.keys()
+ self.log.trace("Setting retimer's driver for " + preset + " preset")
+ deemphasis, swing = self.DRIVER_PRESETS[preset]
+ for chan in channels:
+ self._set_chan_select(chan)
+ self._rmw(0x15, deemphasis, 0x47)
+ self._rmw(0x2D, swing, 0x07)
+
diff --git a/mpm/python/usrp_mpm/periph_manager/n3xx.py b/mpm/python/usrp_mpm/periph_manager/n3xx.py
index 79dddd898..b741fc03f 100644
--- a/mpm/python/usrp_mpm/periph_manager/n3xx.py
+++ b/mpm/python/usrp_mpm/periph_manager/n3xx.py
@@ -21,11 +21,13 @@ from usrp_mpm.mpmtypes import SID
from usrp_mpm.mpmutils import assert_compat_number, str2bool, poll_with_timeout
from usrp_mpm.rpc_server import no_rpc
from usrp_mpm.sys_utils import dtoverlay
+from usrp_mpm.sys_utils import i2c_dev
from usrp_mpm.sys_utils.sysfs_thermal import read_thermal_sensor_value
from usrp_mpm.xports import XportMgrUDP, XportMgrLiberio
from usrp_mpm.periph_manager.n3xx_periphs import TCA6424
from usrp_mpm.periph_manager.n3xx_periphs import BackpanelGPIO
from usrp_mpm.periph_manager.n3xx_periphs import MboardRegsControl
+from usrp_mpm.periph_manager.n3xx_periphs import RetimerQSFP
from usrp_mpm.dboard_manager.magnesium import Magnesium
from usrp_mpm.dboard_manager.eiscat import EISCAT
from usrp_mpm.dboard_manager.rhodium import Rhodium
@@ -36,6 +38,9 @@ N3XX_DEFAULT_TIME_SOURCE = 'internal'
N3XX_DEFAULT_ENABLE_GPS = True
N3XX_DEFAULT_ENABLE_FPGPIO = True
N3XX_DEFAULT_ENABLE_PPS_EXPORT = True
+N32X_DEFAULT_QSFP_RATE_PRESET = 'Ethernet'
+N32X_DEFAULT_QSFP_DRIVER_PRESET = 'Optical'
+N32X_QSFP_I2C_LABEL = 'qsfp-i2c'
N3XX_FPGA_COMPAT = (5, 3)
N3XX_MONITOR_THREAD_INTERVAL = 1.0 # seconds
@@ -342,6 +347,18 @@ class n3xx(ZynqComponents, PeriphManagerBase):
self._init_meas_clock()
# Init GPSd iface and GPS sensors
self._init_gps_sensors()
+ # Init QSFP board (if available)
+ qsfp_i2c = i2c_dev.of_get_i2c_adapter(N32X_QSFP_I2C_LABEL)
+ if qsfp_i2c:
+ self.log.debug("Creating QSFP Retimer control object...")
+ self._qsfp_retimer = RetimerQSFP(qsfp_i2c)
+ self._qsfp_retimer.set_rate_preset(N32X_DEFAULT_QSFP_RATE_PRESET)
+ self._qsfp_retimer.set_driver_preset(N32X_DEFAULT_QSFP_DRIVER_PRESET)
+ elif self.device_info['product'] == 'n320':
+ # If we have an N320, we should also have the QSFP board, but we
+ # won't freak out if we can't find it. Maybe someone removed or
+ # disabled it.
+ self.log.warning("No QSFP board detected!")
# Init CHDR transports
self._xport_mgrs = {
'udp': N3xxXportMgrUDP(self.log.getChild('UDP')),
diff --git a/mpm/python/usrp_mpm/periph_manager/n3xx_periphs.py b/mpm/python/usrp_mpm/periph_manager/n3xx_periphs.py
index 5622285f6..cb6c237c2 100644
--- a/mpm/python/usrp_mpm/periph_manager/n3xx_periphs.py
+++ b/mpm/python/usrp_mpm/periph_manager/n3xx_periphs.py
@@ -8,8 +8,11 @@ N3xx peripherals
"""
import datetime
+from usrp_mpm import lib
from usrp_mpm.sys_utils.sysfs_gpio import SysFSGPIO, GPIOBank
from usrp_mpm.sys_utils.uio import UIO
+from usrp_mpm.sys_utils import i2c_dev
+from usrp_mpm.chips.ds125df410 import DS125DF410
# Map register values to SFP transport types
N3XX_SFP_TYPES = {
@@ -417,3 +420,17 @@ class MboardRegsControl(object):
"Get the RFNoC crossbar base port"
with self.regs:
return self.peek32(self.MB_XBAR_BASEPORT)
+
+class RetimerQSFP(DS125DF410):
+ # (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)