diff options
author | Alex Williams <alex.williams@ni.com> | 2018-10-15 12:16:16 -0700 |
---|---|---|
committer | Brent Stapleton <bstapleton@g.hmc.edu> | 2018-11-07 18:26:13 -0800 |
commit | 5c991d82f718c2f51c2f4b2cb183aeb3c910de3e (patch) | |
tree | f9223bab1644b3756539296c63891b03d03e1b95 /mpm | |
parent | 738b9ec102cdb307413cab23fdcf7fb2522f9895 (diff) | |
download | uhd-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.txt | 1 | ||||
-rw-r--r-- | mpm/python/usrp_mpm/chips/ds125df410.py | 107 | ||||
-rw-r--r-- | mpm/python/usrp_mpm/periph_manager/n3xx.py | 17 | ||||
-rw-r--r-- | mpm/python/usrp_mpm/periph_manager/n3xx_periphs.py | 17 |
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) |