aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorDerek Kozel <derek.kozel@ettus.com>2018-04-30 15:04:35 +0100
committerMartin Braun <martin.braun@ettus.com>2018-05-03 11:30:34 -0700
commit3615873feebe2d86f38e45acafb0265ea7246916 (patch)
tree732aca8f7ed0aa6a238d80184d4588eb18bda54b
parent3a4073799db9cf314b57eb20bb8f8fc085a76631 (diff)
downloaduhd-3615873feebe2d86f38e45acafb0265ea7246916.tar.gz
uhd-3615873feebe2d86f38e45acafb0265ea7246916.tar.bz2
uhd-3615873feebe2d86f38e45acafb0265ea7246916.zip
uhd: Added LMX2592 driver
-rw-r--r--host/lib/ic_reg_maps/CMakeLists.txt5
-rwxr-xr-xhost/lib/ic_reg_maps/gen_lmx2592_regs.py352
-rw-r--r--host/lib/include/uhdlib/usrp/common/lmx2592.hpp55
-rw-r--r--host/lib/usrp/common/CMakeLists.txt1
-rw-r--r--host/lib/usrp/common/lmx2592.cpp471
5 files changed, 884 insertions, 0 deletions
diff --git a/host/lib/ic_reg_maps/CMakeLists.txt b/host/lib/ic_reg_maps/CMakeLists.txt
index c917f9627..de0e0660e 100644
--- a/host/lib/ic_reg_maps/CMakeLists.txt
+++ b/host/lib/ic_reg_maps/CMakeLists.txt
@@ -123,6 +123,11 @@ LIBUHD_PYTHON_GEN_SOURCE(
)
LIBUHD_PYTHON_GEN_SOURCE(
+ ${CMAKE_CURRENT_SOURCE_DIR}/gen_lmx2592_regs.py
+ ${CMAKE_CURRENT_BINARY_DIR}/lmx2592_regs.hpp
+)
+
+LIBUHD_PYTHON_GEN_SOURCE(
${CMAKE_CURRENT_SOURCE_DIR}/gen_lmk04828_regs.py
${CMAKE_CURRENT_BINARY_DIR}/lmk04828_regs.hpp
)
diff --git a/host/lib/ic_reg_maps/gen_lmx2592_regs.py b/host/lib/ic_reg_maps/gen_lmx2592_regs.py
new file mode 100755
index 000000000..c39665ee9
--- /dev/null
+++ b/host/lib/ic_reg_maps/gen_lmx2592_regs.py
@@ -0,0 +1,352 @@
+#!/usr/bin/env python
+#
+# Copyright 2017 Ettus Research, A National Instruments Company
+#
+# SPDX-License-Identifier: GPL-3.0
+#
+
+########################################################################
+# Template for raw text data describing registers
+# name addr[bit range inclusive] default optional enums
+########################################################################
+REGS_TMPL="""\
+########################################################################
+## address 0
+########################################################################
+powerdown 0[0] 0
+reset 0[1] 0
+muxout_sel 0[2] 1 readback, lock_detect
+fcal_enable 0[3] 1
+acal_enable 0[4] 1
+fcal_lpfd_adj 0[5:6] 0 unused, 20mhz, 10mhz, 5mhz
+fcal_hpfd_adf 0[7:8] 0 unused, 100mhz, 150mhz, 200mhz
+reg0_reserved0 0[9:12] 0x1
+ld_enable 0[13] 1
+reg0_reserved1 0[14:15] 0x0
+########################################################################
+## address 1
+########################################################################
+cal_clk_div 1[0:2] 3
+reg1_reserved0 1[3:15] 0x101
+########################################################################
+## address 2
+########################################################################
+reg2_reserved0 2[0:15] 0x500
+########################################################################
+## address 4
+########################################################################
+reg4_reserved0 4[0:7] 0x43
+acal_cmp_delay 4[8:15] 25
+########################################################################
+## address 7
+########################################################################
+reg7_reserved0 7[0:15] 0x28b2
+########################################################################
+## address 8
+########################################################################
+reg8_reserved0 8[0:9] 0x84
+vco_capctrl_ovr 8[10] 0
+reg8_reserved1 8[11:12] 0x2
+vco_idac_ovr 8[13] 0
+reg8_reserved2 8[14:15] 0x0
+########################################################################
+## address 9
+########################################################################
+reg9_reserved0 9[0:8] 0x102
+ref_enable 9[9] 1
+reg9_reserved1 9[10] 0x0
+osc_doubler 9[11] 0
+reg9_reserved2 9[12:15] 0x0
+########################################################################
+## address 10
+########################################################################
+reg10_reserved0 10[0:6] 0x58
+mult 10[7:11] 1
+reg10_reserved1 10[12:15] 0x1
+########################################################################
+## address 11
+########################################################################
+reg11_reserved0 11[0:3] 0x8
+pll_r 11[4:11] 1
+reg11_reserved1 11[12:15] 0x0
+########################################################################
+## address 12
+########################################################################
+pll_r_pre 12[0:11] 1
+reg12_reserved0 12[12:15] 0x7
+########################################################################
+## address 13
+########################################################################
+reg13_reserved0 13[0:7] 0x0
+pdf_ctl 13[8:9] 0 dual_pdf=0, single_pfd=3
+reg13_reserved1 13[10:13] 0x0
+cp_enable 13[14] 1
+reg13_reserved2 13[15] 0x0
+########################################################################
+## address 14
+########################################################################
+cp_icoarse 14[0:1] 1 multiply_by_1, multiply_by_2, multiply_by_1_5, multiply_by_2_5
+cp_iup 14[2:6] 3
+cp_idn 14[7:11] 3
+reg14_reserved0 14[12:15] 0x0
+########################################################################
+## address 19
+########################################################################
+reg19_reserved0 19[0:2] 0x5
+vco_idac 19[3:11] 300
+reg19_reserved1 19[12:15] 0x0
+########################################################################
+## address 20
+########################################################################
+acal_vco_idac_strt 20[0:8] 300
+reg20_reserved0 20[9:15] 0x0
+########################################################################
+## address 22
+########################################################################
+vco_capctrl 22[0:7] 0
+reg22_reserved0 22[8:15] 0x23
+########################################################################
+## address 23
+########################################################################
+reg23_reserved0 23[0:9] 0x42
+vco_sel_force 23[10] 0
+vco_sel 23[11:13] 1
+fcal_vco_sel_strt 23[14] 0
+reg23_reserved1 23[15] 0x1
+########################################################################
+## address 24
+########################################################################
+reg24_reserved0 24[0:15] 0x509
+########################################################################
+## address 25
+########################################################################
+reg25_reserved0 25[0:15] 0x0
+########################################################################
+## address 28
+########################################################################
+reg28_reserved0 28[0:15] 0x2924
+########################################################################
+## address 29
+########################################################################
+reg29_reserved0 29[0:15] 0x84
+########################################################################
+## address 30
+########################################################################
+vco_doubler_en 30[0] 0
+reg30_reserved0 30[1:9] 0x1a
+mash_ditherer 30[10] 0
+reg30_reserved1 30[11:15] 0x0
+########################################################################
+## address 31
+########################################################################
+reg31_reserved0 31[0:6] 0x1
+chdiv_dist_pd 31[7] 0
+reg31_reserved1 31[8] 0x0
+vco_dista_pd 31[9] 0
+vco_distb_pd 31[10] 1
+reg31_reserved2 31[11:15] 0x0
+########################################################################
+## address 32
+########################################################################
+reg32_reserved0 32[0:15] 0x210a
+########################################################################
+## address 33
+########################################################################
+reg33_reserved0 33[0:15] 0x2a0a
+########################################################################
+## address 34
+########################################################################
+reg34_reserved0 34[0:4] 0xa
+chdiv_en 34[5] 1
+reg34_reserved1 34[6:15] 0x30f
+########################################################################
+## address 35
+########################################################################
+reg35_reserved0 35[0] 0x1
+chdiv_seg1_en 35[1] 0
+chdiv_seg1 35[2] 1 divide_by_2, divide_by_3
+reg35_reserved1 35[3:6] 0x3
+chdiv_seg2_en 35[7] 0
+chdiv_seg3_en 35[8] 0
+chdiv_seg2 35[9:12] 1 powerdown=0, divide_by_2=1, divide_by_4=2, divide_by_6=4, divide_by_8=8
+reg35_reserved2 35[13:15] 0x0
+########################################################################
+## address 36
+########################################################################
+chdiv_seg3 36[0:3] 1 powerdown=0, divide_by_2=1, divide_by_4=2, divide_by_6=4, divide_by_8=8
+chdiv_seg_sel 36[4:6] 1 powerdown=0, div_seg_1=1, div_seg_1_and_2=2, div_seg_1_2_and_3=4
+reg36_reserved0 36[7:9] 0x0
+chdiv_dista_en 36[10] 1
+chdiv_distb_en 36[11] 0
+reg36_reserved1 36[12:15] 0x0
+########################################################################
+## address 37
+########################################################################
+reg37_reserved0 37[0:11] 0x0
+pll_n_pre 37[12] 0 divide_by_2, divide_by_4
+reg37_reserved1 37[13:15] 0x2
+########################################################################
+## address 38
+########################################################################
+reg38_reserved0 38[0] 0x0
+pll_n 38[1:12] 27
+reg38_reserved1 38[13:15] 0x0
+########################################################################
+## address 39
+########################################################################
+reg39_reserved0 39[0:7] 0x4
+pfd_dly 39[8:13] 2 4_clk_delay=1, 6_clk_delay=2, 8_clk_delay=4, 12_clk_delay=8, 16_clk_delay=16
+reg39_reserved1 39[14:15] 0x4
+########################################################################
+## address 40
+########################################################################
+pll_den_msb 40[0:15] 1000
+########################################################################
+## address 41
+########################################################################
+pll_den_lsb 41[0:15] 1000
+########################################################################
+## address 42
+########################################################################
+mash_seed_msb 42[0:15] 0
+########################################################################
+## address 43
+########################################################################
+mash_seed_lsb 43[0:15] 0
+########################################################################
+## address 44
+########################################################################
+pll_num_msb 44[0:15] 0
+########################################################################
+## address 45
+########################################################################
+pll_num_lsb 45[0:15] 0
+########################################################################
+## address 46
+########################################################################
+mash_order 46[0:2] 3 int_mode, first, second, third, fourth
+reg46_reserved0 46[3:5] 0x4
+outa_pd 46[6] 0
+outb_pd 46[7] 1
+outa_power 46[8:13] 15
+reg46_reserved1 46[14:15] 0x0
+########################################################################
+## address 47
+########################################################################
+outb_power 47[0:5] 0
+reg47_reserved0 47[6:10] 0x3
+outa_mux 47[11:12] 0 divider=0, vco=1
+reg47_reserved1 47[13:15] 0x0
+########################################################################
+## address 48
+########################################################################
+outb_mux 48[0:1] 0 divider=0, vco=1
+reg48_reserved0 48[2:15] 0xff
+########################################################################
+## address 59
+########################################################################
+reg59_reserved0 59[0:4] 0x0
+muxout_hdrv 59[5] 0
+reg59_reserved1 59[6:15] 0x0
+########################################################################
+## address 61
+########################################################################
+ld_type 61[0] 1 cal_status, vtune_detect
+reg61_reserved0 61[1:15] 0x0
+########################################################################
+## address 62
+########################################################################
+reg62_reserved0 62[0:15] 0x0
+########################################################################
+## address 64
+########################################################################
+fjump_size 64[0:3] 15
+reg64_reserved0 64[4] 0x1
+ajump_size 64[5:7] 3
+fcal_fast 64[8] 0
+acal_fast 64[9] 0
+reg64_reserved1 64[10:15] 0x0
+"""
+
+########################################################################
+# Template for methods in the body of the struct
+########################################################################
+BODY_TMPL="""\
+enum addr_t{
+ ADDR_R0 = 0,
+ ADDR_R1 = 1,
+ ADDR_R2 = 2,
+ ADDR_R4 = 4,
+ ADDR_R7 = 7,
+ ADDR_R8 = 8,
+ ADDR_R9 = 9,
+ ADDR_R10 = 10,
+ ADDR_R11 = 11,
+ ADDR_R12 = 12,
+ ADDR_R13 = 13,
+ ADDR_R14 = 14,
+ ADDR_R19 = 19,
+ ADDR_R20 = 20,
+ ADDR_R22 = 22,
+ ADDR_R23 = 23,
+ ADDR_R24 = 24,
+ ADDR_R25 = 25,
+ ADDR_R28 = 28,
+ ADDR_R29 = 29,
+ ADDR_R30 = 30,
+ ADDR_R31 = 31,
+ ADDR_R32 = 32,
+ ADDR_R33 = 33,
+ ADDR_R34 = 34,
+ ADDR_R35 = 35,
+ ADDR_R36 = 36,
+ ADDR_R37 = 37,
+ ADDR_R38 = 38,
+ ADDR_R39 = 39,
+ ADDR_R40 = 40,
+ ADDR_R41 = 41,
+ ADDR_R42 = 42,
+ ADDR_R43 = 43,
+ ADDR_R44 = 44,
+ ADDR_R45 = 45,
+ ADDR_R46 = 46,
+ ADDR_R47 = 47,
+ ADDR_R48 = 48,
+ ADDR_R59 = 59,
+ ADDR_R61 = 61,
+ ADDR_R62 = 62,
+ ADDR_R64 = 64
+};
+
+uint16_t get_reg(uint8_t addr){
+ uint16_t reg = 0;
+ switch(addr){
+ % for addr in [0,1,2,4,7,8,9,10,11,12,13,14,19,20,22,23,24,25,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,59,61,62,64]:
+ case ${addr}:
+ % for reg in filter(lambda r: r.get_addr() == addr, regs):
+ reg |= (uint16_t(${reg.get_name()}) & ${reg.get_mask()}) << ${reg.get_shift()};
+ % endfor
+ break;
+ % endfor
+ }
+ return reg;
+}
+
+std::set<size_t> get_all_addrs()
+{
+ std::set<size_t> addrs;
+ % for reg in regs:
+ // Hopefully, compilers will optimize out this mess...
+ addrs.insert(${reg.get_addr()});
+ % endfor
+ return addrs;
+}
+"""
+
+if __name__ == '__main__':
+ import common; common.generate(
+ name='lmx2592_regs',
+ regs_tmpl=REGS_TMPL,
+ body_tmpl=BODY_TMPL,
+ file=__file__,
+ )
diff --git a/host/lib/include/uhdlib/usrp/common/lmx2592.hpp b/host/lib/include/uhdlib/usrp/common/lmx2592.hpp
new file mode 100644
index 000000000..0e86d82aa
--- /dev/null
+++ b/host/lib/include/uhdlib/usrp/common/lmx2592.hpp
@@ -0,0 +1,55 @@
+//
+// Copyright 2018 Ettus Research, A National Instruments Company
+//
+// SPDX-License-Identifier: GPL-3.0
+//
+
+#ifndef INCLUDED_LMX2592_HPP
+#define INCLUDED_LMX2592_HPP
+
+#include "lmx2592_regs.hpp"
+#include <uhd/utils/log.hpp>
+#include <uhd/utils/math.hpp>
+#include <uhd/utils/safe_call.hpp>
+#include <boost/format.hpp>
+#include <boost/function.hpp>
+#include <boost/math/common_factor_rt.hpp> //gcd
+#include <algorithm>
+#include <cstdint>
+#include <utility>
+#include <vector>
+
+class lmx2592_iface {
+public:
+ typedef std::shared_ptr<lmx2592_iface> sptr;
+
+ //! SPI write functor: Can take a SPI transaction and clock it out
+ using write_spi_t = std::function<void(uint32_t)>;
+
+ //! SPI read functor: Return SPI
+ using read_spi_t = std::function<uint32_t(uint32_t)>;
+
+ static sptr make(write_spi_t write, read_spi_t read);
+
+ virtual ~lmx2592_iface() = default;
+
+ enum output_t { RF_OUTPUT_A, RF_OUTPUT_B };
+
+ enum mash_order_t { INT_N, FIRST, SECOND, THIRD, FOURTH };
+
+ virtual double set_frequency(double target_freq) = 0;
+
+ virtual void set_mash_order(mash_order_t mash_order) = 0;
+
+ virtual void set_reference_frequency(double ref_freq) = 0;
+
+ virtual void set_output_power(output_t output, unsigned int power) = 0;
+
+ virtual void set_output_enable(output_t output, bool enable) = 0;
+
+ virtual bool get_lock_status() = 0;
+
+ virtual void commit() = 0;
+};
+
+#endif // INCLUDED_LMX2592_HPP
diff --git a/host/lib/usrp/common/CMakeLists.txt b/host/lib/usrp/common/CMakeLists.txt
index 9e27fb880..082a047e7 100644
--- a/host/lib/usrp/common/CMakeLists.txt
+++ b/host/lib/usrp/common/CMakeLists.txt
@@ -29,6 +29,7 @@ LIBUHD_APPEND_SOURCES(
${CMAKE_CURRENT_SOURCE_DIR}/adf4001_ctrl.cpp
${CMAKE_CURRENT_SOURCE_DIR}/adf435x.cpp
${CMAKE_CURRENT_SOURCE_DIR}/adf535x.cpp
+ ${CMAKE_CURRENT_SOURCE_DIR}/lmx2592.cpp
${CMAKE_CURRENT_SOURCE_DIR}/apply_corrections.cpp
${CMAKE_CURRENT_SOURCE_DIR}/validate_subdev_spec.cpp
${CMAKE_CURRENT_SOURCE_DIR}/recv_packet_demuxer.cpp
diff --git a/host/lib/usrp/common/lmx2592.cpp b/host/lib/usrp/common/lmx2592.cpp
new file mode 100644
index 000000000..3f71fb04c
--- /dev/null
+++ b/host/lib/usrp/common/lmx2592.cpp
@@ -0,0 +1,471 @@
+//
+// Copyright 2018, 2017 Ettus Research, A National Instruments Company
+//
+// SPDX-License-Identifier: GPL-3.0-or-later
+//
+
+#include "lmx2592_regs.hpp"
+#include <uhdlib/usrp/common/lmx2592.hpp>
+#include <uhdlib/utils/narrow.hpp>
+#include <boost/math/common_factor_rt.hpp>
+#include <chrono>
+#include <cmath>
+#include <iomanip>
+#include <iostream>
+
+using namespace uhd;
+
+namespace {
+constexpr double LMX2592_DOUBLER_MAX_REF_FREQ = 60e6;
+constexpr double LMX2592_MAX_FREQ_PFD = 125e6;
+
+constexpr double LMX2592_MIN_REF_FREQ = 5e6;
+constexpr double LMX2592_MAX_REF_FREQ = 1400e6;
+
+constexpr double LMX2592_MAX_OUT_FREQ = 9.8e9;
+constexpr double LMX2592_MIN_OUT_FREQ = 20e6;
+
+constexpr double LMX2592_MIN_VCO_FREQ = 3.55e9;
+constexpr double LMX2592_MAX_VCO_FREQ = 7.1e9;
+
+constexpr double LMX2592_MAX_DOUBLER_INPUT_FREQ = 200e6;
+constexpr double LMX2592_MAX_MULT_OUT_FREQ = 250e6;
+constexpr double LMX2592_MAX_MULT_INPUT_FREQ = 70e6;
+constexpr double LMX2592_MAX_POSTR_DIV_OUT_FREQ = 125e6;
+
+constexpr int MAX_N_DIVIDER = 4095;
+
+constexpr int MAX_MASH_ORDER = 4;
+constexpr std::array<int, MAX_MASH_ORDER + 1> LMX2592_MIN_N_DIV = {
+ 9, 11, 16, 18, 30
+}; // includes int-N
+
+constexpr int NUM_DIVIDERS = 14;
+constexpr std::array<int, NUM_DIVIDERS> LMX2592_CHDIV_DIVIDERS = { 1, 2, 3, 4, 6, 8, 12,
+ 16, 24, 32, 64, 96, 128, 192 };
+const std::array<double, NUM_DIVIDERS> LMX2592_CHDIV_MIN_FREQ = {
+ 3550e6, 1775e6, 1183.33e6, 887.5e6, 591.67e6, 443.75e6, 295.83e6,
+ 221.88e6, 147.92e6, 110.94e6, 55.47e6, 36.98e6, 27.73e6, 20e6
+};
+constexpr std::array<double, NUM_DIVIDERS> LMX2592_CHDIV_MAX_FREQ = {
+ 6000e6, 3550.0e6, 2366.67e6, 1775.00e6, 1183.33, 887.50e6, 591.67e6,
+ 443.75e6, 295.83e6, 221.88e6, 110.94e6, 73.96e6, 55.47e6, 36.98
+};
+constexpr int NUM_CHDIV_STAGES = 3;
+constexpr std::array<std::array<int, NUM_CHDIV_STAGES>, NUM_DIVIDERS> LMX2592_CHDIV_SEGS = {
+ { { 1, 1, 1 },
+ { 2, 1, 1 },
+ { 3, 1, 1 },
+ { 2, 2, 1 },
+ { 3, 2, 1 },
+ { 2, 4, 1 },
+ { 2, 6, 1 },
+ { 2, 8, 1 },
+ { 3, 8, 1 },
+ { 2, 8, 2 },
+ { 2, 8, 4 },
+ { 2, 8, 6 },
+ { 2, 8, 8 },
+ { 3, 8, 8 } }
+};
+
+constexpr int SPI_ADDR_SHIFT = 16;
+constexpr int SPI_ADDR_MASK = 0x7f;
+constexpr int SPI_READ_FLAG = 1 << 23;
+}
+
+class lmx2592_impl : public lmx2592_iface {
+public:
+ explicit lmx2592_impl(write_spi_t write_fn, read_spi_t read_fn)
+ : _write_fn([write_fn](const uint8_t addr, const uint16_t data) {
+ const uint32_t spi_transaction =
+ 0 | ((addr & SPI_ADDR_MASK) << SPI_ADDR_SHIFT) | data;
+ write_fn(spi_transaction);
+ }),
+ _read_fn([read_fn](const uint8_t addr) {
+ const uint32_t spi_transaction =
+ SPI_READ_FLAG | ((addr & SPI_ADDR_MASK) << SPI_ADDR_SHIFT);
+ return read_fn(spi_transaction);
+ }),
+ _regs(),
+ _rewrite_regs(true) {
+ UHD_LOG_TRACE("LMX2592", "Initializing Synthesizer");
+
+ // Soft Reset
+ _regs.reset = 1;
+ UHD_LOG_TRACE("LMX2592", "Resetting LMX");
+ _write_fn(_regs.ADDR_R0, _regs.get_reg(_regs.ADDR_R0));
+
+ // The bit is cleared on the synth during the reset
+ _regs.reset = 0;
+
+ // Enable SPI Readback
+ _regs.muxout_sel = lmx2592_regs_t::muxout_sel_t::MUXOUT_SEL_READBACK;
+ UHD_LOG_TRACE("LMX2592", "Enabling SPI Readback");
+ _write_fn(_regs.ADDR_R0, _regs.get_reg(_regs.ADDR_R0));
+
+ // Test Write/Read
+ const auto random_number = // Derived from current time
+ static_cast<uint16_t>(
+ std::chrono::system_clock::to_time_t(std::chrono::system_clock::now()) & 0x0FFF);
+ _write_fn(_regs.ADDR_R40, random_number);
+ const auto readback = _read_fn(_regs.ADDR_R40);
+ if (readback == random_number) {
+ UHD_LOG_TRACE("LMX2592", "Register loopback test passed");
+ } else {
+ throw runtime_error(
+ str(boost::format(
+ "LMX2592 register loopback test failed. Expected 0x%04X, Read 0x%04X") %
+ random_number % readback));
+ }
+
+ // Set register values where driver defaults differ from the datasheet values
+ _regs.acal_enable = 0;
+ _regs.fcal_enable = 0;
+ _regs.cal_clk_div = 0;
+ _regs.vco_idac_ovr = 1;
+ _regs.cp_idn = 12;
+ _regs.cp_iup = 12;
+ _regs.vco_idac = 350;
+ _regs.mash_ditherer = 1;
+ _regs.outa_mux = lmx2592_regs_t::outa_mux_t::OUTA_MUX_VCO;
+ _regs.fcal_fast = 1;
+
+ // Write default register values, ensures register copy is synchronized
+ _rewrite_regs = true;
+ commit();
+
+ _regs.fcal_enable = 1;
+ commit();
+ }
+
+ ~lmx2592_impl() override { UHD_SAFE_CALL(_regs.powerdown = 1; commit();) }
+
+ double set_frequency(const double target_freq) override {
+
+ // Enforce LMX frequency limits
+ if (target_freq < LMX2592_MIN_OUT_FREQ or target_freq > LMX2592_MAX_OUT_FREQ) {
+ throw runtime_error("Requested frequency is out of the supported range");
+ }
+
+ // Find the largest possible divider
+ auto output_divider_index = 0;
+ for (auto limit : LMX2592_CHDIV_MIN_FREQ) {
+ if (target_freq < limit) {
+ output_divider_index++;
+ } else {
+ break;
+ }
+ }
+ const auto output_divider = LMX2592_CHDIV_DIVIDERS[output_divider_index];
+ _set_chdiv_values(output_divider_index);
+
+ // Setup input signal path and PLL loop
+ const int vco_multiplier = target_freq > LMX2592_MAX_VCO_FREQ ? 2 : 1;
+
+ const auto target_vco_freq = target_freq * output_divider;
+ const auto core_vco_freq = target_vco_freq / vco_multiplier;
+
+ double input_freq = _ref_freq;
+
+ // Input Doubler stage
+ if (input_freq <= LMX2592_MAX_DOUBLER_INPUT_FREQ) {
+ _regs.osc_doubler = 1;
+ input_freq *= 2;
+ } else {
+ _regs.osc_doubler = 0;
+ }
+
+ // Pre-R divider
+ _regs.pll_r_pre =
+ narrow_cast<uint16_t>(std::ceil(input_freq / LMX2592_MAX_MULT_INPUT_FREQ));
+ input_freq /= _regs.pll_r_pre;
+
+ // Multiplier
+ _regs.mult = narrow_cast<uint8_t>(std::floor(LMX2592_MAX_MULT_OUT_FREQ / input_freq));
+ input_freq *= _regs.mult;
+
+ // Post R divider
+ _regs.pll_r = narrow_cast<uint8_t>(std::ceil(input_freq / LMX2592_MAX_POSTR_DIV_OUT_FREQ));
+
+ // Default to divide by 2, will be increased later if N exceeds it's limit
+ int prescaler = 2;
+ _regs.pll_n_pre = lmx2592_regs_t::pll_n_pre_t::PLL_N_PRE_DIVIDE_BY_2;
+
+ const int min_n_divider = LMX2592_MIN_N_DIV[_regs.mash_order];
+ double pfd_freq = input_freq / _regs.pll_r;
+ while (pfd_freq * (prescaler + min_n_divider) / vco_multiplier > core_vco_freq) {
+ _regs.pll_r++;
+ pfd_freq = input_freq / _regs.pll_r;
+ }
+
+ const auto spur_dodging_enable = false;
+ const double min_vco_step_size = spur_dodging_enable ? 2e6 : 1;
+
+ auto fden = static_cast<uint32_t>(std::floor(pfd_freq * prescaler / min_vco_step_size));
+ _regs.pll_den_lsb = narrow_cast<uint16_t>(fden);
+ _regs.pll_den_msb = narrow_cast<uint16_t>(fden >> 16);
+
+ auto mash_seed = static_cast<uint32_t>(fden / 2);
+ _regs.mash_seed_lsb = narrow_cast<uint16_t>(mash_seed);
+ _regs.mash_seed_msb = narrow_cast<uint16_t>(mash_seed >> 16);
+
+ // Calculate N and Fnum
+ const auto N_dot_F = target_vco_freq / (pfd_freq * prescaler);
+ auto N = static_cast<uint16_t>(std::floor(N_dot_F));
+ if (N > MAX_N_DIVIDER) {
+ _regs.pll_n_pre = lmx2592_regs_t::pll_n_pre_t::PLL_N_PRE_DIVIDE_BY_4;
+ N /= 2;
+ }
+ const auto frac = N_dot_F - N;
+ const auto fnum = static_cast<uint32_t>(std::round(frac * fden));
+
+ _regs.pll_n = N;
+ _regs.pll_num_lsb = narrow_cast<uint16_t>(fnum);
+ _regs.pll_num_msb = narrow_cast<uint16_t>(fnum >> 16);
+
+ // Calculate actual Fcore_vco, Fvco, F_lo frequencies
+ const auto actual_fvco = pfd_freq * prescaler * (N + double(fnum) / double(fden));
+ const auto actual_fcore_vco = actual_fvco / vco_multiplier;
+ const auto actual_f_lo = actual_fcore_vco * vco_multiplier / output_divider;
+
+ UHD_LOGGER_TRACE("LMX2592") << "Tuned to " << actual_f_lo;
+ commit();
+
+ // Run Frequency Calibration
+ _regs.fcal_enable = 1;
+ commit();
+
+ UHD_LOGGER_TRACE("LMX2592")
+ << "PLL lock status: " << (get_lock_status() ? "Locked" : "Unlocked");
+
+ return actual_f_lo;
+ }
+
+ void set_mash_order(const mash_order_t mash_order) override {
+ if (mash_order == mash_order_t::INT_N) {
+ _regs.mash_order = lmx2592_regs_t::mash_order_t::MASH_ORDER_INT_MODE;
+
+ } else if (mash_order == mash_order_t::FIRST) {
+ _regs.mash_order = lmx2592_regs_t::mash_order_t::MASH_ORDER_FIRST;
+
+ } else if (mash_order == mash_order_t::SECOND) {
+ _regs.mash_order = lmx2592_regs_t::mash_order_t::MASH_ORDER_SECOND;
+
+ } else if (mash_order == mash_order_t::THIRD) {
+ _regs.mash_order = lmx2592_regs_t::mash_order_t::MASH_ORDER_THIRD;
+
+ } else if (mash_order == mash_order_t::FOURTH) {
+ _regs.mash_order = lmx2592_regs_t::mash_order_t::MASH_ORDER_FOURTH;
+ }
+ }
+
+ void set_reference_frequency(const double ref_freq) override {
+ if (ref_freq < LMX2592_MIN_REF_FREQ or ref_freq > LMX2592_MAX_REF_FREQ) {
+ throw std::runtime_error("Reference frequency is out of bounds for the LMX2592");
+ }
+
+ _ref_freq = ref_freq;
+ }
+
+ void set_output_power(const output_t output, const unsigned int power) override {
+ UHD_LOGGER_TRACE("LMX2592")
+ << "Set output: " << (output == RF_OUTPUT_A ? "A" : "B") << " to power " << power;
+
+ const auto MAX_POWER = 63;
+ if (power > MAX_POWER) {
+ UHD_LOGGER_ERROR("LMX2592")
+ << "Requested power level of " << power << " exceeds maximum of " << MAX_POWER;
+ return;
+ }
+
+ if (output == RF_OUTPUT_A) {
+ _regs.outa_power = power;
+ } else {
+ _regs.outb_power = power;
+ }
+
+ commit();
+ }
+
+ void set_output_enable(const output_t output, const bool enable) override {
+ UHD_LOGGER_TRACE("LMX2592") << "Set output " << (output == RF_OUTPUT_A ? "A" : "B")
+ << " to " << (enable ? "On" : "Off");
+
+ if (enable) {
+ _regs.chdiv_dist_pd = 0;
+
+ if (output == RF_OUTPUT_A) {
+ _regs.outa_pd = 0;
+
+ } else {
+ _regs.outb_pd = 0;
+ }
+
+ } else {
+ if (output == RF_OUTPUT_A) {
+ _regs.outa_pd = 1;
+ _regs.vco_dista_pd = 1;
+ _regs.chdiv_dista_en = 0;
+
+ } else {
+ _regs.outb_pd = 1;
+ _regs.vco_distb_pd = 1;
+ _regs.chdiv_distb_en = 0;
+ }
+ }
+
+ // If both channels are disabled
+ if (_regs.outa_pd == 1 and _regs.outb_pd == 1) {
+ _regs.chdiv_dist_pd = 1;
+ }
+
+ commit();
+ }
+
+ bool get_lock_status() override {
+ // MUXOUT is shared between Lock Detect and SPI Readback
+ _regs.muxout_sel = lmx2592_regs_t::muxout_sel_t::MUXOUT_SEL_LOCK_DETECT;
+ commit();
+
+ // The SPI MISO is now being driven by lock detect
+ // If the PLL is locked we expect to read 0xFFFF from any read, else 0x0000
+ const auto value_read = _read_fn(_regs.ADDR_R0);
+ const auto lock_status = (value_read == 0xFFFF);
+
+ UHD_LOG_TRACE(
+ "LMX2592",
+ str(boost::format("Read Lock status: 0x%04X") % static_cast<unsigned int>(value_read)));
+
+ // Restore ability to read registers
+ _regs.muxout_sel = lmx2592_regs_t::muxout_sel_t::MUXOUT_SEL_READBACK;
+ commit();
+
+ return lock_status;
+ }
+
+ void commit() override {
+ UHD_LOGGER_DEBUG("LMX2592")
+ << "Storing register cache " << (_rewrite_regs ? "completely" : "selectively")
+ << " to LMX via SPI...";
+ const auto changed_addrs =
+ _rewrite_regs ? _regs.get_all_addrs() : _regs.get_changed_addrs<size_t>();
+
+ for (const auto addr : changed_addrs) {
+ _write_fn(addr, _regs.get_reg(addr));
+ UHD_LOGGER_TRACE("LMX2592")
+ << "Register " << std::setw(2) << static_cast<unsigned int>(addr) << ": 0x"
+ << std::hex << std::uppercase << std::setw(4) << std::setfill('0')
+ << static_cast<unsigned int>(_regs.get_reg(addr));
+ }
+
+ _regs.save_state();
+ UHD_LOG_DEBUG("LMX2592",
+ "Writing registers complete: "
+ "Updated "
+ << changed_addrs.size()
+ << " registers.");
+
+ _rewrite_regs = false;
+ }
+
+private: // Members
+ //! Write functor: Take address / data pair, craft SPI transaction
+ using write_fn_t = std::function<void(uint8_t, uint16_t)>;
+ //! Read functor: Return value given address
+ using read_fn_t = std::function<uint16_t(uint8_t)>;
+
+ write_fn_t _write_fn;
+ read_fn_t _read_fn;
+ lmx2592_regs_t _regs;
+ bool _rewrite_regs;
+ double _ref_freq;
+
+ void _set_chdiv_values(const int output_divider_index) {
+
+ // Configure divide segments and mux
+ const auto seg1 = LMX2592_CHDIV_SEGS[output_divider_index][0];
+ const auto seg2 = LMX2592_CHDIV_SEGS[output_divider_index][1];
+ const auto seg3 = LMX2592_CHDIV_SEGS[output_divider_index][2];
+
+ _regs.chdiv_seg_sel = lmx2592_regs_t::chdiv_seg_sel_t::CHDIV_SEG_SEL_POWERDOWN;
+
+ if (seg1 > 1) {
+ _regs.chdiv_seg_sel = lmx2592_regs_t::chdiv_seg_sel_t::CHDIV_SEG_SEL_DIV_SEG_1;
+ _regs.chdiv_seg1_en = 1;
+ _regs.outa_mux = lmx2592_regs_t::outa_mux_t::OUTA_MUX_DIVIDER;
+ _regs.outb_mux = lmx2592_regs_t::outb_mux_t::OUTB_MUX_DIVIDER;
+ _regs.vco_dista_pd = 1;
+ _regs.vco_distb_pd = 1;
+ _regs.chdiv_dist_pd = 0;
+
+ if (_regs.outa_pd == 0) {
+ _regs.chdiv_dista_en = 1;
+ }
+ if (_regs.outb_pd == 0) {
+ _regs.chdiv_distb_en = 1;
+ }
+
+ } else {
+ _regs.chdiv_seg1_en = 0;
+ _regs.outa_mux = lmx2592_regs_t::outa_mux_t::OUTA_MUX_VCO;
+ _regs.outb_mux = lmx2592_regs_t::outb_mux_t::OUTB_MUX_VCO;
+ _regs.chdiv_dist_pd = 1;
+
+ if (_regs.outa_pd == 0) {
+ _regs.vco_dista_pd = 0;
+ }
+ if (_regs.outb_pd == 0) {
+ _regs.vco_distb_pd = 0;
+ }
+ }
+
+ if (seg1 == 2) {
+ _regs.chdiv_seg1 = lmx2592_regs_t::chdiv_seg1_t::CHDIV_SEG1_DIVIDE_BY_2;
+ } else if (seg1 == 3) {
+ _regs.chdiv_seg1 = lmx2592_regs_t::chdiv_seg1_t::CHDIV_SEG1_DIVIDE_BY_3;
+ }
+
+ if (seg2 > 1) {
+ _regs.chdiv_seg2_en = 1;
+ _regs.chdiv_seg_sel = lmx2592_regs_t::chdiv_seg_sel_t::CHDIV_SEG_SEL_DIV_SEG_1_AND_2;
+ } else {
+ _regs.chdiv_seg2_en = 0;
+ }
+
+ if (seg2 == 1) {
+ _regs.chdiv_seg2 = lmx2592_regs_t::chdiv_seg2_t::CHDIV_SEG2_POWERDOWN;
+ } else if (seg2 == 2) {
+ _regs.chdiv_seg2 = lmx2592_regs_t::chdiv_seg2_t::CHDIV_SEG2_DIVIDE_BY_2;
+ } else if (seg2 == 4) {
+ _regs.chdiv_seg2 = lmx2592_regs_t::chdiv_seg2_t::CHDIV_SEG2_DIVIDE_BY_4;
+ } else if (seg2 == 6) {
+ _regs.chdiv_seg2 = lmx2592_regs_t::chdiv_seg2_t::CHDIV_SEG2_DIVIDE_BY_6;
+ } else if (seg2 == 8) {
+ _regs.chdiv_seg2 = lmx2592_regs_t::chdiv_seg2_t::CHDIV_SEG2_DIVIDE_BY_8;
+ }
+
+ if (seg3 > 1) {
+ _regs.chdiv_seg3_en = 1;
+ _regs.chdiv_seg_sel = lmx2592_regs_t::chdiv_seg_sel_t::CHDIV_SEG_SEL_DIV_SEG_1_2_AND_3;
+ } else {
+ _regs.chdiv_seg3_en = 0;
+ }
+
+ if (seg3 == 1) {
+ _regs.chdiv_seg3 = lmx2592_regs_t::chdiv_seg3_t::CHDIV_SEG3_POWERDOWN;
+ } else if (seg3 == 2) {
+ _regs.chdiv_seg3 = lmx2592_regs_t::chdiv_seg3_t::CHDIV_SEG3_DIVIDE_BY_2;
+ } else if (seg3 == 4) {
+ _regs.chdiv_seg3 = lmx2592_regs_t::chdiv_seg3_t::CHDIV_SEG3_DIVIDE_BY_4;
+ } else if (seg3 == 6) {
+ _regs.chdiv_seg3 = lmx2592_regs_t::chdiv_seg3_t::CHDIV_SEG3_DIVIDE_BY_6;
+ } else if (seg3 == 8) {
+ _regs.chdiv_seg3 = lmx2592_regs_t::chdiv_seg3_t::CHDIV_SEG3_DIVIDE_BY_8;
+ }
+ }
+};
+
+lmx2592_impl::sptr lmx2592_iface::make(write_spi_t write, read_spi_t read) {
+ return std::make_shared<lmx2592_impl>(write, read);
+}