aboutsummaryrefslogtreecommitdiffstats
path: root/mpm
diff options
context:
space:
mode:
Diffstat (limited to 'mpm')
-rw-r--r--mpm/include/mpm/dboards/eiscat_manager.hpp70
-rw-r--r--mpm/lib/dboards/CMakeLists.txt1
-rw-r--r--mpm/lib/dboards/eiscat_manager.cpp36
-rw-r--r--mpm/python/pyusrp_periphs.cpp2
-rw-r--r--mpm/python/usrp_mpm/dboard_manager/__init__.py4
-rw-r--r--mpm/python/usrp_mpm/dboard_manager/eiscat.py354
-rw-r--r--mpm/python/usrp_mpm/dboard_manager/lmk_eiscat.py228
-rw-r--r--mpm/python/usrp_mpm/periph_manager/base.py2
8 files changed, 690 insertions, 7 deletions
diff --git a/mpm/include/mpm/dboards/eiscat_manager.hpp b/mpm/include/mpm/dboards/eiscat_manager.hpp
new file mode 100644
index 000000000..42f80ff8e
--- /dev/null
+++ b/mpm/include/mpm/dboards/eiscat_manager.hpp
@@ -0,0 +1,70 @@
+//
+// Copyright 2017 Ettus Research (National Instruments)
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see <http://www.gnu.org/licenses/>.
+//
+
+#pragma once
+
+#include <mpm/types/lockable.hpp>
+#include <mpm/types/regs_iface.hpp>
+#include <memory>
+#include <mutex>
+
+namespace mpm { namespace dboards {
+ class eiscat_manager// : public dboard_periph_manager
+ {
+ public:
+ eiscat_manager(
+ const std::string &lmk_spidev,
+ const std::string &adc0_spidev,
+ const std::string &adc1_spidev
+ //const std::string &phdac_spidev,
+ );
+
+ /*! Return a reference to the SPI mutex
+ */
+ mpm::types::lockable::sptr get_spi_lock() { return _spi_lock; }
+
+ /*! Return a reference to the clock chip controls
+ */
+ mpm::types::regs_iface::sptr get_clock_ctrl(){ return _clock_ctrl; }
+
+ mpm::types::regs_iface::sptr get_adc0_ctrl(){ return _adc0_ctrl; }
+ mpm::types::regs_iface::sptr get_adc1_ctrl(){ return _adc1_ctrl; }
+
+ private:
+ std::shared_ptr<std::mutex> _spi_mutex;
+
+ mpm::types::lockable::sptr _spi_lock;
+ mpm::types::regs_iface::sptr _clock_ctrl;
+ mpm::types::regs_iface::sptr _adc0_ctrl;
+ mpm::types::regs_iface::sptr _adc1_ctrl;
+ //mpm::types::regs_iface::sptr _phdac_ctrl;
+ };
+
+}}; /* namespace mpm::dboards */
+
+#ifdef LIBMPM_PYTHON
+void export_eiscat(){
+ LIBMPM_BOOST_PREAMBLE("eiscat")
+ using namespace mpm::dboards;
+ bp::class_<mpm::dboards::eiscat_manager>("eiscat_manager", bp::init<std::string, std::string, std::string>())
+ .def("get_spi_lock", &mpm::dboards::eiscat_manager::get_spi_lock)
+ .def("get_clock_ctrl", &mpm::dboards::eiscat_manager::get_clock_ctrl)
+ .def("get_adc0_ctrl", &mpm::dboards::eiscat_manager::get_adc0_ctrl)
+ .def("get_adc1_ctrl", &mpm::dboards::eiscat_manager::get_adc1_ctrl)
+ ;
+}
+#endif
diff --git a/mpm/lib/dboards/CMakeLists.txt b/mpm/lib/dboards/CMakeLists.txt
index 349be0b93..4ca21ee37 100644
--- a/mpm/lib/dboards/CMakeLists.txt
+++ b/mpm/lib/dboards/CMakeLists.txt
@@ -22,5 +22,6 @@
USRP_PERIPHS_ADD_OBJECT(dboards
magnesium_manager.cpp
+ eiscat_manager.cpp
)
diff --git a/mpm/lib/dboards/eiscat_manager.cpp b/mpm/lib/dboards/eiscat_manager.cpp
new file mode 100644
index 000000000..f3a9794f8
--- /dev/null
+++ b/mpm/lib/dboards/eiscat_manager.cpp
@@ -0,0 +1,36 @@
+//
+// Copyright 2017 Ettus Research (National Instruments)
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see <http://www.gnu.org/licenses/>.
+//
+
+#include <mpm/dboards/eiscat_manager.hpp>
+#include <mpm/chips/lmk04828_spi_iface.hpp>
+
+using namespace mpm::dboards;
+using namespace mpm::chips;
+
+eiscat_manager::eiscat_manager(
+ const std::string &lmk_spidev,
+ const std::string &adc0_spidev,
+ const std::string &adc1_spidev
+) : _spi_mutex(std::make_shared<std::mutex>())
+ , _spi_lock(mpm::types::lockable::make(_spi_mutex))
+ , _clock_ctrl(mpm::chips::make_lmk04828_iface(lmk_spidev))
+ , _adc0_ctrl(mpm::chips::make_lmk04828_iface(adc0_spidev))
+ , _adc1_ctrl(mpm::chips::make_lmk04828_iface(adc1_spidev))
+{
+
+}
+
diff --git a/mpm/python/pyusrp_periphs.cpp b/mpm/python/pyusrp_periphs.cpp
index 5aac12088..c48860ebe 100644
--- a/mpm/python/pyusrp_periphs.cpp
+++ b/mpm/python/pyusrp_periphs.cpp
@@ -33,6 +33,7 @@
#include <mpm/ad937x/ad937x_ctrl.hpp>
#include <mpm/chips/lmk04828_spi_iface.hpp>
#include <mpm/dboards/magnesium_manager.hpp>
+#include <mpm/dboards/eiscat_manager.hpp>
#include <boost/noncopyable.hpp>
namespace bp = boost::python;
@@ -47,4 +48,5 @@ BOOST_PYTHON_MODULE(libpyusrp_periphs)
export_mykonos();
export_xbar();
export_magnesium();
+ export_eiscat();
}
diff --git a/mpm/python/usrp_mpm/dboard_manager/__init__.py b/mpm/python/usrp_mpm/dboard_manager/__init__.py
index e1ae5d3e0..93de24695 100644
--- a/mpm/python/usrp_mpm/dboard_manager/__init__.py
+++ b/mpm/python/usrp_mpm/dboard_manager/__init__.py
@@ -19,11 +19,11 @@ dboards module __init__.py
"""
from .. import libpyusrp_periphs as lib
from .magnesium import Magnesium
-from .eiscat import eiscat
+from .eiscat import EISCAT
from .test import test
from .unknown import unknown
HW_PIDS = {
- eiscat.hw_pid: eiscat,
+ EISCAT.hw_pid: EISCAT,
Magnesium.hw_pid: Magnesium,
}
diff --git a/mpm/python/usrp_mpm/dboard_manager/eiscat.py b/mpm/python/usrp_mpm/dboard_manager/eiscat.py
index 436307c1e..4534e124d 100644
--- a/mpm/python/usrp_mpm/dboard_manager/eiscat.py
+++ b/mpm/python/usrp_mpm/dboard_manager/eiscat.py
@@ -17,13 +17,359 @@
"""
EISCAT rx board implementation module
"""
+
+import time
+from six import iteritems
+from ..mpmlog import get_logger
+from ..uio import UIO
from . import lib
from .base import DboardManagerBase
+from .lmk_eiscat import LMK04828EISCAT
+
+N_CHANS = 8 # Chans per dboard
+
+# Power enable pins
+POWER_ENB = 0x200C # Address of the power enable register
+PWR_CHAN_EN_2V5 = [ (1<<x) for x in xrange(8) ]
+PWR2_5V_DC_CTRL_ENB = 1<<8
+PWR2_5V_DC_PWR_EN = 1<<9
+PWR2_5V_LNA_CTRL_EN = 1<<10
+PWR2_5V_LMK_SPI_EN = 1<<11
+PWR2_5V_ADC0_SPI_EN = 1<<12
+PWR2_5V_ADC1_SPI_EN = 1<<13
+
+class ADS54J56(object):
+ """
+ Controls for ADS54J56 ADC
+ """
+ def __init__(self, regs, log):
+ self.log = log
+ self.regs = regs
+
+ def reset(self):
+ """
+ Perform reset sequence
+ """
+ self.log.trace("Resetting ADS54J56...")
+ self.regs.poke8(0x000000, 0x81) # Analog reset
+ self.regs.poke8(0x004004, 0x68) # Page = Main Digital
+ self.regs.poke8(0x004003, 0x00) # Page = Main Digital
+ self.regs.poke8(0x004002, 0x00) # Page = Main Digital
+ self.regs.poke8(0x004001, 0x00) # Page = Main Digital
+ self.regs.poke8(0x0060F7, 0x01) # Digital top reset
+ self.regs.poke8(0x0070F7, 0x01) # Digital top reset
+ self.regs.poke8(0x006000, 0x01) # Reset Digital (IL RESET)
+ self.regs.poke8(0x007000, 0x01) # Reset Digital (IL RESET)
+ self.regs.poke8(0x006000, 0x00) # Clear Reset
+ self.regs.poke8(0x007000, 0x00) # Clear Reset
+ self.regs.poke8(0x000011, 0x80) # Select Master page in Analog Bank
+ self.regs.poke8(0x000053, 0x80) # Set clk divider to div-2
+ self.regs.poke8(0x000039, 0xC0) # ALWAYS WRITE 1 to this bit
+ self.regs.poke8(0x000059, 0x20) # ALWAYS WRITE 1 to this bit
+
+
+ # def setup(self):
+# 11 80 0 0 Select Master page in Analog Bank
+# 53 80 0 0 Set clk divider to div-2
+# 39 C0 0 0 ALWAYS WRITE 1 to this bit
+# 59 20 0 0 ALWAYS WRITE 1 to this bit
+# 4004 68 0 0
+# 4003 0 0 0
+# 6000 1 0 0 Reset interleaving engine for Ch A-B
+# 6000 0 0 0
+# 7000 1 0 0 Reset interleaving engine for Ch C-D
+# 7000 0 0 0
+# 4004 61 0 0 Select decimation filter page of JESD bank.
+# 4003 41 0 0
+# 6000 E4 0 0 DDC Mode 4 for A-B and E = CH A/B N value
+# 7000 E4 0 0 DDC Mode 4 for A-B and E = CH A/B N value
+# 6001 4 0 0 ALWAYS WRITE 1 to this bit
+# 7001 4 0 0 ALWAYS WRITE 1 to this bit
+# 6002 0E 0 0 Ch A/D N value
+# 7002 0E 0 0 Ch A/D N value
+# 4003 0 0 0 Select analog page in JESD Bank
+# 4004 6A 0 0
+# 6016 2 0 0 PLL mode 40x for A-B
+# 7016 2 0 0 PLL mode 40x for C-D
+# 4003 0 0 0 Select digital page in JESD Bank
+# 4004 69 0 0
+# 6000 40 0 0 Enable JESD Mode control for A-B
+# 6001 2 0 0 Set JESD Mode to 40x for LMFS=2441
+# 7000 40 0 0 Enable JESD Mode control for C-D
+# 7001 2 0 0 Set JESD Mode to 40x for LMFS=2441
+# 6000 80 0 0 Set CTRL K for A-B
+# 6006 0F 0 0 Set K to 16
+# 7000 80 0 0 Set CTRL K for C-D
+# 7006 0F 0 0 Set K to 16
+# 4005 1 0 0 Disable broadcast mode
+# 7001 20 0 0 SyncbAB to issue a SYNC request for all 4 channels
+# 4005 0 0 0 Enable broadcast mode
+
+
+class MMCM(object):
+ """
+ Controller for the MMCM inside the FPGA
+ """
+ RADIO_CLK_CTRL = 0x2000 # Register address
+
+ RADIO_CLK1X_ENABLE = 1<<0
+ RADIO_CLK2X_ENABLE = 1<<1
+ RADIO_CLK3X_ENABLE = 1<<2
+ RADIO_CLK_MMCM_RESET = 1<<3
+ RADIO_CLK_VALID = 1<<4
+
+ def __init__(self, regs, log):
+ self.log = log
+ self.regs = regs
+ self.addr = self.RADIO_CLK_CTRL
+ self.poke32 = lambda bits: self.regs.poke32(self.addr, bits)
+ self.peek32 = lambda: self.regs.peek32(self.addr)
+
+ def reset(self):
+ """
+ Uninitialize and reset the MMCM
+ """
+ self.log.trace("Resetting MMCM, disabling all clocks...")
+ self.poke32(self.RADIO_CLK_MMCM_RESET)
+
+ def enable(self):
+ """
+ Unreset MMCM and poll lock indicators
+ """
+ self.log.trace("Unresetting MMCM...")
+ self.poke32(0x0000) # Take out of reset
+ time.sleep(0.5) # Replace with poll and timeout TODO
+ mmcm_locked = bool(self.peek32() & self.RADIO_CLK_VALID)
+ if not mmcm_locked:
+ self.log.error("MMCM not locked!")
+ raise RuntimeError("MMCM not locked!")
+ self.log.trace("Enabling output clocks on MMCM...")
+ self.poke32( # Enable all the output clocks:
+ self.RADIO_CLK1X_ENABLE | \
+ self.RADIO_CLK2X_ENABLE | \
+ self.RADIO_CLK3X_ENABLE
+ )
+ return True
+
+class JesdCoreEiscat(object):
+ CORE_ID_BASE = 0x4A455344
+ ADDR_BASE = 0x0000
+ ADDR_OFFSET = 0x1000
-class eiscat(DboardManagerBase):
+# Check Core 100 4a455344 1 FFFFFFFF check signature
+ # 104 1 date should match the export number of the core. format: yymmddhh
+
+# GT PLL Power Control C FFFC0000 0 0 Power down unused CPLLs and QPLLs
+
+# GT PLL Lock Control 4 11111111 0 0 Reset CPLLs
+ # 4 11111100 0 0 Unreset the ones we're using
+ # wait 2 ms - alternatively, poll on the locked bit below
+ # 10 10000 0 0 Clear all CPLL sticky bits
+ # 4 22 1 FF Read CPLL locked and no unlocked stickies. Error: GT PLL failed to lock.
+
+# GT RX Reset Routine 24 10 0 0 Place the RX MGTs in reset
+ # 24 20 0 0 Unreset and Enable
+ # 24 F0000 1 FFFF0000 Poll for reset complete for 20 ms. b1111 = number of GTs we use. Error: TX MGTs failed to reset
+
+# Init FPGA JESD204B Deframer (RX) 40 2 0 0 Force assertion of ADC SYNC
+ # 50 0 0 0 Data = 0 = Scrambler enabled. Data = 1 = disabled. Must match ADC settings. Set to 1 for now
+ # GT RX Reset Routine (full sequence)
+ # 50 0 0 0 Stop forcing assertion of ADC SYNC
+
+# Check Deframer Status (RX) 40 3000001C 1 FFFFFFFF
+
+ def __init__(self, regs, slot, core_idx, log):
+ self.log = log
+ self.regs = regs
+ self.slot = slot
+ assert core_idx in (0, 1)
+ self.core_idx = core_idx
+ self.base_addr = self.ADDR_BASE + self.ADDR_OFFSET * self.core_idx
+ self.log.trace("Slot: {} JESD Core {}: Base address {}".format(
+ self.slot, self.core_idx, self.base_addr
+ ))
+ self.peek32 = lambda addr: self.regs.peek32(self.base_addr + addr)
+ self.poke32 = lambda addr, data: self.regs.poke32(self.base_addr + addr, data)
+ if not self.check_core_id():
+ raise RuntimeError("Could not identify JESD core!")
+
+ def check_core_id(self):
+ """
+ Verify that the JESD core ID is correct.
+ """
+ expected_id = self.CORE_ID_BASE + self.core_idx
+ core_id = self.peek32(0x100)
+ self.log.trace("Reading JESD core ID: {:x}".format(core_id))
+ if core_id != expected_id:
+ self.log.error(
+ "Cannot identify JESD core! Read ID: {:x} Expected: {:x}".format(
+ core_id, expected_id
+ )
+ )
+ return False
+ date_info = core_id = self.peek32(0x104)
+ self.log.trace("Reading JESD date info: {:x}".format(date_info))
+ return True
+
+ def init(self):
+ """
+ Run initialization sequence on JESD core.
+ """
+ self._gt_pll_power_control()
+ self._gt_rx_reset(True)
+ if not self._gt_pll_lock_control():
+ raise RuntimeError("JESD CORE {} PLLs not locked!".format(self.core_idx))
+
+ def _gt_pll_power_control(self):
+ """
+ Power down unused CPLLs and QPLLs
+ """
+ self.poke32(0x00C, 0xFFFC0000)
+
+ def _gt_rx_reset(self, reset_only=True):
+ """
+ RX Reset
+ """
+ self.poke32(0x024, 0x10) # Place the RX MGTs in reset
+ if not reset_only:
+ time.sleep(.001) # Probably not necessary
+ self.poke32(0x024, 0x20) # Unreset and Enable
+ time.sleep(0.1) # TODO replace with poll and timeout 20 ms
+ lock_status = self.peek32(0x024) & 0xFFFF0000
+ if lock_status != 0xF0000:
+ self.log.error(
+ "Error: TX MGTs failed to reset! Status: 0x{:x}".format(lock_status)
+ )
+
+ def _gt_pll_lock_control(self):
+ """
+ Make sure PLLs are locked
+ """
+ self.poke32(0x004, 0x11111111) # Reset CPLLs
+ self.poke32(0x004, 0x11111100) # Unreset the ones we're using
+ time.sleep(0.002) # TODO replace with poll and timeout
+ self.poke32(0x010, 0x10000) # Clear all CPLL sticky bits
+ lock_status = self.peek32(0x004) & 0xFF
+ lock_good = bool(lock_status == 0x22)
+ if not lock_good:
+ self.log.error("GT PLL failed to lock! Status: 0x{:x}".format(lock_status))
+ return lock_good
+
+ def _gt_polarity_control(self):
+ """
+ foo
+ """
+ reg_val = {
+ 'A': {0: 0x00, 1: 0x11},
+ 'B': {0: 0x01, 1: 0x01},
+ }
+ self.log.trace(
+ "JESD Core: Slot {}, ADC {}: Setting polarity control to 0b{:2b}".format(
+ self.slot, self.core_idx, reg_val
+ ))
+ self.poke32(0x80, reg_val)
+
+class EISCAT(DboardManagerBase):
+ """
+ EISCAT Daughterboard
+ """
hw_pid = 3
special_eeprom_addrs = {"special0": "something"}
+ spi_chipselect = {
+ "lmk": 0,
+ "adc0": 1,
+ "adc1": 2
+ }
+
+ def __init__(self, spi_devices, *args, **kwargs):
+ super(EISCAT, self).__init__(*args, **kwargs)
+ self.log = get_logger("EISCAT")
+ self.log.trace("Initializing EISCAT daughterbaord")
+ if len(spi_devices) < len(self.spi_chipselect):
+ self.log.error("Expected {0} spi devices, found {1} spi devices".format(
+ len(self.spi_chipselect), len(spi_devices),
+ ))
+ raise RuntimeError("Not enough SPI devices found.")
+ self._spi_nodes = {}
+ for k, v in iteritems(self.spi_chipselect):
+ self._spi_nodes[k] = spi_devices[v]
+ self.log.debug("spidev device node map: {}".format(self._spi_nodes))
+
+ def init_device(self):
+ """
+ Execute necessary actions to bring up the daughterboard
+ """
+ self.log.debug("Loading C++ drivers...")
+ self._device = lib.eiscat.eiscat_manager(
+ self._spi_nodes['lmk'],
+ self._spi_nodes['adc0'],
+ self._spi_nodes['adc1'],
+ # self._spi_nodes['phasedac'],
+ )
+ self.lmk_regs = self._device.get_clock_ctrl()
+ self.adc0_regs = self._device.get_adc0_ctrl()
+ self.adc1_regs = self._device.get_adc1_ctrl()
+ self.spi_lock = self._device.get_spi_lock()
+ self.log.debug("Loaded C++ drivers.")
+ self.log.debug("Getting uio...")
+ self.radio_regs = UIO(label="jesd204b-regs", read_only=False)
+ jesd_id = self.radio_regs.peek32(0x100)
+ self.log.trace("Reading JESD core ID: {:x}".format(jesd_id))
+ if jesd_id != 0x4A455344:
+ self.log.error("Cannot identify JESD core 0! Read ID: {:x}".format(jesd_id))
+ jesd_id = self.radio_regs.peek32(0x1100)
+ self.log.trace("Reading JESD core ID: {:x}".format(jesd_id))
+ if jesd_id != 0x4A455345:
+ self.log.error("Cannot identify JESD core 1! Read ID: {:x}".format(jesd_id))
+ self.log.error("Cannot identify JESD core! Read ID: {:x}".format(jesd_id))
+ jesd_id = self.radio_regs.peek32(0x1100)
+ self.log.trace("Reading JESD core ID: {:x}".format(jesd_id))
+ if jesd_id != 0x4A455345:
+ self.log.error("Cannot identify JESD core! Read ID: {:x}".format(jesd_id))
+ self.log.info("Radio-register UIO object successfully generated!")
+ self.mmcm = MMCM(self.radio_regs, self.log)
+ self.init_power(self.radio_regs)
+ self.mmcm.reset()
+ self.lmk = LMK04828EISCAT(self.lmk_regs, self.spi_lock, "A") # Initializes LMK
+ if not self.mmcm.enable():
+ self.log.error("Could not re-enable MMCM!")
+ raise RuntimeError("Could not re-enable MMCM!")
+ self.log.info("MMCM enabled!")
+ self.adc0 = ADS54J56(self.adc0_regs, self.log)
+ self.adc1 = ADS54J56(self.adc1_regs, self.log)
+ self.adc0.reset()
+ self.adc1.reset()
+ self.log.info("ADCs resetted!")
+# Send SYSREF Pulse the "f2SendSysRefToAdcA" and "f2SendSysRefToAdcB" signals in the JESD nelist for 1 FpgaClk2x cycle. This will eventually be a timed command from the Radio.
+# Initialize ADC -A These are all SPI transactions.
+# Initialize ADC -B See the "ADC Setup" tab.
+# Init FPGA JESD204B Deframer (RX) -A See function call in "JESD204b FPGA Core Setup" tab.
+# Init FPGA JESD204B Deframer (RX) -B Same as -A.
+# Send SYSREF Same as the first Send SYSREF call.
+# Check Deframer Status (RX) -A See function call in "JESD204b FPGA Core Setup" tab.
+# Check Deframer Status (RX) -B Same as -A.
+
+ def init_power(self, regs):
+ """
+ Turn on power to the dboard.
+
+ After this function, we should never touch this register again.
+ """
+ reg_val = PWR2_5V_DC_CTRL_ENB
+ self.log.trace("Asserting power ctrl enable ({})...".format(bin(reg_val)))
+ regs.poke32(POWER_ENB, reg_val)
+ time.sleep(0.001)
+ reg_val = reg_val \
+ | PWR2_5V_DC_CTRL_ENB \
+ | PWR2_5V_DC_PWR_EN \
+ | PWR2_5V_LNA_CTRL_EN \
+ | PWR2_5V_LMK_SPI_EN | PWR2_5V_ADC0_SPI_EN #| PWR2_5V_ADC1_SPI_EN
+ regs.poke32(POWER_ENB, reg_val)
+ self.log.trace("Asserting power enable for all the chips ({})...".format(bin(reg_val)))
+ time.sleep(0.1)
+ for chan in xrange(8):
+ reg_val = reg_val | PWR_CHAN_EN_2V5[chan]
+ self.log.trace("Asserting power enable for all the channels ({})...".format(bin(reg_val)))
+ regs.poke32(POWER_ENB, reg_val)
+
- def __init__(self, spidevs=[], *args, **kwargs):
- # Do own init
- super(eiscat, self).__init__(*args, **kwargs)
diff --git a/mpm/python/usrp_mpm/dboard_manager/lmk_eiscat.py b/mpm/python/usrp_mpm/dboard_manager/lmk_eiscat.py
new file mode 100644
index 000000000..733f8fbef
--- /dev/null
+++ b/mpm/python/usrp_mpm/dboard_manager/lmk_eiscat.py
@@ -0,0 +1,228 @@
+#
+# Copyright 2017 Ettus Research (National Instruments)
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+#
+"""
+LMK04828 driver for use with Magnesium
+"""
+
+import time
+from ..mpmlog import get_logger
+
+LMK_CHIP_ID = 6
+
+class LMK04828EISCAT(object):
+ """
+ LMK04828 controls for EISCAT daughterboard
+ """
+ def __init__(self, regs_iface, spi_lock, slot=None):
+ slot = slot or "-A"
+ self.log = get_logger("LMK04828"+slot)
+ self.regs_iface = regs_iface
+ self.spi_lock = spi_lock
+ self.init()
+ self.config()
+
+ def pokes8(self, addr_vals):
+ """
+ Apply a series of pokes
+ """
+ for addr, val in addr_vals:
+ self.regs_iface.poke8(addr, val)
+
+ def init(self):
+ """
+ Basic init. Turns it on. Let's us read SPI.
+ """
+ self.log.info("Init LMK")
+ self.pokes8((
+ (0x000, 0x90), # Assert reset
+ (0x000, 0x10), # De-assert reset
+ (0x002, 0x00), # De-assert power down
+ (0x16E, 0x3B), # PLL2 Lock Detect Config as SDO
+ ))
+ if not self.verify_chip_id():
+ raise Exception("Unable to locate LMK04828")
+
+
+ def config(self):
+ """
+ Write lots of config foo.
+ """
+ self.log.trace("Setting clkout config...")
+ self.pokes8((
+ (0x100, 0x6C), # CLKout Config
+ (0x101, 0x55), # CLKout Config
+ (0x103, 0x00), # CLKout Config
+ (0x104, 0x20), # CLKout Config
+ (0x105, 0x00), # CLKout Config
+ (0x106, 0xF3), # CLKout Config
+ (0x107, 0x05), # CLKout Config
+ (0x108, 0x6C), # CLKout Config
+ (0x109, 0x55), # CLKout Config
+ (0x10B, 0x00), # CLKout Config
+ (0x10C, 0x20), # CLKout Config
+ (0x10D, 0x00), # CLKout Config
+ (0x10E, 0xF1), # CLKout Config
+ (0x10F, 0x05), # CLKout Config
+ (0x110, 0x6C), # CLKout Config
+ (0x111, 0x55), # CLKout Config
+ (0x113, 0x00), # CLKout Config
+ (0x114, 0x20), # CLKout Config
+ (0x115, 0x00), # CLKout Config
+ (0x116, 0xF1), # CLKout Config
+ (0x117, 0x05), # CLKout Config
+ (0x118, 0x6C), # CLKout Config
+ (0x119, 0x55), # CLKout Config
+ (0x11B, 0x00), # CLKout Config
+ (0x11C, 0x20), # CLKout Config
+ (0x11D, 0x00), # CLKout Config
+ (0x11E, 0xF1), # CLKout Config
+ (0x11F, 0x05), # CLKout Config
+ (0x120, 0x78), # CLKout Config
+ (0x121, 0x55), # CLKout Config
+ (0x123, 0x00), # CLKout Config
+ (0x124, 0x20), # CLKout Config
+ (0x125, 0x00), # CLKout Config
+ (0x126, 0xF3), # CLKout Config
+ (0x127, 0x00), # CLKout Config
+ (0x128, 0x6C), # CLKout Config
+ (0x129, 0x55), # CLKout Config
+ (0x12B, 0x00), # CLKout Config
+ (0x12C, 0x20), # CLKout Config
+ (0x12D, 0x00), # CLKout Config
+ (0x12E, 0xF9), # CLKout Config
+ (0x12F, 0x00), # CLKout Config
+ (0x130, 0x6C), # CLKout Config
+ (0x131, 0x55), # CLKout Config
+ (0x133, 0x00), # CLKout Config
+ (0x134, 0x20), # CLKout Config
+ (0x135, 0x00), # CLKout Config
+ (0x136, 0xF9), # CLKout Config
+ (0x137, 0x00), # CLKout Config
+ (0x138, 0x10), # VCO_MUX to VCO 1; OSCout off
+ (0x139, 0x00), # SYSREF Source = MUX; SYSREF MUX = Normal SYNC
+ (0x13A, 0x01), # SYSREF Divide [12:8]
+ (0x13B, 0xE0), # SYSREF Divide [7:0]
+ (0x13C, 0x00), # SYSREF DDLY [12:8]
+ (0x13D, 0x08), # SYSREF DDLY [7:0] ... 8 is default, <8 is reserved
+ (0x13E, 0x00), # SYSREF Pulse Count = 1 pulse/request
+ (0x13F, 0x0B), # Feedback Mux: Enabled, DCLKout6, drives PLL1N divider
+ (0x140, 0x00), # POWERDOWN options
+ (0x141, 0x00), # Dynamic digital delay enable
+ (0x142, 0x00), # Dynamic digital delay step
+ (0x143, 0xD1), # SYNC edge sensitive; SYSREF_CLR; SYNC Enabled; SYNC fro
+ (0x144, 0x00), # Enable SYNC on all outputs including sysref
+ (0x145, 0x7F), # Always program to d127
+ (0x146, 0x08), # CLKin Type & En
+ (0x147, 0x0E), # CLKin_SEL = CLKin1 manual; CLKin1 to PLL1
+ (0x148, 0x01), # CLKin_SEL0 = input with pullup
+ (0x149, 0x01), # CLKin_SEL1 = input with pulldown
+ (0x14A, 0x02), # RESET type as input w/pulldown
+ (0x14B, 0x01), # Holdover & DAC Manual Mode
+ (0x14C, 0xF6), # DAC Manual Mode
+ (0x14D, 0x00), # DAC Settings (defaults)
+ (0x14E, 0x00), # DAC Settings (defaults)
+ (0x14F, 0x7F), # DAC Settings (defaults)
+ (0x150, 0x03), # Holdover Settings (defaults)
+ (0x151, 0x02), # Holdover Settings (defaults)
+ (0x152, 0x00), # Holdover Settings (defaults)
+ (0x153, 0x00), # CLKin0_R divider [13:8], default = 0
+ (0x154, 0x0A), # CLKin0_R divider [7:0], default = d120
+ (0x155, 0x00), # CLKin1_R divider [13:8], default = 0
+ (0x156, 0x01), # CLKin1_R divider [7:0], default = d120
+ (0x157, 0x00), # CLKin2_R divider [13:8], default = 0
+ (0x158, 0x01), # CLKin2_R divider [7:0], default = d120
+ (0x159, 0x00), # PLL1 N divider [13:8], default = 0
+ (0x15A, 0x68), # PLL1 N divider [7:0], default = d120
+ (0x15B, 0xCF), # PLL1 PFD
+ (0x15C, 0x27), # PLL1 DLD Count [13:8]
+ (0x15D, 0x10), # PLL1 DLD Count [7:0]
+ (0x15E, 0x00), # PLL1 R/N delay, defaults = 0
+ (0x15F, 0x13), # Status LD1 pin = PLL2 LD, push-pull output
+ (0x160, 0x00), # PLL2 R divider [11:8];
+ (0x161, 0x01), # PLL2 R divider [7:0]
+ (0x162, 0x24), # PLL2 prescaler; OSCin freq
+ (0x163, 0x00), # PLL2 Cal = PLL2 normal val
+ (0x164, 0x00), # PLL2 Cal = PLL2 normal val
+ (0x165, 0x0C), # PLL2 Cal = PLL2 normal val
+ (0x171, 0xAA), # Write this val after x165
+ (0x172, 0x02), # Write this val after x165
+ (0x17C, 0x15), # VCo1 Cal; write before x168
+ (0x17D, 0x33), # VCo1 Cal; write before x168
+ (0x166, 0x00), # PLL2 N[17:16]
+ (0x167, 0x00), # PLL2 N[15:8]
+ (0x168, 0x0C), # PLL2 N[7:0]
+ (0x169, 0x51), # PLL2 PFD
+ (0x16A, 0x00), # PLL2 DLD Count [13:8] = default d32
+ (0x16B, 0x10), # PLL2 DLD Count [7:0] = default d0
+ (0x16C, 0x00), # PLL2 Loop filter r = 200 ohm
+ (0x16D, 0x00), # PLL2 loop filter c = 10 pF
+ (0x173, 0x00), # Do not power down PLL2 or prescaler
+ ))
+ time.sleep(0.1)
+ self.pokes8((
+ (0x182, 0x1), # Clear Lock Detect Sticky
+ (0x182, 0x0), # Clear Lock Detect Sticky
+ (0x183, 0x1), # Clear Lock Detect Sticky
+ (0x183, 0x0), # Clear Lock Detect Sticky
+ ))
+ time.sleep(0.1)
+ self.log.trace("Checking PLL lock bits...")
+ def check_pll_lock(pll_id, addr):
+ pll_lock_status = self.regs_iface.peek8(addr)
+ if (pll_lock_status & 0x7) != 0x02:
+ self.log.error("LMK {} did not lock. Status: {:x}".format(pll_id, pll_lock_status))
+ raise RuntimeError("LMK {} did not lock.".format(pll_id))
+ check_pll_lock("PLL1", 0x182)
+ check_pll_lock("PLL2", 0x183)
+ self.log.trace("Setting SYNC and SYSREF config...")
+ self.pokes8((
+ (0x143, 0xF1), # toggle SYNC polarity to trigger SYNC event
+ (0x143, 0xD1), # toggle SYNC polarity to trigger SYNC event
+ (0x139, 0x02), # SYSREF Source = MUX; SYSREF MUX = pulser
+ (0x144, 0xFF), # Disable SYNC on all outputs including sysref
+ (0x143, 0x52), # Pulser selected; SYNC enabled; 1 shot enabled
+ ))
+ self.log.info("LMK init'd and locked!")
+
+ def get_chip_id(self):
+ """
+ Read back the chip ID
+ """
+ chip_id = self.regs_iface.peek8(0x03)
+ self.log.trace("Read chip ID: {}".format(chip_id))
+ return chip_id
+
+ def verify_chip_id(self):
+ """
+ Returns True if the chip ID matches what we expect, False otherwise.
+ """
+ chip_id = self.get_chip_id()
+ if chip_id != LMK_CHIP_ID:
+ self.log.error("wrong chip id {0}".format(chip_id))
+ return False
+ return True
+
+ # TODO delete this
+ # def enable_sysref_pulse(self):
+ # """
+ # Enable SYSREF pulses
+ # """
+ # self.spi_lock.lock()
+ # self.poke8(0x139, 0x2)
+ # self.poke8(0x144, 0xFF)
+ # self.poke8(0x143, 0x52)
+ # self.spi_lock.unlock()
diff --git a/mpm/python/usrp_mpm/periph_manager/base.py b/mpm/python/usrp_mpm/periph_manager/base.py
index 4815bd88d..25f635c68 100644
--- a/mpm/python/usrp_mpm/periph_manager/base.py
+++ b/mpm/python/usrp_mpm/periph_manager/base.py
@@ -75,7 +75,7 @@ class PeriphManagerBase(object):
# eeprom_data = EEPROM().read_eeprom(get_eeprom_path(eeprom_addr))
eeprom_data = None
# I know spidev masters on the dboard slots
- hw_pid = 2
+ hw_pid = 3
if hw_pid in dboard_manager.HW_PIDS:
spi_devices = sorted(get_spidev_nodes("e0006000.spi"))
self.log.debug("Found spidev nodes: {0}".format(spi_devices))