aboutsummaryrefslogtreecommitdiffstats
path: root/mpm/python/usrp_mpm/periph_manager/x4xx_reference_pll.py
diff options
context:
space:
mode:
Diffstat (limited to 'mpm/python/usrp_mpm/periph_manager/x4xx_reference_pll.py')
-rw-r--r--mpm/python/usrp_mpm/periph_manager/x4xx_reference_pll.py339
1 files changed, 339 insertions, 0 deletions
diff --git a/mpm/python/usrp_mpm/periph_manager/x4xx_reference_pll.py b/mpm/python/usrp_mpm/periph_manager/x4xx_reference_pll.py
new file mode 100644
index 000000000..4e3a26de2
--- /dev/null
+++ b/mpm/python/usrp_mpm/periph_manager/x4xx_reference_pll.py
@@ -0,0 +1,339 @@
+#
+# Copyright 2019 Ettus Research, a National Instruments Brand
+#
+# SPDX-License-Identifier: GPL-3.0-or-later
+#
+"""
+LMK03328 driver for use with X4xx
+"""
+
+from time import sleep
+from usrp_mpm.chips import LMK03328
+from usrp_mpm.sys_utils.gpio import Gpio
+
+class LMK03328X4xx(LMK03328):
+ """
+ X4xx-specific subclass of the Reference Clock PLL LMK03328 controls.
+ """
+ def __init__(self, pll_regs_iface, log=None):
+ LMK03328.__init__(self, pll_regs_iface, log)
+
+ self._pll_status_0 = Gpio('REFERENCE-CLOCK-PLL-STATUS-0', Gpio.INPUT)
+ self._pll_status_1 = Gpio('REFERENCE-CLOCK-PLL-STATUS-1', Gpio.INPUT)
+ self._pll_pwrdown_n = Gpio('REFERENCE-CLOCK-PLL-PWRDOWN', Gpio.OUTPUT, 1)
+
+ self._reference_rates = None
+
+ @property
+ def reference_rates(self):
+ """
+ Gets a list of reference source rates indexed by [primary, secondary]
+ """
+ return self._reference_rates
+
+ @reference_rates.setter
+ def reference_rates(self, reference_rates):
+ """
+ Sets a list of reference source rates indexed by [primary, secondary]
+ """
+ assert len(reference_rates) == 2, 'Invalid number of reference rates'
+ self._reference_rates = reference_rates
+
+ def init(self):
+ """
+ Perform a soft reset and verify chip ID
+ """
+ # Clear hard reset
+ self.reset(False, hard=True)
+
+ # Enable sync mute
+ self.poke8(0x0C, 0xC8)
+
+ # Trigger soft reset
+ self.reset(True, hard=False)
+ self.reset(False, hard=False)
+
+ if not self.verify_chip_id():
+ raise Exception("unable to locate LMK03328!")
+
+ 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:
+ # The powerdown pin is active low
+ self._pll_pwrdown_n.set(not value)
+ else:
+ self.soft_reset(value)
+
+ def get_status(self):
+ """
+ Returns PLL lock and outgoing status indicators for the LMK03328
+ """
+ status_indicator_0 = self._pll_status_0.get()
+ status_indicator_1 = self._pll_status_1.get()
+ status_indicator = (status_indicator_1 << 1) | status_indicator_0
+ return {'PLL1 lock': self.check_pll_locked(1),
+ 'PLL2 lock': self.check_pll_locked(2),
+ 'status indicator': status_indicator}
+
+ def config(self, ref_select=2, brc_rate=25e6, usr_clk_rate=156.25e6, brc_select='PLL'):
+ """
+ Configure the RPLL to generate the desired MGT Reference clock sources
+ using the specified internal BRC.
+ ref_select - the reference source to use (primary=1, secondary=2)
+ brc_rate - specifies the desired rate of the output BRC
+ usr_clk_rate - specifies the desired rate to configure PLL1
+ brc_select - specifies whether the BRC out should be from the PLL ('PLL') or
+ a passthrough of the primary reference signal ('bypass')
+ """
+ def calculate_out7_mux(brc_select):
+ """
+ Returns the OUT7 Mux select register value based on the chosen BRC source.
+ Note that OUT7 is wired to the InternalRef clock which is used as the default
+ reference clock source.
+ """
+ return {'bypass': 0x98, 'PLL': 0x58}[brc_select]
+ def calculate_out7_div(brc_rate):
+ """ Returns the OUT7 Divider register value based on the chosen BRC rate """
+ return {25e6: 0x31, 125e6: 0x09}[brc_rate]
+ def calculate_pll2_input_select(ref_select):
+ """ Returns the reference mux register value based on which reference should be used """
+ assert ref_select in (1, 2)
+ return {1: 0x5B, 2: 0x7F}[ref_select]
+ def calculate_pll2_n_div(ref_rate):
+ """ Returns the PLL2 N div value based on the rate of the reference source """
+ return {25e6: 0x00C8, 100e6: 0x0032}[ref_rate]
+ def calculate_pll1_post_div(usr_clk_rate):
+ """ Returns the PLL1 post div value based the usr_clk_rate """
+ assert usr_clk_rate in (156.25e6, 125e6, 312.5e6, 161.1328125e6)
+ return {
+ 156.25e6: 0x0E,
+ 125e6: 0x0E,
+ 312.5e6: 0x0E,
+ 161.1328125e6: 0x1E,
+ }[usr_clk_rate]
+ def calculate_pll1_n_div(usr_clk_rate):
+ """ Returns the PLL1 N div value based the usr_clk_rate """
+ assert usr_clk_rate in (156.25e6, 125e6, 312.5e6, 161.1328125e6)
+ return {
+ 156.25e6: 0x0339,
+ 125e6: 0x0339,
+ 312.5e6: 0x0032,
+ 161.1328125e6: 0x0339,
+ }[usr_clk_rate]
+ def calculate_pll1_m_div(usr_clk_rate):
+ """ Returns the PLL1 M div value based the usr_clk_rate """
+ assert usr_clk_rate in (156.25e6, 125e6, 312.5e6, 161.1328125e6)
+ return {
+ 156.25e6: 0x0F,
+ 125e6: 0x0F,
+ 312.5e6: 0x00,
+ 161.1328125e6: 0x0F,
+ }[usr_clk_rate]
+ def calculate_pll_select(usr_clk_rate):
+ """ Returns the PLL selection based on the usr_clk_rate """
+ assert usr_clk_rate in (156.25e6, 125e6)
+ return {156.25e6: 2, 125e6: 2}[usr_clk_rate]
+ def get_register_from_pll(pll_selection, addr):
+ """ Returns the value to write to a specified register given
+ the desired PLL selection. """
+ assert pll_selection in (1, 2)
+ assert addr in (0x22, 0x29)
+ return {0x22: [0x00, 0x80], 0x29: [0x10, 0x50]}[addr][pll_selection-1]
+ def calculate_out_div(usr_clk_rate):
+ """ Returns the output divider for a given clock rate """
+ assert usr_clk_rate in (156.25e6, 125e6)
+ return {156.25e6: 0x07, 125e6: 0x09}[usr_clk_rate]
+
+ if self._reference_rates is None:
+ self.log.error('Cannot config reference PLL until the reference sources are set.')
+ raise RuntimeError('Cannot config reference PLL until the reference sources are set.')
+ if ref_select not in (1, 2):
+ raise RuntimeError('Selected reference source {} is invalid'.format(ref_select))
+ ref_rate = self._reference_rates[ref_select-1]
+ if ref_rate not in (25e6, 100e6):
+ raise RuntimeError('Selected reference rate {} Hz is invalid'.format(ref_rate))
+ if brc_select not in ('bypass', 'PLL'):
+ raise RuntimeError('Selected BRC source {} is invalid'.format(brc_select))
+ if brc_rate not in (25e6, 125e6):
+ raise RuntimeError('Selected BRC rate {} Hz is invalid'.format(brc_rate))
+ if brc_select == 'bypass':
+ # 'bypass' sends the primary reference directly to out7
+ actual_brc_rate = self._reference_rates[0]
+ if actual_brc_rate != brc_rate:
+ self.log.error('The specified BRC rate does not match the actual '
+ 'rate of the primary ref in bypass mode.')
+ raise RuntimeError('The specified BRC rate does not match the actual '
+ 'rate of the primary ref in bypass mode.')
+ if usr_clk_rate not in (156.25e6, 125e6):
+ raise RuntimeError('Selected RPLL clock rate {} Hz is not supported'.format(usr_clk_rate))
+
+ self.log.trace("Configuring RPLL to ref:{}, brc:{} {} Hz, clock rate:{}"
+ .format(ref_select, brc_select, brc_rate, usr_clk_rate))
+ # Config
+ pll2_input_mux = calculate_pll2_input_select(ref_select)
+ pll2_n_div = calculate_pll2_n_div(ref_rate)
+ pll1_post_div = calculate_pll1_post_div(usr_clk_rate)
+ pll1_n_div = calculate_pll1_n_div(usr_clk_rate)
+ pll1_m_div = calculate_pll1_m_div(usr_clk_rate)
+ pll_select = calculate_pll_select(usr_clk_rate)
+ out_div = calculate_out_div(usr_clk_rate)
+ out7_mux = calculate_out7_mux(brc_select)
+ out7_div = calculate_out7_div(brc_rate)
+
+ self.pokes8((
+ (0x0C, 0xDF),
+ (0x0D, 0x00),
+ (0x0E, 0x00),
+ (0x0F, 0x00),
+ (0x10, 0x00),
+ (0x11, 0x00),
+ (0x12, 0x00),
+ (0x13, 0x00),
+ (0x14, 0xFF),
+ (0x15, 0xFF),
+ (0x16, 0xFF),
+ (0x17, 0x00), # Status 0/1 mute control is disabled. Both status always ON.
+ (0x18, 0x00),
+ (0x19, 0x55),
+ (0x1A, 0x00),
+ (0x1B, 0x58),
+ (0x1C, 0x58),
+ (0x1D, 0x8F),
+ (0x1E, 0x01),
+ (0x1F, 0x00),
+ (0x20, 0x00),
+ (0x21, 0x00),
+ (0x22, get_register_from_pll(pll_select, 0x22)),
+ (0x23, 0x20),
+ (0x24, out_div),
+ (0x25, 0xD0),
+ (0x26, 0x00),
+ (0x27, 0xD0),
+ (0x28, 0x09),
+ (0x29, get_register_from_pll(pll_select, 0x29)),
+ (0x2A, out_div),
+ (0x2B, out7_mux),
+ (0x2C, out7_div),
+ (0x2D, 0x0A), # Disable all PLL divider status outputs. Both status pins are set to normal operation.
+ (0x2E, 0x00), # Disable all PLL divider status outputs.
+ (0x2F, 0x00), # Disable all PLL divider status outputs.
+ (0x30, 0xFF), # Hidden register. Value from TICS software.
+ (0x31, 0x0A), # set both status slew rate to slow (2.1 ns)
+ (0x32, pll2_input_mux),
+ (0x33, 0x03),
+ (0x34, 0x00),
+ (0x35, pll1_m_div),
+ (0x36, 0x00),
+ (0x37, 0x00),
+ (0x38, pll1_post_div), # PLL1 enabled, PLL1 output reset sync enable
+ (0x39, 0x08),
+ (0x3A, (pll1_n_div & 0x0F00) >> 8), # PLL1 N Divider [11:8]
+ (0x3B, (pll1_n_div & 0x00FF) >> 0), # PLL1 N Divider [7:0]
+ (0x3C, 0x00),
+ (0x3D, 0x00),
+ (0x3E, 0x00),
+ (0x3F, 0x00),
+ (0x40, 0x00),
+ (0x41, 0x01),
+ (0x42, 0x0C),
+ (0x43, 0x08), # PLL1 loop filter R2 = 735 ohms
+ (0x44, 0x00), # PLL1 loop filter C1 = 5 pF
+ (0x45, 0x00), # PLL1 loop filter R3 = 18 ohms
+ (0x46, 0x00), # PLL1 loop filter C3 = 0 pF
+ (0x47, 0x0E),
+ (0x48, 0x08),
+ (0x49, (pll2_n_div & 0x0F00) >> 8), # PLL2 N Divider [11:8]
+ (0x4A, (pll2_n_div & 0x00FF) >> 0), # PLL2 N Divider [7:0]
+ (0x4B, 0x00),
+ (0x4C, 0x00),
+ (0x4D, 0x00),
+ (0x4E, 0x00),
+ (0x4F, 0x00),
+ (0x50, 0x01),
+ (0x51, 0x0C),
+ (0x52, 0x08),
+ (0x53, 0x00),
+ (0x54, 0x00),
+ (0x55, 0x00),
+ (0x56, 0x08),
+ (0x57, 0x00),
+ (0x58, 0x00),
+ (0x59, 0xDE),
+ (0x5A, 0x01),
+ (0x5B, 0x18),
+ (0x5C, 0x01),
+ (0x5D, 0x4B),
+ (0x5E, 0x01),
+ (0x5F, 0x86),
+ (0x60, 0x01),
+ (0x61, 0xBE),
+ (0x62, 0x01),
+ (0x63, 0xFE),
+ (0x64, 0x02),
+ (0x65, 0x47),
+ (0x66, 0x02),
+ (0x67, 0x9E),
+ (0x68, 0x00),
+ (0x69, 0x00),
+ (0x6A, 0x05),
+ (0x6B, 0x0F),
+ (0x6C, 0x0F),
+ (0x6D, 0x0F),
+ (0x6E, 0x0F),
+ (0x6F, 0x00),
+ (0x70, 0x00),
+ (0x71, 0x00),
+ (0x72, 0x00),
+ (0x73, 0x08),
+ (0x74, 0x19),
+ (0x75, 0x00),
+ (0x76, 0x03), # PLL1 uses 2nd order loop filter recommended for integer PLL mode.
+ (0x77, 0x01),
+ (0x78, 0x00),
+ (0x79, 0x0F),
+ (0x7A, 0x0F),
+ (0x7B, 0x0F),
+ (0x7C, 0x0F),
+ (0x7D, 0x00),
+ (0x7E, 0x00),
+ (0x7F, 0x00),
+ (0x80, 0x00),
+ (0x81, 0x08),
+ (0x82, 0x19),
+ (0x83, 0x00),
+ (0x84, 0x03), # PLL2 uses 2nd order loop filter recommended for integer PLL mode.
+ (0x85, 0x01),
+ (0x86, 0x00),
+ (0x87, 0x00),
+ (0x88, 0x00),
+ (0x89, 0x10),
+ (0x8A, 0x00),
+ (0x8B, 0x00),
+ (0x8C, 0x00),
+ (0x8D, 0x00),
+ (0x8E, 0x00),
+ (0x8F, 0x00),
+ (0x90, 0x00),
+ (0x91, 0x00),
+ (0xA9, 0x40),
+ (0xAC, 0x24),
+ (0xAD, 0x00),
+ (0x0C, 0x5F), # Initiate VCO calibration
+ (0x0C, 0xDF),
+ ))
+ # wait for VCO calibration to be done and PLL to lock
+ sleep(0.5)
+ # Reset all output and PLL post dividers
+ self.pokes8((
+ (0x0C, 0x9F),
+ (0x0C, 0xDF)
+ ))
+
+ # Check for Lock
+ if not self.check_pll_locked(1):
+ raise RuntimeError('PLL1 did not lock!')
+ if not self.check_pll_locked(2):
+ raise RuntimeError('PLL2 did not lock!')
+ self.log.trace("PLLs are locked!")