1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256
|
#
# 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
|