mirror of
https://github.com/saymrwulf/uhd.git
synced 2026-05-16 21:10:10 +00:00
The pca953x driver introduced a change for how the "label" property
populates. Instead of using the device model, it gives a device specific
name. As a replacement, use device/name. This affects the tca6424
and tca6408.
For the kernel change that causes this see:
5128f8d445
277 lines
8.7 KiB
Python
277 lines
8.7 KiB
Python
#
|
|
# Copyright 2018 Ettus Research, a National Instruments Company
|
|
#
|
|
# SPDX-License-Identifier: GPL-3.0-or-later
|
|
#
|
|
"""
|
|
Rhodium dboard peripherals (CPLD, port expander, dboard regs)
|
|
"""
|
|
|
|
|
|
import time
|
|
from usrp_mpm.sys_utils.sysfs_gpio import SysFSGPIO, GPIOBank, find_gpio_device
|
|
from usrp_mpm.mpmutils import poll_with_timeout
|
|
|
|
|
|
class TCA6408(object):
|
|
"""
|
|
Abstraction layer for the port/gpio expander
|
|
"""
|
|
pins = (
|
|
'PWR-GOOD-3.6V', #3.6V
|
|
'PWR-GOOD-1.1V', #1.1V
|
|
'PWR-GOOD-2.0V', #2.0V
|
|
'PWR-GOOD-5.4V', #5.4V
|
|
'PWR-GOOD-5.5V', #6.8V
|
|
)
|
|
|
|
def __init__(self, i2c_dev):
|
|
assert i2c_dev is not None
|
|
self._gpios = SysFSGPIO({'device/name': 'tca6408'}, 0x3F, 0x00, 0x00, i2c_dev)
|
|
|
|
def set(self, name, value=None):
|
|
"""
|
|
Assert a pin by name
|
|
"""
|
|
assert name in self.pins
|
|
self._gpios.set(self.pins.index(name), value=value)
|
|
|
|
def reset(self, name):
|
|
"""
|
|
Deassert a pin by name
|
|
"""
|
|
self.set(name, value=0)
|
|
|
|
def get(self, name):
|
|
"""
|
|
Read back a pin by name
|
|
"""
|
|
assert name in self.pins
|
|
return self._gpios.get(self.pins.index(name))
|
|
|
|
class FPGAtoLoDist(object):
|
|
"""
|
|
Abstraction layer for the port/gpio expander on the LO Distribution board
|
|
"""
|
|
EXPECTED_BOARD_REV = 0
|
|
POWER_ON_TIMEOUT = 20 #ms
|
|
POWER_ON_POLL_INTERVAL = 1 #ms
|
|
GPIO_DEV_ID = {
|
|
'device/name': 'tca6424',
|
|
'device/of_node/name': 'rhodium-lodist-gpio',
|
|
}
|
|
|
|
pins = (
|
|
'BD_REV_0', #Board revision bit 0
|
|
'BD_REV_1', #Board revision bit 1
|
|
'BD_REV_2', #Board revision bit 2
|
|
'P6_8V_PG', #6.8V Power good
|
|
'4', #No connect
|
|
'5', #No connect
|
|
'6', #No connect
|
|
'7', #No connect
|
|
'RX_OUT0_CTRL', #RX Out0 Port Termination Switch
|
|
'RX_OUT1_CTRL', #RX Out1 Port Termination Switch
|
|
'RX_OUT2_CTRL', #RX Out2 Port Termination Switch
|
|
'RX_OUT3_CTRL', #RX Out3 Port Termination Switch
|
|
'RX_INSWITCH_CTRL', #RX 1:4 splitter input select
|
|
'TX_OUT0_CTRL', #TX Out0 Port Termination Switch
|
|
'TX_OUT1_CTRL', #TX Out1 Port Termination Switch
|
|
'TX_OUT2_CTRL', #TX Out2 Port Termination Switch
|
|
'TX_OUT3_CTRL', #TX Out3 Port Termination Switch
|
|
'TX_INSWITCH_CTRL', #TX 1:4 splitter input select
|
|
'P6_8V_EN', #6.8V supply enable
|
|
'P6_5V_LDO_EN', #6.5V LDO enable
|
|
'P3_3V_RF_EN' #3.3V LDO for RF enable
|
|
)
|
|
|
|
@staticmethod
|
|
def lo_dist_present(i2c_dev):
|
|
"""
|
|
Returns True if the I2C board is present.
|
|
"""
|
|
dev, _ = find_gpio_device(FPGAtoLoDist.GPIO_DEV_ID, i2c_dev)
|
|
return dev is not None
|
|
|
|
def __init__(self, i2c_dev):
|
|
assert i2c_dev is not None
|
|
self._gpios = \
|
|
SysFSGPIO(self.GPIO_DEV_ID, 0x1FFF0F, 0x1FFF00, 0x00A500, i2c_dev)
|
|
board_rev = self._gpios.get(self.pins.index('BD_REV_0')) + \
|
|
self._gpios.get(self.pins.index('BD_REV_1')) << 1 + \
|
|
self._gpios.get(self.pins.index('BD_REV_2')) << 2
|
|
if board_rev != self.EXPECTED_BOARD_REV:
|
|
raise RuntimeError(
|
|
'LO distribution board revision did not match: '
|
|
'Expected: {0} Actual: {1}'
|
|
.format(self.EXPECTED_BOARD_REV, board_rev))
|
|
self._gpios.set(self.pins.index('P6_8V_EN'), 1)
|
|
if not poll_with_timeout(
|
|
lambda: bool(self._gpios.get(self.pins.index('P6_8V_PG'))),
|
|
self.POWER_ON_TIMEOUT,
|
|
self.POWER_ON_POLL_INTERVAL):
|
|
self._gpios.set(self.pins.index('P6_8V_EN'), 0)
|
|
raise RuntimeError('Power on failure for LO Distribution board')
|
|
self._gpios.set(self.pins.index('P6_5V_LDO_EN'), 1)
|
|
self._gpios.set(self.pins.index('P3_3V_RF_EN'), 1)
|
|
|
|
def set(self, name, value=None):
|
|
"""
|
|
Assert a pin by name
|
|
"""
|
|
assert name in self.pins
|
|
self._gpios.set(self.pins.index(name), value=value)
|
|
|
|
def reset(self, name):
|
|
"""
|
|
Deassert a pin by name
|
|
"""
|
|
assert name in self.pins
|
|
self.set(name, value=0)
|
|
|
|
def get(self, name):
|
|
"""
|
|
Read back a pin by name
|
|
"""
|
|
assert name in self.pins
|
|
return self._gpios.get(self.pins.index(name))
|
|
|
|
|
|
class FPGAtoDbGPIO(GPIOBank):
|
|
"""
|
|
Abstraction layer for the FPGA to Daughterboard GPIO
|
|
"""
|
|
EMIO_BASE = 54+8
|
|
DB_POWER_ENABLE = 0
|
|
RF_POWER_ENABLE = 1
|
|
|
|
def __init__(self, slot_idx):
|
|
pwr_base = self.EMIO_BASE + 2*slot_idx
|
|
GPIOBank.__init__(
|
|
self,
|
|
{'label': 'zynq_gpio'},
|
|
pwr_base,
|
|
0x3, # use_mask
|
|
0x3, # ddr
|
|
)
|
|
|
|
|
|
class RhCPLD(object):
|
|
"""
|
|
Control class for the CPLD.
|
|
"""
|
|
|
|
REG_SIGNATURE = 0x0000
|
|
REG_MINOR_REVISION = 0x0001
|
|
REG_MAJOR_REVISION = 0x0002
|
|
REG_BUILD_CODE_LSB = 0x0003
|
|
REG_BUILD_CODE_MSB = 0x0004
|
|
REG_SCRATCH = 0x0005
|
|
REG_GAIN_TABLE_SEL = 0x0006
|
|
REG_DAC_ALARM = 0x0007
|
|
|
|
CPLD_SIGNATURE = 0x0045
|
|
CPLD_MAJOR_REV = 4
|
|
CPLD_MINOR_REV = 0
|
|
|
|
def __init__(self, regs, log):
|
|
"""
|
|
Initialize communication with the Rh CPLD
|
|
"""
|
|
self.log = log.getChild("CPLD")
|
|
self.log.debug("Initializing CPLD...")
|
|
self.cpld_regs = regs
|
|
self.poke16 = self.cpld_regs.peek16
|
|
self.peek16 = self.cpld_regs.peek16
|
|
# According to the datasheet, The CPLD's internal configuration time can take
|
|
# anywhere from 0.6 to 3.4 ms. Until then, any ifc accesses will be invalid,
|
|
# We will blind wait 5 ms and check the signature register to verify operation
|
|
time.sleep(0.005)
|
|
signature = self.peek16(self.REG_SIGNATURE)
|
|
if self.CPLD_SIGNATURE != signature:
|
|
self.log.error("CPLD Signature Mismatch! " \
|
|
"Expected: 0x{:04X} Got: 0x{:04X}".format(self.CPLD_SIGNATURE, signature))
|
|
raise RuntimeError("CPLD Signature Check Failed! "
|
|
"Incorrect signature readback.")
|
|
minor_rev = self.peek16(self.REG_MINOR_REVISION)
|
|
major_rev = self.peek16(self.REG_MAJOR_REVISION)
|
|
if major_rev != self.CPLD_MAJOR_REV:
|
|
self.log.error(
|
|
"CPLD Major Revision check mismatch! Expected: %d Got: %d",
|
|
self.CPLD_MAJOR_REV,
|
|
major_rev
|
|
)
|
|
raise RuntimeError("CPLD Revision Check Failed! MPM is not "
|
|
"compatible with the loaded CPLD image.")
|
|
date_code = self.peek16(self.REG_BUILD_CODE_LSB) | \
|
|
(self.peek16(self.REG_BUILD_CODE_MSB) << 16)
|
|
self.log.debug(
|
|
"CPLD Signature: 0x{:04X} "
|
|
"Revision: {}.{} "
|
|
"Date code: 0x{:08X}"
|
|
.format(signature, major_rev, minor_rev, date_code))
|
|
|
|
def get_dac_alarm(self):
|
|
"""
|
|
This function polls and returns the DAC's ALARM signal connected to the CPLD.
|
|
"""
|
|
return (self.peek16(self.REG_DAC_ALARM) & 0x0001)
|
|
|
|
|
|
class DboardClockControl(object):
|
|
"""
|
|
Control the FPGA MMCM for Radio Clock control.
|
|
"""
|
|
# Clocking Register address constants
|
|
RADIO_CLK_MMCM = 0x0020
|
|
PHASE_SHIFT_CONTROL = 0x0024
|
|
RADIO_CLK_ENABLES = 0x0028
|
|
MGT_REF_CLK_STATUS = 0x0030
|
|
|
|
def __init__(self, regs, log):
|
|
self.log = log
|
|
self.regs = regs
|
|
self.poke32 = self.regs.poke32
|
|
self.peek32 = self.regs.peek32
|
|
|
|
def enable_outputs(self, enable=True):
|
|
"""
|
|
Enables or disables the MMCM outputs.
|
|
"""
|
|
if enable:
|
|
self.poke32(self.RADIO_CLK_ENABLES, 0x011)
|
|
else:
|
|
self.poke32(self.RADIO_CLK_ENABLES, 0x000)
|
|
|
|
def reset_mmcm(self):
|
|
"""
|
|
Uninitialize and reset the MMCM
|
|
"""
|
|
self.log.trace("Disabling all Radio Clocks, then resetting MMCM...")
|
|
self.enable_outputs(False)
|
|
self.poke32(self.RADIO_CLK_MMCM, 0x1)
|
|
|
|
def enable_mmcm(self):
|
|
"""
|
|
Unreset MMCM and poll lock indicators
|
|
|
|
If MMCM is not locked after unreset, an exception is thrown.
|
|
"""
|
|
self.log.trace("Un-resetting MMCM...")
|
|
self.poke32(self.RADIO_CLK_MMCM, 0x2)
|
|
if not poll_with_timeout(
|
|
lambda: bool(self.peek32(self.RADIO_CLK_MMCM) & 0x10),
|
|
500,
|
|
10,
|
|
):
|
|
self.log.error("MMCM not locked!")
|
|
raise RuntimeError("MMCM not locked!")
|
|
self.log.trace("MMCM locked. Enabling output MMCM clocks...")
|
|
self.enable_outputs(True)
|
|
|
|
def check_refclk(self):
|
|
"""
|
|
Not technically a clocking reg, but related.
|
|
"""
|
|
return bool(self.peek32(self.MGT_REF_CLK_STATUS) & 0x1)
|
|
|