aboutsummaryrefslogtreecommitdiffstats
path: root/mpm/python/usrp_mpm/periph_manager/x4xx_clk_aux.py
diff options
context:
space:
mode:
authorLars Amsel <lars.amsel@ni.com>2021-06-04 08:27:50 +0200
committerAaron Rossetto <aaron.rossetto@ni.com>2021-06-10 12:01:53 -0500
commit2a575bf9b5a4942f60e979161764b9e942699e1e (patch)
tree2f0535625c30025559ebd7494a4b9e7122550a73 /mpm/python/usrp_mpm/periph_manager/x4xx_clk_aux.py
parente17916220cc955fa219ae37f607626ba88c4afe3 (diff)
downloaduhd-2a575bf9b5a4942f60e979161764b9e942699e1e.tar.gz
uhd-2a575bf9b5a4942f60e979161764b9e942699e1e.tar.bz2
uhd-2a575bf9b5a4942f60e979161764b9e942699e1e.zip
uhd: Add support for the USRP X410
Co-authored-by: Lars Amsel <lars.amsel@ni.com> Co-authored-by: Michael Auchter <michael.auchter@ni.com> Co-authored-by: Martin Braun <martin.braun@ettus.com> Co-authored-by: Paul Butler <paul.butler@ni.com> Co-authored-by: Cristina Fuentes <cristina.fuentes-curiel@ni.com> Co-authored-by: Humberto Jimenez <humberto.jimenez@ni.com> Co-authored-by: Virendra Kakade <virendra.kakade@ni.com> Co-authored-by: Lane Kolbly <lane.kolbly@ni.com> Co-authored-by: Max Köhler <max.koehler@ni.com> Co-authored-by: Andrew Lynch <andrew.lynch@ni.com> Co-authored-by: Grant Meyerhoff <grant.meyerhoff@ni.com> Co-authored-by: Ciro Nishiguchi <ciro.nishiguchi@ni.com> Co-authored-by: Thomas Vogel <thomas.vogel@ni.com>
Diffstat (limited to 'mpm/python/usrp_mpm/periph_manager/x4xx_clk_aux.py')
-rw-r--r--mpm/python/usrp_mpm/periph_manager/x4xx_clk_aux.py682
1 files changed, 682 insertions, 0 deletions
diff --git a/mpm/python/usrp_mpm/periph_manager/x4xx_clk_aux.py b/mpm/python/usrp_mpm/periph_manager/x4xx_clk_aux.py
new file mode 100644
index 000000000..fcd655d9d
--- /dev/null
+++ b/mpm/python/usrp_mpm/periph_manager/x4xx_clk_aux.py
@@ -0,0 +1,682 @@
+#
+# Copyright 2021 Ettus Research, a National Instruments Brand
+#
+# SPDX-License-Identifier: GPL-3.0-or-later
+#
+"""
+Drivers for the X410 clocking aux board.
+"""
+
+import subprocess
+from usrp_mpm import lib # Pulls in everything from C++-land
+from usrp_mpm import tlv_eeprom
+from usrp_mpm.mpmlog import get_logger
+from usrp_mpm.sys_utils.gpio import Gpio
+from usrp_mpm.sys_utils.udev import dt_symbol_get_spidev
+from usrp_mpm.sys_utils.udev import get_eeprom_paths_by_symbol
+from usrp_mpm.sys_utils import i2c_dev
+from usrp_mpm.chips import LMK05318
+
+X400_CLKAUX_DEFAULT_TUNING_WORD = 0x200 # 1.65V which would be a DAC value of 512
+X400_CLKAUX_DEFAULT_REVISION = 0x1
+X400_CLKAUX_I2C_LABEL = 'clkaux_i2c'
+X400_CLKAUX_SPI_LABEL = 'clkaux_spi'
+X400_CLKAUX_GPSDO_PID = 0x4004
+X400_CLKAUX_NOGPSDO_PID = 0x4005
+
+
+def _check_i2c_bus():
+ """
+ Assert that the I2C connection to the clocking board is available in the
+ device tree.
+ """
+ i2c_bus = i2c_dev.dt_symbol_get_i2c_bus(X400_CLKAUX_I2C_LABEL)
+ if i2c_bus is None:
+ raise RuntimeError("ClockingAuxBrdControl I2C bus not found")
+
+def _check_spi_bus():
+ """
+ Assert that the SPI connection to the clocking board is available in the
+ device tree.
+ """
+ spi_node = dt_symbol_get_spidev(X400_CLKAUX_SPI_LABEL)
+ if spi_node is None:
+ raise RuntimeError("ClockingAuxBrdControl SPI bus not found")
+
+
+# pylint: disable=too-few-public-methods
+class ClkAuxTagMap:
+ """
+ x4xx main tagmap is in the main class. Only the subset relevant to the
+ clocking aux board is included below.
+ """
+ magic = 0x55535250
+ tagmap = {
+ # 0x23: usrp_eeprom_clkaux_tuning_word
+ 0x23: tlv_eeprom.NamedStruct('< H',
+ ['tuning_word']),
+ # 0x10: usrp_eeprom_board_info
+ 0x10: tlv_eeprom.NamedStruct('< H H H 7s 1x',
+ ['pid', 'rev', 'rev_compat', 'serial']),
+ }
+
+# We are encapsulating a complicated piece of hardware here, so let's live with
+# the fact that there will be many things hanging off of it.
+# pylint: disable=too-many-public-methods
+# pylint: disable=too-many-instance-attributes
+class ClockingAuxBrdControl:
+ """
+ Control interface for the Clocking Aux Board over I2C and SPI
+ """
+ SOURCE_INTERNAL = "internal"
+ SOURCE_EXTERNAL = "external"
+ SOURCE_GPSDO = "gpsdo"
+ SOURCE_NSYNC = "nsync"
+
+ SOURCE_NSYNC_LMK_PRI_FABRIC_CLK = "fabric_clk"
+ SOURCE_NSYNC_LMK_PRI_GTY_RCV_CLK = "gty_rcv_clk"
+
+ NSYNC_PRI_REF = "pri_ref"
+ NSYNC_SEC_REF = "sec_ref"
+
+ DIRECTION_INPUT = "input"
+ DIRECTION_OUTPUT = "output"
+
+ VALID_CLK_EXPORTS = (SOURCE_INTERNAL, SOURCE_GPSDO, SOURCE_NSYNC)
+ VALID_NSYNC_LMK_PRI_REF_SOURCES = (SOURCE_NSYNC_LMK_PRI_FABRIC_CLK,
+ SOURCE_NSYNC_LMK_PRI_GTY_RCV_CLK)
+
+ def __init__(self, default_source=None, parent_log=None):
+ self.log = \
+ parent_log.getChild(self.__class__.__name__) if parent_log is not None \
+ else get_logger(self.__class__.__name__)
+ _check_i2c_bus()
+ self._revision = self._get_eeprom_field('rev', X400_CLKAUX_DEFAULT_REVISION)
+ self._pid = self._get_eeprom_field('pid', X400_CLKAUX_NOGPSDO_PID)
+ self._nsync_support = self._revision >= 2
+ self._gps_support = self._pid == X400_CLKAUX_GPSDO_PID
+ default_source = default_source or ClockingAuxBrdControl.SOURCE_INTERNAL
+
+ # Some GPIO lines are named differently in the overlays for rev 1 and
+ # rev 2 and some perform different functions in rev 1 and 2 even if
+ # named similarly.
+
+ # GPIO common to rev 1 and 2
+ self._3v3_power_good = Gpio("CLKAUX_3V3_CLK_PG", Gpio.INPUT)
+ self._pps_term = Gpio("CLKAUX_PPS_TERM", Gpio.OUTPUT, 1)
+ self._trig_oe_n = Gpio("CLKAUX_TRIG_OEn", Gpio.OUTPUT, 1)
+ self._trig_dir = Gpio("CLKAUX_TRIG_DIR", Gpio.OUTPUT, 0)
+ self._ref_lck_led = Gpio("CLKAUX_REF_LCK", Gpio.OUTPUT, 0)
+
+ if self._revision == 1:
+ self._ref_clk_sel_usr = Gpio("CLKAUX_REF_CLK_SEL_USR", Gpio.OUTPUT, 1)
+ self._mbrefclk_bias = Gpio("CLKAUX_MBRefCLK_En", Gpio.OUTPUT, 0)
+ self._exportclk_bias = Gpio("CLKAUX_ExportClk_En", Gpio.OUTPUT, 0)
+ elif self._revision >= 2:
+ self._ref_clk_sel_usr = Gpio("CLKAUX_UsrRefSel", Gpio.OUTPUT, 0)
+ self._mbrefclk_bias = Gpio("CLKAUX_MBRefCLK_Bias", Gpio.OUTPUT, 0)
+ self._exportclk_bias = Gpio("CLKAUX_ExportClk_Bias", Gpio.OUTPUT, 0)
+ self._tcxo_en = Gpio("CLKAUX_TCXO_EN", Gpio.OUTPUT, 0)
+ self._exportclk_en = Gpio("CLKAUX_EXP_CLK_EN", Gpio.OUTPUT, 0)
+ self._nsync_refsel = Gpio("CLKAUX_NSYNC_REFSEL", Gpio.OUTPUT, 0)
+ self._nsync_power_ctrl = Gpio("CLKAUX_NSYNC_PDN", Gpio.OUTPUT, 0)
+ self._ref_clk_select_net = Gpio("CLKAUX_REF_CLK_SEL_NET", Gpio.OUTPUT, 0)
+ self._nsync_gpio0 = Gpio("CLKAUX_NSYNC_GPIO0", Gpio.INPUT)
+ self._nsync_status1 = Gpio("CLKAUX_NSYNC_STATUS1", Gpio.INPUT)
+ self._nsync_status0 = Gpio("CLKAUX_NSYNC_STATUS0", Gpio.INPUT)
+ self._fpga_clk_gty_fabric_sel = Gpio("CLKAUX_FPGA_CLK_SEL", Gpio.OUTPUT, 0)
+ self._lmk05318_bias = Gpio("CLKAUX_05318Ref_Bias", Gpio.OUTPUT, 0)
+
+ if self._gps_support:
+ self._gps_phase_lock = Gpio("CLKAUX_GPS_PHASELOCK", Gpio.INPUT)
+ self._gps_warmup = Gpio("CLKAUX_GPS_WARMUP", Gpio.INPUT)
+ self._gps_survey = Gpio("CLKAUX_GPS_SURVEY", Gpio.INPUT)
+ self._gps_lock = Gpio("CLKAUX_GPS_LOCK", Gpio.INPUT)
+ self._gps_alarm = Gpio("CLKAUX_GPS_ALARM", Gpio.INPUT)
+ self._gps_rst_n = Gpio("CLKAUX_GPS_RSTn", Gpio.OUTPUT, 0)
+ if self._revision == 1:
+ self._gps_initsrv_n = Gpio("CLKAUX_GPS_INITSRVn", Gpio.OUTPUT, 1)
+ elif self._revision >= 2:
+ self._gps_initsrv_n = Gpio("CLKAUX_GPS_INITSURVn", Gpio.OUTPUT, 0)
+
+ if self._revision >= 2:
+ _check_spi_bus()
+ # Create SPI interface to the LMK05318 registers
+ nsync_spi_node = dt_symbol_get_spidev(X400_CLKAUX_SPI_LABEL)
+ nsync_lmk_regs_iface = lib.spi.make_spidev_regs_iface(
+ nsync_spi_node,
+ 1000000, # Speed (Hz)
+ 0x0, # SPI mode
+ 8, # Addr shift
+ 0, # Data shift
+ 1<<23, # Read flag
+ 0, # Write flag
+ )
+ self._nsync_pll = LMK05318(nsync_lmk_regs_iface, self.log)
+
+ self.set_source(default_source)
+ self.set_trig(False, self.DIRECTION_OUTPUT)
+ self._init_dac()
+
+ def _init_dac(self):
+ """
+ Initializes i2c bus to communicate with the DAC and configures the
+ tuning word for both voltage outputs
+ """
+ dac_i2c_bus = i2c_dev.dt_symbol_get_i2c_bus(X400_CLKAUX_I2C_LABEL)
+ self._dac_i2c_iface = lib.i2c.make_i2cdev(
+ dac_i2c_bus,
+ 0xC, # addr
+ False, # ten_bit_addr
+ 100
+ )
+ tuning_word = self._get_eeprom_field('tuning_word', X400_CLKAUX_DEFAULT_TUNING_WORD)
+ self.config_dac(tuning_word, 0)
+ self.config_dac(tuning_word, 1)
+
+ def _get_eeprom_field(self, field_name, default_val):
+ """
+ Return the value of the requested eeprom field.
+ """
+ eeprom_paths = get_eeprom_paths_by_symbol("clkaux_eeprom")
+ path = eeprom_paths['clkaux_eeprom']
+ val = default_val
+ try:
+ eeprom, _ = tlv_eeprom.read_eeprom(
+ path, ClkAuxTagMap.tagmap, ClkAuxTagMap.magic, None)
+ val = eeprom.get(field_name, default_val)
+ except TypeError as err:
+ self.log.warning(f"Error reading eeprom; will use defaults. ({err})")
+ return val
+
+ def store_tuning_word(self, tuning_word):
+ """ Store the dac tuning word in the ID EEPROM"""
+ cmd = ["eeprom-update",
+ "clkaux",
+ "--clkaux_tuning_word",
+ str(tuning_word)]
+ try:
+ subprocess.call(cmd)
+ except subprocess.CalledProcessError as ex:
+ self.log.warning("Failed to write to clkaux EEPROM: %s", str(ex))
+
+ def config_dac(self, tuning_word, out_select):
+ """Configure tuning word on the the selected DAC output through i2c"""
+ high, low = divmod(tuning_word << 6, 0x100)
+ command = 0x38 if out_select else 0x31
+ tx_cmd = [command, high, low]
+ # Send command to write tuning word and update output
+ self._dac_i2c_iface.transfer(tx_cmd, 0, True)
+
+ def read_dac(self, out_select):
+ """Read the tuning word on the selected DAC output through i2c"""
+ command = 0x8 if out_select else 0x1
+ rx_buffer = self._dac_i2c_iface.transfer([command], 2, True)
+ val = ((rx_buffer[0] << 8) | rx_buffer[1]) >> 6
+ return val
+
+ def is_nsync_supported(self):
+ """Return True if nsync clock source is supported by hardware"""
+ return self._nsync_support
+
+ def _check_nsync_supported(self):
+ """
+ Assert nsync clock source is supported by hardware or throw otherwise
+ """
+ if not self.is_nsync_supported():
+ raise RuntimeError("NSYNC related features are not supported!")
+
+ def is_gps_supported(self):
+ """Return True if GPS clock source is supported by hardware"""
+ return self._gps_support
+
+ def is_gps_enabled(self):
+ """
+ Return True if the GPS is currently enabled (i.e., not held in reset).
+ """
+ return bool(self._gps_rst_n.get())
+
+ def _assert_gps_supported(self):
+ """ Throw a RuntimeError if GPS is not supported on this board. """
+ if not self.is_gps_supported():
+ raise RuntimeError("GPS related features are not supported!")
+
+ def _init_nsync_lmk(self):
+ """Initialize the LMK05318 network sync IC"""
+ self._check_nsync_supported()
+ self.set_nsync_lmk_power_en(1)
+ self._nsync_pll.soft_reset(True)
+ self._nsync_pll.soft_reset(False)
+ if not self._nsync_pll.is_chip_id_valid():
+ raise RuntimeError("ClockingAuxBrdControl Unable to locate LMK05318!")
+
+ def set_source(self, clock_source, time_source=None):
+ """
+ Select the clock and time source on the clock auxbrd.
+
+ Notes:
+ - The clocking aux board has a PPS termination which must be disabled
+ when the PPS input is active. Note that we can only have an external
+ time source when the clock source is also external. We enable it in
+ all other cases.
+ - The pin that selects the reference clock (UsrRefSel or ref_clk_sel_usr)
+ selects *both* the clock reference and the time reference (external
+ or GPSDO). So if clock source is set to external, but time source to
+ internal, then we are still connecting the PPS In SMA to the FPGA.
+ - This function will disable clock export if we switch to external clock.
+ - The time source is irrelevant unless the clock source is EXTERNAL, so
+ we allow not specifying it for the other cases.
+ - We actually put the GPS chip into reset when it's unused so we don't
+ collect spurs from there. However, this means the GPS will need to
+ warm up when being selected. Selecting the GPS as a source at runtime
+ is possible, it just won't be available until it's warmed up.
+ """
+ if clock_source not in self.VALID_CLK_EXPORTS:
+ self.export_clock(False)
+ if clock_source == self.SOURCE_INTERNAL:
+ self._set_gps_rstn(0)
+ self._set_ref_clk_sel_usr(0)
+ # If we are using an internal PPS, then we terminate the connector
+ use_pps_term = time_source == self.SOURCE_INTERNAL
+ self._pps_term.set(use_pps_term)
+ self._mbrefclk_bias.set(1)
+ if self._revision >= 2:
+ self.set_nsync_lmk_power_en(0)
+ self._lmk05318_bias.set(0)
+ self._ref_clk_select_net.set(0)
+ elif clock_source == self.SOURCE_EXTERNAL:
+ self._set_gps_rstn(0)
+ self._set_ref_clk_sel_usr(1)
+ self._exportclk_bias.set(0)
+ self._pps_term.set(1)
+ self._mbrefclk_bias.set(1)
+ if self._revision >= 2:
+ self.set_nsync_lmk_power_en(0)
+ self._lmk05318_bias.set(0)
+ self._ref_clk_select_net.set(0)
+ elif clock_source == self.SOURCE_GPSDO:
+ self._assert_gps_supported()
+ self._set_gps_rstn(1)
+ self._set_ref_clk_sel_usr(0)
+ self._pps_term.set(1)
+ self._mbrefclk_bias.set(1)
+ if self._revision >= 2:
+ self.set_nsync_lmk_power_en(0)
+ self._lmk05318_bias.set(0)
+ self._ref_clk_select_net.set(0)
+ elif clock_source == self.SOURCE_NSYNC:
+ self._check_nsync_supported()
+ self._set_gps_rstn(0)
+ self._set_ref_clk_sel_usr(0)
+ self._tcxo_en.set(1)
+ self._nsync_refsel.set(1)
+ self._mbrefclk_bias.set(0)
+ self._lmk05318_bias.set(1)
+ self._pps_term.set(1)
+ self._ref_clk_select_net.set(1)
+ self._init_nsync_lmk()
+ else:
+ raise RuntimeError('Invalid clock source {}'.format(clock_source))
+ self._source = clock_source
+ self.log.trace("set clock source to: {}".format(self._source))
+
+
+ def export_clock(self, enable=True):
+ """Export clock source to RefOut"""
+ clock_source = self.get_clock_source()
+ if not enable:
+ self._exportclk_bias.set(0)
+ self._pps_term.set(1)
+ if self._revision >= 2:
+ self._exportclk_en.set(0)
+ elif clock_source in self.VALID_CLK_EXPORTS:
+ self._exportclk_bias.set(1)
+ self._pps_term.set(0)
+ if self._revision >= 2:
+ self._exportclk_en.set(1)
+ else:
+ raise RuntimeError('Invalid source to export: {}'.format(clock_source))
+
+ def set_trig(self, enable, direction=None):
+ """Enable/disable the Trig IO out"""
+ if direction is None:
+ direction = ClockingAuxBrdControl.DIRECTION_OUTPUT
+
+ if enable:
+ self._trig_oe_n.set(0)
+ else:
+ self._trig_oe_n.set(1)
+
+ if direction == self.DIRECTION_INPUT:
+ self._trig_dir.set(0)
+ elif direction == self.DIRECTION_OUTPUT:
+ self._trig_dir.set(1)
+ else:
+ raise RuntimeError(
+ 'Invalid direction {}, valid options are {} and {}'
+ .format(direction, self.DIRECTION_INPUT, self.DIRECTION_OUTPUT))
+
+ def get_clock_source(self):
+ """Returns the clock source"""
+ return self._source
+
+ def get_gps_phase_lock(self):
+ """Returns true if the GPS Phase is locked, and false if it is not"""
+ return self._gps_phase_lock.get()
+
+ def get_gps_warmup(self):
+ """Returns true if the GPS is warming up"""
+ return self._gps_warmup.get()
+
+ def get_gps_survey(self):
+ """Returns whether or not an auto survey is in progress"""
+ return self._gps_survey.get()
+
+ def get_gps_lock(self):
+ """Returns whether or not the GPS has a lock"""
+ return self._gps_lock.get()
+
+ def get_gps_alarm(self):
+ """Returns true if the GPS detects a hardware fault or software alarm"""
+ return self._gps_alarm.get()
+
+ def get_3v3_pg(self):
+ """Returns true if the 3.3V rail is good, false otherwise"""
+ return self._3v3_power_good.get()
+
+ def _set_ref_clk_sel_usr(self, value):
+ """Sets REF_CLK_SEL_USR to value"""
+ value = int(value)
+ assert value in (0, 1)
+ if value == 1:
+ #Never set REF_CLK_SEL_USR and GPS_RSTn high at the same time, hardware can be damaged
+ self._set_gps_rstn(0)
+ self._ref_clk_sel_usr.set(value)
+
+ def _set_gps_rstn(self, value):
+ """
+ Sets GPS_RSTn to value
+
+ If value == 0, then the GPS is held in reset and is not usable.
+ """
+ value = int(value)
+ assert value in (0, 1)
+ if value == 1:
+ # Never set REF_CLK_SEL_USR and GPS_RSTn high at the same time,
+ # hardware can be damaged
+ self._set_ref_clk_sel_usr(0)
+ if self._gps_support:
+ self._gps_rst_n.set(value)
+ elif value == 1:
+ raise RuntimeError("No GPS, so can't bring it out of reset")
+
+ def get_nsync_chip_id_valid(self):
+ """Returns whether the chip ID of the LMK is valid"""
+ return self._nsync_pll.is_chip_id_valid()
+
+ def set_nsync_soft_reset(self, value=True):
+ """Soft reset LMK chip"""
+ return self._nsync_pll.soft_reset(value)
+
+ def get_nsync_status0(self):
+ """Returns value of STATUS0 pin on LMK05318 NSYNC IC"""
+ self._check_nsync_supported()
+ return self._nsync_status0.get()
+
+ def get_nsync_status1(self):
+ """Returns value of STATUS1 pin on LMK05318 NSYNC IC"""
+ self._check_nsync_supported()
+ return self._nsync_status1.get()
+
+ def set_nsync_pri_ref_source(self, source):
+ """Sets LMK05318 PRIMREF (primary reference) to specified source"""
+ self._check_nsync_supported()
+
+ if source not in self.VALID_NSYNC_LMK_PRI_REF_SOURCES:
+ raise RuntimeError(
+ "Invalid primary reference clock source for LMK05318 NSYNC IC")
+
+ self.config_dpll(source)
+ if source == self.SOURCE_NSYNC_LMK_PRI_FABRIC_CLK:
+ self._fpga_clk_gty_fabric_sel.set(1)
+ else:
+ self._fpga_clk_gty_fabric_sel.set(0)
+
+ def set_nsync_ref_select(self, source):
+ """Sets LMK05318 REFSEL to PRIREF or SECREF"""
+ self._check_nsync_supported()
+ if source == self.NSYNC_PRI_REF:
+ self._nsync_refsel.set(0)
+ elif source == self.NSYNC_SEC_REF:
+ self._nsync_refsel.set(1)
+ else:
+ raise RuntimeError(
+ "Invalid setting for LMK05318 NSYNC REFSEL")
+
+ def set_nsync_tcxo_en(self, enable):
+ """
+ Enables/Disables the 10 MHz TCXO chip output; this signal serves as the
+ oscillator input to the LMK05318 NSYNC IC.
+ """
+ self._check_nsync_supported()
+ if enable:
+ self._tcxo_en.set(1)
+ else:
+ self._tcxo_en.set(0)
+
+ def set_nsync_lmk_power_en(self, enable):
+ """Turn on/off the LMK05318 IC using the PDN pin"""
+ self._check_nsync_supported()
+ if enable:
+ self.log.trace("enable LMK05318 power")
+ self._nsync_power_ctrl.set(1)
+ else:
+ self.log.trace("disable LMK05318 power")
+ self._nsync_power_ctrl.set(0)
+
+ def write_nsync_lmk_cfg_regs_to_eeprom(self, method):
+ """program the current LMK config to LMK eeprom"""
+ self._check_nsync_supported()
+ self.log.trace("LMK05318: store cfg in eeprom")
+ self._nsync_pll.write_cfg_regs_to_eeprom(method)
+
+ def write_nsync_lmk_eeprom_to_cfg_regs(self):
+ """read register cfg from eeprom and store it into registers"""
+ self._check_nsync_supported()
+ self.log.trace("LMK05318: read cfg from eeprom")
+ self._nsync_pll.write_eeprom_to_cfg_regs()
+
+ def get_nsync_lmk_eeprom_prog_cycles(self):
+ """
+ returns the number of eeprom programming cycles
+ note:
+ the actual counter only increases after programming AND power-cycle/hard-reset
+ so multiple programming cycles without power cycle will lead to wrong
+ counter values
+ """
+ self._check_nsync_supported()
+ return self._nsync_pll.get_eeprom_prog_cycles()
+
+ def get_nsync_lmk_status_dpll(self):
+ """
+ returns the DPLL status register as human readable string
+ """
+ self._check_nsync_supported()
+ return self._nsync_pll.get_status_dpll()
+
+ def get_nsync_lmk_status_pll_xo(self):
+ """
+ returns the PLL and XO status register as human readable string
+ """
+ self._check_nsync_supported()
+ return self._nsync_pll.get_status_pll_xo()
+
+ def peek8(self, addr):
+ """Read from addr over SPI"""
+ self._check_nsync_supported()
+ val = self._nsync_pll.peek8(addr)
+ return val
+
+ def poke8(self, addr, val, overwrite_mask=False):
+ """
+ Write val to addr over SPI
+ Some register of the LMK IC are supposed not to be written and therefore
+ the whole register or just some bits. are protected by masking.
+ If you are really sure what you are doing you can overwrite the masking
+ by setting overwrite_mask=True
+ """
+ self._check_nsync_supported()
+ self._nsync_pll.poke8(addr, val, overwrite_mask)
+
+ def config_dpll(self, source):
+ """
+ configures the dpll registers needed to lock to the expected signal
+
+ Initial config files were created with TICSpro, then files were compared
+ against each other to determine which registers needed to be changed
+ """
+ if source == self.SOURCE_NSYNC_LMK_PRI_GTY_RCV_CLK:
+ self._nsync_pll.pokes8((
+ (0xC5,0x0B),
+ (0xCC,0x05),
+ (0xD1,0x08),
+ (0xD3,0x0A),
+ (0xD5,0x08),
+ (0xD7,0x0A),
+ (0xDA,0x02),
+ (0xDB,0xFA),
+ (0xDC,0xF1),
+ (0xDE,0x06),
+ (0xDF,0x1A),
+ (0xE0,0x81),
+ (0xE3,0x30),
+ (0xE4,0xD4),
+ (0xE6,0x06),
+ (0xE7,0x1A),
+ (0xE8,0x80),
+ (0x100,0x00),
+ (0x101,0x7D),
+ (0x103,0x08),
+ (0x109,0x0F),
+ (0x10A,0xA0),
+ (0x10F,0x78),
+ (0x110,0x00),
+ (0x111,0x00),
+ (0x112,0x00),
+ (0x113,0x0F),
+ (0x114,0x0E),
+ (0x115,0x0F),
+ (0x116,0x08),
+ (0x118,0x08),
+ (0x119,0x06),
+ (0x11A,0x08),
+ (0x11B,0x06),
+ (0x11E,0x00),
+ (0x11F,0x71),
+ (0x121,0xEB),
+ (0x123,0x09),
+ (0x128,0x03),
+ (0x129,0x05),
+ (0x12A,0x03),
+ (0x12D,0x3E),
+ (0x12E,0x3F),
+ (0x130,0x01),
+ (0x133,0x01),
+ (0x134,0x4D),
+ (0x135,0x55),
+ (0x136,0x55),
+ (0x137,0x55),
+ (0x138,0x55),
+ (0x139,0x55),
+ (0x13A,0xFF),
+ (0x13B,0xFF),
+ (0x13C,0xFF),
+ (0x13D,0xFF),
+ (0x13E,0xFF),
+ (0x141,0x19),
+ (0x145,0x78),
+ (0x147,0x00),
+ (0x148,0x27),
+ (0x149,0x10),
+ (0x14B,0x32),
+ (0x14F,0x78),
+ (0x151,0x00),
+ (0x152,0x27),
+ (0x153,0x10)))
+ elif source == self.SOURCE_NSYNC_LMK_PRI_FABRIC_CLK:
+ self._nsync_pll.pokes8((
+ (0xC5,0x0D),
+ (0xCC,0x07),
+ (0xD1,0x04),
+ (0xD3,0x05),
+ (0xD5,0x04),
+ (0xD7,0x05),
+ (0xDA,0x01),
+ (0xDB,0x2C),
+ (0xDC,0x00),
+ (0xDE,0x03),
+ (0xDF,0x0D),
+ (0xE0,0x40),
+ (0xE3,0x18),
+ (0xE4,0x6A),
+ (0xE6,0x03),
+ (0xE7,0x0D),
+ (0xE8,0x40),
+ (0x100,0x06),
+ (0x101,0x00),
+ (0x103,0x7D),
+ (0x109,0xF4),
+ (0x10A,0x24),
+ (0x10F,0x7A),
+ (0x110,0x1F),
+ (0x111,0x1F),
+ (0x112,0x1F),
+ (0x113,0x13),
+ (0x114,0x10),
+ (0x115,0x13),
+ (0x116,0x04),
+ (0x118,0x04),
+ (0x119,0x02),
+ (0x11A,0x07),
+ (0x11B,0x02),
+ (0x11E,0x02),
+ (0x11F,0x6C),
+ (0x121,0xE7),
+ (0x123,0x25),
+ (0x128,0x00),
+ (0x129,0x06),
+ (0x12A,0x00),
+ (0x12D,0x17),
+ (0x12E,0x1B),
+ (0x130,0x00),
+ (0x133,0x1E),
+ (0x134,0x84),
+ (0x135,0x80),
+ (0x136,0x00),
+ (0x137,0x00),
+ (0x138,0x00),
+ (0x139,0x00),
+ (0x13A,0x00),
+ (0x13B,0x00),
+ (0x13C,0x00),
+ (0x13D,0x00),
+ (0x13E,0x00),
+ (0x141,0x0A),
+ (0x145,0x0A),
+ (0x147,0x03),
+ (0x148,0x0F),
+ (0x149,0x49),
+ (0x14B,0x14),
+ (0x14F,0x9A),
+ (0x151,0x03),
+ (0x152,0x0F),
+ (0x153,0x49)))
+ else:
+ raise RuntimeError(
+ "Invalid source for dpll programming")
+
+ def set_ref_lock_led(self, val):
+ """
+ Set the reference-locked LED on the back panel
+ """
+ self._ref_lck_led.set(int(val))