mirror of
https://github.com/saymrwulf/uhd.git
synced 2026-05-16 21:10:10 +00:00
256 lines
13 KiB
Python
256 lines
13 KiB
Python
#
|
|
# Copyright 2017 Ettus Research, a National Instruments Company
|
|
#
|
|
# SPDX-License-Identifier: GPL-3.0-or-later
|
|
#
|
|
"""
|
|
LMK04828 driver for use with Magnesium
|
|
"""
|
|
|
|
import time
|
|
import math
|
|
from usrp_mpm.chips import LMK04828
|
|
|
|
class LMK04828Mg(LMK04828):
|
|
"""
|
|
Magnesium-specific subclass of the LMK04828 controls.
|
|
"""
|
|
def __init__(self, regs_iface, spi_lock, ref_clock_freq, master_clock_freq, log=None):
|
|
LMK04828.__init__(self, regs_iface, log)
|
|
self.log.debug("Using reference clock frequency: {} MHz".format(ref_clock_freq/1e6))
|
|
self.log.debug("Using master clock frequency: {} MHz".format(master_clock_freq/1e6))
|
|
self.spi_lock = spi_lock
|
|
assert hasattr(self.spi_lock, 'lock')
|
|
assert hasattr(self.spi_lock, 'unlock')
|
|
self.ref_clock_freq = ref_clock_freq
|
|
self.master_clock_freq = master_clock_freq
|
|
# VCXO on Mg runs at 96 MHz
|
|
self.vcxo_freq = 96e6
|
|
# Feedback clock divider is constant for Mg regardless of the master_clock_freq.
|
|
self.clkfb_divider = 24
|
|
# PLL2 R value is also constant across all sample clock combinations.
|
|
self.pll2_r_divider = 4
|
|
# PLL1 PFD is 1 MHz. Divide the ref_clock_freq by 1 MHz to set the R divider.
|
|
self.clkin_r_divider = int(math.floor(self.ref_clock_freq/1e6))
|
|
self.clkout_divider = {122.88e6: 25, 125e6: 24, 153.6e6: 20}[self.master_clock_freq]
|
|
self.pll1_n_divider = {122.88e6: 128, 125e6: 125, 153.6e6: 128}[self.master_clock_freq]
|
|
self.sysref_divider = {122.88e6: 500, 125e6: 480, 153.6e6: 400}[self.master_clock_freq]
|
|
self.pll2_prescaler = {122.88e6: 2, 125e6: 5, 153.6e6: 2}[self.master_clock_freq]
|
|
self.pll2_n_divider = {122.88e6: 64, 125e6: 25, 153.6e6: 64}[self.master_clock_freq]
|
|
self.pll2_vco_freq = (self.vcxo_freq/self.pll2_r_divider)*self.pll2_prescaler*self.pll2_n_divider
|
|
self.log.trace("Variable Configuration Report: "
|
|
"clkin1_r = 0d{}, clkout_div = 0d{}, pll1_n = 0d{}"
|
|
.format(self.clkin_r_divider, self.clkout_divider, self.pll1_n_divider))
|
|
self.log.trace("Variable Configuration Report: "
|
|
"sysref_divider = 0d{}, pll2_pre = 0d{}, pll2_n = 0d{}"
|
|
.format(self.sysref_divider, self.pll2_prescaler, self.pll2_n_divider))
|
|
self.log.trace("Variable Configuration Report: "
|
|
"pll2_vco_freq = 0d{}"
|
|
.format(self.pll2_vco_freq))
|
|
# Run .init() and .config() right off the bat. Save clock shifty-ness for later.
|
|
self.init()
|
|
self.config()
|
|
|
|
def get_vco_freq(self):
|
|
"""
|
|
Return the calculated VCO frequency in the LMK PLL2.
|
|
"""
|
|
return self.pll2_vco_freq
|
|
|
|
def init(self):
|
|
"""
|
|
Basic init. Turns it on. Let's read SPI.
|
|
"""
|
|
self.log.trace("Reset and Verify Chip ID")
|
|
self.pokes8((
|
|
(0x000, 0x90), # Assert reset
|
|
(0x000, 0x10), # De-assert reset
|
|
(0x002, 0x00), # De-assert power down
|
|
(0x148, 0x33), # Clock Select as SDO
|
|
))
|
|
if not self.verify_chip_id():
|
|
raise Exception("Unable to locate LMK04828!")
|
|
|
|
def config(self):
|
|
"""
|
|
Write lots of config foo.
|
|
"""
|
|
clkout_div_val = self.divide_to_reg(self.clkout_divider)
|
|
clkout_cnt_val = self.divide_to_cnth_cntl_reg(self.clkout_divider)
|
|
|
|
self.log.trace("Register Initialization Commencing...")
|
|
self.pokes8((
|
|
(0x100, clkout_div_val), # CLKout Config
|
|
(0x101, clkout_cnt_val), # CLKout Config
|
|
(0x102, 0xCC), # CLKout Config
|
|
(0x103, 0x00), # CLKout Config
|
|
(0x104, 0x20), # CLKout Config
|
|
(0x105, 0x00), # CLKout Config
|
|
(0x106, 0x72), # CLKout Config MYK: (0xAB where A = SYSREF, B = CLK)
|
|
(0x107, 0x15), # CLKout Config 0x15 = LVDS, 0x55 = LVPECL
|
|
(0x108, clkout_div_val), # CLKout Config
|
|
(0x109, clkout_cnt_val), # CLKout Config
|
|
(0x10A, 0xFF), # CLKout Config
|
|
(0x10B, 0x00), # CLKout Config
|
|
(0x10C, 0x00), # CLKout Config
|
|
(0x10D, 0x00), # CLKout Config
|
|
(0x10E, 0x70), # CLKout Config
|
|
(0x10F, 0x55), # CLKout Config
|
|
(0x110, clkout_div_val), # CLKout Config
|
|
(0x111, clkout_cnt_val), # CLKout Config
|
|
(0x112, 0xCC), # CLKout Config
|
|
(0x113, 0x00), # CLKout Config
|
|
(0x114, 0x00), # CLKout Config
|
|
(0x115, 0x00), # CLKout Config
|
|
(0x116, 0xF9), # CLKout Config
|
|
(0x117, 0x00), # CLKout Config
|
|
(0x118, self.divide_to_reg(self.clkfb_divider)), # CLKout Config
|
|
(0x119, self.divide_to_cnth_cntl_reg(self.clkfb_divider)), # CLKout Config
|
|
(0x11A, 0xCC), # CLKout Config
|
|
(0x11B, 0x00), # CLKout Config
|
|
(0x11C, 0x20), # CLKout Config
|
|
(0x11D, 0x00), # CLKout Config
|
|
(0x11E, 0xF1), # CLKout Config
|
|
(0x11F, 0x00), # CLKout Config
|
|
(0x120, clkout_div_val), # CLKout Config
|
|
(0x121, clkout_cnt_val), # CLKout Config
|
|
(0x122, 0xCC), # CLKout Config
|
|
(0x123, 0x00), # CLKout Config
|
|
(0x124, 0x20), # CLKout Config 0x20 = SYSREF output, 0x00 = DEVCLK
|
|
(0x125, 0x00), # CLKout Config
|
|
(0x126, 0x72), # CLKout Config FPGA: (0xAB where A = SYSREF, B = CLK)
|
|
(0x127, 0x55), # CLKout Config 0x1 = LVDS, 0x5 = LVPECL
|
|
(0x128, clkout_div_val), # CLKout Config
|
|
(0x129, clkout_cnt_val), # CLKout Config
|
|
(0x12A, 0xCC), # CLKout Config
|
|
(0x12B, 0x00), # CLKout Config
|
|
(0x12C, 0x00), # CLKout Config
|
|
(0x12D, 0x00), # CLKout Config
|
|
(0x12E, 0x72), # CLKout Config
|
|
(0x12F, 0xD0), # CLKout Config
|
|
(0x130, clkout_div_val), # CLKout Config
|
|
(0x131, clkout_cnt_val), # CLKout Config
|
|
(0x132, 0xCC), # CLKout Config
|
|
(0x133, 0x00), # CLKout Config
|
|
(0x134, 0x20), # CLKout Config
|
|
(0x135, 0x00), # CLKout Config
|
|
(0x136, 0xF1), # CLKout Config
|
|
(0x137, 0x05), # CLKout Config
|
|
(0x138, 0x30), # VCO_MUX to VCO 1; OSCout off
|
|
(0x139, 0x00), # SYSREF Source = MUX; SYSREF MUX = Normal SYNC
|
|
(0x13A, (self.sysref_divider & 0x1F00) >> 8), # SYSREF Divide [12:8]
|
|
(0x13B, (self.sysref_divider & 0x00FF) >> 0), # SYSREF Divide [7:0]
|
|
(0x13C, 0x00), # SYSREF DDLY [12:8]
|
|
(0x13D, 0x08), # SYSREF DDLY [7:0] ... 8 is default, <8 is reserved
|
|
(0x13E, 0x00), # SYSREF Pulse Count = 1 pulse/request
|
|
(0x13F, 0x09), # Feedback Mux: Enabled, DCLKout6, drives PLL1N divider
|
|
(0x140, 0x00), # POWERDOWN options
|
|
(0x141, 0x00), # Dynamic digital delay enable
|
|
(0x142, 0x00), # Dynamic digital delay step
|
|
(0x143, 0xD1), # SYNC edge sensitive; SYSREF_CLR; SYNC Enabled; SYNC from pin no pulser
|
|
(0x144, 0x00), # Enable SYNC on all outputs including sysref
|
|
(0x145, 0x7F), # Always program to d127
|
|
(0x146, 0x10), # CLKin Type & En
|
|
(0x147, 0x1A), # CLKin_SEL = CLKin1 manual; CLKin1 to PLL1
|
|
# (0x148, 0x01), # CLKin_SEL0 = input with pullup: previously written above!
|
|
(0x149, 0x01), # CLKin_SEL1 = input with pulldown
|
|
(0x14A, 0x02), # RESET type
|
|
(0x14B, 0x01), # Holdover & DAC Manual Mode
|
|
(0x14C, 0xF6), # DAC Manual Mode
|
|
(0x14D, 0x00), # DAC Settings (defaults)
|
|
(0x14E, 0x00), # DAC Settings (defaults)
|
|
(0x14F, 0x7F), # DAC Settings (defaults)
|
|
(0x150, 0x00), # Holdover Settings; bits 0/1 = '0' per long PLL1 lock time debug
|
|
(0x151, 0x02), # Holdover Settings (defaults)
|
|
(0x152, 0x00), # Holdover Settings (defaults)
|
|
(0x153, 0x00), # CLKin0_R divider [13:8], default = 0
|
|
(0x154, 0x78), # CLKin0_R divider [7:0], default = d120
|
|
(0x155, (self.clkin_r_divider & 0x3F00) >> 8), # CLKin1_R divider [13:8], default = 0
|
|
(0x156, (self.clkin_r_divider & 0x00FF) >> 0), # CLKin1_R divider [7:0], default = d120
|
|
(0x157, 0x00), # CLKin2_R divider [13:8], default = 0
|
|
(0x158, 0x01), # CLKin2_R divider [7:0], default = d120
|
|
(0x159, (self.pll1_n_divider & 0x3F00) >> 8), # PLL1 N divider [13:8], default = 0
|
|
(0x15A, (self.pll1_n_divider & 0x00FF) >> 0), # PLL1 N divider [7:0], default = d120
|
|
(0x15B, 0xCF), # PLL1 PFD
|
|
(0x15C, 0x27), # PLL1 DLD Count [13:8]
|
|
(0x15D, 0x10), # PLL1 DLD Count [7:0]
|
|
(0x15E, 0x00), # PLL1 R/N delay, defaults = 0
|
|
(0x15F, 0x0B), # Status LD1 pin = PLL1 LD, push-pull output
|
|
(0x160, 0x00), # PLL2 R divider [11:8];
|
|
(0x161, 0x04), # PLL2 R divider [7:0]
|
|
(0x162, self.pll2_pre_to_reg(self.pll2_prescaler)), # PLL2 prescaler; OSCin freq 0xA4
|
|
(0x163, 0x00), # PLL2 Cal = PLL2 normal val
|
|
(0x164, 0x00), # PLL2 Cal = PLL2 normal val
|
|
(0x165, 0x19), # PLL2 Cal = PLL2 normal val
|
|
(0x171, 0xAA), # Write this val after x165
|
|
(0x172, 0x02), # Write this val after x165
|
|
(0x17C, 0x15), # VCo1 Cal; write before x168
|
|
(0x17D, 0x33), # VCo1 Cal; write before x168
|
|
(0x166, (self.pll2_n_divider & 0x030000) >> 16), # PLL2 N[17:16]
|
|
(0x167, (self.pll2_n_divider & 0x00FF00) >> 8), # PLL2 N[15:8]
|
|
(0x168, (self.pll2_n_divider & 0x0000FF) >> 0), # PLL2 N[7:0]
|
|
(0x169, 0x51), # PLL2 PFD
|
|
(0x16A, 0x27), # PLL2 DLD Count [13:8] = default d32
|
|
(0x16B, 0x10), # PLL2 DLD Count [7:0] = default d0
|
|
(0x16C, 0x00), # PLL2 Loop filter r = 200 ohm
|
|
(0x16D, 0x00), # PLL2 loop filter c = 10 pF
|
|
(0x16E, 0x13), # Status LD2 pin = Output push-pull, PLL2 DLD
|
|
(0x173, 0x00), # Do not power down PLL2 or prescaler
|
|
))
|
|
|
|
# Poll for PLL1/2 lock. Total time = 6 * 50 ms = 300 ms
|
|
self.log.trace("Polling for PLL lock...")
|
|
locked = False
|
|
for _ in range(6):
|
|
time.sleep(0.050)
|
|
# Clear stickies
|
|
self.pokes8((
|
|
(0x182, 0x1), # Clear Lock Detect Sticky
|
|
(0x182, 0x0), # Clear Lock Detect Sticky
|
|
(0x183, 0x1), # Clear Lock Detect Sticky
|
|
(0x183, 0x0), # Clear Lock Detect Sticky
|
|
))
|
|
if self.check_plls_locked():
|
|
locked = True
|
|
self.log.trace("PLLs are Locked!")
|
|
break
|
|
if not locked:
|
|
raise RuntimeError("At least one PLL did not lock! Check the logs for details.")
|
|
|
|
self.log.trace("Synchronizing output dividers...")
|
|
self.pokes8((
|
|
(0x143, 0xF1), # toggle SYNC polarity to trigger SYNC event
|
|
(0x143, 0xD1), # toggle SYNC polarity to trigger SYNC event
|
|
(0x139, 0x02), # SYSREF Source = MUX; SYSREF MUX = pulser
|
|
(0x144, 0xFF), # Disable SYNC on all outputs including sysref
|
|
(0x143, 0x52), # Pulser selected; SYNC enabled; 1 shot enabled
|
|
))
|
|
self.log.debug("Clocks Initialized and PLLs Locked!")
|
|
|
|
def lmk_shift(self, num_shifts=0):
|
|
"""
|
|
Apply time shift using the dynamic digital delays inside the LMK.
|
|
"""
|
|
self.log.trace("Clock Shifting Commencing using Dynamic Digital Delay...")
|
|
ddly_value = self.divide_to_cnth_cntl_reg(self.clkout_divider+1) \
|
|
if num_shifts >= 0 else self.divide_to_cnth_cntl_reg(self.clkout_divider-1)
|
|
ddly_value_sysref = self.sysref_divider+1 if num_shifts >= 0 else self.sysref_divider-1
|
|
self.pokes8((
|
|
(0x141, 0xB1), # Dynamic digital delay enable on outputs 0, 8, 10
|
|
(0x143, 0x53), # SYSREF_CLR; SYNC Enabled; SYNC from pulser @ regwrite
|
|
(0x139, 0x02), # SYSREF_MUX = Pulser
|
|
(0x101, ddly_value), # Set DDLY values for DCLKout0 +/-1 on low cnt.
|
|
(0x102, ddly_value), # Hidden register. Write the same as previous based on inc/dec.
|
|
(0x121, ddly_value), # Set DDLY values for DCLKout8 +/-1 on low cnt
|
|
(0x122, ddly_value), # Hidden register. Write the same as previous based on inc/dec.
|
|
(0x129, ddly_value), # Set DDLY values for DCLKout10 +/-1 on low cnt
|
|
(0x12A, ddly_value), # Hidden register. Write the same as previous based on inc/dec.
|
|
(0x13C, (ddly_value_sysref & 0x1F00) >> 8), # SYSREF DDLY value [12:8]
|
|
(0x13D, (ddly_value_sysref & 0x00FF) >> 0), # SYSREF DDLY value [7:0]
|
|
(0x144, 0x4E), # Enable SYNC on outputs 0, 8, 10
|
|
))
|
|
for _ in range(abs(num_shifts)):
|
|
self.poke8(0x142, 0x1)
|
|
# Put everything back the way it was before shifting.
|
|
self.poke8(0x144, 0xFF) # Disable SYNC on all outputs including SYSREF
|
|
self.poke8(0x143, 0x52) # Pulser selected; SYNC enabled; 1 shot enabled
|