aboutsummaryrefslogtreecommitdiffstats
path: root/mpm
diff options
context:
space:
mode:
authorBrent Stapleton <brent.stapleton@ettus.com>2018-04-17 13:15:38 -0700
committerMartin Braun <martin.braun@ettus.com>2018-06-27 10:22:25 -0700
commit4f49b4a937c0f6724828fa6ecfa9b14cc0f23f34 (patch)
tree8d2db9a33b03123669f9b9446cf9547cf4da2baa /mpm
parent74c41781390ba0352431167d97ecec22c88e2336 (diff)
downloaduhd-4f49b4a937c0f6724828fa6ecfa9b14cc0f23f34.tar.gz
uhd-4f49b4a937c0f6724828fa6ecfa9b14cc0f23f34.tar.bz2
uhd-4f49b4a937c0f6724828fa6ecfa9b14cc0f23f34.zip
mpm: adding adf400x support to chips
Adding ADF400X driver to MPM. This uses the Boost.Python bound spidev, and is largely a translation from the C++ driver in UHD.
Diffstat (limited to 'mpm')
-rw-r--r--mpm/include/mpm/spi/spi_iface.hpp4
-rw-r--r--mpm/include/mpm/spi/spi_python.hpp7
-rw-r--r--mpm/python/usrp_mpm/chips/CMakeLists.txt4
-rw-r--r--mpm/python/usrp_mpm/chips/__init__.py1
-rw-r--r--mpm/python/usrp_mpm/chips/adf400x.py213
5 files changed, 226 insertions, 3 deletions
diff --git a/mpm/include/mpm/spi/spi_iface.hpp b/mpm/include/mpm/spi/spi_iface.hpp
index 2cc5d740f..c3e17c0f3 100644
--- a/mpm/include/mpm/spi/spi_iface.hpp
+++ b/mpm/include/mpm/spi/spi_iface.hpp
@@ -6,6 +6,8 @@
#pragma once
+#include <mpm/types/regs_iface.hpp>
+#include <boost/noncopyable.hpp>
#include <memory>
#include <string>
@@ -13,7 +15,7 @@ namespace mpm { namespace spi {
/*! Implementation of a uhd::spi_iface that uses Linux' spidev underneath.
*/
- class spi_iface
+ class spi_iface : public boost::noncopyable
{
public:
using sptr = std::shared_ptr<spi_iface>;
diff --git a/mpm/include/mpm/spi/spi_python.hpp b/mpm/include/mpm/spi/spi_python.hpp
index 49c34b0b6..ab6a7a232 100644
--- a/mpm/include/mpm/spi/spi_python.hpp
+++ b/mpm/include/mpm/spi/spi_python.hpp
@@ -7,10 +7,17 @@
#pragma once
#include "spi_regs_iface.hpp"
+#include "spi_iface.hpp"
void export_spi() {
LIBMPM_BOOST_PREAMBLE("spi")
bp::def("make_spidev_regs_iface", &mpm::spi::make_spidev_regs_iface);
+ bp::def("make_spidev", &mpm::spi::spi_iface::make_spidev);
+
+ bp::class_<mpm::spi::spi_iface, boost::noncopyable, std::shared_ptr<mpm::spi::spi_iface> >("spi_iface", bp::no_init)
+ .def("transfer24_8", &mpm::spi::spi_iface::transfer24_8)
+ ;
+
}
diff --git a/mpm/python/usrp_mpm/chips/CMakeLists.txt b/mpm/python/usrp_mpm/chips/CMakeLists.txt
index ffe1b2419..94b62f33d 100644
--- a/mpm/python/usrp_mpm/chips/CMakeLists.txt
+++ b/mpm/python/usrp_mpm/chips/CMakeLists.txt
@@ -1,5 +1,5 @@
#
-# Copyright 2017 Ettus Research, National Instruments Company
+# Copyright 2017-2018 Ettus Research, a National Instruments Company
#
# SPDX-License-Identifier: GPL-3.0
#
@@ -8,7 +8,7 @@ SET(USRP_MPM_FILES ${USRP_MPM_FILES})
SET(USRP_MPM_CHIP_FILES
${CMAKE_CURRENT_SOURCE_DIR}/__init__.py
${CMAKE_CURRENT_SOURCE_DIR}/lmk04828.py
+ ${CMAKE_CURRENT_SOURCE_DIR}/adf400x.py
)
LIST(APPEND USRP_MPM_FILES ${USRP_MPM_CHIP_FILES})
SET(USRP_MPM_FILES ${USRP_MPM_FILES} PARENT_SCOPE)
-
diff --git a/mpm/python/usrp_mpm/chips/__init__.py b/mpm/python/usrp_mpm/chips/__init__.py
index c8d50d714..15b4a3704 100644
--- a/mpm/python/usrp_mpm/chips/__init__.py
+++ b/mpm/python/usrp_mpm/chips/__init__.py
@@ -7,4 +7,5 @@
Chips submodule
"""
+from .adf400x import ADF400x
from .lmk04828 import LMK04828
diff --git a/mpm/python/usrp_mpm/chips/adf400x.py b/mpm/python/usrp_mpm/chips/adf400x.py
new file mode 100644
index 000000000..4a33a33a9
--- /dev/null
+++ b/mpm/python/usrp_mpm/chips/adf400x.py
@@ -0,0 +1,213 @@
+#
+# Copyright 2018 Ettus Research, a National Instruments Company
+#
+# SPDX-License-Identifier: GPL-3.0-or-later
+#
+"""
+ADF400x driver class
+
+Compatible with ADF4001 and ADF4002.
+"""
+
+from builtins import object
+from usrp_mpm.mpmlog import get_logger
+
+
+BASE_REF_CLOCK_FREQ = 40e6
+DEFAULT_REF_CLOCK_FREQ = 20e6
+
+class ADF400x(object):
+ """
+ Generic driver class for ADF4002 access.
+
+ Inputs:
+ freq : frequency of reference input
+ parent_log : logger of parent
+ """
+ def __init__(self, regs_iface, freq=None, parent_log=None):
+ self.log = \
+ parent_log.getChild("ADF400x") if parent_log is not None \
+ else get_logger("ADF400x")
+ self.regs_iface = regs_iface
+ assert hasattr(self.regs_iface, 'transfer24_8')
+ self.transfer24_8 = regs_iface.transfer24_8
+
+ # Instantiate our own copy of the register mapping and update some values
+ self.adf400x_regs = ADF400xRegs()
+ self.adf400x_regs.ref_counter = 1
+ self.adf400x_regs.charge_pump_current_1 = 7
+ self.adf400x_regs.charge_pump_current_2 = 7
+ self.adf400x_regs.muxout = ADF400xRegs.MUXOUT_DLD
+ self.adf400x_regs.counter_reset = ADF400xRegs.COUNTER_RESET_NORMAL
+ self.adf400x_regs.phase_detector_polarity = ADF400xRegs.PHASE_DETECTOR_POLARITY_POS
+ self.adf400x_regs.charge_pump_mode = ADF400xRegs.CHARGE_PUMP_TRISTATE
+ # Set the N counter
+ if freq is None:
+ freq = DEFAULT_REF_CLOCK_FREQ
+ self._set_n_counter(freq)
+
+ # Now initialize the ADF400x
+ self.program_regs()
+
+ def program_regs(self):
+ """
+ Run through the programming sequence
+ """
+ # No control over CE, only LE, therefore we use the initialization latch method
+ self._write_reg(3)
+ # Conduct a function latch (2)
+ self._write_reg(2)
+ # Write R counter latch (0)
+ self._write_reg(0)
+ # Write N counter latch (1)
+ self._write_reg(1)
+
+ def _write_reg(self, addr):
+ """Write the expected value to the given addr"""
+ reg_val = self.adf400x_regs.get_reg(addr)
+ self.log.trace("Writing {:06x} to spidev".format(reg_val))
+ self.transfer24_8(reg_val)
+
+ def set_lock_to_ext_ref(self, external):
+ """Set the clock source to external"""
+ if bool(external):
+ self.adf400x_regs.charge_pump_mode = ADF400xRegs.CHARGE_PUMP_NORMAL
+ else:
+ self.adf400x_regs.charge_pump_mode = ADF400xRegs.CHARGE_PUMP_TRISTATE
+ self.program_regs()
+
+ def _set_n_counter(self, freq):
+ n_counter = int(BASE_REF_CLOCK_FREQ / freq)
+ if self.adf400x_regs.n_counter == n_counter:
+ self.log.trace("No change to N counter value ({}); returning early".format(n_counter))
+ return
+ self.log.trace("Setting N counter to {}".format(n_counter))
+ # Limits from the datasheet
+ assert 1 <= n_counter <= 8191
+ self.adf400x_regs.n_counter = n_counter
+
+ def set_ref_freq(self, freq):
+ """Set the input reference frequency"""
+ self._set_n_counter(freq)
+ self.program_regs()
+
+
+class ADF400xRegs(object):
+ """Register map for ADF400x"""
+ # TODO: Move each field into an Enum or something
+ # TODO: Add setters/getters for each field
+ # anti backlash widths
+ ANTI_BACKLASH_WIDTH_2_9NS = 0
+ ANTI_BACKLASH_WIDTH_1_3NS = 1
+ ANTI_BACKLASH_WIDTH_6_0NS = 2
+ ANTI_BACKLASH_WIDTH_2_9NS_WAT = 3
+
+ # lock detect precision
+ LOCK_DETECT_PRECISION_3CYC = 0
+ LOCK_DETECT_PRECISION_5CYC = 1
+
+ # charge pump gain
+ CHARGE_PUMP_GAIN_1 = 0
+ CHARGE_PUMP_GAIN_2 = 1
+
+ # counter reset
+ COUNTER_RESET_NORMAL = 0
+ COUNTER_RESET_RESET = 1
+
+ # power down
+ POWER_DOWN_NORMAL = 0
+ POWER_DOWN_ASYNC = 1
+ POWER_DOWN_SYNC = 3
+
+ # muxout
+ MUXOUT_TRISTATE_OUT = 0
+ MUXOUT_DLD = 1
+ MUXOUT_NDIV = 2
+ MUXOUT_AVDD = 3
+ MUXOUT_RDIV = 4
+ MUXOUT_NCH_OD_ALD = 5
+ MUXOUT_SDO = 6
+ MUXOUT_GND = 7
+
+ # phase detector polarity
+ PHASE_DETECTOR_POLARITY_NEG = 0
+ PHASE_DETECTOR_POLARITY_POS = 1
+
+ # charge pump mode
+ CHARGE_PUMP_NORMAL = 0
+ CHARGE_PUMP_TRISTATE = 1
+
+ # fastlock mode
+ FASTLOCK_MODE_DISABLED = 0
+ FASTLOCK_MODE_1 = 1
+ FASTLOCK_MODE_2 = 2
+
+ # timer counter control
+ TIMEOUT_3CYC = 0
+ TIMEOUT_7CYC = 1
+ TIMEOUT_11CYC = 2
+ TIMEOUT_15CYC = 3
+ TIMEOUT_19CYC = 4
+ TIMEOUT_23CYC = 5
+ TIMEOUT_27CYC = 6
+ TIMEOUT_31CYC = 7
+ TIMEOUT_35CYC = 8
+ TIMEOUT_39CYC = 9
+ TIMEOUT_43CYC = 10
+ TIMEOUT_47CYC = 11
+ TIMEOUT_51CYC = 12
+ TIMEOUT_55CYC = 13
+ TIMEOUT_59CYC = 14
+ TIMEOUT_63CYC = 15
+
+ def __init__(self):
+ """Set the default configuration"""
+ self.ref_counter = 0
+ self.n_counter = 0
+ self.charge_pump_current_1 = 0
+ self.charge_pump_current_2 = 0
+ self.anti_backlash_width = ADF400xRegs.ANTI_BACKLASH_WIDTH_2_9NS
+ self.lock_detect_precision = ADF400xRegs.LOCK_DETECT_PRECISION_3CYC
+ self.charge_pump_gain = ADF400xRegs.CHARGE_PUMP_GAIN_1
+ self.counter_reset = ADF400xRegs.COUNTER_RESET_NORMAL
+ self.power_down = ADF400xRegs.POWER_DOWN_NORMAL
+ self.muxout = ADF400xRegs.MUXOUT_TRISTATE_OUT
+ self.phase_detector_polarity = ADF400xRegs.PHASE_DETECTOR_POLARITY_NEG
+ self.charge_pump_mode = ADF400xRegs.CHARGE_PUMP_TRISTATE
+ self.fastlock_mode = ADF400xRegs.FASTLOCK_MODE_DISABLED
+ self.timer_counter_control = ADF400xRegs.TIMEOUT_3CYC
+
+ def get_reg(self, addr):
+ """Get the register value to write to the given addr"""
+ reg = 0
+ if addr == 0:
+ reg |= (self.ref_counter & 0x003FFF) << 2
+ reg |= (self.anti_backlash_width & 0x000003) << 16
+ reg |= (self.lock_detect_precision & 0x000001) << 20
+ elif addr == 1:
+ reg |= (self.n_counter & 0x001FFF) << 8
+ reg |= (self.charge_pump_gain & 0x000001) << 21
+ elif addr == 2:
+ reg |= (self.counter_reset & 0x000001) << 2
+ reg |= (self.power_down & 0x000001) << 3
+ reg |= (self.muxout & 0x000007) << 4
+ reg |= (self.phase_detector_polarity & 0x000001) << 7
+ reg |= (self.charge_pump_mode & 0x000001) << 8
+ reg |= (self.fastlock_mode & 0x000003) << 9
+ reg |= (self.timer_counter_control & 0x00000F) << 11
+ reg |= (self.charge_pump_current_1 & 0x000007) << 15
+ reg |= (self.charge_pump_current_2 & 0x000007) << 18
+ reg |= (self.power_down & 0x000002) << 20
+ elif addr == 3:
+ reg |= (self.counter_reset & 0x000001) << 2
+ reg |= (self.power_down & 0x000001) << 3
+ reg |= (self.muxout & 0x000007) << 4
+ reg |= (self.phase_detector_polarity & 0x000001) << 7
+ reg |= (self.charge_pump_mode & 0x000001) << 8
+ reg |= (self.fastlock_mode & 0x000003) << 9
+ reg |= (self.timer_counter_control & 0x00000F) << 11
+ reg |= (self.charge_pump_current_1 & 0x000007) << 15
+ reg |= (self.charge_pump_current_2 & 0x000007) << 18
+ reg |= (self.power_down & 0x000002) << 20
+ reg |= addr
+ return reg