diff options
author | Derek Kozel <derek.kozel@ettus.com> | 2018-04-30 15:04:35 +0100 |
---|---|---|
committer | Martin Braun <martin.braun@ettus.com> | 2018-05-03 11:30:34 -0700 |
commit | 3615873feebe2d86f38e45acafb0265ea7246916 (patch) | |
tree | 732aca8f7ed0aa6a238d80184d4588eb18bda54b /host | |
parent | 3a4073799db9cf314b57eb20bb8f8fc085a76631 (diff) | |
download | uhd-3615873feebe2d86f38e45acafb0265ea7246916.tar.gz uhd-3615873feebe2d86f38e45acafb0265ea7246916.tar.bz2 uhd-3615873feebe2d86f38e45acafb0265ea7246916.zip |
uhd: Added LMX2592 driver
Diffstat (limited to 'host')
-rw-r--r-- | host/lib/ic_reg_maps/CMakeLists.txt | 5 | ||||
-rwxr-xr-x | host/lib/ic_reg_maps/gen_lmx2592_regs.py | 352 | ||||
-rw-r--r-- | host/lib/include/uhdlib/usrp/common/lmx2592.hpp | 55 | ||||
-rw-r--r-- | host/lib/usrp/common/CMakeLists.txt | 1 | ||||
-rw-r--r-- | host/lib/usrp/common/lmx2592.cpp | 471 |
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); +} |