mirror of
https://github.com/saymrwulf/uhd.git
synced 2026-05-16 21:10:10 +00:00
196 lines
6.9 KiB
Python
196 lines
6.9 KiB
Python
#
|
|
# 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}
|
|
"""
|