From 2df06060b57f33e095bfd08ce8143a7053c6fb6e Mon Sep 17 00:00:00 2001
From: Scott Torborg <storborg@gmail.com>
Date: Sun, 4 Mar 2018 18:24:13 -0800
Subject: x300: New mode to configure master clock rate

Add a new clocking mode to automatically configure arbitrary master
clock rates.

Co-authored-by: Brent Stapleton <brent.stapleton@ettus.com>
Co-authored-by: Martin Braun <martin.braun@ettus.com>
---
 host/lib/usrp/x300/x300_clock_ctrl.cpp  | 97 ++++++++++++++++++++++++++++++++-
 host/lib/usrp/x300/x300_defaults.hpp    |  7 ++-
 host/lib/usrp/x300/x300_device_args.hpp | 10 ++--
 3 files changed, 104 insertions(+), 10 deletions(-)

(limited to 'host/lib/usrp/x300')

diff --git a/host/lib/usrp/x300/x300_clock_ctrl.cpp b/host/lib/usrp/x300/x300_clock_ctrl.cpp
index 7d99dfd71..f5d49c97d 100644
--- a/host/lib/usrp/x300/x300_clock_ctrl.cpp
+++ b/host/lib/usrp/x300/x300_clock_ctrl.cpp
@@ -7,6 +7,7 @@
 
 #include "lmk04816_regs.hpp"
 #include "x300_clock_ctrl.hpp"
+#include "x300_defaults.hpp"
 #include <uhd/utils/safe_call.hpp>
 #include <uhd/utils/math.hpp>
 #include <stdint.h>
@@ -18,6 +19,10 @@
 
 static const double X300_REF_CLK_OUT_RATE  = 10e6;
 static const uint16_t X300_MAX_CLKOUT_DIV = 1045;
+constexpr double MIN_VCO_FREQ = 2370e6;
+constexpr double MAX_VCO_FREQ = 2600e6;
+constexpr double VCXO_FREQ = 96.0e6;        // VCXO runs at 96MHz
+constexpr int VCXO_PLL2_N = 2;              // Assume that the PLL2 N predivider is set to /2.
 
 struct x300_clk_delays {
     x300_clk_delays() :
@@ -44,6 +49,7 @@ static const x300_clk_delays X300_REV7_CLK_DELAYS = x300_clk_delays(
     /*fpga=*/0.000, /*adc=*/0.000, /*dac=*/0.000, /*db_rx=*/0.000, /*db_tx=*/0.000);
 
 using namespace uhd;
+using namespace uhd::math::fp_compare;
 
 x300_clock_ctrl::~x300_clock_ctrl(void){
     /* NOP */
@@ -412,6 +418,68 @@ public:
 
 private:
 
+    double autoset_pll2_config(const double output_freq)
+    {
+        // VCXO runs at 96MHz, assume PLL2 reference doubler is enabled
+        const double ref = VCXO_FREQ * 2;
+
+        const int lowest_vcodiv = std::ceil(MIN_VCO_FREQ / output_freq);
+        const int highest_vcodiv = std::floor(MAX_VCO_FREQ / output_freq);
+
+        // Find the PLL2 configuration with the lowest frequency error, favoring
+        // higher phase comparison frequencies.
+        double best_error = 1e10;
+        double best_mcr = 0.0;
+        double best_vco_freq = _vco_freq;
+        int best_N = _lmk04816_regs.PLL2_N_30;
+        int best_R = _lmk04816_regs.PLL2_R_28;
+
+        for (int vcodiv = lowest_vcodiv; vcodiv <= highest_vcodiv; vcodiv++) {
+            const double try_vco_freq = vcodiv * output_freq;
+
+            // Start at R=2: with a min value of 2 for R, we don't have to worry
+            // about exceeding the maximum phase comparison frequency for PLL2.
+            for (int r = 2; r <= 50; r++)
+            {
+                // Note: We could accomplish somewhat higher resolution if we change
+                // the N predivider to odd values as well, and we may be able to get
+                // better spur performance by balancing the predivider and the
+                // divider.
+                const int n =
+                    boost::math::round((r * try_vco_freq) / (VCXO_PLL2_N * ref));
+
+                const double actual_mcr = (ref * VCXO_PLL2_N * n) / (vcodiv * r);
+                const double error = std::abs(actual_mcr - output_freq);
+                if (error < best_error) {
+                    best_error = error;
+                    best_mcr = actual_mcr;
+                    best_vco_freq = try_vco_freq;
+                    best_N = n;
+                    best_R = r;
+                }
+            }
+        }
+        UHD_ASSERT_THROW(best_mcr > 0.0);
+
+        _vco_freq = best_vco_freq;
+        _lmk04816_regs.PLL2_N_30 = best_N;
+        _lmk04816_regs.PLL2_R_28 = best_R;
+        _lmk04816_regs.PLL2_P_30 = lmk04816_regs_t::PLL2_P_30_DIV_2A;
+
+        if (fp_compare_epsilon<double>(best_error) > 0.0) {
+            UHD_LOGGER_WARNING("X300")
+            << boost::format("Attempted master clock rate %0.2f MHz, got %0.2f MHz")
+            % (output_freq / 1e6) % (best_mcr / 1e6);
+        }
+
+        UHD_LOGGER_TRACE("X300") << boost::format(
+            "Using automatic LMK04816 PLL2 config: N=%d, R=%d, VCO=%0.2f MHz, MCR=%0.2f MHz")
+            % _lmk04816_regs.PLL2_N_30 % _lmk04816_regs.PLL2_R_28
+            % (_vco_freq / 1e6) % (best_mcr / 1e6);
+
+        return best_mcr;
+    }
+
     void init() {
         /* The X3xx has two primary rates. The first is the
          * _system_ref_rate, which is sourced from the "clock_source"/"value" field
@@ -429,12 +497,14 @@ private:
                         m23_04M_184_32M_ZDEL,  // LTE with 23.04 MHz ref
                         m30_72M_184_32M_ZDEL,  // LTE with external ref, aka CPRI Mode
                         m10M_184_32M_NOZDEL,   // LTE with 10 MHz ref
-                        m10M_120M_ZDEL };       // NI USRP 120 MHz Clocking
+                        m10M_120M_ZDEL,        // NI USRP 120 MHz Clocking
+                        m10M_AUTO_NOZDEL };    // automatic for arbitrary clock from 10MHz ref
 
         /* The default clocking mode is 10MHz reference generating a 200 MHz master
          * clock, in zero-delay mode. */
         opmode_t clocking_mode = INVALID;
 
+        using namespace uhd::math::fp_compare;
         if (math::frequencies_are_equal(_system_ref_rate, 10e6)) {
             if (math::frequencies_are_equal(_master_clock_rate, 184.32e6)) {
                 /* 10MHz reference, 184.32 MHz master clock out, NOT Zero Delay. */
@@ -445,6 +515,15 @@ private:
             } else if (math::frequencies_are_equal(_master_clock_rate, 120e6)) {
                 /* 10MHz reference, 120 MHz master clock rate, Zero Delay */
                 clocking_mode = m10M_120M_ZDEL;
+            } else if (
+                    fp_compare_epsilon<double>(_master_clock_rate) >= uhd::usrp::x300::MIN_TICK_RATE
+                    && fp_compare_epsilon<double>(_master_clock_rate) <= uhd::usrp::x300::MAX_TICK_RATE
+                    ) {
+                /* 10MHz reference, attempt to automatically configure PLL
+                 * for arbitrary master clock rate, Zero Delay */
+                UHD_LOGGER_WARNING("X300")
+                    << "Using automatic master clock PLL config. This is an experimental feature.";
+                clocking_mode = m10M_AUTO_NOZDEL;
             } else {
                 throw uhd::runtime_error(str(
                     boost::format("Invalid master clock rate: %.2f MHz.\n"
@@ -654,6 +733,19 @@ private:
 
                 break;
 
+            case m10M_AUTO_NOZDEL:
+                _lmk04816_regs.MODE = lmk04816_regs_t::MODE_DUAL_INT;
+
+                // PLL1 - 2MHz compare frequency
+                _lmk04816_regs.PLL1_N_28 = 48;
+                _lmk04816_regs.PLL1_R_27 = 5;
+                _lmk04816_regs.PLL1_CP_GAIN_27 = lmk04816_regs_t::PLL1_CP_GAIN_27_100UA;
+
+                // PLL2 - this call will set _vco_freq and PLL2 P/N/R registers.
+                _master_clock_rate = autoset_pll2_config(_master_clock_rate);
+
+                break;
+
             default:
                 UHD_THROW_INVALID_CODE_PATH();
                 break;
@@ -790,7 +882,8 @@ private:
     const spi_iface::sptr   _spiface;
     const size_t            _slaveno;
     const size_t            _hw_rev;
-    const double            _master_clock_rate;
+    // This is technically constant, but it can be coerced during initialization
+    double                  _master_clock_rate;
     const double            _dboard_clock_rate;
     const double            _system_ref_rate;
     lmk04816_regs_t         _lmk04816_regs;
diff --git a/host/lib/usrp/x300/x300_defaults.hpp b/host/lib/usrp/x300/x300_defaults.hpp
index 1f5a52fec..363a90314 100644
--- a/host/lib/usrp/x300/x300_defaults.hpp
+++ b/host/lib/usrp/x300/x300_defaults.hpp
@@ -31,9 +31,10 @@ static constexpr size_t SRC_ADDR0  = 0;
 static constexpr size_t SRC_ADDR1  = 1;
 static constexpr size_t DST_ADDR   = 2;
 
-static constexpr double DEFAULT_TICK_RATE     = 200e6; // Hz
-static constexpr double BUS_CLOCK_RATE        = 187.5e6; //Hz
-static const std::vector<double> TICK_RATE_OPTIONS{184.32e6, 200e6};
+static constexpr double DEFAULT_TICK_RATE     = 200e6;    // Hz
+static constexpr double MAX_TICK_RATE         = 200e6;    // Hz
+static constexpr double MIN_TICK_RATE         = 187.5e6;  // Hz
+static constexpr double BUS_CLOCK_RATE        = 187.5e6;  // Hz
 
 static const std::string FW_FILE_NAME         = "usrp_x300_fw.bin";
 
diff --git a/host/lib/usrp/x300/x300_device_args.hpp b/host/lib/usrp/x300/x300_device_args.hpp
index 56b3c1078..db1a01212 100644
--- a/host/lib/usrp/x300/x300_device_args.hpp
+++ b/host/lib/usrp/x300/x300_device_args.hpp
@@ -8,6 +8,7 @@
 #define INCLUDED_X300_DEV_ARGS_HPP
 
 #include "x300_impl.hpp"
+#include "x300_defaults.hpp"
 #include <uhdlib/usrp/constrained_device_args.hpp>
 
 namespace uhd { namespace usrp { namespace x300 {
@@ -117,10 +118,9 @@ private:
             // Some daughterboards may require other rates, but this default
             // works best for all newer daughterboards (i.e. CBX, WBX, SBX,
             // UBX, and TwinRX).
-            if (_master_clock_rate.get() == 200e6) {
-                _dboard_clock_rate.set(50e6);
-            } else if (_master_clock_rate.get() == 184.32e6) {
-                _dboard_clock_rate.set(46.08e6);
+            if (_master_clock_rate.get() >= MIN_TICK_RATE &&
+                _master_clock_rate.get() <= MAX_TICK_RATE) {
+                _dboard_clock_rate.set(_master_clock_rate.get() / 4);
             } else {
                 throw uhd::value_error(
                     "Can't infer daughterboard clock rate. Specify "
@@ -157,7 +157,7 @@ private:
         }
 
         //Sanity check params
-        _enforce_discrete(_master_clock_rate, TICK_RATE_OPTIONS);
+        _enforce_range(_master_clock_rate, MIN_TICK_RATE, MAX_TICK_RATE);
         _enforce_discrete(_system_ref_rate, EXTERNAL_FREQ_OPTIONS);
         _enforce_discrete(_clock_source, CLOCK_SOURCE_OPTIONS);
         _enforce_discrete(_time_source, TIME_SOURCE_OPTIONS);
-- 
cgit v1.2.3