aboutsummaryrefslogtreecommitdiffstats
path: root/mpm/python/usrp_mpm/periph_manager/x4xx_sample_pll.py
diff options
context:
space:
mode:
Diffstat (limited to 'mpm/python/usrp_mpm/periph_manager/x4xx_sample_pll.py')
-rw-r--r--mpm/python/usrp_mpm/periph_manager/x4xx_sample_pll.py315
1 files changed, 315 insertions, 0 deletions
diff --git a/mpm/python/usrp_mpm/periph_manager/x4xx_sample_pll.py b/mpm/python/usrp_mpm/periph_manager/x4xx_sample_pll.py
new file mode 100644
index 000000000..3c3040d55
--- /dev/null
+++ b/mpm/python/usrp_mpm/periph_manager/x4xx_sample_pll.py
@@ -0,0 +1,315 @@
+#
+# Copyright 2019 Ettus Research, a National Instruments Brand
+#
+# SPDX-License-Identifier: GPL-3.0-or-later
+#
+"""
+LMK04832 driver for use with X4xx
+"""
+
+from usrp_mpm.chips import LMK04832
+from usrp_mpm.sys_utils.gpio import Gpio
+
+class LMK04832X4xx(LMK04832):
+ """
+ X4xx-specific subclass of the Sample Clock PLL LMK04832 controls.
+ """
+ def __init__(self, pll_regs_iface, log=None):
+ LMK04832.__init__(self, pll_regs_iface, log)
+ self._output_freq = None
+ self._is_legacy_mode = None
+
+ self._sclk_pll_reset = Gpio('SAMPLE-CLOCK-PLL-RESET', Gpio.OUTPUT, 0)
+ self._sclk_pll_select = Gpio('SAMPLE-CLOCK-PLL-VCXO-SELECT', Gpio.OUTPUT, 0)
+
+ @property
+ def is_legacy_mode(self):
+ if self._is_legacy_mode is None:
+ self.log.error('The Sample PLL was never configured before '
+ 'checking for legacy mode!')
+ raise RuntimeError('The Sample PLL was never configured before '
+ 'checking for legacy mode!')
+ return self._is_legacy_mode
+
+ @property
+ def output_freq(self):
+ if self._output_freq is None:
+ self.log.error('The Sample PLL was never configured before '
+ 'checking the output frequency!')
+ raise RuntimeError('The Sample PLL was never configured before '
+ 'checking the output frequency!')
+ return self._output_freq
+
+ def init(self):
+ """
+ Perform a soft reset, configure SPI readback, and verify chip ID
+ """
+ self.reset(False, hard=True)
+ self.reset(True)
+ self.reset(False)
+ if not self.verify_chip_id():
+ raise Exception("unable to locate LMK04832!")
+
+ def reset(self, value=True, hard=False):
+ """
+ Perform a hard reset from the GPIO pins or a soft reset from the LMK register
+ """
+ if hard:
+ self._sclk_pll_reset.set(value)
+ else:
+ self.soft_reset(value)
+
+ if not value:
+ # Enable 4-wire spi readback after a reset. 4-wire SPI is disabled
+ # by default after a reset of the LMK, but is required to perform
+ # SPI reads on the x4xx.
+ self.enable_4wire_spi()
+
+ def enable_4wire_spi(self):
+ """ Enable 4-wire SPI readback from the CLKin_SEL0 pin """
+ self.poke8(0x148, 0x33)
+ self.enable_3wire_spi = False
+
+ def set_vcxo(self, source_freq):
+ """
+ Selects either the 100e6 MHz or 122.88e6 MHz VCXO for the PLL1 loop of the LMK04832.
+ """
+ if source_freq == 100e6:
+ source_index = 0
+ elif source_freq == 122.88e6:
+ source_index = 1
+ else:
+ self.log.warning(
+ 'Selected VCXO source of {:g} is not a valid selection'
+ .format(source_freq))
+ return
+ self.log.trace(
+ 'Selected VCXO source of {:g}'
+ .format(source_freq))
+ self._sclk_pll_select.set(source_index)
+
+ def get_status(self):
+ """
+ Returns PLL lock status
+ """
+ pll1_status = self.check_plls_locked(pll='PLL1')
+ pll2_status = self.check_plls_locked(pll='PLL2')
+ return {'PLL1 lock': pll1_status,
+ 'PLL2 lock': pll2_status}
+
+ def config(self, output_freq, brc_freq, is_legacy_mode=False):
+ """
+ Configures the LMK04832 to generate the desired output_freq
+ """
+ def calculate_vcxo_freq(output_freq):
+ """
+ Returns the vcxo frequency based on the desired output frequency
+ """
+ return {2.94912e9: 122.88e6, 3e9: 100e6, 3.072e9: 122.88e6}[output_freq]
+ def calculate_pll1_n_div(output_freq):
+ """
+ Returns the PLL1 N divider value based on the desired output frequency
+ """
+ return {2.94912e9: 64, 3e9: 50, 3.072e9: 64}[output_freq]
+ def calculate_pll2_n_div(output_freq):
+ """
+ Returns the PLL2 N divider value based on the desired output frequency
+ """
+ return {2.94912e9: 12, 3e9: 10, 3.072e9: 5}[output_freq]
+ def calculate_pll2_pre(output_freq):
+ """
+ Returns the PLL2 prescaler value based on the desired output frequency
+ """
+ return {2.94912e9: 2, 3e9: 3, 3.072e9: 5}[output_freq]
+ def calculate_n_cal_div(output_freq):
+ """
+ Returns the PLL2 N cal value based on the desired output frequency
+ """
+ return {2.94912e9: 12, 3e9: 10, 3.072e9: 5}[output_freq]
+ def calculate_sysref_div(output_freq):
+ """
+ Returns the SYSREF divider value based on the desired output frequency
+ """
+ return {2.94912e9: 1152, 3e9: 1200, 3.072e9: 1200}[output_freq]
+ def calculate_clk_in_0_r_div(output_freq, brc_freq):
+ """
+ Returns the CLKin0 R divider value based on the desired output frequency
+ and current base reference clock frequency
+ """
+ pfd1 = {2.94912e9: 40e3, 3e9: 50e3, 3.072e9: 40e3}[output_freq]
+ return int(brc_freq / pfd1)
+
+ if output_freq not in (2.94912e9, 3e9, 3.072e9):
+ # A failure to config the SPLL could lead to an invalid state for
+ # downstream clocks, so throw here to alert the caller.
+ raise RuntimeError(
+ 'Selected output_freq of {:g} is not a valid selection'
+ .format(output_freq))
+
+ self._is_legacy_mode = is_legacy_mode
+ self._output_freq = output_freq
+
+ self.log.trace(
+ f"Configuring SPLL to output frequency of {output_freq} Hz, used "
+ f"BRC frquency is {brc_freq} Hz, legacy mode is {is_legacy_mode}")
+
+ self.set_vcxo(calculate_vcxo_freq(output_freq))
+
+ # Clear hard reset and trigger soft reset
+ self.reset(False, hard=True)
+ self.reset(True, hard=False)
+ self.reset(False, hard=False)
+
+ prc_divider = 0x3C if is_legacy_mode else 0x30
+
+ # CLKout Config
+ self.pokes8((
+ (0x0100, 0x01),
+ (0x0101, 0x0A),
+ (0x0102, 0x70),
+ (0x0103, 0x44),
+ (0x0104, 0x10),
+ (0x0105, 0x00),
+ (0x0106, 0x00),
+ (0x0107, 0x55),
+ (0x0108, 0x01),
+ (0x0109, 0x0A),
+ (0x010A, 0x70),
+ (0x010B, 0x44),
+ (0x010C, 0x10),
+ (0x010D, 0x00),
+ (0x010E, 0x00),
+ (0x010F, 0x55),
+ (0x0110, prc_divider),
+ (0x0111, 0x0A),
+ (0x0112, 0x60),
+ (0x0113, 0x40),
+ (0x0114, 0x10),
+ (0x0115, 0x00),
+ (0x0116, 0x00),
+ (0x0117, 0x44),
+ (0x0118, prc_divider),
+ (0x0119, 0x0A),
+ (0x011A, 0x60),
+ (0x011B, 0x40),
+ (0x011C, 0x10),
+ (0x011D, 0x00),
+ (0x011E, 0x00),
+ (0x011F, 0x44),
+ (0x0120, prc_divider),
+ (0x0121, 0x0A),
+ (0x0122, 0x60),
+ (0x0123, 0x40),
+ (0x0124, 0x20),
+ (0x0125, 0x00),
+ (0x0126, 0x00),
+ (0x0127, 0x44),
+ (0x0128, 0x01),
+ (0x0129, 0x0A),
+ (0x012A, 0x60),
+ (0x012B, 0x60),
+ (0x012C, 0x20),
+ (0x012D, 0x00),
+ (0x012E, 0x00),
+ (0x012F, 0x44),
+ (0x0130, 0x01),
+ (0x0131, 0x0A),
+ (0x0132, 0x70),
+ (0x0133, 0x44),
+ (0x0134, 0x10),
+ (0x0135, 0x00),
+ (0x0136, 0x00),
+ (0x0137, 0x55),
+ ))
+
+ # PLL Config
+ sysref_div = calculate_sysref_div(output_freq)
+ clk_in_0_r_div = calculate_clk_in_0_r_div(output_freq, brc_freq)
+ pll1_n_div = calculate_pll1_n_div(output_freq)
+ prescaler = self.pll2_pre_to_reg(calculate_pll2_pre(output_freq))
+ pll2_n_cal_div = calculate_n_cal_div(output_freq)
+ pll2_n_div = calculate_pll2_n_div(output_freq)
+ self.pokes8((
+ (0x0138, 0x20),
+ (0x0139, 0x00), # Set SysRef source to 'Normal SYNC' as we initially use the sync signal to synchronize dividers
+ (0x013A, (sysref_div & 0x1F00) >> 8), # SYSREF Divide [12:8]
+ (0x013B, (sysref_div & 0x00FF) >> 0), # SYSREF Divide [7:0]
+ (0x013C, 0x00), # set sysref delay value
+ (0x013D, 0x20), # shift SYSREF with respect to falling edge of data clock
+ (0x013E, 0x03), # set number of SYSREF pulse to 8(Default)
+ (0x013F, 0x0F), # PLL1_NCLK_MUX = Feedback mux, FB_MUX = External, FB_MUX_EN = enabled
+ (0x0140, 0x00), # All power down controls set to false.
+ (0x0141, 0x00), # Disable dynamic digital delay.
+ (0x0142, 0x00), # Set dynamic digtial delay step count to 0.
+ (0x0143, 0x81), # Enable SYNC pin, disable sync functionality, SYSREF_CLR='0, SYNC is level sensitive.
+ (0x0144, 0x00), # Allow SYNC to synchronize all SysRef and clock outputs
+ (0x0145, 0x10), # Disable PLL1 R divider SYNC, use SYNC pin for PLL1 R divider SYNC, disable PLL2 R divider SYNC
+ (0x0146, 0x00), # CLKIN0/1 type = Bipolar, disable CLKin_sel pin, disable both CLKIn source for auto-switching.
+ (0x0147, 0x06), # ClkIn0_Demux= PLL1, CLKIn1-Demux=Feedback mux (need for 0-delay mode)
+ (0x0148, 0x33), # CLKIn_Sel0 = SPI readback with output set to push-pull
+ (0x0149, 0x02), # Set SPI readback ouput to open drain (needed for 4-wire)
+ (0x014A, 0x00), # Set RESET pin as input
+ (0x014B, 0x02), # Default
+ (0x014C, 0x00), # Default
+ (0x014D, 0x00), # Default
+ (0x014E, 0xC0), # Default
+ (0x014F, 0x7F), # Default
+ (0x0150, 0x00), # Default and disable holdover
+ (0x0151, 0x02), # Default
+ (0x0152, 0x00), # Default
+ (0x0153, (clk_in_0_r_div & 0x3F00) >> 8), # CLKin0_R divider [13:8], default = 0
+ (0x0154, (clk_in_0_r_div & 0x00FF) >> 0), # CLKin0_R divider [7:0], default = d120
+ (0x0155, 0x00), # Set CLKin1 R divider to 1
+ (0x0156, 0x01), # Set CLKin1 R divider to 1
+ (0x0157, 0x00), # Set CLKin2 R divider to 1
+ (0x0158, 0x01), # Set CLKin2 R divider to 1
+ (0x0159, (pll1_n_div & 0x3F00) >> 8), # PLL1 N divider [13:8], default = 0
+ (0x015A, (pll1_n_div & 0x00FF) >> 0), # PLL1 N divider [7:0], default = d120
+ (0x015B, 0xCF), # Set PLL1 window size to 43ns, PLL1 CP ON, negative polarity, CP gain is 1.55 mA.
+ (0x015C, 0x20), # Pll1 lock detect count is 8192 cycles (default)
+ (0x015D, 0x00), # Pll1 lock detect count is 8192 cycles (default)
+ (0x015E, 0x1E), # Default holdover relative time between PLL1 R and PLL1 N divider
+ (0x015F, 0x1B), # PLL1 and PLL2 locked status in Status_LD1 pin. Status_LD1 pin is ouput (push-pull)
+ (0x0160, 0x00), # PLL2 R divider is 1
+ (0x0161, 0x01), # PLL2 R divider is 1
+ (0x0162, prescaler), # PLL2 prescaler; OSCin freq; Lower nibble must be 0x4!!!
+ (0x0163, (pll2_n_cal_div & 0x030000) >> 16), # PLL2 N Cal [17:16]
+ (0x0164, (pll2_n_cal_div & 0x00FF00) >> 8), # PLL2 N Cal [15:8]
+ (0x0165, (pll2_n_cal_div & 0x0000FF) >> 0), # PLL2 N Cal [7:0]
+ (0x0169, 0x59), # Write this val after x165. PLL2 CP gain is 3.2 mA, PLL2 window is 1.8 ns
+ (0x016A, 0x20), # PLL2 lock detect count is 8192 cycles (default)
+ (0x016B, 0x00), # PLL2 lock detect count is 8192 cycles (default)
+ (0x016E, 0x13), # Stautus_LD2 pin not used. Don't care about this register
+ (0x0173, 0x10), # PLL2 prescaler and PLL2 are enabled.
+ (0x0177, 0x00), # PLL1 R divider not in reset
+ (0x0166, (pll2_n_div & 0x030000) >> 16), # PLL2 N[17:16]
+ (0x0167, (pll2_n_div & 0x00FF00) >> 8), # PLL2 N[15:8]
+ (0x0168, (pll2_n_div & 0x0000FF) >> 0), # PLL2 N[7:0]
+ ))
+
+ # Synchronize Output and SYSREF Dividers
+ self.pokes8((
+ (0x0143, 0x91),
+ (0x0143, 0xB1),
+ (0x0143, 0x91),
+ (0x0144, 0xFF),
+ (0x0143, 0x11),
+ (0x0139, 0x12),
+ (0x0143, 0x31),
+ ))
+
+ # Check for Lock
+ # PLL2 should lock first and be relatively fast (300 us)
+ if self.wait_for_pll_lock('PLL2', timeout=5):
+ self.log.trace("PLL2 is locked after SPLL config.")
+ else:
+ self.log.error('Sample Clock PLL2 failed to lock!')
+ raise RuntimeError('Sample Clock PLL2 failed to lock! '
+ 'Check the logs for details.')
+ # PLL1 may take up to 2 seconds to lock
+ if self.wait_for_pll_lock('PLL1', timeout=2000):
+ self.log.trace("PLL1 is locked after SPLL config.")
+ else:
+ self.log.error('Sample Clock PLL1 failed to lock!')
+ raise RuntimeError('Sample Clock PLL1 failed to lock! '
+ 'Check the logs for details.')