aboutsummaryrefslogtreecommitdiffstats
path: root/mpm/python
diff options
context:
space:
mode:
authorThomas Vogel <thomas.vogel@ni.com>2021-06-02 15:33:19 +0200
committerAaron Rossetto <aaron.rossetto@ni.com>2021-06-03 10:21:00 -0500
commit747746f3909555a942282dd522a97ad4aaec3975 (patch)
tree3200d4e616c8f1be26c060dcbd1ed85fbee9d3e2 /mpm/python
parentd7ba6ae7c2d53d86a295117d0af8f97cacd03b3a (diff)
downloaduhd-747746f3909555a942282dd522a97ad4aaec3975.tar.gz
uhd-747746f3909555a942282dd522a97ad4aaec3975.tar.bz2
uhd-747746f3909555a942282dd522a97ad4aaec3975.zip
mpm: Add chip driver for LMK05318 PLL
Diffstat (limited to 'mpm/python')
-rw-r--r--mpm/python/usrp_mpm/chips/CMakeLists.txt1
-rw-r--r--mpm/python/usrp_mpm/chips/__init__.py1
-rw-r--r--mpm/python/usrp_mpm/chips/lmk05318.py196
3 files changed, 198 insertions, 0 deletions
diff --git a/mpm/python/usrp_mpm/chips/CMakeLists.txt b/mpm/python/usrp_mpm/chips/CMakeLists.txt
index 484df8572..5c5670b14 100644
--- a/mpm/python/usrp_mpm/chips/CMakeLists.txt
+++ b/mpm/python/usrp_mpm/chips/CMakeLists.txt
@@ -12,6 +12,7 @@ set(USRP_MPM_CHIP_FILES
${CMAKE_CURRENT_SOURCE_DIR}/lmk03328.py
${CMAKE_CURRENT_SOURCE_DIR}/adf400x.py
${CMAKE_CURRENT_SOURCE_DIR}/ds125df410.py
+ ${CMAKE_CURRENT_SOURCE_DIR}/lmk05318.py
)
list(APPEND USRP_MPM_FILES ${USRP_MPM_CHIP_FILES})
add_subdirectory(ic_reg_maps)
diff --git a/mpm/python/usrp_mpm/chips/__init__.py b/mpm/python/usrp_mpm/chips/__init__.py
index 1b43ede6a..2aedf946d 100644
--- a/mpm/python/usrp_mpm/chips/__init__.py
+++ b/mpm/python/usrp_mpm/chips/__init__.py
@@ -11,4 +11,5 @@ from .adf400x import ADF400x
from .lmk04828 import LMK04828
from .lmk04832 import LMK04832
from .lmk03328 import LMK03328
+from .lmk05318 import LMK05318
from . import ic_reg_maps
diff --git a/mpm/python/usrp_mpm/chips/lmk05318.py b/mpm/python/usrp_mpm/chips/lmk05318.py
new file mode 100644
index 000000000..8d369c7be
--- /dev/null
+++ b/mpm/python/usrp_mpm/chips/lmk05318.py
@@ -0,0 +1,196 @@
+#
+# Copyright 2020 Ettus Research, a National Instruments Brand
+#
+# SPDX-License-Identifier: GPL-3.0-or-later
+#
+"""
+LMK05318 parent driver class
+"""
+
+import time
+import datetime
+from usrp_mpm.mpmlog import get_logger
+
+class LMK05318:
+ """
+ Generic driver class for LMK05318 access.
+ """
+
+ LMK_VENDOR_ID = 0x100B
+ LMK_PROD_ID = 0x35
+
+ LMK_EEPROM_REG_COMMIT = "register_commit"
+ LMK_EEPROM_DIRECT_WRITE = "direct_write"
+
+ def __init__(self, regs_iface, parent_log=None):
+ self.log = \
+ parent_log.getChild("LMK05318") if parent_log is not None \
+ else get_logger("LMK05318")
+ self.regs_iface = regs_iface
+ assert hasattr(self.regs_iface, 'peek8')
+ assert hasattr(self.regs_iface, 'poke8')
+ self.peek8 = regs_iface.peek8
+
+ def poke8(self, addr, val, overwrite_mask=False):
+ """
+ Write val to addr via register interface
+ """
+ # TI LMK UserGuide chapter 9.5.5 states that some register require bit masks
+ # to be applied to bits to avoid writing to them
+ # mask is in the form that a 1 means that the bit shall not be modified
+ # in order to write to the address without the mask applied
+ # the overwrite_mask parameter can be set to True
+ if not overwrite_mask:
+ mask = None
+ if addr == 0x0C:
+ mask = 0xA7
+ elif addr == 0x9D:
+ mask = 0xFF
+ elif addr == 0xA4:
+ mask = 0xFF
+ elif addr in range(0x161, 0x1B3):
+ mask = 0xFF
+
+ if mask is not None:
+ current_val = self.peek8(addr)
+ val = val & ~mask
+ val = val | current_val
+ self.log.trace(
+ "Attention: writing to register {:02x} with masked bits, "
+ "mask 0x{:02x} was applied, resulting in value {:02x}"
+ .format(addr, mask, val))
+ self.regs_iface.poke8(addr, val)
+
+ def pokes8(self, addr_vals, overwrite_mask=False):
+ """
+ Apply a series of pokes.
+ pokes8((0,1),(0,2)) is the same as calling poke8(0,1), poke8(0,2).
+ """
+ for addr, val in addr_vals:
+ self.poke8(addr, val, overwrite_mask)
+
+ def get_vendor_id(self):
+ """ Read back the chip's vendor ID"""
+ vendor_id_high = self.peek8(0x00)
+ vendor_id_low = self.peek8(0x01)
+ vendor_id = (vendor_id_high << 8) \
+ | vendor_id_low
+ self.log.trace("Vendor ID Readback: 0x{:X}".format(vendor_id))
+ return vendor_id
+
+ def get_product_id(self):
+ """
+ Read back the chip's product ID
+ """
+ prod_id = self.peek8(0x02)
+ self.log.trace("Product ID Readback: 0x{:X}".format(prod_id))
+ return prod_id
+
+ def is_chip_id_valid(self):
+ """
+ Returns True if the chip ID and product ID matches what we expect,
+ False otherwise.
+ """
+ vendor_id = self.get_vendor_id()
+ prod_id = self.get_product_id()
+ if vendor_id != self.LMK_VENDOR_ID:
+ self.log.error("Wrong Vendor ID 0x{:X}".format(vendor_id))
+ return False
+ if prod_id != self.LMK_PROD_ID:
+ self.log.error("Wrong Product ID 0x{:X}".format(prod_id))
+ return False
+ return True
+
+ def soft_reset(self, value=True):
+ """
+ Performs a soft reset of the LMK05318 by setting or unsetting
+ the reset register
+ """
+ reset_addr = 0xC #DEV_CTL
+ if value: # Reset
+ reset_byte = 0x80
+ else: # Clear Reset
+ reset_byte = 0x7F & self.peek8(reset_addr)
+ self.poke8(reset_addr, reset_byte, overwrite_mask=True)
+
+ def write_cfg_regs_to_eeprom(self, method, eeprom_data=None):
+ """
+ program the current register config to LMK eeprom
+ """
+ def _wait_for_busy(self, value):
+ wait_until = datetime.datetime.now() + datetime.timedelta(seconds=2)
+ while datetime.datetime.now() < wait_until:
+ self.log.trace("wait till busy bit becomes {}".format(value))
+ busy = (self.peek8(0x9D) >> 2) & 1 # check if busy bit is cleared
+ if busy == value:
+ return True
+ time.sleep(0.01)
+ return False
+
+ if method == self.LMK_EEPROM_REG_COMMIT:
+ self.log.trace("write current device register content to EEPROM")
+ #store current cfg to SRAM
+ self.poke8(0x9D, 0x40, overwrite_mask=True)
+ time.sleep(0.01)
+ #unlock EEPROM
+ self.poke8(0xA4, 0xEA, overwrite_mask=True)
+ time.sleep(0.01)
+ #store SRAM into EEPROM
+ self.poke8(0x9D, 0x03, overwrite_mask=True)
+ #the actual programming takes about 230ms, poll the busy bit to see when it's done
+ if not _wait_for_busy(self, 1):
+ self.log.error("EEPROM does not start programming, something went wrong")
+ if not _wait_for_busy(self, 0):
+ self.log.error("EEPROM is still busy after programming, something went wrong")
+
+ elif method == self.LMK_EEPROM_DIRECT_WRITE:
+ raise RuntimeError("direct LMK05318 EEPROM programming not implemented")
+ else:
+ raise RuntimeError("Invalid method for LMK05318 EEPROM programming")
+
+ #lock EEPROM
+ self.poke8(0xA4, 0x00, overwrite_mask=True)
+ self.log.trace("programming EEPROM done, power-cycle or hard-reset to take effect")
+
+ def write_eeprom_to_cfg_regs(self):
+ """
+ read register cfg from eeprom and store it into registers
+ """
+ self.poke8(0x9D, 0x08, overwrite_mask=True)
+
+ def get_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
+ """
+ return self.peek8(0x9C)
+
+ def get_status_dpll(self):
+ """
+ returns the status register of the DPLL as human readable string
+ """
+ status = self.peek8(0x0E)
+ return f"""
+ Loss of phase lock: {status>>7 & 1}
+ Loss of freq. lock: {status>>6 & 1}
+ Tuning word update: {status>>5 & 1}
+ Holdover Event: {status>>4 & 1}
+ Reference Switch Event: {status>>3 & 1}
+ Active ref. missing clk: {status>>2 & 1}
+ Active ref. loss freq.: {status>>1 & 1}
+ Active ref. loss ampl.: {status & 1}
+ """
+
+ def get_status_pll_xo(self):
+ """
+ returns the status register of the PLLs and XO as human readable string
+ """
+ status = self.peek8(0x0D)
+ return f"""
+ Loss of freq. detection XO: {status>>4 & 1}
+ Loss of lock APLL2: {status>>3 & 1}
+ Loss of lock APLL1: {status>>2 & 1}
+ Loss of source XO: {status & 1}
+ """