uhd/mpm/python/usrp_mpm/chips/max10_cpld_flash_ctrl.py
Virendra Kakade 6d6fc2a01d uhd: add support for max10 variants
Signed-off-by: Virendra Kakade <virendra.kakade@ni.com>
2021-12-02 06:42:09 -08:00

259 lines
10 KiB
Python

#!/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:
# determine M04 or M08 variant based on
# value encoded in the FLASH_CFM0_START_ADDR_REG
# register
start_addr = self.get_start_addr()
if start_addr == 0x9C00:
self.max10_variant = "m04"
elif start_addr == 0xAC00:
self.max10_variant = "m08"
else:
raise RuntimeError('Unknown MAX10 variant (FLASH_CFM0_START_ADDR_REG=0x{:04X})'.format(start_addr))
# check for sectors to be erased:
if self.is_memory_initialization_enabled():
if self.max10_variant == "m04":
sectors = [2, 3, 4]
else:
sectors = [3, 4, 5]
else:
if self.max10_variant == "m04":
sectors = [4]
else:
sectors = [5]
# 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):
# 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.debug("CPLD image mismatch! address %d, expected value 0x%08X,"
" read value 0x%08X" %
(i+self.cpld_start_address, data, device_data))
return False
# status display
if (i%1000 == 0):
self.log.debug('%d%% verified', i*4/self.file_size*100)
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('Checking if update is necessary...')
if self.verify_flash_memory(raw_data):
self.log.info('CPLD already programmed with specified image, not reprogramming.')
return True
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