aboutsummaryrefslogtreecommitdiffstats
path: root/mpm/python/usrp_mpm/chips/max10_cpld_flash_ctrl.py
blob: c1e756124d232737ece9b85e1136c80b3944f258 (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
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
#!/usr/bin/env python3
#
# Copyright 2020 Ettus Research, a National Instruments Brand
#
# SPDX-License-Identifier: GPL-3.0-or-later
#
"""
Update the CPLD image using the on-chip flash on Intel MAX10 devices
"""
import os
import time

class Max10CpldFlashCtrl():
    """
    Context manager class used to handle CPLD Flash reconfiguration.
    Calling "with" using an instance of this class will disable write
    protection for the duration of that context.
    """
    REVISION_REG                = 0x0004
    # Addresses relative to reconfiguration register offset
    FLASH_STATUS_REG            = 0x0000
    FLASH_CONTROL_REG           = 0x0004
    FLASH_ADDR_REG              = 0x0008
    FLASH_WRITE_DATA_REG        = 0x000C
    FLASH_READ_DATA_REG         = 0x0010
    FLASH_CFM0_START_ADDR_REG   = 0x0014
    FLASH_CFM0_END_ADDR_REG     = 0x0018

    # Masks for FLASH_STATUS_REG
    FLASH_MEM_INIT_ENABLED_MASK = 0x10000

    def __init__(self, logger, regs, reconfig_regs_offset, cpld_min_revision):
        if logger == None:
            logger = get_logger('update_cpld')
        self.log = logger
        self.regs = regs
        self.reconfig_regs_offset = reconfig_regs_offset
        self.cpld_min_revision = cpld_min_revision

    def peek32(self, addr):
        return self.regs.peek32(addr + self.reconfig_regs_offset)

    def poke32(self, addr, val):
        self.regs.poke32(addr + self.reconfig_regs_offset, val)

    def __enter__(self):
        self.enabled_write_protection(enable=False)

    def __exit__(self, exc_type, exc_value, traceback):
        self.enabled_write_protection(enable=True)

    def enabled_write_protection(self, enable=True):
        if enable:
            self.poke32(self.FLASH_CONTROL_REG, 1 << 0) # FLASH_ENABLE_WP_STB
        else:
            self.poke32(self.FLASH_CONTROL_REG, 1 << 1) # FLASH_DISABLE_WP_STB

    def check_revision(self):
        self.log.debug('Checking for compatible CPLD revision')
        cpld_revision = self.regs.peek32(self.REVISION_REG)
        if cpld_revision < self.cpld_min_revision:
            self.log.error("Unexpected CPLD revision 0x{:X}".format(cpld_revision))
            return False
        return True

    def get_start_addr(self):
        return self.peek32(self.FLASH_CFM0_START_ADDR_REG)

    def get_end_addr(self):
        return self.peek32(self.FLASH_CFM0_END_ADDR_REG)

    def is_memory_initialization_enabled(self):
        return self.peek32(self.FLASH_STATUS_REG) & self.FLASH_MEM_INIT_ENABLED_MASK

    # expected value of 0x1110 indicates idle state of read, write, and erase
    # routines (see function wait_for_idle for details), 0x0001 indicates the
    # write protection is enabled
    def check_reconfig_engine_status(self, expected_value=0x1111):
        status = self.peek32(self.FLASH_STATUS_REG)
        status = status & ~self.FLASH_MEM_INIT_ENABLED_MASK
        if (status != expected_value):
            self.log.error("Unexpected reconfig engine status 0x%08X" % status)
            return False
        return True

    def wait_for_idle(self, operation, timeout=350):
        """
        Wait for the idle bit to assert for the given operation.
        If the idle bit is not True before the timeout (given in ms),
        return False.
        """
        if operation == 'write':
            status_bit = 1 << 12   # FLASH_WRITE_IDLE
        elif operation == 'erase':
            status_bit = 1 << 8    # FLASH_ERASE_IDLE
        elif operation == 'read':
            status_bit = 1 << 4    # FLASH_READ_IDLE
        else:
            self.log.error('Cannot wait for unknown operation {}'.format(operation))
            raise RuntimeError('Cannot wait for unknown operation {}'.format(operation))
        for _ in range(0, timeout):
            status = self.peek32(self.FLASH_STATUS_REG)
            if (status & status_bit):
                return True
            time.sleep(0.001) # 1 ms
        return False

    def erase_flash_memory(self):
        with self:
            # check for sectors to be erased:
            if self.is_memory_initialization_enabled():
                sectors = [2, 3, 4]
            else:
                sectors = [4]
            # erase each sector individually
            for sector in sectors:
                # start erase
                self.poke32(self.FLASH_CONTROL_REG, (1 << 4) | ((sector & 0x7) << 5))
                # wait for erase to finish
                if not self.wait_for_idle('erase', timeout=350):
                    self.log.error('There was a timeout waiting for '
                                   'Flash erase to complete!')
                    return False
            return True

    def program_flash_memory(self, raw_data):
        with self:
            # write words one at a time
            for i, data in enumerate(raw_data):
                # status display
                if (i%1000 == 0):
                    self.log.debug('%d%% written', i*4/self.file_size*100)
                # write address and data
                self.poke32(self.FLASH_ADDR_REG, self.cpld_start_address+i)
                self.poke32(self.FLASH_WRITE_DATA_REG, data)
                # start write operation
                self.poke32(self.FLASH_CONTROL_REG, 1 << 3)
                # wait for write to finish
                if not self.wait_for_idle('write', timeout=2):
                    self.log.error('There was a timeout waiting for '
                                   'Flash write to complete!')
                    return False
                if not self.check_reconfig_engine_status(expected_value=0x1110):
                    return False
        return True

    def verify_flash_memory(self, raw_data):
        # read words one at a time
        for i, data in enumerate(raw_data):
            # status display
            if (i%1000 == 0):
                self.log.debug('%d%% done', i*4/self.file_size*100)
            # write address
            self.poke32(self.FLASH_ADDR_REG, self.cpld_start_address+i)
            # start read operation
            self.poke32(self.FLASH_CONTROL_REG, 1 << 2)
            # wait for read to finish
            if not self.wait_for_idle('read', timeout=1):
                self.log.error('There was a timeout waiting for '
                               'Flash read to complete!')
                return False
            # read data from device
            device_data = self.peek32(self.FLASH_READ_DATA_REG)
            if (data != device_data):
                self.log.error("Data mismatch! address %d, expected value 0x%08X,"
                               " read value 0x%08X" %
                               (i+self.cpld_start_address, data, device_data))
                return False
        return True

    def reverse_bits_in_byte(self, n):
        result = 0
        for _ in range(8):
            result <<= 1
            result |= n & 1
            n >>= 1
        return result

    def update(self, filename):
        if not self.check_revision():
            return False

        self.log.debug('Checking CPLD image file')
        self.file_size = os.path.getsize(filename)
        self.cpld_start_address = self.get_start_addr()
        cpld_end_address = self.get_end_addr()
        expected_size = (cpld_end_address+1-self.cpld_start_address)*4
        if (self.file_size != expected_size):
            self.log.error("Unexpected file size! Required size: %d bytes" % expected_size)
            return False

        # Convert data from bytes to 32-bit words and reverse bit order
        # to be compatible with Altera's on-chip flash IP
        raw_data = []
        with open(filename, 'rb') as binary_file:
            for _ in range(self.file_size//4):
                number = 0
                for _ in range(4):
                    number <<= 8
                    number |= self.reverse_bits_in_byte(int.from_bytes(binary_file.read(1), 'big'))
                raw_data.append(number)

        if not self.check_reconfig_engine_status():
            return False

        self.log.debug('Erasing CPLD flash memory...')
        if not (self.erase_flash_memory()
                and self.check_reconfig_engine_status()):
            self.log.error('There was an error while reprogramming the CPLD image. '
                           'Please program the CPLD again with a valid image before power '
                           'cycling the board to ensure it is in a valid state.')
            return False
        self.log.debug('CPLD flash memory erased.')

        self.log.debug('Programming flash memory...')
        if not (self.program_flash_memory(raw_data)
                and self.check_reconfig_engine_status()):
            self.log.error('There was an error while reprogramming the CPLD image. '
                           'Please program the CPLD again with a valid image before power '
                           'cycling the board to ensure it is in a valid state.')
            return False
        self.log.debug('Flash memory programming complete.')

        self.log.debug('Verifying image in flash...')
        if not (self.verify_flash_memory(raw_data)
                and self.check_reconfig_engine_status()):
            self.log.error('There was an error while reprogramming the CPLD image. '
                           'Please program the CPLD again with a valid image before power '
                           'cycling the board to ensure it is in a valid state.')
            return False
        self.log.debug('Flash image verification complete.')

        self.log.info('CPLD reprogrammed! Please power-cycle the device.')

        return True