mirror of
https://github.com/saymrwulf/uhd.git
synced 2026-05-16 21:10:10 +00:00
Co-authored-by: Lars Amsel <lars.amsel@ni.com> Co-authored-by: Michael Auchter <michael.auchter@ni.com> Co-authored-by: Martin Braun <martin.braun@ettus.com> Co-authored-by: Paul Butler <paul.butler@ni.com> Co-authored-by: Cristina Fuentes <cristina.fuentes-curiel@ni.com> Co-authored-by: Humberto Jimenez <humberto.jimenez@ni.com> Co-authored-by: Virendra Kakade <virendra.kakade@ni.com> Co-authored-by: Lane Kolbly <lane.kolbly@ni.com> Co-authored-by: Max Köhler <max.koehler@ni.com> Co-authored-by: Andrew Lynch <andrew.lynch@ni.com> Co-authored-by: Grant Meyerhoff <grant.meyerhoff@ni.com> Co-authored-by: Ciro Nishiguchi <ciro.nishiguchi@ni.com> Co-authored-by: Thomas Vogel <thomas.vogel@ni.com>
204 lines
7.8 KiB
Python
204 lines
7.8 KiB
Python
#
|
|
# Copyright 2021 Ettus Research, a National Instruments Brand
|
|
#
|
|
# SPDX-License-Identifier: GPL-3.0-or-later
|
|
#
|
|
"""
|
|
X4xx motherboard CPLD control
|
|
"""
|
|
|
|
from usrp_mpm import lib # Pulls in everything from C++-land
|
|
|
|
def parse_encoded_git_hash(encoded):
|
|
git_hash = encoded & 0x0FFFFFFF
|
|
tree_dirty = ((encoded & 0xF0000000) > 0)
|
|
dirtiness_qualifier = 'dirty' if tree_dirty else 'clean'
|
|
return (git_hash, dirtiness_qualifier)
|
|
|
|
class MboardCPLD:
|
|
"""
|
|
Control for the CPLD.
|
|
"""
|
|
# pylint: disable=bad-whitespace
|
|
SIGNATURE_OFFSET = 0x0000
|
|
COMPAT_REV_OFFSET = 0x0004
|
|
OLDEST_COMPAT_REV_OFFSET = 0x0008
|
|
GIT_HASH_OFFSET = 0x0010
|
|
DB_ENABLE_OFFSET = 0x0020
|
|
SERIAL_NO_LO_OFFSET = 0x0034
|
|
SERIAL_NO_HI_OFFSET = 0x0038
|
|
CMI_OFFSET = 0x003C
|
|
|
|
# change these revisions only on breaking changes
|
|
OLDEST_REQ_COMPAT_REV = 0x20122114
|
|
REQ_COMPAT_REV = 0x20122114
|
|
SIGNATURE = 0x0A522D27
|
|
PLL_REF_CLOCK_ENABLED = 1 << 2
|
|
ENABLE_CLK_DB0 = 1 << 8
|
|
ENABLE_CLK_DB1 = 1 << 9
|
|
ENABLE_PRC = 1 << 10
|
|
DISABLE_CLK_DB0 = 1 << 12
|
|
DISABLE_CLK_DB1 = 1 << 13
|
|
DISABLE_PRC = 1 << 14
|
|
RELEASE_RST_DB0 = 1 << 16
|
|
RELEASE_RST_DB1 = 1 << 17
|
|
ASSERT_RST_DB0 = 1 << 20
|
|
ASSERT_RST_DB1 = 1 << 21
|
|
# pylint: enable=bad-whitespace
|
|
|
|
def __init__(self, spi_dev_node, log):
|
|
self.log = log.getChild("CPLD")
|
|
self.regs = lib.spi.make_spidev_regs_iface(
|
|
spi_dev_node,
|
|
1000000, # Speed (Hz)
|
|
0, # SPI mode
|
|
32, # Addr shift
|
|
0, # Data shift
|
|
0, # Read flag
|
|
1<<47 # Write flag
|
|
)
|
|
self.poke32 = self.regs.poke32
|
|
self.peek32 = self.regs.peek32
|
|
|
|
def enable_pll_ref_clk(self, enable=True):
|
|
"""
|
|
Enables or disables the PLL reference clock.
|
|
|
|
This makes no assumptions on the prior state of the clock. It does check
|
|
if the clock-enable was successful by polling the CPLD, and throws if
|
|
not.
|
|
"""
|
|
def check_pll_enabled():
|
|
return self.peek32(self.DB_ENABLE_OFFSET) & self.PLL_REF_CLOCK_ENABLED
|
|
if enable:
|
|
self.poke32(self.DB_ENABLE_OFFSET, self.ENABLE_PRC)
|
|
if not check_pll_enabled():
|
|
self.log.error("PRC enable failed!")
|
|
raise RuntimeError('PRC enable failed!')
|
|
return
|
|
# Disable PRC:
|
|
self.poke32(self.DB_ENABLE_OFFSET, self.DISABLE_PRC)
|
|
if check_pll_enabled():
|
|
self.log.error('PRC reset failed!')
|
|
raise RuntimeError('PRC reset failed!')
|
|
|
|
def enable_daughterboard(self, db_id, enable=True):
|
|
""" Enable or disable clock forwarding to a given DB """
|
|
if db_id == 0:
|
|
release_reset = self.RELEASE_RST_DB0
|
|
assert_reset = self.ASSERT_RST_DB0
|
|
else:
|
|
release_reset = self.RELEASE_RST_DB1
|
|
assert_reset = self.ASSERT_RST_DB1
|
|
value = self.peek32(self.DB_ENABLE_OFFSET)
|
|
if enable:
|
|
# De-assert reset
|
|
value = (value | release_reset) & (~assert_reset)
|
|
else: #disable
|
|
# Assert reset
|
|
value = (value | assert_reset) & (~release_reset)
|
|
self.poke32(self.DB_ENABLE_OFFSET, value)
|
|
|
|
def enable_daughterboard_support_clock(self, db_id, enable=True):
|
|
""" Enable or disable clock forwarding to a given DB """
|
|
if db_id == 0:
|
|
clk_enable = self.ENABLE_CLK_DB0
|
|
clk_disable = self.DISABLE_CLK_DB0
|
|
else:
|
|
clk_enable = self.ENABLE_CLK_DB1
|
|
clk_disable = self.DISABLE_CLK_DB1
|
|
value = self.peek32(self.DB_ENABLE_OFFSET)
|
|
if enable:
|
|
# Enable clock
|
|
value = (value | clk_enable) & (~clk_disable)
|
|
else: #disable
|
|
# Disable clock
|
|
value = (value | clk_disable) & (~clk_enable)
|
|
self.poke32(self.DB_ENABLE_OFFSET, value)
|
|
|
|
def check_signature(self):
|
|
"""
|
|
Assert that the CPLD signature is correct. If the CPLD 'signature'
|
|
register returns something unexpectected, throws a RuntimeError.
|
|
"""
|
|
read_signature = self.peek32(self.SIGNATURE_OFFSET)
|
|
if self.SIGNATURE != read_signature:
|
|
self.log.error('MB PS CPLD signature {:X} does not match '
|
|
'expected value {:X}'.format(read_signature, self.SIGNATURE))
|
|
raise RuntimeError('MB PS CPLD signature {:X} does not match '
|
|
'expected value {:X}'.format(read_signature, self.SIGNATURE))
|
|
|
|
def check_compat_version(self):
|
|
"""
|
|
Check oldest compatible revision offset of HW against required revision.
|
|
The value has to match as the register offsets depends on them.
|
|
Furthermore there needs to be a minimum revision to check for existence
|
|
of functionality.
|
|
"""
|
|
cpld_image_compat_revision = self.peek32(self.OLDEST_COMPAT_REV_OFFSET)
|
|
if cpld_image_compat_revision < self.OLDEST_REQ_COMPAT_REV:
|
|
error_message = (
|
|
'MB CPLD oldest compatible revision'
|
|
f' 0x{cpld_image_compat_revision:08x} is out of date. Update'
|
|
f' your CPLD image to 0x{self.OLDEST_REQ_COMPAT_REV:08x}.')
|
|
self.log.error(error_message)
|
|
raise RuntimeError(error_message)
|
|
if cpld_image_compat_revision > self.OLDEST_REQ_COMPAT_REV:
|
|
error_message = (
|
|
'MB CPLD oldest compatible revision'
|
|
f' 0x{cpld_image_compat_revision:08x} is unknown. Downgrade'
|
|
f' your CPLD image to 0x{self.OLDEST_REQ_COMPAT_REV:08x}.')
|
|
self.log.error(error_message)
|
|
raise RuntimeError(error_message)
|
|
|
|
if not self.has_compat_version(self.REQ_COMPAT_REV):
|
|
error_message = (
|
|
"MB CPLD compatible revision is too old. Update your CPLD"
|
|
f" image to at least 0x{self.REQ_COMPAT_REV:08x}.")
|
|
self.log.error(error_message)
|
|
raise RuntimeError(error_message)
|
|
|
|
def has_compat_version(self, min_required_version):
|
|
"""
|
|
Check for a minimum required version.
|
|
"""
|
|
if min_required_version < self.REQ_COMPAT_REV:
|
|
self.log.warning(
|
|
"Somebody called MB CPLD has_compat_version with revision"
|
|
f" 0x{min_required_version:x} which is older than the mandated"
|
|
f" version 0x{self.REQ_COMPAT_REV:x}.")
|
|
cpld_image_compat_revision = self.peek32(self.COMPAT_REV_OFFSET)
|
|
return cpld_image_compat_revision >= min_required_version
|
|
|
|
def trace_git_hash(self):
|
|
"""
|
|
Trace build of MB CPLD
|
|
"""
|
|
git_hash_rb = self.peek32(self.GIT_HASH_OFFSET)
|
|
(git_hash, dirtiness_qualifier) = parse_encoded_git_hash(git_hash_rb)
|
|
self.log.trace("MB CPLD build GIT Hash: {:07x} ({})".format(
|
|
git_hash, dirtiness_qualifier))
|
|
|
|
def set_serial_number(self, serial_number):
|
|
"""
|
|
Set serial number register
|
|
"""
|
|
assert len(serial_number) > 0
|
|
assert len(serial_number) <= 8
|
|
serial_number_string = str(serial_number, 'ascii')
|
|
serial_number_int = int(serial_number_string, 16)
|
|
self.poke32(self.SERIAL_NO_LO_OFFSET, serial_number_int & 0xFFFFFFFF)
|
|
self.poke32(self.SERIAL_NO_HI_OFFSET, serial_number_int >> 32)
|
|
|
|
def set_cmi_device_ready(self, ready=True):
|
|
"""
|
|
Inform CMI partner that this device is ready for PCI-Express communication.
|
|
"""
|
|
value = 1 if ready else 0
|
|
self.poke32(self.CMI_OFFSET, value)
|
|
|
|
def get_cmi_status(self):
|
|
"""
|
|
Return true if upstream CMI device was found.
|
|
"""
|
|
return bool(self.peek32(self.CMI_OFFSET))
|