diff options
author | Derek Kozel <derek.kozel@ettus.com> | 2017-12-07 18:04:36 +0000 |
---|---|---|
committer | michael-west <michael.west@ettus.com> | 2017-12-18 10:11:46 -0800 |
commit | 893609252b1935fc6d27aedaaf653f4ae62cfed4 (patch) | |
tree | 45220aa98662aa09298c9ed8017afc2219a02efb /host | |
parent | b25312cb7192a60935e30e2c64fc02adb8cf6385 (diff) | |
download | uhd-893609252b1935fc6d27aedaaf653f4ae62cfed4.tar.gz uhd-893609252b1935fc6d27aedaaf653f4ae62cfed4.tar.bz2 uhd-893609252b1935fc6d27aedaaf653f4ae62cfed4.zip |
TwinRX: Added ADF5356 synth and TwinRX Rev C support
ADF5355 and ADF5356 support merged into adf535x class
Default register values moved into regmap
Reviewed-By: Martin Braun <martin.braun@ettus.com>
Reviewed-By: Ashish Chaudhari <ashish@ettus.com>
Reviewed-By: Mark Meserve <mark.meserve@ni.com>
Diffstat (limited to 'host')
-rw-r--r-- | host/lib/ic_reg_maps/CMakeLists.txt | 5 | ||||
-rwxr-xr-x | host/lib/ic_reg_maps/gen_adf5355_regs.py | 4 | ||||
-rwxr-xr-x | host/lib/ic_reg_maps/gen_adf5356_regs.py | 164 | ||||
-rw-r--r-- | host/lib/usrp/common/CMakeLists.txt | 2 | ||||
-rw-r--r-- | host/lib/usrp/common/adf5355.cpp | 354 | ||||
-rw-r--r-- | host/lib/usrp/common/adf5355.hpp | 58 | ||||
-rw-r--r-- | host/lib/usrp/common/adf535x.cpp | 17 | ||||
-rw-r--r-- | host/lib/usrp/common/adf535x.hpp | 477 | ||||
-rw-r--r-- | host/lib/usrp/dboard/db_twinrx.cpp | 22 | ||||
-rw-r--r-- | host/lib/usrp/dboard/twinrx/twinrx_ctrl.cpp | 77 | ||||
-rw-r--r-- | host/lib/usrp/dboard/twinrx/twinrx_ctrl.hpp | 8 | ||||
-rw-r--r-- | host/lib/usrp/dboard/twinrx/twinrx_ids.hpp | 16 |
12 files changed, 744 insertions, 460 deletions
diff --git a/host/lib/ic_reg_maps/CMakeLists.txt b/host/lib/ic_reg_maps/CMakeLists.txt index f8ccd9814..ad5835460 100644 --- a/host/lib/ic_reg_maps/CMakeLists.txt +++ b/host/lib/ic_reg_maps/CMakeLists.txt @@ -127,4 +127,9 @@ LIBUHD_PYTHON_GEN_SOURCE( ${CMAKE_CURRENT_BINARY_DIR}/adf5355_regs.hpp ) +LIBUHD_PYTHON_GEN_SOURCE( + ${CMAKE_CURRENT_SOURCE_DIR}/gen_adf5356_regs.py + ${CMAKE_CURRENT_BINARY_DIR}/adf5356_regs.hpp +) + SET(LIBUHD_PYTHON_GEN_SOURCE_DEPS) diff --git a/host/lib/ic_reg_maps/gen_adf5355_regs.py b/host/lib/ic_reg_maps/gen_adf5355_regs.py index a69f126cc..6a4f5d8a7 100755 --- a/host/lib/ic_reg_maps/gen_adf5355_regs.py +++ b/host/lib/ic_reg_maps/gen_adf5355_regs.py @@ -56,7 +56,7 @@ pd_polarity 4[7] 1 negative, positive mux_logic 4[8] 1 1_8V, 3_3V ref_mode 4[9] 0 single, diff <% current_setting_enums = ', '.join(map(lambda x: '_'.join(("%0.2fma"%(round(x*31.27 + 31.27)/100)).split('.')), range(0,16))) %>\ -charge_pump_current 4[10:13] 0 ${current_setting_enums} +charge_pump_current 4[10:13] 2 ${current_setting_enums} double_buff_div 4[14] 0 disabled, enabled r_counter_10_bit 4[15:24] 0 reference_divide_by_2 4[25] 0 disabled, enabled @@ -76,7 +76,7 @@ reg6_reserved0 6[7:9] 0x0 rf_out_b_enabled 6[10] 1 enabled, disabled mute_till_lock_detect 6[11] 0 mute_disabled, mute_enabled reg6_reserved1 6[12] 0 -cp_bleed_current 6[13:20] 0 +cp_bleed_current 6[13:20] 2 rf_divider_select 6[21:23] 0 div1, div2, div4, div8, div16, div32, div64 feedback_select 6[24] 0 divided, fundamental reg6_reserved2 6[25:28] 0xA diff --git a/host/lib/ic_reg_maps/gen_adf5356_regs.py b/host/lib/ic_reg_maps/gen_adf5356_regs.py new file mode 100755 index 000000000..d2076a807 --- /dev/null +++ b/host/lib/ic_reg_maps/gen_adf5356_regs.py @@ -0,0 +1,164 @@ +#!/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 +######################################################################## +int_16_bit 0[4:19] 0 +prescaler 0[20] 0 4_5, 8_9 +autocal_en 0[21] 1 disabled, enabled +reg0_reserved0 0[22:31] 0x000 +######################################################################## +## address 1 +######################################################################## +frac1_24_bit 1[4:27] 0 +reg1_reserved0 1[28:31] 0x0 +######################################################################## +## address 2 +######################################################################## +mod2_lsb 2[4:17] 0 +frac2_lsb 2[18:31] 0 +######################################################################## +## address 3 +######################################################################## +phase_24_bit 3[4:27] 0 +phase_adjust 3[28] 0 disabled, enabled +phase_resync 3[29] 0 disabled, enabled +sd_load_reset 3[30] 0 on_reg0_update, disabled +##reserved 3[31] 0 +######################################################################## +## address 4 +######################################################################## +counter_reset 4[4] 0 disabled, enabled +cp_three_state 4[5] 0 disabled, enabled +power_down 4[6] 0 disabled, enabled +pd_polarity 4[7] 1 negative, positive +mux_logic 4[8] 1 1_8V, 3_3V +ref_mode 4[9] 0 single, diff +charge_pump_current 4[10:13] 0 0_30ma, 0_60ma, 0_90ma, 1_20ma, 1_50ma, 1_80ma, 2_10ma, 2_40ma, 2_70ma, 3_00ma, 3_30ma, 3_60ma, 3_90ma, 4_20ma, 4_50ma, 4_80ma +double_buff_div 4[14] 0 disabled, enabled +r_counter_10_bit 4[15:24] 0 +reference_divide_by_2 4[25] 0 disabled, enabled +reference_doubler 4[26] 0 disabled, enabled +muxout 4[27:29] 1 3state, dvdd, dgnd, rdiv, ndiv, analog_ld, dld, reserved +reg4_reserved0 4[30:31] 0 +######################################################################## +## address 5 +######################################################################## +reg5_reserved0 5[4:31] 0x0080002 +######################################################################## +## address 6 +######################################################################## +output_power 6[4:5] 0 m4dbm, m1dbm, 2dbm, 5dbm +rf_out_a_enabled 6[6] 0 disabled, enabled +reg6_reserved0 6[7:9] 0x0 +rf_out_b_enabled 6[10] 1 enabled, disabled +mute_till_lock_detect 6[11] 0 mute_disabled, mute_enabled +reg6_reserved1 6[12] 0 +cp_bleed_current 6[13:20] 0 +rf_divider_select 6[21:23] 0 div1, div2, div4, div8, div16, div32, div64 +feedback_select 6[24] 0 divided, fundamental +reg6_reserved2 6[25:28] 0xA +negative_bleed 6[29] 0 disabled, enabled +gated_bleed 6[30] 0 disabled, enabled +bleed_polarity 6[31] 0 negative, positive +######################################################################## +## address 7 +######################################################################## +ld_mode 7[4] 0 frac_n, int_n +frac_n_ld_precision 7[5:6] 0 5ns, 6ns, 8ns, 12ns +loss_of_lock_mode 7[7] 0 disabled, enabled +ld_cyc_count 7[8:9] 0 1024, 2048, 4096, 8192 +reg7_reserved0 7[10:24] 0x0 +le_sync 7[25] 1 disabled, le_synced_to_refin +reg7_reserved1 7[26] 1 +le_sel_sync_edge 7[27] 0 falling_edge, rising_edge +reg7_reserved2 7[28:31] 0x0 +######################################################################## +## address 8 +######################################################################## +reg8_reserved0 8[4:31] 0x5559656 +######################################################################## +## address 9 +######################################################################## +synth_lock_timeout 9[4:8] 0 +auto_level_timeout 9[9:13] 0 +timeout 9[14:23] 0 +vco_band_div 9[24:31] 0 +######################################################################## +## address 10 +######################################################################## +adc_enable 10[4] 0 disabled, enabled +adc_conversion 10[5] 0 disabled, enabled +adc_clock_divider 10[6:13] 1 +reg10_reserved0 10[14:31] 0x300 +######################################################################## +## address 11 +######################################################################## +reg11_reserved0 11[4:23] 0x0061200 +vco_band_hold 11[24] 0 normal, hold +reg11_reserved1 11[25:31] 0 +######################################################################## +## address 12 +######################################################################## +reg12_reserved0 12[4:15] 0x5f +phase_resync_clk_div 12[16:31] 1 +######################################################################## +## address 13 +######################################################################## +mod2_msb 13[4:17] 0 +frac2_msb 13[18:31] 0 +""" + +######################################################################## +# Template for methods in the body of the struct +######################################################################## +BODY_TMPL="""\ +enum addr_t{ + ADDR_R0 = 0, + ADDR_R1 = 1, + ADDR_R2 = 2, + ADDR_R3 = 3, + ADDR_R4 = 4, + ADDR_R5 = 5, + ADDR_R6 = 6, + ADDR_R7 = 7, + ADDR_R8 = 8, + ADDR_R9 = 9, + ADDR_R10 = 10, + ADDR_R11 = 11, + ADDR_R12 = 12, + ADDR_R13 = 13 +}; + +uint32_t get_reg(uint8_t addr){ + uint32_t reg = addr & 0xF; + switch(addr){ + % for addr in range(13+1): + case ${addr}: + % for reg in filter(lambda r: r.get_addr() == addr, regs): + reg |= (uint32_t(${reg.get_name()}) & ${reg.get_mask()}) << ${reg.get_shift()}; + % endfor + break; + % endfor + } + return reg; +} +""" + +if __name__ == '__main__': + import common; common.generate( + name='adf5356_regs', + regs_tmpl=REGS_TMPL, + body_tmpl=BODY_TMPL, + file=__file__, + ) diff --git a/host/lib/usrp/common/CMakeLists.txt b/host/lib/usrp/common/CMakeLists.txt index 9f4cf09b5..4c240d425 100644 --- a/host/lib/usrp/common/CMakeLists.txt +++ b/host/lib/usrp/common/CMakeLists.txt @@ -30,7 +30,7 @@ INCLUDE_DIRECTORIES("${CMAKE_CURRENT_SOURCE_DIR}/ad9361_driver") LIBUHD_APPEND_SOURCES( ${CMAKE_CURRENT_SOURCE_DIR}/adf4001_ctrl.cpp ${CMAKE_CURRENT_SOURCE_DIR}/adf435x.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/adf5355.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/adf535x.cpp ${CMAKE_CURRENT_SOURCE_DIR}/ad9361_ctrl.cpp ${CMAKE_CURRENT_SOURCE_DIR}/ad936x_manager.cpp ${CMAKE_CURRENT_SOURCE_DIR}/ad9361_driver/ad9361_device.cpp diff --git a/host/lib/usrp/common/adf5355.cpp b/host/lib/usrp/common/adf5355.cpp deleted file mode 100644 index 36d41a5c3..000000000 --- a/host/lib/usrp/common/adf5355.cpp +++ /dev/null @@ -1,354 +0,0 @@ -// -// Copyright 2013-2014 Ettus Research LLC -// -// 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 "adf5355.hpp" -#include "adf5355_regs.hpp" -#include <uhd/utils/math.hpp> -#include <boost/math/common_factor_rt.hpp> //gcd -#include <boost/thread.hpp> - -using namespace uhd; - -template<typename data_t> -data_t clamp(data_t val, data_t min, data_t max) { - return (val < min) ? min : ((val > max) ? max : val); -} - -template<typename data_t> -double todbl(data_t val) { - return static_cast<double>(val); -} - -static const double ADF5355_DOUBLER_MAX_REF_FREQ = 60e6; -//static const double ADF5355_MAX_FREQ_PFD = 125e6; -//static const double ADF5355_PRESCALER_THRESH = 7e9; - -static const double ADF5355_MIN_VCO_FREQ = 3.4e9; -//static const double ADF5355_MAX_VCO_FREQ = 6.8e9; -static const double ADF5355_MAX_OUT_FREQ = 6.8e9; -static const double ADF5355_MIN_OUT_FREQ = (3.4e9 / 64); -//static const double ADF5355_MAX_OUTB_FREQ = (6.8e9 * 2); -//static const double ADF5355_MIN_OUTB_FREQ = (3.4e9 * 2); - -static const double ADF5355_PHASE_RESYNC_TIME = 400e-6; - -static const uint32_t ADF5355_MOD1 = 16777216; -static const uint32_t ADF5355_MAX_MOD2 = 16383; -static const uint32_t ADF5355_MAX_FRAC2 = 16383; -//static const uint16_t ADF5355_MIN_INT_PRESCALER_89 = 75; - -class adf5355_impl : public adf5355_iface -{ -public: - adf5355_impl(write_fn_t write_fn) : - _write_fn(write_fn), - _regs(), - _rewrite_regs(true), - _wait_time_us(0), - _ref_freq(0.0), - _pfd_freq(0.0), - _fb_after_divider(false) - { - - // TODO This is a hardware specific value, but can be made the default in the ic_reg_map - _regs.charge_pump_current = adf5355_regs_t::CHARGE_PUMP_CURRENT_0_94MA; - - // TODO cleanup these magic numbers - _regs.cp_bleed_current = 2; - _regs.r_counter_10_bit = 8; - - _regs.vco_band_div = 3; - _regs.timeout = 11; - _regs.auto_level_timeout = 30; - _regs.synth_lock_timeout = 12; - - _regs.adc_clock_divider = 16; - _regs.adc_conversion = adf5355_regs_t::ADC_CONVERSION_ENABLED; - _regs.adc_enable = adf5355_regs_t::ADC_ENABLE_ENABLED; - - - // TODO Needs to be enabled for phase resync - _regs.phase_resync = adf5355_regs_t::PHASE_RESYNC_DISABLED; - - // TODO Default should be divided, but there seems to be a bug preventing that. Needs rechecking - _regs.feedback_select = adf5355_regs_t::FEEDBACK_SELECT_FUNDAMENTAL; - - // TODO 0 is an invalid value for this field. Setting to 1 seemed to break phase sync, needs retesting. - _regs.phase_resync_clk_div = 0; - } - - ~adf5355_impl() - { - _regs.power_down = adf5355_regs_t::POWER_DOWN_ENABLED; - commit(); - } - - void set_feedback_select(feedback_sel_t fb_sel) - { - _fb_after_divider = (fb_sel == FB_SEL_DIVIDED); - - if (_fb_after_divider) { - _regs.feedback_select = adf5355_regs_t::FEEDBACK_SELECT_DIVIDED; - } else { - _regs.feedback_select = adf5355_regs_t::FEEDBACK_SELECT_FUNDAMENTAL; - } - } - - void set_reference_freq(double fref, bool force = false) - { - //Skip the body if the reference frequency does not change - if (uhd::math::frequencies_are_equal(fref, _ref_freq) and (not force)) - return; - - _ref_freq = fref; - - //----------------------------------------------------------- - //Set reference settings - - //Reference doubler for 50% duty cycle - bool doubler_en = (_ref_freq <= ADF5355_DOUBLER_MAX_REF_FREQ); - - /* Calculate and maximize PFD frequency */ - // TODO Target PFD should be configurable - /* TwinRX requires PFD of 6.25 MHz or less */ - const double TWINRX_PFD_FREQ = 6.25e6; - _pfd_freq = TWINRX_PFD_FREQ; - - int ref_div_factor = 16; - - //Reference divide-by-2 for 50% duty cycle - // if R even, move one divide by 2 to to regs.reference_divide_by_2 - bool div2_en = (ref_div_factor % 2 == 0); - if (div2_en) { - ref_div_factor /= 2; - } - - _regs.reference_divide_by_2 = div2_en ? - adf5355_regs_t::REFERENCE_DIVIDE_BY_2_ENABLED : - adf5355_regs_t::REFERENCE_DIVIDE_BY_2_DISABLED; - _regs.reference_doubler = doubler_en ? - adf5355_regs_t::REFERENCE_DOUBLER_ENABLED : - adf5355_regs_t::REFERENCE_DOUBLER_DISABLED; - _regs.r_counter_10_bit = ref_div_factor; - UHD_ASSERT_THROW((_regs.r_counter_10_bit & ((uint16_t)~0x3FF)) == 0); - - //----------------------------------------------------------- - //Set timeouts (code from ADI driver) - _regs.timeout = clamp<uint16_t>( - static_cast<uint16_t>(ceil(_pfd_freq / (20e3 * 30))), 1, 1023); - UHD_ASSERT_THROW((_regs.timeout & ((uint16_t)~0x3FF)) == 0); - _regs.synth_lock_timeout = - static_cast<uint8_t>(ceil((_pfd_freq * 2) / (100e3 * _regs.timeout))); - UHD_ASSERT_THROW((_regs.synth_lock_timeout & ((uint16_t)~0x1F)) == 0); - _regs.auto_level_timeout = - static_cast<uint8_t>(ceil((_pfd_freq * 5) / (100e3 * _regs.timeout))); - - //----------------------------------------------------------- - //Set VCO band divider - _regs.vco_band_div = - static_cast<uint8_t>(ceil(_pfd_freq / 2.4e6)); - - //----------------------------------------------------------- - //Set ADC delay (code from ADI driver) - _regs.adc_enable = adf5355_regs_t::ADC_ENABLE_ENABLED; - _regs.adc_conversion = adf5355_regs_t::ADC_CONVERSION_ENABLED; - _regs.adc_clock_divider = clamp<uint8_t>( - static_cast<uint8_t>(ceil(((_pfd_freq / 100e3) - 2) / 4)), 1, 255); - _wait_time_us = static_cast<uint32_t>( - ceil(16e6 / (_pfd_freq / ((4 * _regs.adc_clock_divider) + 2)))); - - //----------------------------------------------------------- - //Phase resync - // TODO Renable here, in initialization, or through separate set_phase_resync(bool enable) function - _regs.phase_resync = adf5355_regs_t::PHASE_RESYNC_DISABLED; - - _regs.phase_adjust = adf5355_regs_t::PHASE_ADJUST_DISABLED; - _regs.sd_load_reset = adf5355_regs_t::SD_LOAD_RESET_ON_REG0_UPDATE; - _regs.phase_resync_clk_div = static_cast<uint16_t>( - floor(ADF5355_PHASE_RESYNC_TIME * _pfd_freq)); - - _rewrite_regs = true; - } - - void set_output_power(output_power_t power) - { - adf5355_regs_t::output_power_t setting; - switch (power) { - case OUTPUT_POWER_M4DBM: setting = adf5355_regs_t::OUTPUT_POWER_M4DBM; break; - case OUTPUT_POWER_M1DBM: setting = adf5355_regs_t::OUTPUT_POWER_M1DBM; break; - case OUTPUT_POWER_2DBM: setting = adf5355_regs_t::OUTPUT_POWER_2DBM; break; - case OUTPUT_POWER_5DBM: setting = adf5355_regs_t::OUTPUT_POWER_5DBM; break; - default: UHD_THROW_INVALID_CODE_PATH(); - } - if (_regs.output_power != setting) _rewrite_regs = true; - _regs.output_power = setting; - } - - void set_output_enable(output_t output, bool enable) { - - switch (output) { - case RF_OUTPUT_A: _regs.rf_out_a_enabled = enable ? adf5355_regs_t::RF_OUT_A_ENABLED_ENABLED : - adf5355_regs_t::RF_OUT_A_ENABLED_DISABLED; - break; - case RF_OUTPUT_B: _regs.rf_out_b_enabled = enable ? adf5355_regs_t::RF_OUT_B_ENABLED_ENABLED : - adf5355_regs_t::RF_OUT_B_ENABLED_DISABLED; - break; - } - } - - void set_muxout_mode(muxout_t mode) - { - switch (mode) { - case MUXOUT_3STATE: _regs.muxout = adf5355_regs_t::MUXOUT_3STATE; break; - case MUXOUT_DVDD: _regs.muxout = adf5355_regs_t::MUXOUT_DVDD; break; - case MUXOUT_DGND: _regs.muxout = adf5355_regs_t::MUXOUT_DGND; break; - case MUXOUT_RDIV: _regs.muxout = adf5355_regs_t::MUXOUT_RDIV; break; - case MUXOUT_NDIV: _regs.muxout = adf5355_regs_t::MUXOUT_NDIV; break; - case MUXOUT_ALD: _regs.muxout = adf5355_regs_t::MUXOUT_ANALOG_LD; break; - case MUXOUT_DLD: _regs.muxout = adf5355_regs_t::MUXOUT_DLD; break; - default: UHD_THROW_INVALID_CODE_PATH(); - } - } - - double set_frequency(double target_freq, double freq_resolution, bool flush = false) - { - if (target_freq > ADF5355_MAX_OUT_FREQ or target_freq < ADF5355_MIN_OUT_FREQ) { - throw uhd::runtime_error("requested frequency out of range."); - } - if ((uint32_t) freq_resolution == 0) { - throw uhd::runtime_error("requested resolution cannot be less than 1."); - } - - /* Calculate target VCOout frequency */ - //Increase RF divider until acceptable VCO frequency - double target_vco_freq = target_freq; - uint32_t rf_divider = 1; - while (target_vco_freq < ADF5355_MIN_VCO_FREQ && rf_divider < 64) { - target_vco_freq *= 2; - rf_divider *= 2; - } - - switch (rf_divider) { - case 1: _regs.rf_divider_select = adf5355_regs_t::RF_DIVIDER_SELECT_DIV1; break; - case 2: _regs.rf_divider_select = adf5355_regs_t::RF_DIVIDER_SELECT_DIV2; break; - case 4: _regs.rf_divider_select = adf5355_regs_t::RF_DIVIDER_SELECT_DIV4; break; - case 8: _regs.rf_divider_select = adf5355_regs_t::RF_DIVIDER_SELECT_DIV8; break; - case 16: _regs.rf_divider_select = adf5355_regs_t::RF_DIVIDER_SELECT_DIV16; break; - case 32: _regs.rf_divider_select = adf5355_regs_t::RF_DIVIDER_SELECT_DIV32; break; - case 64: _regs.rf_divider_select = adf5355_regs_t::RF_DIVIDER_SELECT_DIV64; break; - default: UHD_THROW_INVALID_CODE_PATH(); - } - - //Compute fractional PLL params - double prescaler_input_freq = target_vco_freq; - if (_fb_after_divider) { - prescaler_input_freq /= rf_divider; - } - - double N = prescaler_input_freq / _pfd_freq; - uint16_t INT = static_cast<uint16_t>(floor(N)); - uint32_t FRAC1 = static_cast<uint32_t>(floor((N - INT) * ADF5355_MOD1)); - double residue = (N - INT) * ADF5355_MOD1 - FRAC1; - - double gcd = boost::math::gcd(static_cast<int>(_pfd_freq), static_cast<int>(freq_resolution)); - uint16_t MOD2 = static_cast<uint16_t>(std::min(floor(_pfd_freq / gcd), static_cast<double>(ADF5355_MAX_MOD2))); - uint16_t FRAC2 = static_cast<uint16_t>(std::min(ceil(residue * MOD2), static_cast<double>(ADF5355_MAX_FRAC2))); - - double coerced_vco_freq = _pfd_freq * ( - todbl(INT) + ( - (todbl(FRAC1) + - (todbl(FRAC2) / todbl(MOD2))) - / todbl(ADF5355_MOD1) - ) - ); - - double coerced_out_freq = coerced_vco_freq / rf_divider; - - /* Update registers */ - _regs.int_16_bit = INT; - _regs.frac1_24_bit = FRAC1; - _regs.frac2_14_bit = FRAC2; - _regs.mod2_14_bit = MOD2; - _regs.phase_24_bit = 0; - -/* - if (_regs.int_16_bit >= ADF5355_MIN_INT_PRESCALER_89) { - _regs.prescaler = adf5355_regs_t::PRESCALER_8_9; - } else { - _regs.prescaler = adf5355_regs_t::PRESCALER_4_5; - } - - // ADI: Tests have shown that the optimal bleed set is the following: - // 4/N < IBLEED/ICP < 10/N */ -/* - uint32_t cp_curr_ua = - (static_cast<uint32_t>(_regs.charge_pump_current) + 1) * 315; - _regs.cp_bleed_current = clamp<uint8_t>( - ceil((todbl(400)*cp_curr_ua) / (_regs.int_16_bit*375)), 1, 255); - _regs.negative_bleed = adf5355_regs_t::NEGATIVE_BLEED_ENABLED; - _regs.gated_bleed = adf5355_regs_t::GATED_BLEED_DISABLED; -*/ - - if (flush) commit(); - return coerced_out_freq; - } - - void commit() - { - if (_rewrite_regs) { - //For a full state sync write registers in reverse order 12 - 0 - addr_vtr_t regs; - for (int addr = 12; addr >= 0; addr--) { - regs.push_back(_regs.get_reg(uint32_t(addr))); - } - _write_fn(regs); - _rewrite_regs = false; - - } else { - //Frequency update sequence from data sheet - static const size_t ONE_REG = 1; - _write_fn(addr_vtr_t(ONE_REG, _regs.get_reg(6))); - _regs.counter_reset = adf5355_regs_t::COUNTER_RESET_ENABLED; - _write_fn(addr_vtr_t(ONE_REG, _regs.get_reg(4))); - _write_fn(addr_vtr_t(ONE_REG, _regs.get_reg(2))); - _write_fn(addr_vtr_t(ONE_REG, _regs.get_reg(1))); - _regs.autocal_en = adf5355_regs_t::AUTOCAL_EN_DISABLED; - _write_fn(addr_vtr_t(ONE_REG, _regs.get_reg(0))); - _regs.counter_reset = adf5355_regs_t::COUNTER_RESET_DISABLED; - _write_fn(addr_vtr_t(ONE_REG, _regs.get_reg(4))); - boost::this_thread::sleep(boost::posix_time::microsec(_wait_time_us)); - _regs.autocal_en = adf5355_regs_t::AUTOCAL_EN_ENABLED; - _write_fn(addr_vtr_t(ONE_REG, _regs.get_reg(0))); - } - } - -private: //Members - typedef std::vector<uint32_t> addr_vtr_t; - - write_fn_t _write_fn; - adf5355_regs_t _regs; - bool _rewrite_regs; - uint32_t _wait_time_us; - double _ref_freq; - double _pfd_freq; - double _fb_after_divider; -}; - -adf5355_iface::sptr adf5355_iface::make(write_fn_t write) -{ - return sptr(new adf5355_impl(write)); -} diff --git a/host/lib/usrp/common/adf5355.hpp b/host/lib/usrp/common/adf5355.hpp deleted file mode 100644 index 55294cda1..000000000 --- a/host/lib/usrp/common/adf5355.hpp +++ /dev/null @@ -1,58 +0,0 @@ -// -// Copyright 2015 Ettus Research LLC -// -// 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/>. -// - -#ifndef INCLUDED_ADF5355_HPP -#define INCLUDED_ADF5355_HPP - -#include <boost/function.hpp> -#include <vector> -#include <stdint.h> - -class adf5355_iface -{ -public: - typedef boost::shared_ptr<adf5355_iface> sptr; - typedef boost::function<void(std::vector<uint32_t>)> write_fn_t; - - static sptr make(write_fn_t write); - - virtual ~adf5355_iface() {} - - enum output_t { RF_OUTPUT_A, RF_OUTPUT_B }; - - enum feedback_sel_t { FB_SEL_FUNDAMENTAL, FB_SEL_DIVIDED }; - - enum output_power_t { OUTPUT_POWER_M4DBM, OUTPUT_POWER_M1DBM, OUTPUT_POWER_2DBM, OUTPUT_POWER_5DBM }; - - enum muxout_t { MUXOUT_3STATE, MUXOUT_DVDD, MUXOUT_DGND, MUXOUT_RDIV, MUXOUT_NDIV, MUXOUT_ALD, MUXOUT_DLD }; - - virtual void set_reference_freq(double fref, bool force = false) = 0; - - virtual void set_feedback_select(feedback_sel_t fb_sel) = 0; - - virtual void set_output_power(output_power_t power) = 0; - - virtual void set_output_enable(output_t output, bool enable) = 0; - - virtual void set_muxout_mode(muxout_t mode) = 0; - - virtual double set_frequency(double target_freq, double freq_resolution, bool flush = false) = 0; - - virtual void commit(void) = 0; -}; - -#endif // INCLUDED_ADF5355_HPP diff --git a/host/lib/usrp/common/adf535x.cpp b/host/lib/usrp/common/adf535x.cpp new file mode 100644 index 000000000..1cd802b41 --- /dev/null +++ b/host/lib/usrp/common/adf535x.cpp @@ -0,0 +1,17 @@ +// +// Copyright 2013-2014, 2017 Ettus Research, A National Instruments Company +// +// SPDX-License-Identifier: GPL-3.0 +// + +#include "adf535x.hpp" + +adf535x_iface::sptr adf535x_iface::make_adf5355(write_fn_t write) +{ + return sptr(new adf535x_impl<adf5355_regs_t>(write)); +} + +adf535x_iface::sptr adf535x_iface::make_adf5356(write_fn_t write) +{ + return sptr(new adf535x_impl<adf5356_regs_t>(write)); +}
\ No newline at end of file diff --git a/host/lib/usrp/common/adf535x.hpp b/host/lib/usrp/common/adf535x.hpp new file mode 100644 index 000000000..8ef8a7f9e --- /dev/null +++ b/host/lib/usrp/common/adf535x.hpp @@ -0,0 +1,477 @@ +// +// Copyright 2015, 2017 Ettus Research, A National Instruments Company +// +// SPDX-License-Identifier: GPL-3.0 +// + +#ifndef INCLUDED_ADF535X_HPP +#define INCLUDED_ADF535X_HPP + +#include "adf5355_regs.hpp" +#include "adf5356_regs.hpp" +#include <uhd/utils/math.hpp> +#include <uhd/utils/log.hpp> +#include <boost/function.hpp> +#include <boost/math/common_factor_rt.hpp> //gcd +#include <utility> +#include <vector> +#include <algorithm> +#include <stdint.h> +#include <boost/format.hpp> +#include <uhd/utils/safe_call.hpp> + +class adf535x_iface +{ +public: + typedef boost::shared_ptr<adf535x_iface> sptr; + typedef boost::function<void(std::vector<uint32_t>)> write_fn_t; + + static sptr make_adf5355(write_fn_t write); + static sptr make_adf5356(write_fn_t write); + + virtual ~adf535x_iface() {}; + + enum output_t { RF_OUTPUT_A, RF_OUTPUT_B }; + + enum feedback_sel_t { FB_SEL_FUNDAMENTAL, FB_SEL_DIVIDED }; + + enum output_power_t { OUTPUT_POWER_M4DBM, OUTPUT_POWER_M1DBM, OUTPUT_POWER_2DBM, OUTPUT_POWER_5DBM }; + + enum muxout_t { MUXOUT_3STATE, MUXOUT_DVDD, MUXOUT_DGND, MUXOUT_RDIV, MUXOUT_NDIV, MUXOUT_ALD, MUXOUT_DLD }; + + virtual void set_reference_freq(double fref, bool force = false) = 0; + + virtual void set_pfd_freq(double pfd_freq) = 0; + + virtual void set_feedback_select(feedback_sel_t fb_sel) = 0; + + virtual void set_output_power(output_power_t power) = 0; + + virtual void set_output_enable(output_t output, bool enable) = 0; + + virtual void set_muxout_mode(muxout_t mode) = 0; + + virtual double set_frequency(double target_freq, double freq_resolution, bool flush = false) = 0; + + virtual void commit() = 0; +}; + +using namespace uhd; + +namespace { + const double ADF535X_DOUBLER_MAX_REF_FREQ = 60e6; + const double ADF535X_MAX_FREQ_PFD = 125e6; +//const double ADF535X_PRESCALER_THRESH = 7e9; + + const double ADF535X_MIN_VCO_FREQ = 3.4e9; +//const double ADF535X_MAX_VCO_FREQ = 6.8e9; + const double ADF535X_MAX_OUT_FREQ = 6.8e9; + const double ADF535X_MIN_OUT_FREQ = (3.4e9 / 64); +//const double ADF535X_MAX_OUTB_FREQ = (6.8e9 * 2); +//const double ADF535X_MIN_OUTB_FREQ = (3.4e9 * 2); + + const double ADF535X_PHASE_RESYNC_TIME = 400e-6; + + const uint32_t ADF535X_MOD1 = 16777216; + const uint32_t ADF535X_MAX_MOD2 = 16383; + const uint32_t ADF535X_MAX_FRAC2 = 16383; +//const uint16_t ADF535X_MIN_INT_PRESCALER_89 = 75; +} + +template <typename adf535x_regs_t> +class adf535x_impl : public adf535x_iface +{ +public: + explicit adf535x_impl(write_fn_t write_fn) : + _write_fn(write_fn), + _regs(), + _rewrite_regs(true), + _wait_time_us(0), + _ref_freq(0.0), + _pfd_freq(0.0), + _fb_after_divider(false) + { + + _regs.vco_band_div = 3; + _regs.timeout = 11; + _regs.auto_level_timeout = 30; + _regs.synth_lock_timeout = 12; + + _regs.adc_clock_divider = 16; + _regs.adc_conversion = adf535x_regs_t::ADC_CONVERSION_ENABLED; + _regs.adc_enable = adf535x_regs_t::ADC_ENABLE_ENABLED; + + // TODO Needs to be enabled for phase resync + _regs.phase_resync = adf535x_regs_t::PHASE_RESYNC_DISABLED; + + // TODO Default should be divided, but there seems to be a bug preventing that. Needs rechecking + _regs.feedback_select = adf535x_regs_t::FEEDBACK_SELECT_FUNDAMENTAL; + + // TODO 0 is an invalid value for this field. Setting to 1 seemed to break phase sync, needs retesting. + _regs.phase_resync_clk_div = 0; + } + + ~adf535x_impl() + { + UHD_SAFE_CALL( + _regs.power_down = adf535x_regs_t::POWER_DOWN_ENABLED; + commit(); + ) + } + + void set_feedback_select(const feedback_sel_t fb_sel) + { + _fb_after_divider = (fb_sel == FB_SEL_DIVIDED); + + if (_fb_after_divider) { + _regs.feedback_select = adf535x_regs_t::FEEDBACK_SELECT_DIVIDED; + } else { + _regs.feedback_select = adf535x_regs_t::FEEDBACK_SELECT_FUNDAMENTAL; + } + } + + void set_pfd_freq(const double pfd_freq) + { + if (pfd_freq > ADF535X_MAX_FREQ_PFD) { + UHD_MSG(error) << boost::format("ADF535x: %f MHz is above the maximum PFD frequency of %f MHz\n") + % (pfd_freq/1e6) % (ADF535X_MAX_FREQ_PFD/1e6); + return; + } + _pfd_freq = pfd_freq; + + set_reference_freq(_ref_freq); + } + + void set_reference_freq(const double fref, const bool force = false) + { + //Skip the body if the reference frequency does not change + if (uhd::math::frequencies_are_equal(fref, _ref_freq) and (not force)) + return; + + _ref_freq = fref; + + //----------------------------------------------------------- + //Set reference settings + int ref_div_factor = static_cast<int>(std::floor(_ref_freq / _pfd_freq)); + + //Reference doubler for 50% duty cycle + const bool doubler_en = (_ref_freq <= ADF535X_DOUBLER_MAX_REF_FREQ); + if (doubler_en) { + ref_div_factor *= 2; + } + + //Reference divide-by-2 for 50% duty cycle + // if R even, move one divide by 2 to to regs.reference_divide_by_2 + const bool div2_en = (ref_div_factor % 2 == 0); + if (div2_en) { + ref_div_factor /= 2; + } + + _regs.reference_divide_by_2 = div2_en ? + adf535x_regs_t::REFERENCE_DIVIDE_BY_2_ENABLED : + adf535x_regs_t::REFERENCE_DIVIDE_BY_2_DISABLED; + _regs.reference_doubler = doubler_en ? + adf535x_regs_t::REFERENCE_DOUBLER_ENABLED : + adf535x_regs_t::REFERENCE_DOUBLER_DISABLED; + _regs.r_counter_10_bit = ref_div_factor; + UHD_ASSERT_THROW((_regs.r_counter_10_bit & ((uint16_t)~0x3FF)) == 0); + + //----------------------------------------------------------- + //Set timeouts (code from ADI driver) + _regs.timeout = std::max(1, std::min(int(ceil(_pfd_freq / (20e3 * 30))), 1023)); + + UHD_ASSERT_THROW((_regs.timeout & ((uint16_t)~0x3FF)) == 0); + _regs.synth_lock_timeout = + static_cast<uint8_t>(ceil((_pfd_freq * 2) / (100e3 * _regs.timeout))); + UHD_ASSERT_THROW((_regs.synth_lock_timeout & ((uint16_t)~0x1F)) == 0); + _regs.auto_level_timeout = + static_cast<uint8_t>(ceil((_pfd_freq * 5) / (100e3 * _regs.timeout))); + + //----------------------------------------------------------- + //Set VCO band divider + _regs.vco_band_div = + static_cast<uint8_t>(ceil(_pfd_freq / 2.4e6)); + + //----------------------------------------------------------- + //Set ADC delay (code from ADI driver) + _regs.adc_enable = adf535x_regs_t::ADC_ENABLE_ENABLED; + _regs.adc_conversion = adf535x_regs_t::ADC_CONVERSION_ENABLED; + _regs.adc_clock_divider = std::max(1, std::min(int(ceil(((_pfd_freq / 100e3) - 2) / 4)),255)); + + _wait_time_us = static_cast<uint32_t>( + ceil(16e6 / (_pfd_freq / ((4 * _regs.adc_clock_divider) + 2)))); + + //----------------------------------------------------------- + //Phase resync + // TODO Renable here, in initialization, or through separate set_phase_resync(bool enable) function + _regs.phase_resync = adf535x_regs_t::PHASE_RESYNC_DISABLED; + + _regs.phase_adjust = adf535x_regs_t::PHASE_ADJUST_DISABLED; + _regs.sd_load_reset = adf535x_regs_t::SD_LOAD_RESET_ON_REG0_UPDATE; + _regs.phase_resync_clk_div = static_cast<uint16_t>( + floor(ADF535X_PHASE_RESYNC_TIME * _pfd_freq)); + + _rewrite_regs = true; + } + + double set_frequency(const double target_freq, const double freq_resolution, const bool flush = false) + { + return _set_frequency(target_freq, freq_resolution, flush); + } + + void set_output_power(const output_power_t power) + { + typename adf535x_regs_t::output_power_t setting; + switch (power) { + case OUTPUT_POWER_M4DBM: setting = adf535x_regs_t::OUTPUT_POWER_M4DBM; break; + case OUTPUT_POWER_M1DBM: setting = adf535x_regs_t::OUTPUT_POWER_M1DBM; break; + case OUTPUT_POWER_2DBM: setting = adf535x_regs_t::OUTPUT_POWER_2DBM; break; + case OUTPUT_POWER_5DBM: setting = adf535x_regs_t::OUTPUT_POWER_5DBM; break; + default: UHD_THROW_INVALID_CODE_PATH(); + } + if (_regs.output_power != setting) _rewrite_regs = true; + _regs.output_power = setting; + } + + void set_output_enable(const output_t output, const bool enable) + { + switch (output) { + case RF_OUTPUT_A: _regs.rf_out_a_enabled = enable ? adf535x_regs_t::RF_OUT_A_ENABLED_ENABLED : + adf535x_regs_t::RF_OUT_A_ENABLED_DISABLED; + break; + case RF_OUTPUT_B: _regs.rf_out_b_enabled = enable ? adf535x_regs_t::RF_OUT_B_ENABLED_ENABLED : + adf535x_regs_t::RF_OUT_B_ENABLED_DISABLED; + break; + } + } + + void set_muxout_mode(const muxout_t mode) + { + switch (mode) { + case MUXOUT_3STATE: _regs.muxout = adf535x_regs_t::MUXOUT_3STATE; break; + case MUXOUT_DVDD: _regs.muxout = adf535x_regs_t::MUXOUT_DVDD; break; + case MUXOUT_DGND: _regs.muxout = adf535x_regs_t::MUXOUT_DGND; break; + case MUXOUT_RDIV: _regs.muxout = adf535x_regs_t::MUXOUT_RDIV; break; + case MUXOUT_NDIV: _regs.muxout = adf535x_regs_t::MUXOUT_NDIV; break; + case MUXOUT_ALD: _regs.muxout = adf535x_regs_t::MUXOUT_ANALOG_LD; break; + case MUXOUT_DLD: _regs.muxout = adf535x_regs_t::MUXOUT_DLD; break; + default: UHD_THROW_INVALID_CODE_PATH(); + } + } + + void commit() + { + _commit(); + } + +protected: + double _set_frequency(double, double, bool); + void _commit(); + +private: //Members + typedef std::vector<uint32_t> addr_vtr_t; + + write_fn_t _write_fn; + adf535x_regs_t _regs; + bool _rewrite_regs; + uint32_t _wait_time_us; + double _ref_freq; + double _pfd_freq; + double _fb_after_divider; +}; + +// ADF5355 Functions +template <> +inline double adf535x_impl<adf5355_regs_t>::_set_frequency(double target_freq, double freq_resolution, bool flush) +{ + if (target_freq > ADF535X_MAX_OUT_FREQ or target_freq < ADF535X_MIN_OUT_FREQ) { + throw uhd::runtime_error("requested frequency out of range."); + } + if ((uint32_t) freq_resolution == 0) { + throw uhd::runtime_error("requested resolution cannot be less than 1."); + } + + /* Calculate target VCOout frequency */ + //Increase RF divider until acceptable VCO frequency + double target_vco_freq = target_freq; + uint32_t rf_divider = 1; + while (target_vco_freq < ADF535X_MIN_VCO_FREQ && rf_divider < 64) { + target_vco_freq *= 2; + rf_divider *= 2; + } + + switch (rf_divider) { + case 1: _regs.rf_divider_select = adf5355_regs_t::RF_DIVIDER_SELECT_DIV1; break; + case 2: _regs.rf_divider_select = adf5355_regs_t::RF_DIVIDER_SELECT_DIV2; break; + case 4: _regs.rf_divider_select = adf5355_regs_t::RF_DIVIDER_SELECT_DIV4; break; + case 8: _regs.rf_divider_select = adf5355_regs_t::RF_DIVIDER_SELECT_DIV8; break; + case 16: _regs.rf_divider_select = adf5355_regs_t::RF_DIVIDER_SELECT_DIV16; break; + case 32: _regs.rf_divider_select = adf5355_regs_t::RF_DIVIDER_SELECT_DIV32; break; + case 64: _regs.rf_divider_select = adf5355_regs_t::RF_DIVIDER_SELECT_DIV64; break; + default: UHD_THROW_INVALID_CODE_PATH(); + } + + //Compute fractional PLL params + double prescaler_input_freq = target_vco_freq; + if (_fb_after_divider) { + prescaler_input_freq /= rf_divider; + } + + const double N = prescaler_input_freq / _pfd_freq; + const uint16_t INT = static_cast<uint16_t>(floor(N)); + const uint32_t FRAC1 = static_cast<uint32_t>(floor((N - INT) * ADF535X_MOD1)); + const double residue = (N - INT) * ADF535X_MOD1 - FRAC1; + + const double gcd = boost::math::gcd(static_cast<int>(_pfd_freq), static_cast<int>(freq_resolution)); + const uint16_t MOD2 = static_cast<uint16_t>(std::min(floor(_pfd_freq / gcd), static_cast<double>(ADF535X_MAX_MOD2))); + const uint16_t FRAC2 = static_cast<uint16_t>(std::min(ceil(residue * MOD2), static_cast<double>(ADF535X_MAX_FRAC2))); + + const double coerced_vco_freq = _pfd_freq * ( + double(INT) + ( + (double(FRAC1) + + (double(FRAC2) / double(MOD2))) + / double(ADF535X_MOD1) + ) + ); + + const double coerced_out_freq = coerced_vco_freq / rf_divider; + + /* Update registers */ + _regs.int_16_bit = INT; + _regs.frac1_24_bit = FRAC1; + _regs.frac2_14_bit = FRAC2; + _regs.mod2_14_bit = MOD2; + _regs.phase_24_bit = 0; + + if (flush) commit(); + return coerced_out_freq; +} + +template <> +inline void adf535x_impl<adf5355_regs_t>::_commit() +{ + const size_t ONE_REG = 1; + + if (_rewrite_regs) { + //For a full state sync write registers in reverse order 12 - 0 + addr_vtr_t regs; + for (uint8_t addr = 12; addr > 0; addr--) { + regs.push_back(_regs.get_reg(addr)); + } + _write_fn(regs); + // TODO Add FPGA based delay between these writes + _write_fn(addr_vtr_t(ONE_REG, _regs.get_reg(0))); + _rewrite_regs = false; + + } else { + //Frequency update sequence from data sheet + _write_fn(addr_vtr_t(ONE_REG, _regs.get_reg(6))); + _regs.counter_reset = adf5355_regs_t::COUNTER_RESET_ENABLED; + _write_fn(addr_vtr_t(ONE_REG, _regs.get_reg(4))); + _write_fn(addr_vtr_t(ONE_REG, _regs.get_reg(2))); + _write_fn(addr_vtr_t(ONE_REG, _regs.get_reg(1))); + _regs.autocal_en = adf5355_regs_t::AUTOCAL_EN_DISABLED; + _write_fn(addr_vtr_t(ONE_REG, _regs.get_reg(0))); + _regs.counter_reset = adf5355_regs_t::COUNTER_RESET_DISABLED; + _write_fn(addr_vtr_t(ONE_REG, _regs.get_reg(4))); + // TODO Add FPGA based delay between these writes + _regs.autocal_en = adf5355_regs_t::AUTOCAL_EN_ENABLED; + _write_fn(addr_vtr_t(ONE_REG, _regs.get_reg(0))); + } +} + +// ADF5356 Functions +template <> +inline double adf535x_impl<adf5356_regs_t>::_set_frequency(double target_freq, double freq_resolution, bool flush) +{ + if (target_freq > ADF535X_MAX_OUT_FREQ or target_freq < ADF535X_MIN_OUT_FREQ) { + throw uhd::runtime_error("requested frequency out of range."); + } + if ((uint32_t) freq_resolution == 0) { + throw uhd::runtime_error("requested resolution cannot be less than 1."); + } + + /* Calculate target VCOout frequency */ + //Increase RF divider until acceptable VCO frequency + double target_vco_freq = target_freq; + uint32_t rf_divider = 1; + while (target_vco_freq < ADF535X_MIN_VCO_FREQ && rf_divider < 64) { + target_vco_freq *= 2; + rf_divider *= 2; + } + + switch (rf_divider) { + case 1: _regs.rf_divider_select = adf5356_regs_t::RF_DIVIDER_SELECT_DIV1; break; + case 2: _regs.rf_divider_select = adf5356_regs_t::RF_DIVIDER_SELECT_DIV2; break; + case 4: _regs.rf_divider_select = adf5356_regs_t::RF_DIVIDER_SELECT_DIV4; break; + case 8: _regs.rf_divider_select = adf5356_regs_t::RF_DIVIDER_SELECT_DIV8; break; + case 16: _regs.rf_divider_select = adf5356_regs_t::RF_DIVIDER_SELECT_DIV16; break; + case 32: _regs.rf_divider_select = adf5356_regs_t::RF_DIVIDER_SELECT_DIV32; break; + case 64: _regs.rf_divider_select = adf5356_regs_t::RF_DIVIDER_SELECT_DIV64; break; + default: UHD_THROW_INVALID_CODE_PATH(); + } + + //Compute fractional PLL params + double prescaler_input_freq = target_vco_freq; + if (_fb_after_divider) { + prescaler_input_freq /= rf_divider; + } + + const double N = prescaler_input_freq / _pfd_freq; + const uint16_t INT = static_cast<uint16_t>(floor(N)); + const uint32_t FRAC1 = static_cast<uint32_t>(floor((N - INT) * ADF535X_MOD1)); + const double residue = (N - INT) * ADF535X_MOD1 - FRAC1; + + const double gcd = boost::math::gcd(static_cast<int>(_pfd_freq), static_cast<int>(freq_resolution)); + const uint16_t MOD2 = static_cast<uint16_t>(std::min(floor(_pfd_freq / gcd), static_cast<double>(ADF535X_MAX_MOD2))); + const uint16_t FRAC2 = static_cast<uint16_t>(std::min(ceil(residue * MOD2), static_cast<double>(ADF535X_MAX_FRAC2))); + + const double coerced_vco_freq = _pfd_freq * ( + double(INT) + ( + (double(FRAC1) + + (double(FRAC2) / double(MOD2))) + / double(ADF535X_MOD1) + ) + ); + + const double coerced_out_freq = coerced_vco_freq / rf_divider; + + /* Update registers */ + _regs.int_16_bit = INT; + _regs.frac1_24_bit = FRAC1; + _regs.frac2_msb = FRAC2; + _regs.mod2_msb = MOD2; + _regs.phase_24_bit = 0; + + if (flush) commit(); + return coerced_out_freq; +} + +template <> +inline void adf535x_impl<adf5356_regs_t>::_commit() +{ + const size_t ONE_REG = 1; + if (_rewrite_regs) { + //For a full state sync write registers in reverse order 12 - 0 + addr_vtr_t regs; + for (uint8_t addr = 13; addr > 0; addr--) { + regs.push_back(_regs.get_reg(addr)); + } + _write_fn(regs); + // TODO Add FPGA based delay between these writes + _write_fn(addr_vtr_t(ONE_REG, _regs.get_reg(0))); + _rewrite_regs = false; + + } else { + //Frequency update sequence from data sheet + _write_fn(addr_vtr_t(ONE_REG, _regs.get_reg(13))); + _write_fn(addr_vtr_t(ONE_REG, _regs.get_reg(10))); + _write_fn(addr_vtr_t(ONE_REG, _regs.get_reg(6))); + _write_fn(addr_vtr_t(ONE_REG, _regs.get_reg(2))); + _write_fn(addr_vtr_t(ONE_REG, _regs.get_reg(1))); + // TODO Add FPGA based delay between these writes + _write_fn(addr_vtr_t(ONE_REG, _regs.get_reg(0))); + } +} + +#endif // INCLUDED_ADF535X_HPP diff --git a/host/lib/usrp/dboard/db_twinrx.cpp b/host/lib/usrp/dboard/db_twinrx.cpp index 20adbf849..b1cf185b4 100644 --- a/host/lib/usrp/dboard/db_twinrx.cpp +++ b/host/lib/usrp/dboard/db_twinrx.cpp @@ -18,6 +18,7 @@ #include "twinrx/twinrx_experts.hpp" #include "twinrx/twinrx_ctrl.hpp" #include "twinrx/twinrx_io.hpp" +#include "twinrx/twinrx_ids.hpp" #include <expert_factory.hpp> #include <uhd/types/device_addr.hpp> #include <uhd/types/ranges.hpp> @@ -38,9 +39,6 @@ using namespace uhd::usrp; using namespace uhd::usrp::dboard::twinrx; using namespace uhd::experts; -static const dboard_id_t TWINRX_V100_000_ID(0x91); -static const dboard_id_t TWINRX_V100_100_ID(0x93); - /*! * twinrx_rcvr_fe is the dbaord class (dboard_base) that * represents each front-end of a TwinRX board. UHD will @@ -227,7 +225,7 @@ public: twinrx_gpio::sptr gpio_iface = boost::make_shared<twinrx_gpio>(_db_iface); twinrx_cpld_regmap::sptr cpld_regs = boost::make_shared<twinrx_cpld_regmap>(); cpld_regs->initialize(*gpio_iface, false); - _ctrl = twinrx_ctrl::make(_db_iface, gpio_iface, cpld_regs); + _ctrl = twinrx_ctrl::make(_db_iface, gpio_iface, cpld_regs, get_rx_id()); _expert = expert_factory::create_container("twinrx_expert"); } @@ -335,17 +333,25 @@ static dboard_base::sptr make_twinrx_container(dboard_base::ctor_args_t args) UHD_STATIC_BLOCK(reg_twinrx_dboards) { dboard_manager::register_dboard_restricted( - TWINRX_V100_000_ID, + twinrx::TWINRX_REV_A_ID, + &twinrx_rcvr::make_twinrx_fe, + "TwinRX Rev A", + boost::assign::list_of("0")("1"), + &make_twinrx_container + ); + + dboard_manager::register_dboard_restricted( + twinrx::TWINRX_REV_B_ID, &twinrx_rcvr::make_twinrx_fe, - "TwinRX v1.0", + "TwinRX Rev B", boost::assign::list_of("0")("1"), &make_twinrx_container ); dboard_manager::register_dboard_restricted( - TWINRX_V100_100_ID, + twinrx::TWINRX_REV_C_ID, &twinrx_rcvr::make_twinrx_fe, - "TwinRX v1.1", + "TwinRX Rev C", boost::assign::list_of("0")("1"), &make_twinrx_container ); diff --git a/host/lib/usrp/dboard/twinrx/twinrx_ctrl.cpp b/host/lib/usrp/dboard/twinrx/twinrx_ctrl.cpp index 3bb5931bc..29ab7e874 100644 --- a/host/lib/usrp/dboard/twinrx/twinrx_ctrl.cpp +++ b/host/lib/usrp/dboard/twinrx/twinrx_ctrl.cpp @@ -1,42 +1,38 @@ // -// Copyright 2015 Ettus Research LLC +// Copyright 2015-2017 Ettus Research, A National Instruments Company // -// 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/>. +// SPDX-License-Identifier: GPL-3.0 // -#include <boost/thread/thread.hpp> -#include <uhd/utils/math.hpp> -#include <uhd/utils/safe_call.hpp> #include "twinrx_ctrl.hpp" +#include "twinrx_ids.hpp" #include "adf435x.hpp" -#include "adf5355.hpp" +#include "adf535x.hpp" +#include <uhd/utils/math.hpp> +#include <uhd/utils/safe_call.hpp> +#include <boost/thread/thread.hpp> using namespace uhd; using namespace usrp; using namespace dboard::twinrx; -typedef twinrx_cpld_regmap rm; -static uint32_t bool2bin(bool x) { return x ? 1 : 0; } +namespace { + typedef twinrx_cpld_regmap rm; -static const double TWINRX_DESIRED_REFERENCE_FREQ = 50e6; + uint32_t bool2bin(bool x) { return x ? 1 : 0; } + + const double TWINRX_DESIRED_REFERENCE_FREQ = 50e6; + const double TWINRX_REV_AB_PFD_FREQ = 6.25e6; + const double TWINRX_REV_C_PFD_FREQ = 12.6e6; +} class twinrx_ctrl_impl : public twinrx_ctrl { public: twinrx_ctrl_impl( dboard_iface::sptr db_iface, twinrx_gpio::sptr gpio_iface, - twinrx_cpld_regmap::sptr cpld_regmap + twinrx_cpld_regmap::sptr cpld_regmap, + const dboard_id_t rx_id ) : _db_iface(db_iface), _gpio_iface(gpio_iface), _cpld_regs(cpld_regmap) { @@ -50,10 +46,24 @@ public: } } //Initialize synthesizer objects - _lo1_iface[size_t(CH1)] = adf5355_iface::make( - boost::bind(&twinrx_ctrl_impl::_write_lo_spi, this, dboard_iface::UNIT_TX, _1)); - _lo1_iface[size_t(CH2)] = adf5355_iface::make( - boost::bind(&twinrx_ctrl_impl::_write_lo_spi, this, dboard_iface::UNIT_TX, _1)); + if (rx_id == twinrx::TWINRX_REV_C_ID) { + _lo1_iface[size_t(CH1)] = adf535x_iface::make_adf5356( + boost::bind(&twinrx_ctrl_impl::_write_lo_spi, this, dboard_iface::UNIT_TX, _1)); + _lo1_iface[size_t(CH2)] = adf535x_iface::make_adf5356( + boost::bind(&twinrx_ctrl_impl::_write_lo_spi, this, dboard_iface::UNIT_TX, _1)); + + _lo1_iface[size_t(CH1)]->set_pfd_freq(TWINRX_REV_C_PFD_FREQ); + _lo1_iface[size_t(CH2)]->set_pfd_freq(TWINRX_REV_C_PFD_FREQ); + + } else { + _lo1_iface[size_t(CH1)] = adf535x_iface::make_adf5355( + boost::bind(&twinrx_ctrl_impl::_write_lo_spi, this, dboard_iface::UNIT_TX, _1)); + _lo1_iface[size_t(CH2)] = adf535x_iface::make_adf5355( + boost::bind(&twinrx_ctrl_impl::_write_lo_spi, this, dboard_iface::UNIT_TX, _1)); + + _lo1_iface[size_t(CH1)]->set_pfd_freq(TWINRX_REV_AB_PFD_FREQ); + _lo1_iface[size_t(CH2)]->set_pfd_freq(TWINRX_REV_AB_PFD_FREQ); + } _lo2_iface[size_t(CH1)] = adf435x_iface::make_adf4351( boost::bind(&twinrx_ctrl_impl::_write_lo_spi, this, dboard_iface::UNIT_RX, _1)); @@ -104,11 +114,9 @@ public: for (size_t i = 0; i < NUM_CHANS; i++) { _config_lo1_route(i==0?LO_CONFIG_CH1:LO_CONFIG_CH2); _config_lo2_route(i==0?LO_CONFIG_CH1:LO_CONFIG_CH2); - _lo1_iface[i]->set_output_power(adf5355_iface::OUTPUT_POWER_5DBM); + _lo1_iface[i]->set_output_power(adf535x_iface::OUTPUT_POWER_5DBM); _lo1_iface[i]->set_reference_freq(TWINRX_DESIRED_REFERENCE_FREQ); - // Divided feedback did not appear to be correctly implemented during bringup. Necessary for phase resync -// _lo1_iface[i]->set_feedback_select(adf5355_iface::FB_SEL_DIVIDED); - _lo1_iface[i]->set_muxout_mode(adf5355_iface::MUXOUT_DLD); + _lo1_iface[i]->set_muxout_mode(adf535x_iface::MUXOUT_DLD); _lo1_iface[i]->set_frequency(3e9, 1.0e3); _lo2_iface[i]->set_feedback_select(adf435x_iface::FB_SEL_DIVIDED); _lo2_iface[i]->set_output_power(adf435x_iface::OUTPUT_POWER_5DBM); @@ -535,8 +543,8 @@ private: //Functions _lo2_src[size_t(CH1)] == LO_COMPANION || _lo2_export == LO_CH2_SYNTH; - _lo1_iface[size_t(CH1)]->set_output_enable(adf5355_iface::RF_OUTPUT_A, _lo1_enable[size_t(CH1)].get()); - _lo1_iface[size_t(CH2)]->set_output_enable(adf5355_iface::RF_OUTPUT_A, _lo1_enable[size_t(CH2)].get()); + _lo1_iface[size_t(CH1)]->set_output_enable(adf535x_iface::RF_OUTPUT_A, _lo1_enable[size_t(CH1)].get()); + _lo1_iface[size_t(CH2)]->set_output_enable(adf535x_iface::RF_OUTPUT_A, _lo1_enable[size_t(CH2)].get()); _lo2_iface[size_t(CH1)]->set_output_enable(adf435x_iface::RF_OUTPUT_A, _lo2_enable[size_t(CH1)].get()); _lo2_iface[size_t(CH2)]->set_output_enable(adf435x_iface::RF_OUTPUT_A, _lo2_enable[size_t(CH2)].get()); @@ -624,7 +632,7 @@ private: //Members dboard_iface::sptr _db_iface; twinrx_gpio::sptr _gpio_iface; twinrx_cpld_regmap::sptr _cpld_regs; - adf5355_iface::sptr _lo1_iface[NUM_CHANS]; + adf535x_iface::sptr _lo1_iface[NUM_CHANS]; adf435x_iface::sptr _lo2_iface[NUM_CHANS]; lo_source_t _lo1_src[NUM_CHANS]; lo_source_t _lo2_src[NUM_CHANS]; @@ -639,7 +647,8 @@ private: //Members twinrx_ctrl::sptr twinrx_ctrl::make( dboard_iface::sptr db_iface, twinrx_gpio::sptr gpio_iface, - twinrx_cpld_regmap::sptr cpld_regmap + twinrx_cpld_regmap::sptr cpld_regmap, + const dboard_id_t rx_id ) { - return sptr(new twinrx_ctrl_impl(db_iface, gpio_iface, cpld_regmap)); + return sptr(new twinrx_ctrl_impl(db_iface, gpio_iface, cpld_regmap, rx_id)); } diff --git a/host/lib/usrp/dboard/twinrx/twinrx_ctrl.hpp b/host/lib/usrp/dboard/twinrx/twinrx_ctrl.hpp index 2439addc4..68e3022f1 100644 --- a/host/lib/usrp/dboard/twinrx/twinrx_ctrl.hpp +++ b/host/lib/usrp/dboard/twinrx/twinrx_ctrl.hpp @@ -18,9 +18,9 @@ #ifndef INCLUDED_DBOARD_TWINRX_CTRL_HPP #define INCLUDED_DBOARD_TWINRX_CTRL_HPP -#include <boost/noncopyable.hpp> -#include <uhd/types/wb_iface.hpp> #include "twinrx_io.hpp" +#include <uhd/types/wb_iface.hpp> +#include <boost/noncopyable.hpp> namespace uhd { namespace usrp { namespace dboard { namespace twinrx { @@ -31,7 +31,9 @@ public: static sptr make( dboard_iface::sptr db_iface, twinrx_gpio::sptr gpio_iface, - twinrx_cpld_regmap::sptr cpld_regmap); + twinrx_cpld_regmap::sptr cpld_regmap, + dboard_id_t rx_id + ); virtual ~twinrx_ctrl() {} diff --git a/host/lib/usrp/dboard/twinrx/twinrx_ids.hpp b/host/lib/usrp/dboard/twinrx/twinrx_ids.hpp new file mode 100644 index 000000000..599b1d456 --- /dev/null +++ b/host/lib/usrp/dboard/twinrx/twinrx_ids.hpp @@ -0,0 +1,16 @@ + +#ifndef _TWINRX_IDS_HPP +#define _TWINRX_IDS_HPP + +#include <uhd/usrp/dboard_id.hpp> + +using namespace uhd; +using namespace usrp; + +namespace twinrx { + const dboard_id_t TWINRX_REV_A_ID(0x91); + const dboard_id_t TWINRX_REV_B_ID(0x93); + const dboard_id_t TWINRX_REV_C_ID(0x95); +} + +#endif |