aboutsummaryrefslogtreecommitdiffstats
path: root/mpm/python/usrp_mpm/chips/lmx2572.py
diff options
context:
space:
mode:
Diffstat (limited to 'mpm/python/usrp_mpm/chips/lmx2572.py')
-rw-r--r--mpm/python/usrp_mpm/chips/lmx2572.py338
1 files changed, 338 insertions, 0 deletions
diff --git a/mpm/python/usrp_mpm/chips/lmx2572.py b/mpm/python/usrp_mpm/chips/lmx2572.py
new file mode 100644
index 000000000..e480dd53b
--- /dev/null
+++ b/mpm/python/usrp_mpm/chips/lmx2572.py
@@ -0,0 +1,338 @@
+#
+# Copyright 2019-2020 Ettus Research, a National Instruments Brand
+#
+# SPDX-License-Identifier: GPL-3.0-or-later
+#
+"""
+LMX2572 parent driver class
+"""
+
+import math
+from builtins import object
+from usrp_mpm.mpmlog import get_logger
+from usrp_mpm.chips.ic_reg_maps import lmx2572_regs_t
+
+NUMBER_OF_LMX2572_REGISTERS = 126
+
+class LMX2572(object):
+ """
+ Generic driver class for LMX2572 access.
+ """
+
+ READ_ONLY_REGISTERS = [107, 108, 109, 110, 111, 112, 113]
+
+ def __init__(self, regs_iface, parent_log = None):
+ self.log = parent_log
+
+ self.regs_iface = regs_iface
+ assert hasattr(self.regs_iface, 'peek16')
+ assert hasattr(self.regs_iface, 'poke16')
+ self._poke16 = regs_iface.poke16
+ self._peek16 = regs_iface.peek16
+
+ self._lmx2572_regs = lmx2572_regs_t()
+
+ self._need_recalculation = True
+ self._enabled = False
+
+ @property
+ def enabled(self):
+ return self._enabled
+
+ @enabled.setter
+ def enabled(self, enable):
+ self._set_chip_enable(bool(enable))
+ self._enabled = bool(enable)
+
+ def reset(self):
+ """
+ Performs a reset of the LMX2572 by using the software reset register.
+ """
+ self._lmx2572_regs = lmx2572_regs_t()
+ self._lmx2572_regs.reset = lmx2572_regs_t.reset_t.RESET_RESET
+ self._poke16(0, self._lmx2572_regs.get_reg(0))
+ self._lmx2572_regs.reset = lmx2572_regs_t.reset_t.RESET_NORMAL_OPERATION
+ self._set_default_values()
+ self._power_up_sequence()
+
+ def commit(self):
+ """
+ Calculates the settings when needed and writes the settings to the device
+ """
+ if self._need_recalculation:
+ self._calculate_settings()
+ self._need_recalculation = False
+ self._write_registers_reference_chain()
+ self._write_registers_frequency_tuning()
+
+ def check_pll_locked(self):
+ """
+ Returns True if the PLL is locked, False otherwise.
+ """
+ # SPI MISO is multiplexed to lock detect and register readback. Reading any
+ # register when the mux is set to the lock detect will return just the lock detect signal
+ self._lmx2572_regs.muxout_ld_sel = lmx2572_regs_t.muxout_ld_sel_t.MUXOUT_LD_SEL_LOCK_DETECT
+ self._poke16(0, self._lmx2572_regs.get_reg(0))
+
+ # If the PLL is locked we expect to read 0xFFFF from any read, else 0x0000
+ value_read = self._peek16(0)
+
+ return value_read == 0xFFFF
+
+ def set_synchronization(self, enable_synchronization):
+ """
+ Enables and disables the phase synchronization
+ """
+ vco_phase_sync = lmx2572_regs_t.vco_phase_sync_en_t.VCO_PHASE_SYNC_EN_PHASE_SYNC_MODE if \
+ enable_synchronization else \
+ lmx2572_regs_t.vco_phase_sync_en_t.VCO_PHASE_SYNC_EN_NORMAL_OPERATION
+ self._lmx2572_regs.vco_phase_sync_en = vco_phase_sync
+ self._need_recalculation = True
+
+ def get_synchronization(self):
+ """
+ Returns the enabled/disabled state of the phase synchronization
+ """
+ return self._lmx2572_regs.vco_phase_sync_en == \
+ lmx2572_regs_t.vco_phase_sync_en_t.VCO_PHASE_SYNC_EN_PHASE_SYNC_MODE
+
+ def set_output_enable_all(self, enable_output):
+ """
+ Enables or disables the output on both ports
+ """
+ self._set_output_a_enable(enable_output)
+ self._set_output_b_enable(enable_output)
+
+ def _set_chip_enable(self, chip_enable):
+ """
+ Enables or disables the LMX2572 using the powerdown register
+ All other registers are maintained during powerdown
+ """
+ powerdown = lmx2572_regs_t.powerdown_t.POWERDOWN_NORMAL_OPERATION if chip_enable else \
+ lmx2572_regs_t.powerdown_t.POWERDOWN_POWER_DOWN
+ self._lmx2572_regs.powerdown = powerdown
+ self._poke16(0, self._lmx2572_regs.get_reg(0))
+
+ def peek16(self, address):
+ """
+ Wraps _peek16 to account for mux_ld_sel
+ """
+ # SPI MISO is multiplexed to lock detect and register readback. Set the mux to register
+ # readback before trying to read the register.
+ self._lmx2572_regs.muxout_ld_sel = \
+ lmx2572_regs_t.muxout_ld_sel_t.MUXOUT_LD_SEL_REGISTER_READBACK
+ self._poke16(0, self._lmx2572_regs.get_reg(0))
+
+ value_read = self._peek16(address)
+
+ self._lmx2572_regs.muxout_ld_sel = lmx2572_regs_t.muxout_ld_sel_t.MUXOUT_LD_SEL_LOCK_DETECT
+ self._poke16(0, self._lmx2572_regs.get_reg(0))
+
+ return value_read
+
+
+ def _calculate_settings(self):
+ """
+ This function is intended to be called for calculating the register settings,
+ however it is implementation, not chip specific, so it is defined but not implemented
+ """
+ raise NotImplementedError("This function is meant to be overriden by a child class.")
+
+ def _set_default_values(self):
+ """
+ The register map has all base class defaults.
+ Subclasses can override this function to have the values populated on reset.
+ """
+ pass
+
+ def _pokes16(self, addr_vals):
+ """
+ Apply a series of pokes.
+ pokes16((0,1),(0,2)) is the same as calling poke16(0,1), poke16(0,2).
+ """
+ for addr, val in addr_vals:
+ self._poke16(addr, val)
+
+ def _set_output_a_enable(self, enable_output):
+ """
+ Sets output A (OUTA_PD)
+ """
+ new_value = lmx2572_regs_t.outa_pd_t.OUTA_PD_NORMAL_OPERATION if enable_output \
+ else lmx2572_regs_t.outa_pd_t.OUTA_PD_POWER_DOWN
+ self._lmx2572_regs.outa_pd = new_value
+
+ def _set_output_b_enable(self, enable_output):
+ """
+ Sets output B (OUTB_PD)
+ """
+ new_value = lmx2572_regs_t.outb_pd_t.OUTB_PD_NORMAL_OPERATION if enable_output \
+ else lmx2572_regs_t.outb_pd_t.OUTB_PD_POWER_DOWN
+ self._lmx2572_regs.outb_pd = new_value
+
+ def _set_output_a_power(self, power):
+ """
+ Sets the output A power
+ """
+ self._lmx2572_regs.outa_pwr = power & self._lmx2572_regs.outa_pwr_mask
+
+ def _set_output_b_power(self, power):
+ """
+ Sets the output B power
+ """
+ self._lmx2572_regs.outb_pwr = power & self._lmx2572_regs.outb_pwr_mask
+
+ def _set_fcal_hpfd_adj(self, phase_detector_frequency):
+ """
+ Sets the FCAL_HPFD_ADJ value based on the Fpfd
+ """
+ # These magic number frequency constants are from the data sheet
+ if phase_detector_frequency <= 37.5e6:
+ self._lmx2572_regs.fcal_hpfd_adj = 0x0
+ elif 37.5e6 < phase_detector_frequency <= 75e6:
+ self._lmx2572_regs.fcal_hpfd_adj = 0x1
+ elif 75e6 < phase_detector_frequency <= 100e6:
+ self._lmx2572_regs.fcal_hpfd_adj = 0x2
+ else: # 100MHz < phase_detector_frequency
+ self._lmx2572_regs.fcal_hpfd_adj = 0x3
+
+ def _set_fcal_lpfd_adj(self, phase_detector_frequency):
+ """
+ Sets the FCAL_LPFD_ADJ value based on the Fpfd
+ """
+ # These magic number frequency constants are from the data sheet
+ if phase_detector_frequency >= 10e6:
+ self._lmx2572_regs.fcal_lpfd_adj = 0x0
+ elif 10e6 > phase_detector_frequency >= 5e6:
+ self._lmx2572_regs.fcal_lpfd_adj = 0x1
+ elif 5e6 > phase_detector_frequency >= 2.5e6:
+ self._lmx2572_regs.fcal_lpfd_adj = 0x2
+ else: # phase_detector_frequency < 2.5MHz
+ self._lmx2572_regs.fcal_lpfd_adj = 0x3
+
+ def _set_pll_n(self, n):
+ """
+ Sets the pll_n registers
+ """
+ self._lmx2572_regs.pll_n_upper_3_bits = (n >> 16) & \
+ self._lmx2572_regs.pll_n_upper_3_bits_mask
+ self._lmx2572_regs.pll_n_lower_16_bits = n & self._lmx2572_regs.pll_n_lower_16_bits_mask
+
+ def _set_pll_den(self, den):
+ """
+ Sets the pll_den registers
+ """
+ self._lmx2572_regs.pll_den_upper = (den >> 16) & self._lmx2572_regs.pll_den_upper_mask
+ self._lmx2572_regs.pll_den_lower = den & self._lmx2572_regs.pll_den_lower_mask
+
+ def _set_mash_seed(self, mash_seed):
+ """
+ Sets the mash seed register
+ """
+ self._lmx2572_regs.mash_seed_upper = (mash_seed >> 16) & \
+ self._lmx2572_regs.mash_seed_upper_mask
+ self._lmx2572_regs.mash_seed_lower = mash_seed & self._lmx2572_regs.mash_seed_lower_mask
+
+ def _set_pll_num(self, num):
+ """
+ Sets the pll_num registers
+ """
+ self._lmx2572_regs.pll_num_upper = (num >> 16) & self._lmx2572_regs.pll_num_upper_mask
+ self._lmx2572_regs.pll_num_lower = num & self._lmx2572_regs.pll_num_lower_mask
+
+ def _set_mash_rst_count(self, mash_rst_count):
+ """
+ Sets the mash_rst_count registers
+ """
+ self._lmx2572_regs.mash_rst_count_upper = (mash_rst_count >> 16) & \
+ self._lmx2572_regs.mash_rst_count_upper_mask
+ self._lmx2572_regs.mash_rst_count_lower = mash_rst_count & \
+ self._lmx2572_regs.mash_rst_count_lower_mask
+
+ def _compute_and_set_mult_hi(self, reference_frequency):
+ multiplier_output_frequency = (reference_frequency*(int(self._lmx2572_regs.osc_2x.value)\
+ +1)*self._lmx2572_regs.mult) / self._lmx2572_regs.pll_r_pre
+ new_mult_hi = lmx2572_regs_t.mult_hi_t.MULT_HI_GREATER_THAN_100M \
+ if self._lmx2572_regs.mult > 1 and multiplier_output_frequency > 100e6 else \
+ lmx2572_regs_t.mult_hi_t.MULT_HI_LESS_THAN_EQUAL_TO_100M
+ self._lmx2572_regs.mult_hi = new_mult_hi
+
+ def _power_up_sequence(self):
+ """
+ Performs the intial register writes for the LMX2572
+ """
+ for register in reversed(range(NUMBER_OF_LMX2572_REGISTERS)):
+ if register in LMX2572.READ_ONLY_REGISTERS:
+ continue
+ self._poke16(register, self._lmx2572_regs.get_reg(register))
+
+ def _write_registers_frequency_tuning(self):
+ """
+ This function writes just the registers for frequency tuning
+ """
+ # Write PLL_N to registers R34 and R36
+ self._poke16(34, self._lmx2572_regs.get_reg(34))
+ self._poke16(36, self._lmx2572_regs.get_reg(36))
+ # Write PLL_DEN to registers R38 and R39
+ self._poke16(38, self._lmx2572_regs.get_reg(38))
+ self._poke16(39, self._lmx2572_regs.get_reg(39))
+ # Write PLL_NUM to registers R42 and R43
+ self._poke16(42, self._lmx2572_regs.get_reg(42))
+ self._poke16(43, self._lmx2572_regs.get_reg(43))
+
+ # MASH_SEED to registers R40 and R41
+ self._poke16(40, self._lmx2572_regs.get_reg(40))
+ self._poke16(41, self._lmx2572_regs.get_reg(41))
+
+ # Write OUTA_PWR to register R44 or OUTB_PWR to register R45
+ # Write OUTA_MUX to register R45 and/or OUTB_MUX to register R46
+ self._poke16(44, self._lmx2572_regs.get_reg(44))
+ self._poke16(45, self._lmx2572_regs.get_reg(45))
+ self._poke16(46, self._lmx2572_regs.get_reg(46))
+
+ # Write CHDIV to register R75
+ self._poke16(75, self._lmx2572_regs.get_reg(75))
+
+ # Write CPG to register R14
+ self._poke16(14, self._lmx2572_regs.get_reg(14))
+
+ # Write PFD_DLY_SEL to register R37
+ self._poke16(37, self._lmx2572_regs.get_reg(37))
+
+ # Write VCO_SEL to register R20
+ self._poke16(20, self._lmx2572_regs.get_reg(20))
+
+ # Write VCO_DACISET_STRT to register R17
+ self._poke16(17, self._lmx2572_regs.get_reg(17))
+
+ # Write VCO_CALCTRL_STRT to register R78
+ self._poke16(78, self._lmx2572_regs.get_reg(78))
+
+ # Write R0 to latch double buffered registers
+ self._poke16(0, self._lmx2572_regs.get_reg(0))
+ self._poke16(0, self._lmx2572_regs.get_reg(0))
+
+ def _write_registers_reference_chain(self):
+ """
+ This function writes the registers that are used for setting the reference chain
+ """
+ # Write FCAL_HPFD_ADJ to register R0
+ # Write FCAL_LPFD_ADJ to register R0
+ self._poke16(0, self._lmx2572_regs.get_reg(0))
+
+ # Write MULT_HI to register R9
+ # Write OSC_2X to register R9
+ self._poke16(9, self._lmx2572_regs.get_reg(9))
+
+ # Write MULT to register R10
+ self._poke16(10, self._lmx2572_regs.get_reg(10))
+
+ # Write PLL_R to register R11
+ self._poke16(11, self._lmx2572_regs.get_reg(11))
+ # Write PLL_R_PRE to register R12
+ self._poke16(12, self._lmx2572_regs.get_reg(12))
+
+ # if Phase SYNC being used:
+ # Write MASH_RST_COUNT to registers R69 and 70
+ if self.get_synchronization():
+ self._poke16(70, self._lmx2572_regs.get_reg(70))
+ self._poke16(69, self._lmx2572_regs.get_reg(69))