aboutsummaryrefslogtreecommitdiffstats
path: root/mpm/python/usrp_mpm/chips/max10_cpld_flash_ctrl.py
diff options
context:
space:
mode:
Diffstat (limited to 'mpm/python/usrp_mpm/chips/max10_cpld_flash_ctrl.py')
-rw-r--r--mpm/python/usrp_mpm/chips/max10_cpld_flash_ctrl.py235
1 files changed, 235 insertions, 0 deletions
diff --git a/mpm/python/usrp_mpm/chips/max10_cpld_flash_ctrl.py b/mpm/python/usrp_mpm/chips/max10_cpld_flash_ctrl.py
new file mode 100644
index 000000000..c1e756124
--- /dev/null
+++ b/mpm/python/usrp_mpm/chips/max10_cpld_flash_ctrl.py
@@ -0,0 +1,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