diff options
author | Thomas Vogel <thomas.vogel@ni.com> | 2021-06-02 15:33:19 +0200 |
---|---|---|
committer | Aaron Rossetto <aaron.rossetto@ni.com> | 2021-06-03 10:21:00 -0500 |
commit | 747746f3909555a942282dd522a97ad4aaec3975 (patch) | |
tree | 3200d4e616c8f1be26c060dcbd1ed85fbee9d3e2 /mpm | |
parent | d7ba6ae7c2d53d86a295117d0af8f97cacd03b3a (diff) | |
download | uhd-747746f3909555a942282dd522a97ad4aaec3975.tar.gz uhd-747746f3909555a942282dd522a97ad4aaec3975.tar.bz2 uhd-747746f3909555a942282dd522a97ad4aaec3975.zip |
mpm: Add chip driver for LMK05318 PLL
Diffstat (limited to 'mpm')
-rw-r--r-- | mpm/python/usrp_mpm/chips/CMakeLists.txt | 1 | ||||
-rw-r--r-- | mpm/python/usrp_mpm/chips/__init__.py | 1 | ||||
-rw-r--r-- | mpm/python/usrp_mpm/chips/lmk05318.py | 196 |
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} + """ |