1# 2# Copyright 2018 Ettus Research, a National Instruments Company 3# 4# SPDX-License-Identifier: GPL-3.0-or-later 5# 6""" 7LMK04828 driver for use with Rhodium 8""" 9 10import time 11from ..mpmlog import get_logger 12from ..chips import LMK04828 13 14class LMK04828Rh(LMK04828): 15 """ 16 This class provides an interface to configure the LMK04828 IC through SPI. 17 """ 18 19 def __init__(self, slot_idx, regs_iface, ref_clock_freq, sampling_clock_freq, parent_log=None): 20 self.log = \ 21 parent_log.getChild("LMK04828-{}".format(slot_idx)) if parent_log is not None \ 22 else get_logger("LMK04828-{}".format(slot_idx)) 23 LMK04828.__init__(self, regs_iface, parent_log) 24 self.log.debug("Using reference clock frequency {:.2f} MHz".format(ref_clock_freq/1e6)) 25 self.log.debug("Using sampling clock frequency: {:.2f} MHz" 26 .format(sampling_clock_freq/1e6)) 27 self.ref_clock_freq = ref_clock_freq 28 self.sampling_clock_freq = sampling_clock_freq 29 # VCXO on Rh runs at 122.88 MHz 30 self.vcxo_freq = 122.88e6 31 self.clkin_r_divider = { 10e6: 250, 20e6: 500, 25e6: 625, 30.72e6: 750}[self.ref_clock_freq] 32 self.pll1_n_divider = { 10e6: 3072, 20e6: 3072, 25e6: 3072, 30.72e6: 3000}[self.ref_clock_freq] 33 self.pll2_r_divider = {400e6: 32, 491.52e6: 32, 500e6: 128}[self.sampling_clock_freq] 34 self.pll2_prescaler = {400e6: 5, 491.52e6: 2, 500e6: 5}[self.sampling_clock_freq] 35 self.pll2_n_divider = {400e6: 125, 491.52e6: 320, 500e6: 625}[self.sampling_clock_freq] 36 self.pll2_vco_freq = (self.vcxo_freq/self.pll2_r_divider)*self.pll2_prescaler*self.pll2_n_divider 37 self.vco_selection = self.get_vco_selection() 38 self.sysref_divider = {400e6: 144, 491.52e6: 120, 500e6: 144}[self.sampling_clock_freq] 39 self.fpga_sysref_dly = {400e6: 11, 491.52e6: 8, 500e6: 10}[self.sampling_clock_freq] 40 self.clkout_divider = {400e6: 6, 491.52e6: 5, 500e6: 6}[self.sampling_clock_freq] 41 self.lb_lo_divider = {400e6: 2, 491.52e6: 2, 500e6: 2}[self.sampling_clock_freq] 42 self.log.trace("Variable Configuration Report: " 43 "clkin0/1_r = 0d{}, clkout_div = 0d{}, pll1_n = 0d{}" 44 .format(self.clkin_r_divider, self.clkout_divider, self.pll1_n_divider)) 45 self.log.trace("Variable Configuration Report: " 46 "sysref_divider = 0d{}, fpga_sysref_dly = 0d{}," 47 .format(self.sysref_divider, self.fpga_sysref_dly)) 48 self.log.trace("Variable Configuration Report: " 49 "pll2_pre = 0d{}, pll2_n = 0d{}, pll2_vco_freq = 0d{}" 50 .format(self.pll2_prescaler, self.pll2_n_divider, self.pll2_vco_freq)) 51 52 self.init() 53 self.config() 54 55 def get_vco_freq(self): 56 """ 57 Return the calculated VCO frequency in the LMK PLL2. 58 """ 59 return self.pll2_vco_freq 60 61 62 def get_vco_selection(self): 63 """ 64 The internal VCO (0/1) is selected depending on the pll2_vco_freq value. 65 According to the datasheet: 66 VCO0 Frequency -> 2370 to 2630 MHz (Default) 67 VCO1 Frequency -> 2920 to 3080 MHz 68 The VCO selection is configured with bits 6:5 (VCO_MUX) in register 0x138: 69 0x00 | VCO 0 70 0x01 | VCO 1 71 0x01 | CLKin1 (ext) 72 0x03 | Reserved 73 This function returns the full register value with ONLY the VCO selected 74 (i.e. the returned value must be or-ed with the rest of the configuration). 75 """ 76 if (self.pll2_vco_freq >= 2370e6) and (self.pll2_vco_freq <= 2630e6): 77 self.log.trace("VCO0 selected for PLL2.") 78 return 0x00 << 5 79 elif (self.pll2_vco_freq >= 2920e6) and (self.pll2_vco_freq <= 3080e6): 80 self.log.trace("Internal VCO1 selected for PLL2.") 81 return 0x01 << 5 82 else: 83 self.log.error("The calculated PLL2 VCO frequency ({}) is not supported \ 84 by neither internal VCO".format(self.pll2_vco_freq)) 85 raise Exception("PLL2 VCO frequency not supported. Check log for details.") 86 87 88 def init(self): 89 """ 90 Basic init. Turns it on. Enables SPI reads. 91 """ 92 self.log.debug("Reset LMK & Verify") 93 self.pokes8(( 94 (0x000, 0x80), # Assert reset 95 (0x000, 0x00), # De-assert reset 96 (0x002, 0x00), # De-assert power down 97 )) 98 if not self.verify_chip_id(): 99 raise Exception("Unable to locate LMK04828") 100 101 102 def config(self): 103 """ 104 Writes the entire configuration necessary for Rhodium operation. 105 """ 106 self.log.trace("LMK Initialization") 107 108 # The sampling clocks going to the converters must be set at the sampling_clock_freq 109 # rate. But, the JESD204B IP at the FPGA expects the clocks to be half that rate; 110 # therefore, the actual divider for these clocks must be twice the normal. 111 convclk_div_val = self.divide_to_reg(self.clkout_divider) 112 convclk_cnt_val = self.divide_to_cnth_cntl_reg(self.clkout_divider) 113 fpgaclk_div_val = self.divide_to_reg(self.clkout_divider*2) 114 fpgaclk_cnt_val = self.divide_to_cnth_cntl_reg(self.clkout_divider*2) 115 # The LMK provides both the TX and RX low-band references, which are configured 116 # with the same divider values. 117 lb_lo_div_val = self.divide_to_reg(self.lb_lo_divider) 118 lb_lo_cnt_val = self.divide_to_cnth_cntl_reg(self.lb_lo_divider) 119 120 # Determine one of the SDCLkOut configuration registers (0x104) for the SYSREF 121 # signal going to the FPGA. 122 # SYSREF out VCO cycles to delay SYSREF 123 fpga_sysref_config = (0b1 << 5) | ((self.fpga_sysref_dly - 1) << 1) 124 self.log.trace("FPGA SYSREF delay register (0x104): 0x{:02X}".format(fpga_sysref_config)) 125 126 self.pokes8(( 127 (0x100, fpgaclk_div_val), # CLKout Config (FPGA Clock) 128 (0x101, fpgaclk_cnt_val), # CLKout Config 129 (0x102, 0x88), # CLKout Config 130 (0x103, 0x00), # CLKout Config 131 (0x104, fpga_sysref_config), # CLKout Config 132 (0x105, 0x00), # CLKout Config 133 (0x106, 0x72), # CLKout Config 134 (0x107, 0x11), # CLKout Config 135 (0x108, fpgaclk_div_val), # CLKout Config (MGT Reference Clock) 136 (0x109, fpgaclk_cnt_val), # CLKout Config 137 (0x10A, 0x88), # CLKout Config 138 (0x10B, 0x00), # CLKout Config 139 (0x10C, 0x00), # CLKout Config 140 (0x10D, 0x00), # CLKout Config 141 (0x10E, 0xF1), # CLKout Config 142 (0x10F, 0x05), # CLKout Config 143 (0x110, convclk_div_val), # CLKout Config (DAC Clock) 144 (0x111, convclk_cnt_val), # CLKout Config 145 (0x112, 0x22), # CLKout Config 146 (0x113, 0x00), # CLKout Config 147 (0x114, 0x20), # CLKout Config 148 (0x115, 0x00), # CLKout Config 149 (0x116, 0x72), # CLKout Config 150 (0x117, 0x75), # CLKout Config 151 (0x118, lb_lo_div_val), # CLKout Config (TX LB LO) 152 (0x119, lb_lo_cnt_val), # CLKout Config 153 (0x11A, 0x11), # CLKout Config 154 (0x11B, 0x00), # CLKout Config 155 (0x11C, 0x00), # CLKout Config 156 (0x11D, 0x00), # CLKout Config 157 (0x11E, 0x71), # CLKout Config 158 (0x11F, 0x05), # CLKout Config 159 (0x120, fpgaclk_div_val), # CLKout Config (Test Point Clock) 160 (0x121, fpgaclk_cnt_val), # CLKout Config 161 (0x122, 0x22), # CLKout Config 162 (0x123, 0x00), # CLKout Config 163 (0x124, 0x20), # CLKout Config 164 (0x125, 0x00), # CLKout Config 165 (0x126, 0x72), # CLKout Config 166 (0x127, 0x10), # CLKout Config 167 (0x128, lb_lo_div_val), # CLKout Config (RX LB LO) 168 (0x129, lb_lo_cnt_val), # CLKout Config 169 (0x12A, 0x22), # CLKout Config 170 (0x12B, 0x00), # CLKout Config 171 (0x12C, 0x00), # CLKout Config 172 (0x12D, 0x00), # CLKout Config 173 (0x12E, 0x71), # CLKout Config 174 (0x12F, 0x05), # CLKout Config 175 (0x130, convclk_div_val), # CLKout Config (ADC Clock) 176 (0x131, convclk_cnt_val), # CLKout Config 177 (0x132, 0x22), # CLKout Config 178 (0x133, 0x00), # CLKout Config 179 (0x134, 0x20), # CLKout Config 180 (0x135, 0x00), # CLKout Config 181 (0x136, 0x72), # CLKout Config 182 (0x137, 0x55), # CLKout Config 183 (0x138, (0x04 | self.vco_selection)), # VCO_MUX to VCO 0; OSCin->OSCout @ LVPECL 1600 mV 184 (0x139, 0x00), # SYSREF Source = MUX; SYSREF MUX = Normal SYNC 185 (0x13A, (self.sysref_divider & 0x1F00) >> 8), # SYSREF Divide [12:8] 186 (0x13B, (self.sysref_divider & 0x00FF) >> 0), # SYSREF Divide [7:0] 187 (0x13C, 0x00), # SYSREF DDLY [12:8] 188 (0x13D, 0x0A), # SYSREF DDLY [7:0] ... 8 is default, <8 is reserved 189 (0x13E, 0x00), # SYSREF Pulse Count = 1 pulse/request 190 (0x13F, 0x00), # Feedback Mux: Disabled. OSCin, drives PLL1N divider (Dual PLL non 0-delay). PLL2_P drives PLL2N divider. 191 (0x140, 0x00), # POWERDOWN options 192 (0x141, 0x00), # Dynamic digital delay enable 193 (0x142, 0x00), # Dynamic digital delay step 194 (0x143, 0xD1), # SYNC edge sensitive; SYSREF_CLR; SYNC Enabled; SYNC from pin no pulser 195 (0x144, 0x00), # Enable SYNC on all outputs including sysref 196 (0x145, 0x7F), # Always program to d127 197 (0x146, 0x00), # CLKin Type & En 198 (0x147, 0x0A), # CLKin_SEL = CLKin0 manual (10/20/25 MHz) / CLKin1 manual (30.72 MHz); CLKin0/1 to PLL1 199 (0x148, 0x02), # CLKin_SEL0 = input /w pulldown (default) 200 (0x149, 0x02), # CLKin_SEL1 = input w/ pulldown +SDIO RDBK (push-pull) 201 (0x14A, 0x02), # RESET type: in. w/ pulldown (default) 202 (0x14B, 0x02), # Holdover & DAC Manual Mode 203 (0x14C, 0x00), # DAC Manual Mode 204 (0x14D, 0x00), # DAC Settings (defaults) 205 (0x14E, 0x00), # DAC Settings (defaults) 206 (0x14F, 0x7F), # DAC Settings (defaults) 207 (0x150, 0x00), # Holdover Settings; bit 1 = '0' per long PLL1 lock time debug 208 (0x151, 0x02), # Holdover Settings (defaults) 209 (0x152, 0x00), # Holdover Settings (defaults) 210 (0x153, (self.clkin_r_divider & 0x3F00) >> 8), # CLKin0_R divider [13:8], default = 0 211 (0x154, (self.clkin_r_divider & 0x00FF) >> 0), # CLKin0_R divider [7:0], default = d120 212 (0x155, (self.clkin_r_divider & 0x3F00) >> 8), # CLKin1_R divider [13:8], default = 0 213 (0x156, (self.clkin_r_divider & 0x00FF) >> 0), # CLKin1_R divider [7:0], default = d150 214 (0x157, 0x00), # CLKin2_R divider [13:8], default = 0 (Not used) 215 (0x158, 0x01), # CLKin2_R divider [7:0], default = d1 (Not used) 216 (0x159, (self.pll1_n_divider & 0x3F00) >> 8), # PLL1 N divider [13:8], default = d6 217 (0x15A, (self.pll1_n_divider & 0x00FF) >> 0), # PLL1 N divider [7:0], default = d0 218 (0x15B, 0xC7), # PLL1 PFD: negative slope for active filter / CP = 750 uA 219 (0x15C, 0x27), # PLL1 DLD Count [13:8] 220 (0x15D, 0x10), # PLL1 DLD Count [7:0] 221 (0x15E, 0x00), # PLL1 R/N delay, defaults = 0 222 (0x15F, 0x0B), # Status LD1 pin = PLL1 LD, push-pull output 223 (0x160, (self.pll2_r_divider & 0x0F00) >> 8), # PLL2 R divider [11:8]; 224 (0x161, (self.pll2_r_divider & 0x00FF) >> 0), # PLL2 R divider [7:0] 225 (0x162, self.pll2_pre_to_reg(self.pll2_prescaler)), # PLL2 prescaler; OSCin freq 226 (0x163, 0x00), # PLL2 Cal = PLL2 normal val 227 (0x164, 0x00), # PLL2 Cal = PLL2 normal val 228 (0x165, 0x0A), # PLL2 Cal = PLL2 normal val 229 (0x171, 0xAA), # Write this val after x165 230 (0x172, 0x02), # Write this val after x165 231 (0x17C, 0x15), # VCo1 Cal; write before x168 232 (0x17D, 0x33), # VCo1 Cal; write before x168 233 (0x166, (self.pll2_n_divider & 0x030000) >> 16), # PLL2 N[17:16] 234 (0x167, (self.pll2_n_divider & 0x00FF00) >> 8), # PLL2 N[15:8] 235 (0x168, (self.pll2_n_divider & 0x0000FF) >> 0), # PLL2 N[7:0] 236 (0x169, 0x59), # PLL2 PFD 237 (0x16A, 0x27), # PLL2 DLD Count [13:8] = default d39; SYSREF_REQ_EN disabled. 238 (0x16B, 0x10), # PLL2 DLD Count [7:0] = default d16 239 (0x16C, 0x00), # PLL2 Loop filter R3/4 = 200 ohm 240 (0x16D, 0x00), # PLL2 loop filter C3/4 = 10 pF 241 (0x16E, 0x13), # Status LD2 pin = Output push-pull, PLL2 DLD 242 (0x173, 0x00), # Do not power down PLL2 or prescaler 243 )) 244 245 # Poll for PLL1/2 lock. Total time = 10 * 100 ms = 1000 ms max. 246 self.log.trace("Polling for PLL lock...") 247 locked = False 248 for _ in range(10): 249 time.sleep(0.100) 250 # Clear stickies 251 self.pokes8(( 252 (0x182, 0x1), # Clear Lock Detect Sticky 253 (0x182, 0x0), # Clear Lock Detect Sticky 254 (0x183, 0x1), # Clear Lock Detect Sticky 255 (0x183, 0x0), # Clear Lock Detect Sticky 256 )) 257 if self.check_plls_locked(): 258 locked = True 259 self.log.trace("LMK PLLs Locked!") 260 break 261 if not locked: 262 raise RuntimeError("At least one LMK PLL did not lock! Check the logs for details.") 263 264 self.log.trace("Setting SYNC and SYSREF config...") 265 self.pokes8(( 266 (0x143, 0xF1), # toggle SYNC polarity to trigger SYNC event 267 (0x143, 0xD1), # toggle SYNC polarity to trigger SYNC event 268 (0x139, 0x02), # SYSREF Source = MUX; SYSREF MUX = pulser 269 (0x144, 0xFF), # Disable SYNC on all outputs including sysref 270 (0x143, 0x52), # Pulser selected; SYNC enabled; 1 shot enabled 271 )) 272 self.log.info("LMK initialized and locked!") 273 274 def lmk_shift(self, num_shifts=0): 275 """ 276 Apply time shift 277 """ 278 self.log.trace("Clock Shifting Commencing using Dynamic Digital Delay...") 279 # The sampling clocks going to the converters are set at the sampling_clock_freq 280 # rate. But, the JESD204B IP at the FPGA expects the clocks to be half that rate; 281 # therefore, the actual divider for the FPGA clocks is twice the normal. 282 ddly_value_conv = self.divide_to_cnth_cntl_reg(self.clkout_divider+1) \ 283 if num_shifts >= 0 else self.divide_to_cnth_cntl_reg(self.clkout_divider-1) 284 ddly_value_fpga = self.divide_to_cnth_cntl_reg(self.clkout_divider*2+1) \ 285 if num_shifts >= 0 else self.divide_to_cnth_cntl_reg(self.clkout_divider*2-1) 286 ddly_value_sysref = self.sysref_divider+1 if num_shifts >= 0 else self.sysref_divider-1 287 # Since the LMK provides the low-band LO references for RX/TX, these need to be 288 # also shifted along with the sampling clocks to achieve the best possible 289 # phase alignment perfomance at this band. 290 ddly_value_lb_lo = self.divide_to_cnth_cntl_reg(self.lb_lo_divider+1) \ 291 if num_shifts >= 0 else self.divide_to_cnth_cntl_reg(self.lb_lo_divider-1) 292 self.pokes8(( 293 # Clocks to shift: 0(FPGA CLK), 4(DAC), 6(TX LB-LO), 8(Test), 10(RX LB-LO), 12(ADC) 294 (0x141, 0xFD), # Dynamic digital delay enable on outputs. 295 (0x143, 0x53), # SYSREF_CLR; SYNC Enabled; SYNC from pulser @ regwrite 296 (0x139, 0x02), # SYSREF_MUX = Pulser 297 (0x101, ddly_value_fpga), # Set DDLY values for DCLKout0 +/-1 on low cnt. 298 (0x102, ddly_value_fpga), # Hidden register. Write the same as previous based on inc/dec. 299 (0x111, ddly_value_conv), # Set DDLY values for DCLKout4 +/-1 on low cnt 300 (0x112, ddly_value_conv), # Hidden register. Write the same as previous based on inc/dec. 301 (0x119, ddly_value_lb_lo), # Set DDLY values for DCLKout6 +/-1 on low cnt 302 (0x11A, ddly_value_lb_lo), # Hidden register. Write the same as previous based on inc/dec. 303 (0x121, ddly_value_fpga), # Set DDLY values for DCLKout8 +/-1 on low cnt 304 (0x122, ddly_value_fpga), # Hidden register. Write the same as previous based on inc/dec. 305 (0x129, ddly_value_lb_lo), # Set DDLY values for DCLKout10 +/-1 on low cnt 306 (0x12A, ddly_value_lb_lo), # Hidden register. Write the same as previous based on inc/dec. 307 (0x131, ddly_value_conv), # Set DDLY values for DCLKout12 +/-1 on low cnt 308 (0x132, ddly_value_conv), # Hidden register. Write the same as previous based on inc/dec. 309 (0x13C, (ddly_value_sysref & 0x1F00) >> 8), # SYSREF DDLY value 310 (0x13D, (ddly_value_sysref & 0x00FF) >> 0), # SYSREF DDLY value 311 (0x144, 0x02), # Enable SYNC on outputs 0, 4, 6, 8, 10, 12 312 )) 313 for _ in range(abs(num_shifts)): 314 self.poke8(0x142, 0x1) 315 # Put everything back the way it was before shifting. 316 self.poke8(0x144, 0xFF) # Disable SYNC on all outputs including SYSREF 317 self.poke8(0x143, 0x52) # Pulser selected; SYNC enabled; 1 shot enabled 318 319 def enable_tx_lb_lo(self, enb): 320 self.poke8(0x11F, 0x05 if enb else 0x00) 321 322 def enable_rx_lb_lo(self, enb): 323 self.poke8(0x12F, 0x05 if enb else 0x00) 324