mirror of
https://github.com/saymrwulf/uhd.git
synced 2026-05-16 21:10:10 +00:00
- 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()
287 lines
12 KiB
Python
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.')
|