diff options
author | Mark Meserve <mark.meserve@ni.com> | 2018-10-24 15:24:21 -0500 |
---|---|---|
committer | Brent Stapleton <bstapleton@g.hmc.edu> | 2018-10-25 10:30:59 -0700 |
commit | d87ae61d0490339bfbd7de5d57e692e9e8961237 (patch) | |
tree | 3e11b3c5f3c0aab3624dadf78ddcbcb2af85b3b9 /mpm/python/usrp_mpm/dboard_manager/adc_rh.py | |
parent | ec2977d8cbb233988b93f81deab7af99daec4165 (diff) | |
download | uhd-d87ae61d0490339bfbd7de5d57e692e9e8961237.tar.gz uhd-d87ae61d0490339bfbd7de5d57e692e9e8961237.tar.bz2 uhd-d87ae61d0490339bfbd7de5d57e692e9e8961237.zip |
rh: add support for rhodium devices
Co-authored-by: Humberto Jimenez <humberto.jimenez@ni.com>
Co-authored-by: Alex Williams <alex.williams@ni.com>
Co-authored-by: Derek Kozel <derek.kozel@ni.com>
Diffstat (limited to 'mpm/python/usrp_mpm/dboard_manager/adc_rh.py')
-rw-r--r-- | mpm/python/usrp_mpm/dboard_manager/adc_rh.py | 242 |
1 files changed, 242 insertions, 0 deletions
diff --git a/mpm/python/usrp_mpm/dboard_manager/adc_rh.py b/mpm/python/usrp_mpm/dboard_manager/adc_rh.py new file mode 100644 index 000000000..2befa011f --- /dev/null +++ b/mpm/python/usrp_mpm/dboard_manager/adc_rh.py @@ -0,0 +1,242 @@ +# +# Copyright 2018 Ettus Research, a National Instruments Company +# +# SPDX-License-Identifier: GPL-3.0-or-later +# +""" +AD9695 driver for use with Rhodium +""" + +import time +from builtins import object +from ..mpmlog import get_logger + +class AD9695Rh(object): + """ + This class provides an interface to configure the AD9695 IC through SPI. + """ + + ADC_CHIP_ID = 0xDE + + CHIP_CONFIGURATION_REG = 0x0002 + CHIP_ID_LSB_REG = 0x0004 + SCRATCH_PAD_REG = 0x000A + + def __init__(self, slot_idx, regs_iface, parent_log=None): + self.log = parent_log.getChild("AD9695") if parent_log is not None \ + else get_logger("AD9695-{}".format(slot_idx)) + self.regs = regs_iface + assert hasattr(self.regs, 'peek8') + assert hasattr(self.regs, 'poke8') + + def _verify_chip_id(): + chip_id = self.regs.peek8(self.CHIP_ID_LSB_REG) + self.log.trace("ADC Chip ID: 0x{:X}".format(chip_id)) + if chip_id != self.ADC_CHIP_ID: + self.log.error("Wrong Chip ID 0x{:X}".format(chip_id)) + return False + return True + + if not _verify_chip_id(): + raise RuntimeError("Unable to locate AD9695") + + + def assert_scratch(self, scratch_val=0xAD): + """ + Method that validates the scratch register by poking and peeking. + """ + self.regs.poke8(self.SCRATCH_PAD_REG, scratch_val) + self.log.trace("Scratch write value: 0x{:X}".format(scratch_val)) + scratch_rb = self.regs.peek8(self.SCRATCH_PAD_REG) & 0xFF + self.log.trace("Scratch readback: 0x{:X}".format(scratch_rb)) + if scratch_rb != scratch_val: + raise RuntimeError("Wrong ADC scratch readback: 0x{:X}".format(scratch_rb)) + + def pokes8(self, addr_vals): + """ + Apply a series of pokes. + pokes8((0,1),(0,2)) is the same as calling poke8(0,1), poke8(0,2). + """ + for addr, val in addr_vals: + self.regs.poke8(addr, val) + + + def power_down_channel(self, power_down=False): + """ + This method either powers up/down the channel according to register 0x0002 [1:0]: + power_down = True -> Power-down mode. + Digital datapath clocks disabled; digital datapath held + in reset; JESD204B interface disabled. + power_down = False -> Normal mode. + Channel powered up. + """ + power_mode = 0b11 if power_down else 0b00 + self.regs.poke8(self.CHIP_CONFIGURATION_REG, power_mode) + + + def init(self): + """ + Basic init that resets the ADC and verifies it. + """ + self.power_down_channel(False) # Power-up the channel. + self.log.trace("Reset ADC & Verify") + self.regs.poke8(0x0000, 0x81) # Soft-reset the ADC (self-clearing). + time.sleep(0.005) # We must allow 5 ms for the ADC's bootloader. + self.assert_scratch(0xAD) # Verify scratch register R/W access. + self.regs.poke8(0x0571, 0x15) # Powerdown the JESD204B serial transmit link. + self.log.trace("ADC's JESD204B link powered down.") + + + def config(self): + """ + Check the clock status, and write configuration values! + Before performing the above, the chip is soft-reset through the + serial interface. + """ + self.init() + + clock_status = self.regs.peek8(0x011B) & 0xFF + self.log.trace("Clock status readback: 0x{:X}".format(clock_status)) + if clock_status != 0x01: + self.log.error("Input clock not detected") + raise RuntimeError("Input clock not detected for ADC") + + self.log.trace("ADC Configuration.") + self.pokes8(( + (0x003F, 0x80), # Disable PDWN/STBY pin. + (0x0040, 0x00), # FD_A|B pins configured as Fast Detect outputs. + (0x0559, 0x11), # Configure tail bits as overrange bits (1:0) (Based on VST2). + (0x055A, 0x01), # Configure tail bits as overrange bits (2) (Based on VST2). + (0x058D, 0x17), # Set K = d23 (0x17) (Frames per multiframe). + (0x0550, 0x00), # Test mode pattern generation: OFF. + (0x0571, 0x15), # JESD Link mode: ILA enabled with K28.3 and K28.7; link powered down. + (0x058F, 0x0D), # CS = 0 (Control bits); N = 14 (ADC resolution). + (0x056E, 0x10), # JESD204B lane rate range: 3.375 Gbps to 6.75 Gbps. + (0x058B, 0x03), # Scrambling disabled; L = 4 (number of lanes). + (0x058C, 0x00), # F = 1 (0x0 + 1) (Number of octets per frame) + (0x058E, 0x01), # M = 2 (0x1) (Number of converters per link). + (0x05B2, 0x01), # SERDOUT0 mapped to lane 1. + (0x05B3, 0x00), # SERDOUT1 mapped to lane 0. + (0x05C0, 0x10), # SERDOUT0 voltage swing adjust (improves RX margin at FPGA). + (0x05C1, 0x10), # SERDOUT1 voltage swing adjust (improves RX margin at FPGA). + (0x05C2, 0x10), # SERDOUT2 voltage swing adjust (improves RX margin at FPGA). + (0x05C3, 0x10), # SERDOUT3 voltage swing adjust (improves RX margin at FPGA). + )) + self.log.trace("ADC register dump finished.") + + + def init_framer(self): + """ + Initialize the ADC's framer, and check the PLL for lock. + """ + def _check_pll_lock(): + pll_lock_status = self.regs.peek8(0x056F) + if (pll_lock_status & 0x88) != 0x80: + self.log.debug("PLL reporting unlocked... Status: 0x{:x}" + .format(pll_lock_status)) + return False + return True + + self.log.trace("Initializing framer...") + self.pokes8(( + (0x0571, 0x14), # Powerup the link before JESD204B initialization. + (0x1228, 0x4F), # Reset JESD204B start-up circuit + (0x1228, 0x0F), # JESD204B start-up circuit in normal operation. The value may be 0x00. + (0x1222, 0x00), # JESD204B PLL force normal operation + (0x1222, 0x04), # Reset JESD204B PLL calibration + (0x1222, 0x00), # JESD204B PLL normal operation + (0x1262, 0x08), # Clear loss of lock bit + (0x1262, 0x00), # Loss of lock bit normal operation + )) + + self.log.trace("Polling for PLL lock...") + locked = False + for _ in range(6): + time.sleep(0.001) + # Clear stickies possibly? + if _check_pll_lock(): + locked = True + self.log.info("ADC PLL Locked!") + break + if not locked: + raise RuntimeError("ADC PLL did not lock! Check the logs for details.") + + self.log.trace("ADC framer initialized.") + + def enable_sysref_capture(self, enabled=False): + """ + Enable the SYSREF capture block. + """ + sysref_ctl1 = 0x00 # Default value is disabled. + if enabled: + sysref_ctl1 = 0b1 << 2 # N-Shot SYSREF mode + + self.log.trace("%s ADC SYSREF capture..." % {True: 'Enabling', False: 'Disabling'}[enabled]) + self.pokes8(( + (0x0120, sysref_ctl1), # Capture low-to-high N-shot SYSREF transitions on CLK's RE + (0x0121, 0x00), # Capture the next SYSREF only. + )) + self.log.trace("ADC SYSREF capture %s." % {True: 'enabled', False: 'disabled'}[enabled]) + + + def check_framer_status(self): + """ + This function checks the status of the framer by checking SYSREF capture regs. + """ + SYSREF_MONITOR_MESSAGES = { + 0 : "Condition not defined!", + 1 : "Possible setup error. The smaller the setup, the smaller its margin.", + 2 : "No setup or hold error (best hold margin).", + 3 : "No setup or hold error (best setup and hold margin).", + 4 : "No setup or hold error (best setup margin).", + 5 : "Possible hold error. The largest the hold, the smaller its margin.", + 6 : "Possible setup or hold error." + } + + # This is based of Table 37 in the AD9695's datasheet. + def _decode_setup_hold(setup, hold): + status = 0 + if setup == 0x0: + if hold == 0x0: + status = 6 + elif hold == 0x8: + status = 4 + elif (hold >= 0x9) and (hold <= 0xF): + status = 5 + elif (setup <= 0x7) and (hold == 0x0): + status = 1 + elif (setup == 0x8) and ((hold >= 0x0) and (hold <= 0x8)): + status = 2 + elif hold == 0x8: + status = 3 + return status + + self.log.trace("Checking ADC's framer status.") + + # Read the SYSREF setup and hold monitor register. + sysref_setup_hold_monitor = self.regs.peek8(0x0128) + sysref_setup = sysref_setup_hold_monitor & 0x0F + sysref_hold = (sysref_setup_hold_monitor & 0xF0) >> 4 + sysref_monitor_status = _decode_setup_hold(sysref_setup, sysref_hold) + self.log.trace("SYSREF setup: 0x{:X}".format(sysref_setup)) + self.log.trace("SYSREF hold: 0x{:X}".format(sysref_hold)) + self.log.debug("SYSREF monitor: %s" % SYSREF_MONITOR_MESSAGES[sysref_monitor_status]) + + # Read the Clock divider phase when SYSREF is captured. + sysref_phase = self.regs.peek8(0x0129) & 0x0F + self.log.trace("SYSREF capture was %.2f cycle(s) delayed from clock.", (sysref_phase * 0.5)) + self.log.trace("Clk divider phase when SYSREF was captured: 0x{:X}".format(sysref_phase)) + + # Read the SYSREF counter. + sysref_count = self.regs.peek8(0x012A) & 0xFF + self.log.trace("%d SYSREF events were captured." % sysref_count) + + if sysref_count == 0x0: + self.log.error("A SYSREF event was not captured by the ADC.") + elif sysref_monitor_status == 0: + self.log.trace("The SYSREF setup & hold monitor status is not defined.") + elif sysref_monitor_status in (1, 5, 6): + self.log.warning("SYSREF monitor: %s" % SYSREF_MONITOR_MESSAGES[sysref_monitor_status]) + elif sysref_phase > 0x0: + self.log.trace("SYSREF capture was %.2f cycle(s) delayed from clock." % (sysref_phase * 0.5)) + return sysref_count >= 0x01 |