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