aboutsummaryrefslogtreecommitdiffstats
path: root/mpm/python/usrp_mpm/chips/lmk05318.py
blob: 8d369c7bef417cf9c8fd83aa261be503a063f68e (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
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}
        """