diff options
author | Brent Stapleton <brent.stapleton@ettus.com> | 2018-04-17 13:15:38 -0700 |
---|---|---|
committer | Martin Braun <martin.braun@ettus.com> | 2018-06-27 10:22:25 -0700 |
commit | 4f49b4a937c0f6724828fa6ecfa9b14cc0f23f34 (patch) | |
tree | 8d2db9a33b03123669f9b9446cf9547cf4da2baa /mpm | |
parent | 74c41781390ba0352431167d97ecec22c88e2336 (diff) | |
download | uhd-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.hpp | 4 | ||||
-rw-r--r-- | mpm/include/mpm/spi/spi_python.hpp | 7 | ||||
-rw-r--r-- | mpm/python/usrp_mpm/chips/CMakeLists.txt | 4 | ||||
-rw-r--r-- | mpm/python/usrp_mpm/chips/__init__.py | 1 | ||||
-rw-r--r-- | mpm/python/usrp_mpm/chips/adf400x.py | 213 |
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 |