uhd/mpm/python/usrp_mpm/periph_manager/x4xx_sample_pll.py
Martin Braun d98e0e4994 mpm: Refactor LMK04832X4xx and LMK03328X4xx
- Add class-level attributes to store constants on the chips
- LMK04832X4xx.config() is changed use a SpllConfig object, which is
  temporarily defined in x4xx_clk_mgr.py before calling config()
2023-04-27 22:38:59 -05:00

287 lines
12 KiB
Python

#
# Copyright 2019 Ettus Research, a National Instruments Brand
#
# SPDX-License-Identifier: GPL-3.0-or-later
#
"""
LMK04832 driver for use with X4xx
On the X4xx, we also refer to this as the "sample PLL". It generates the clock
that drives the RFdcs, as well as the PRC which is both a reference clock for
the daughterboards (LOs, CPLD) and for the FPGA (it generates data_clk, rfdc_clk
from this).
"""
from usrp_mpm.chips import LMK04832
from usrp_mpm.sys_utils.gpio import Gpio
from usrp_mpm.periph_manager.x4xx_clock_types import Spll1Vco
class LMK04832X4xx(LMK04832):
"""
X4xx-specific subclass of the Sample Clock PLL LMK04832 controls.
"""
# Sysref depening on the PLL1 VCO freq
SYSREFFREQ = {100e6: 2.5e6, 122.88e6: 2.56e6}
# PLL1 N Divider depending on the PLL1 VCO freq
PLL1NDIV = {100e6: 50, 122.88e6: 64}
# PLL1 Phase Detector Frequency depending on PLL1 VCO freq
PDF = {100e6: 50e3, 122.88e6: 40e3}
# maximum: 1,1023, but limited as greater dividers generate too low Fin for RFDC anyway
LMK_OUT_DIVIDERS = range(1, 35)
PRC_OUT_DIVIDERS = range(1, 1023)
def __init__(self, pll_regs_iface, log=None):
LMK04832.__init__(self, pll_regs_iface, log)
self._output_freq = None
self._prc_freq = None
self.int_vco = 0x1
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 output_freq(self):
""" Return the sample clock output frequency """
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
@property
def prc_freq(self):
"""
Return the PRC rate. This is the rate that the SPLL provides to the
MMCM. On X410, the typical setup is such that the MMCM outputs the same
clock frequency as PRC rate, but on other special cases, this
SPLL might provide a different PRC frequency to the MMCM than the MMCM
outputs to its downstream consumers of the PRC.
"""
if self._prc_freq is None:
self.log.error('The Sample PLL was never configured before '
'checking the PRC frequency!')
raise RuntimeError('The Sample PLL was never configured before '
'checking the PRC frequency!')
return self._prc_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 set_vcxo(self, source_freq: Spll1Vco):
"""
Selects either the 100e6 MHz or 122.88e6 MHz VCXO for the PLL1 loop of
the LMK04832.
"""
assert source_freq in Spll1Vco
self.log.trace(f"Selected PLL1 VCXO source {source_freq.name}")
self._sclk_pll_select.set(source_freq.value)
def config(self, cfg):
"""
Configures the LMK04832 according to the given configuration object.
"""
self.log.trace("Applying configuration: %s", str(cfg))
# Decide which internal VCO to take depending on frequency
vco_freq = cfg.output_freq * cfg.output_divider
if (LMK04832.LMK_VCO0_RANGE_MIN <= vco_freq
<= LMK04832.LMK_VCO0_RANGE_MAX):
self.int_vco = 0x0
elif (LMK04832.LMK_VCO1_RANGE_MIN <= vco_freq
<= LMK04832.LMK_VCO1_RANGE_MAX):
self.int_vco = 0x1
else:
self.log.error(f"Desired LMK VCO frequency {vco_freq/1e6} MHz "
"cannot be generated with any of the internal VCOs.")
raise RuntimeError(f"Desired LMK VCO frequency {vco_freq/1e6} MHz "
"cannot be generated with any of the internal VCOs.")
self._output_freq = cfg.output_freq
self._prc_freq = vco_freq / cfg.prc_divider
self.log.trace(
f"Configuring SPLL to output frequency of {cfg.output_freq} Hz, "
f"BRC frequency is {cfg.ref_freq/1e6} MHz, "
f"PRC rate is {self._prc_freq/1e6} MHz")
self.set_vcxo(cfg.vcxo_freq)
# Clear hard reset and trigger soft reset
self.reset(False, hard=True)
self.reset(True, hard=False)
self.reset(False, hard=False)
prescaler = self.pll2_pre_to_reg(cfg.pll2_prescaler)
# CLKout Config
self.pokes8((
# For the output divider we only use the first 8 bits as otherwise the
# input frequency to the RFDC will get too small anyway.
(0x0100, cfg.output_divider),
(0x0101, 0x0A),
(0x0102, 0x70),
(0x0103, 0x44),
(0x0104, 0x10),
(0x0105, 0x00),
(0x0106, 0x00),
(0x0107, 0x55),
(0x0108, cfg.output_divider),
(0x0109, 0x0A),
(0x010A, 0x70),
(0x010B, 0x44),
(0x010C, 0x10),
(0x010D, 0x00),
(0x010E, 0x00),
(0x010F, 0x55),
(0x0110, cfg.prc_divider),
(0x0111, 0x0A),
(0x0112, 0x60),
(0x0113, 0x40),
(0x0114, 0x10),
(0x0115, 0x00),
(0x0116, 0x00),
(0x0117, 0x44),
(0x0118, cfg.prc_divider),
(0x0119, 0x0A),
(0x011A, 0x60),
(0x011B, 0x40),
(0x011C, 0x10),
(0x011D, 0x00),
(0x011E, 0x00),
(0x011F, 0x44),
(0x0120, cfg.prc_divider),
(0x0121, 0x0A),
(0x0122, 0x60),
(0x0123, 0x40),
(0x0124, 0x20),
(0x0125, 0x00),
(0x0126, 0x00),
(0x0127, 0x44),
(0x0128, cfg.output_divider),
(0x0129, 0x0A),
(0x012A, 0x60),
(0x012B, 0x60),
(0x012C, 0x20),
(0x012D, 0x00),
(0x012E, 0x00),
(0x012F, 0x44),
(0x0130, cfg.output_divider),
(0x0131, 0x0A),
(0x0132, 0x70),
(0x0133, 0x44),
(0x0134, 0x10),
(0x0135, 0x00),
(0x0136, 0x00),
(0x0137, 0x55),
))
# We need longer lines to properly document the PLL settings
# pylint: disable=line-too-long
# PLL Config
self.pokes8((
(0x0138, (self.int_vco & 0x1) << 5), # VCO_MUX, choose VCO0 or VCO1
(0x0139, 0x00), # Set SysRef source to 'Normal SYNC' as we initially use the sync signal to synchronize dividers
(0x013A, (cfg.sysref_div & 0x1F00) >> 8), # SYSREF Divide [12:8]
(0x013B, (cfg.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, (cfg.clkin0_r_div & 0x3F00) >> 8), # CLKin0_R divider [13:8], default = 0
(0x0154, (cfg.clkin0_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, (cfg.pll1_n_div & 0x3F00) >> 8), # PLL1 N divider [13:8], default = 0
(0x015A, (cfg.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 output (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, (cfg.pll2_n_cal_div & 0x030000) >> 16), # PLL2 N Cal [17:16]
(0x0164, (cfg.pll2_n_cal_div & 0x00FF00) >> 8), # PLL2 N Cal [15:8]
(0x0165, (cfg.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, (cfg.pll2_n_div & 0x030000) >> 16), # PLL2 N[17:16]
(0x0167, (cfg.pll2_n_div & 0x00FF00) >> 8), # PLL2 N[15:8]
(0x0168, (cfg.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),
))
# pylint: enable=line-too-long
# 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.')