diff options
author | Ashish Chaudhari <ashish@ettus.com> | 2015-04-21 10:40:37 -0700 |
---|---|---|
committer | Ashish Chaudhari <ashish@ettus.com> | 2015-04-21 10:40:37 -0700 |
commit | 893819c87f1cc9e845e29df0c26e1d2d0012b750 (patch) | |
tree | a300f39af94d6831df2bdee9ea0518dba949bb59 | |
parent | 625724d7f81c214e9955334860a364802be3fda9 (diff) | |
parent | 6e918dab8c3dbdc9774389812953cc495496a8e8 (diff) | |
download | uhd-893819c87f1cc9e845e29df0c26e1d2d0012b750.tar.gz uhd-893819c87f1cc9e845e29df0c26e1d2d0012b750.tar.bz2 uhd-893819c87f1cc9e845e29df0c26e1d2d0012b750.zip |
Merge branch 'master' into vivado
Conflicts:
host/lib/usrp/x300/x300_clock_ctrl.cpp
26 files changed, 1674 insertions, 1137 deletions
@@ -1,6 +1,18 @@ Change Log for Releases ============================== +## 003.008.003 +* UBX: Fixed phase synchronization issues + (Related changes: Change X300 daughterboard frequency, increase + N210 FIFO depth) +* Fixed many compiler warnings +* B200: Fixed timing issues, fixed tick rate issue, stabilized + operations at high clock rates +* X300: Improved phase alignment across devices +* CMake: Build fixes +* E300: Flow control fix + + ## 003.008.002 * CMake: Introducing named versions for dev branches, enable static libs, * E300: Docs updates, compat number bump, VCTCXO updates, diff --git a/fpga-src b/fpga-src -Subproject 78c9077afe5ff35c5528689dcb2b0debbd57c41 +Subproject 8aca31329f2fd1612df32383bb8bea80d5b7e47 diff --git a/host/CMakeLists.txt b/host/CMakeLists.txt index 6e364ba4c..8dddb6552 100644 --- a/host/CMakeLists.txt +++ b/host/CMakeLists.txt @@ -284,8 +284,8 @@ UHD_INSTALL(FILES #{{{IMG_SECTION # This section is written automatically by /images/create_imgs_package.py # Any manual changes in here will be overwritten. -SET(UHD_IMAGES_MD5SUM "f5f67a3b7037e254ecf47fbfee920743") -SET(UHD_IMAGES_DOWNLOAD_SRC "uhd-images_003.008.002-157-gbe03e694.zip") +SET(UHD_IMAGES_MD5SUM "37d4899e320809951149c3670fb054ee") +SET(UHD_IMAGES_DOWNLOAD_SRC "uhd-images_003.009.git-139-g3e3e236a.zip") #}}} ######################################################################## @@ -423,5 +423,19 @@ ELSEIF(UHDHOST_PKG) SET(PRINT_APPEND " (Debian uhd-host package configuration)") ENDIF(LIBUHD_PKG) UHD_PRINT_COMPONENT_SUMMARY() +IF(UHD_VERSION_DEVEL) + MESSAGE(STATUS "******************************************************") + IF(UHD_VERSION_PATCH STREQUAL "git") + MESSAGE(STATUS "* You are building the UHD development master branch.") + MESSAGE(STATUS "* For production code, we recommend our stable,") + MESSAGE(STATUS "* releases or using the release branch (maint).") + ELSE() + MESSAGE(STATUS "* You are building a development branch of UHD.") + MESSAGE(STATUS "* These branches are designed to provide early access") + MESSAGE(STATUS "* to UHD and USRP features, but should be considered") + MESSAGE(STATUS "* unstable and/or experimental!") + ENDIF(UHD_VERSION_PATCH STREQUAL "git") + MESSAGE(STATUS "******************************************************") +ENDIF(UHD_VERSION_DEVEL) MESSAGE(STATUS "Building version: ${UHD_VERSION}${PRINT_APPEND}") MESSAGE(STATUS "Using install prefix: ${CMAKE_INSTALL_PREFIX}") diff --git a/host/cmake/Modules/UHDVersion.cmake b/host/cmake/Modules/UHDVersion.cmake index 014943b53..aefe516d6 100644 --- a/host/cmake/Modules/UHDVersion.cmake +++ b/host/cmake/Modules/UHDVersion.cmake @@ -27,9 +27,9 @@ FIND_PACKAGE(Git QUIET) # - set UHD_VERSION_DEVEL to true for master and development branches ######################################################################## SET(UHD_VERSION_MAJOR 003) -SET(UHD_VERSION_MINOR 008) -SET(UHD_VERSION_PATCH 002) -SET(UHD_VERSION_DEVEL FALSE) +SET(UHD_VERSION_MINOR 009) +SET(UHD_VERSION_PATCH git) +SET(UHD_VERSION_DEVEL TRUE) ######################################################################## # Set up trimmed version numbers for DLL resource files and packages diff --git a/host/docs/octoclock.dox b/host/docs/octoclock.dox index 9c1ca7b47..45a12e93a 100644 --- a/host/docs/octoclock.dox +++ b/host/docs/octoclock.dox @@ -29,14 +29,15 @@ schematics</a>. Once you verify that the programmer is properly connected, run t cd <install path>/share/uhd/images avrdude -p atmega128 -c <programmer name> -P usb -U efuse:w:0xFF:m -U hfuse:w:0x80:m -U lfuse:w:0xFF:m -U flash:w:octoclock_bootloader.hex:i -**Note:** On Linux, **sudo** must be used with the **avrdude** command. +\b Note: +On Linux, `sudo avrdude ...` might be necessary to gain access to the programmer. Once the bootloader has been burned, power-cycle your OctoClock device and refer to the below instructions on burning the OctoClock's primary firmware. \subsection application Primary Octoclock firmware -To load firmware onto the OctoClock, you must use the *octoclock_firmware_burner* utility, specifying the IP +To load firmware onto the OctoClock, you must use the `octoclock_firmware_burner` utility, specifying the IP address of the OctoClock device, as follows: octoclock_firmware_burner --addr=192.168.10.3 @@ -45,12 +46,13 @@ address of the OctoClock device, as follows: \subsection host_interface Setting up the host interface -The OctoClock communicates with the host machine at the UDP layer over Gigabit Ethernet. The default device -of the OctoClock is **192.168.10.3**. You will need to configure the host machine's Ethernet interface with -a static IP address to enable communication. An address of **192.168.10.1** and a subnet mask of -**255.255.255.0** is recommended. +The OctoClock communicates with the host machine at the UDP layer over Ethernet. The default device +of the OctoClock is `192.168.10.3`. You will need to configure the host machine's Ethernet interface with +a static IP address to enable communication. An address of `192.168.10.1` and a subnet mask of +`255.255.255.0` is recommended. -**Note:** When using UHD software, if an IP address for the OctoClock is not specified, the software will +\b Note: +When using UHD software, if an IP address for the OctoClock is not specified, the software will use UDP broadcast packets to locate the OctoClock. On some systems, the firewall will block UDP broadcast packets. It is recommended that you change your firewall settings. @@ -110,17 +112,17 @@ The same applies for an external signal. The following sensors are available on both the OctoClock and Octoclock-G; these can be queried through the <a href="classuhd_1_1octoclock.html">API</a>. -- **ext_ref_detected:** whether or not the device detects an external reference -- **gps_detected:** whether or not the device detects an internal GPSDO -- **using_ref:** which reference the device is using (internal or external) -- **switch_pos:** the position of the front switch (internal or external) +- `ext_ref_detected:` whether or not the device detects an external reference +- `gps_detected:` whether or not the device detects an internal GPSDO +- `using_ref:` which reference the device is using (internal or external) +- `switch_pos:` the position of the front switch (internal or external) On the OctoClock-G, the following sensors are added: -- **gps_gpgga:** the latest GPGGA string sent by the GPSDO -- **gps_gprmc:** the latest GPRMC string sent by the GPSDO -- **gps_time:** the time reported by the GPSDO -- **gps_locked:** whether or not the GPSDO is locked (true/false) -- **gps_servo:** the latest debug trace information sent by the GPSDO +- `gps_gpgga:` the latest GPGGA string sent by the GPSDO +- `gps_gprmc:` the latest GPRMC string sent by the GPSDO +- `gps_time:` the time reported by the GPSDO +- `gps_locked:` whether or not the GPSDO is locked (true/false) +- `gps_servo:` the latest debug trace information sent by the GPSDO */ diff --git a/host/docs/sync.dox b/host/docs/sync.dox index 8c16eb046..aaae88702 100644 --- a/host/docs/sync.dox +++ b/host/docs/sync.dox @@ -152,7 +152,7 @@ metadata should have a time spec set: : size_t num_tx_samps = tx_streamer->send(buffs, samps_to_send, md); ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -\subsection sync_phase_lo Align LOs in the front-end (SBX, WBX, CBX) +\subsection sync_phase_lo Align LOs in the front-end (SBX, WBX, UBX) Using timed commands, multiple frontends can be tuned at a specific time. This timed-tuning ensures that the phase offsets between VCO/PLL @@ -161,7 +161,9 @@ chains will remain constant after each re-tune. See notes below: - There is a random phase offset between any two frontends - This phase offset is different for different LO frequencies - This phase offset remains constant after retuning - - Due to a divider, WBX phase offset will be randomly +/- 180 deg after re-tune + - Due to a divider, WBX phase offset will be randomly +/- 180 deg after re-tune on all USRPs. + - Due to a divider, UBX phase offset will be randomly +/- 180 deg after re-tune on N200/N210. + On X300/X310, phase sync with UBX fully works. - This phase offset will drift over time due to thermal and other characteristics - Periodic calibration will be necessary for phase-coherent applications diff --git a/host/docs/uhd_find_devices.1 b/host/docs/uhd_find_devices.1 index dfd5c0751..a29793872 100644 --- a/host/docs/uhd_find_devices.1 +++ b/host/docs/uhd_find_devices.1 @@ -43,6 +43,8 @@ type=b200 | USRP B200, USRP B210 type=e100 | USRP E100, USRP E110 +type=e3x0 | USRP E310 + type=x300 | USRP X300, USRP X310 type=octoclock | OctoClock diff --git a/host/docs/uhd_usrp_probe.1 b/host/docs/uhd_usrp_probe.1 index 178027aea..b5a48fc15 100644 --- a/host/docs/uhd_usrp_probe.1 +++ b/host/docs/uhd_usrp_probe.1 @@ -51,8 +51,11 @@ type=b200 | USRP B200, USRP B210 type=e100 | USRP E100, USRP E110 +type=e3x0 | USRP E310 + type=x300 | USRP X300, USRP X310 + .SS Identifying by serial number All USRP devices are given a unique serial number, which can be used to identify a device as follows: diff --git a/host/docs/usrp_b200.dox b/host/docs/usrp_b200.dox index 9d3550b98..1da7f2aee 100644 --- a/host/docs/usrp_b200.dox +++ b/host/docs/usrp_b200.dox @@ -9,10 +9,12 @@ - External PPS reference input - External 10 MHz reference input - Configurable clock rate + - Variable analog bandwidth (200 kHz - 56 MHz) - Internal GPSDO option (see \subpage page_gpsdo_b2x0 for details) - B210 Only: - MICTOR Debug Connector - JTAG Connector + - Revision 6 with GPIO header - FPGA Capabilities: - Timed commands in FPGA - Timed sampling in FPGA @@ -90,6 +92,24 @@ frontends have 73 dB of available gain; and the transmit frontends have it is recommended that users consider using at least half of the available gain to get reasonable dynamic range. +\subsection b200_fe_bw Frontend bandwidth + +The analog frontend has a seamlessly adjustable bandwidth of 200 kHz to 56 MHz. + +Generally, when requesting any possible master clock rate, UHD will +automatically configure the analog filters to avoid any aliasing (RX) or +out-of-band emissions whilst letting through the cleanest possible signal. + +If you, however, happen to have a very strong interferer within half the master +clock rate of your RX LO frequency, you might want to reduce this analog +bandwidth. You can do so by calling +uhd::usrp::multi_usrp::set_rx_bandwidth(bw). + +The property to control the analog RX bandwidth is `bandwidth/value`. + +UHD will not allow you to set bandwidths larger than your current master clock +rate. + \section Hardware Reference \subsection LED Indicators @@ -181,11 +201,17 @@ Below is a table showing the external connections and respective power informati Below is a table showing the on-board connectors and switches: -Component ID | Description | Details ---------------|----------------------------|---------------------------------------------------------- - J502* | Mictor Connector | Interface to FPGA for I/O and inspection. - J503* | JTAG Header | Interface to FPGA for programming and debugging. - S700 | FX3 Hard Reset Switch | - +Component ID | Description | Details +------------------------|----------------------------|--------------------------------------------------- + J502<sup>1</sup> | Mictor Connector | Interface to FPGA for I/O and inspection. + J503<sup>1</sup> | JTAG Header | Interface to FPGA for programming and debugging. + J504<sup>2</sup> | GPIO Header | Header running to the FPGA for GPIO purposes. + S700 | FX3 Hard Reset Switch | Resets the USB controller / System reset + U100 | GPSDO socket | Interface to GPS disciplined reference oscillator + +<sup>1</sup> Only on the B210 + +<sup>2</sup> Only since rev. 6 (green board) */ diff --git a/host/lib/usrp/b200/b200_io_impl.cpp b/host/lib/usrp/b200/b200_io_impl.cpp index 60b925517..d6df726af 100644 --- a/host/lib/usrp/b200/b200_io_impl.cpp +++ b/host/lib/usrp/b200/b200_io_impl.cpp @@ -118,8 +118,7 @@ void b200_impl::set_auto_tick_rate( // Step 2: Determine whether if we can use lcm_rate (preferred), // or have to give up because too large: - const double max_tick_rate = - ((num_chans <= 1) ? ad9361_device_t::AD9361_RECOMMENDED_MAX_CLOCK_RATE : ad9361_device_t::AD9361_MAX_CLOCK_RATE/2); + const double max_tick_rate = ad9361_device_t::AD9361_MAX_CLOCK_RATE/num_chans; double base_rate = static_cast<double>(lcm_rate); if (base_rate > max_tick_rate) { UHD_MSG(warning) @@ -176,7 +175,6 @@ void b200_impl::update_tick_rate(const double new_tick_rate) } } - double b200_impl::coerce_rx_samp_rate(rx_dsp_core_3000::sptr ddc, size_t dspno, const double rx_rate) { // Have to set tick rate first, or the ddc will change the requested rate based on default tick rate @@ -187,6 +185,14 @@ double b200_impl::coerce_rx_samp_rate(rx_dsp_core_3000::sptr ddc, size_t dspno, return ddc->set_host_rate(rx_rate); } +#define CHECK_BANDWIDTH(dir) \ + if (rate > _codec_ctrl->get_bw_filter_range(dir).stop()) { \ + UHD_MSG(warning) \ + << "Selected " << dir << " bandwidth (" << (rate/1e6) << " MHz) exceeds\n" \ + << "analog frontend filter bandwidth (" << (_codec_ctrl->get_bw_filter_range(dir).stop()/1e6) << " MHz)." \ + << std::endl; \ + } + void b200_impl::update_rx_samp_rate(const size_t dspno, const double rate) { boost::shared_ptr<sph::recv_packet_streamer> my_streamer = @@ -195,6 +201,7 @@ void b200_impl::update_rx_samp_rate(const size_t dspno, const double rate) my_streamer->set_samp_rate(rate); const double adj = _radio_perifs[dspno].ddc->get_scaling_adjustment(); my_streamer->set_scale_factor(adj); + CHECK_BANDWIDTH("Rx"); } double b200_impl::coerce_tx_samp_rate(tx_dsp_core_3000::sptr duc, size_t dspno, const double tx_rate) @@ -215,6 +222,7 @@ void b200_impl::update_tx_samp_rate(const size_t dspno, const double rate) my_streamer->set_samp_rate(rate); const double adj = _radio_perifs[dspno].duc->get_scaling_adjustment(); my_streamer->set_scale_factor(adj); + CHECK_BANDWIDTH("Tx"); } /*********************************************************************** diff --git a/host/lib/usrp/common/ad9361_ctrl.cpp b/host/lib/usrp/common/ad9361_ctrl.cpp index 9c17a582d..f3ab36247 100644 --- a/host/lib/usrp/common/ad9361_ctrl.cpp +++ b/host/lib/usrp/common/ad9361_ctrl.cpp @@ -1,5 +1,5 @@ // -// Copyright 2012-2014 Ettus Research LLC +// Copyright 2012-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 @@ -133,12 +133,6 @@ public: { boost::lock_guard<boost::mutex> lock(_mutex); - //warning for known trouble rates - if (rate > ad9361_device_t::AD9361_RECOMMENDED_MAX_CLOCK_RATE) UHD_MSG(warning) << boost::format( - "The requested clock rate %f MHz may cause slow configuration.\n" - "The driver recommends a master clock rate less than %f MHz.\n" - ) % (rate/1e6) % (ad9361_device_t::AD9361_RECOMMENDED_MAX_CLOCK_RATE/1e6) << std::endl; - //clip to known bounds const meta_range_t clock_rate_range = ad9361_ctrl::get_clock_rate_range(); const double clipped_rate = clock_rate_range.clip(rate); diff --git a/host/lib/usrp/common/ad9361_driver/ad9361_device.cpp b/host/lib/usrp/common/ad9361_driver/ad9361_device.cpp index c3eb5fb9d..8737837b3 100644 --- a/host/lib/usrp/common/ad9361_driver/ad9361_device.cpp +++ b/host/lib/usrp/common/ad9361_driver/ad9361_device.cpp @@ -79,8 +79,9 @@ int get_num_taps(int max_num_taps) { const double ad9361_device_t::AD9361_MAX_GAIN = 89.75; const double ad9361_device_t::AD9361_MAX_CLOCK_RATE = 61.44e6; -const double ad9361_device_t::AD9361_RECOMMENDED_MAX_CLOCK_RATE = 56e6; const double ad9361_device_t::AD9361_CAL_VALID_WINDOW = 100e6; +// Max bandwdith is due to filter rolloff in analog filter stage +const double ad9361_device_t::AD9361_RECOMMENDED_MAX_BANDWIDTH = 56e6; /* Program either the RX or TX FIR filter. * diff --git a/host/lib/usrp/common/ad9361_driver/ad9361_device.h b/host/lib/usrp/common/ad9361_driver/ad9361_device.h index a242f35e9..0c7a7e4f7 100644 --- a/host/lib/usrp/common/ad9361_driver/ad9361_device.h +++ b/host/lib/usrp/common/ad9361_driver/ad9361_device.h @@ -132,8 +132,8 @@ public: //Constants static const double AD9361_MAX_GAIN; static const double AD9361_MAX_CLOCK_RATE; - static const double AD9361_RECOMMENDED_MAX_CLOCK_RATE; static const double AD9361_CAL_VALID_WINDOW; + static const double AD9361_RECOMMENDED_MAX_BANDWIDTH; private: //Methods void _program_fir_filter(direction_t direction, int num_taps, boost::uint16_t *coeffs); diff --git a/host/lib/usrp/common/max287x.hpp b/host/lib/usrp/common/max287x.hpp new file mode 100644 index 000000000..d084dcfbe --- /dev/null +++ b/host/lib/usrp/common/max287x.hpp @@ -0,0 +1,800 @@ +// +// 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 MAX287X_HPP_INCLUDED +#define MAX287X_HPP_INCLUDED + +#include <uhd/exception.hpp> +#include <uhd/types/dict.hpp> +#include <uhd/types/ranges.hpp> +#include <uhd/utils/log.hpp> +#include <boost/assign.hpp> +#include <boost/function.hpp> +#include <boost/thread.hpp> +#include <boost/math/special_functions/round.hpp> +#include <vector> +#include "max2870_regs.hpp" +#include "max2871_regs.hpp" + +/** + * MAX287x interface + */ +class max287x_iface +{ +public: + typedef boost::shared_ptr<max287x_iface> sptr; + + typedef boost::function<void(std::vector<boost::uint32_t>)> write_fn; + + /** + * LD Pin Modes + */ + typedef enum{ + LD_PIN_MODE_LOW, + LD_PIN_MODE_DLD, + LD_PIN_MODE_ALD, + LD_PIN_MODE_HIGH + } ld_pin_mode_t; + + /** + * MUXOUT Modes + */ + typedef enum{ + MUXOUT_TRI_STATE, + MUXOUT_HIGH, + MUXOUT_LOW, + MUXOUT_RDIV, + MUXOUT_NDIV, + MUXOUT_ALD, + MUXOUT_DLD, + MUXOUT_SYNC, + MUXOUT_SPI + } muxout_mode_t; + + /** + * Charge Pump Currents + */ + typedef enum{ + CHARGE_PUMP_CURRENT_0_32MA, + CHARGE_PUMP_CURRENT_0_64MA, + CHARGE_PUMP_CURRENT_0_96MA, + CHARGE_PUMP_CURRENT_1_28MA, + CHARGE_PUMP_CURRENT_1_60MA, + CHARGE_PUMP_CURRENT_1_92MA, + CHARGE_PUMP_CURRENT_2_24MA, + CHARGE_PUMP_CURRENT_2_56MA, + CHARGE_PUMP_CURRENT_2_88MA, + CHARGE_PUMP_CURRENT_3_20MA, + CHARGE_PUMP_CURRENT_3_52MA, + CHARGE_PUMP_CURRENT_3_84MA, + CHARGE_PUMP_CURRENT_4_16MA, + CHARGE_PUMP_CURRENT_4_48MA, + CHARGE_PUMP_CURRENT_4_80MA, + CHARGE_PUMP_CURRENT_5_12MA + } charge_pump_current_t; + + /** + * Output Powers + */ + typedef enum{ + OUTPUT_POWER_M4DBM, + OUTPUT_POWER_M1DBM, + OUTPUT_POWER_2DBM, + OUTPUT_POWER_5DBM + } output_power_t; + + typedef enum { + LOW_NOISE_AND_SPUR_LOW_NOISE, + LOW_NOISE_AND_SPUR_LOW_SPUR_1, + LOW_NOISE_AND_SPUR_LOW_SPUR_2 + } low_noise_and_spur_t; + + typedef enum { + CLOCK_DIV_MODE_CLOCK_DIVIDER_OFF, + CLOCK_DIV_MODE_FAST_LOCK, + CLOCK_DIV_MODE_PHASE + } clock_divider_mode_t; + + /** + * Make a synthesizer + * @param write write function + * @return shared pointer to object + */ + template <typename max287X_t> static sptr make(write_fn write) + { + return sptr(new max287X_t(write)); + } + + /** + * Destructor + */ + virtual ~max287x_iface() {}; + + /** + * Power up the synthesizer + */ + virtual void power_up(void) = 0; + + /** + * Shut down the synthesizer + */ + virtual void shutdown(void) = 0; + + /** + * Check if the synthesizer is shut down + */ + virtual bool is_shutdown(void) = 0; + + /** + * Set frequency + * @param target_freq target frequency + * @param ref_freq reference frequency + * @param target_pfd_freq target phase detector frequency + * @param is_int_n enable integer-N tuning + * @return actual frequency + */ + virtual double set_frequency( + double target_freq, + double ref_freq, + double target_pfd_freq, + bool is_int_n) = 0; + + /** + * Set output power + * @param power output power + */ + virtual void set_output_power(output_power_t power) = 0; + + /** + * Set lock detect pin mode + * @param mode lock detect pin mode + */ + virtual void set_ld_pin_mode(ld_pin_mode_t mode) = 0; + + /** + * Set muxout pin mode + * @param mode muxout mode + */ + virtual void set_muxout_mode(muxout_mode_t mode) = 0; + + /** + * Set charge pump current + * @param cp_current charge pump current + */ + virtual void set_charge_pump_current(charge_pump_current_t cp_current) = 0; + + /** + * Enable or disable auto retune + * @param enabled enable auto retune + */ + virtual void set_auto_retune(bool enabled) = 0; + + /** + * Set clock divider mode + * @param mode clock divider mode + */ + virtual void set_clock_divider_mode(clock_divider_mode_t mode) = 0; + + /** + * Enable or disable cycle slip mode + * @param enabled enable cycle slip mode + */ + virtual void set_cycle_slip_mode(bool enabled) = 0; + + /** + * Set low noise and spur mode + * @param mode low noise and spur mode + */ + virtual void set_low_noise_and_spur(low_noise_and_spur_t mode) = 0; + + /** + * Set phase + * @param phase the phase offset + */ + virtual void set_phase(boost::uint16_t phase) = 0; + + /** + * Write values configured by the set_* functions. + */ + virtual void commit(void) = 0; + + /** + * Check whether this is in a state where it can be synchronized + */ + virtual bool can_sync(void) = 0; +}; + +/** + * MAX287x + * Base class for all MAX287x synthesizers + */ +template <typename max287x_regs_t> +class max287x : public max287x_iface +{ +public: + max287x(write_fn func); + virtual ~max287x(); + virtual void power_up(void); + virtual void shutdown(void); + virtual bool is_shutdown(void); + virtual double set_frequency( + double target_freq, + double ref_freq, + double target_pfd_freq, + bool is_int_n); + virtual void set_output_power(output_power_t power); + virtual void set_ld_pin_mode(ld_pin_mode_t mode); + virtual void set_muxout_mode(muxout_mode_t mode); + virtual void set_charge_pump_current(charge_pump_current_t cp_current); + virtual void set_auto_retune(bool enabled); + virtual void set_clock_divider_mode(clock_divider_mode_t mode); + virtual void set_cycle_slip_mode(bool enabled); + virtual void set_low_noise_and_spur(low_noise_and_spur_t mode); + virtual void set_phase(boost::uint16_t phase); + virtual void commit(); + virtual bool can_sync(); + +protected: + max287x_regs_t _regs; + bool _can_sync; + bool _write_all_regs; + +private: + write_fn _write; + bool _delay_after_write; +}; + +/** + * MAX2870 + */ +class max2870 : public max287x<max2870_regs_t> +{ +public: + max2870(write_fn func) : max287x<max2870_regs_t>(func) {} + ~max2870() {} + double set_frequency( + double target_freq, + double ref_freq, + double target_pfd_freq, + bool is_int_n) + { + _regs.cpoc = is_int_n ? max2870_regs_t::CPOC_ENABLED : max2870_regs_t::CPOC_DISABLED; + _regs.feedback_select = target_freq >= 3.0e9 ? + max2870_regs_t::FEEDBACK_SELECT_DIVIDED : + max2870_regs_t::FEEDBACK_SELECT_FUNDAMENTAL; + + return max287x<max2870_regs_t>::set_frequency(target_freq, ref_freq, target_pfd_freq, is_int_n); + } + void commit(void) + { + // For MAX2870, we always need to write all registers. + _write_all_regs = true; + max287x<max2870_regs_t>::commit(); + } +}; + +/** + * MAX2871 + */ +class max2871 : public max287x<max2871_regs_t> +{ +public: + max2871(write_fn func) : max287x<max2871_regs_t>(func) {} + ~max2871() {}; + void set_muxout_mode(muxout_mode_t mode) + { + switch(mode) + { + case MUXOUT_SYNC: + _regs.muxout = max2871_regs_t::MUXOUT_SYNC; + break; + case MUXOUT_SPI: + _regs.muxout = max2871_regs_t::MUXOUT_SPI; + break; + default: + max287x<max2871_regs_t>::set_muxout_mode(mode); + } + } + + double set_frequency( + double target_freq, + double ref_freq, + double target_pfd_freq, + bool is_int_n) + { + _regs.feedback_select = max2871_regs_t::FEEDBACK_SELECT_DIVIDED; + double freq = max287x<max2871_regs_t>::set_frequency(target_freq, ref_freq, target_pfd_freq, is_int_n); + + // According to Maxim support, the following factors must be true to allow for synchronization + if (_regs.r_counter_10_bit == 1 and + _regs.reference_divide_by_2 == max2871_regs_t::REFERENCE_DIVIDE_BY_2_DISABLED and + _regs.reference_doubler == max2871_regs_t::REFERENCE_DOUBLER_DISABLED and + _regs.rf_divider_select <= max2871_regs_t::RF_DIVIDER_SELECT_DIV16 and + _regs.low_noise_and_spur == max2871_regs_t::LOW_NOISE_AND_SPUR_LOW_NOISE) + { + _can_sync = true; + } + return freq; + } +}; + + +// Implementation of max287x template class +// To avoid linker errors, it was either include +// it here or put it in a .cpp file and include +// that file in this header file. Decided to just +// include it here. + +template <typename max287x_regs_t> +max287x<max287x_regs_t>::max287x(write_fn func) : + _can_sync(false), + _write_all_regs(true), + _write(func), + _delay_after_write(true) +{ + power_up(); +} + +template <typename max287x_regs_t> +max287x<max287x_regs_t>::~max287x() +{ + shutdown(); +} + +template <typename max287x_regs_t> +void max287x<max287x_regs_t>::power_up(void) +{ + _regs.power_down = max287x_regs_t::POWER_DOWN_NORMAL; + _regs.double_buffer = max287x_regs_t::DOUBLE_BUFFER_ENABLED; + + // According to MAX287x data sheets: + // "Upon power-up, the registers should be programmed twice with at + // least a 20ms pause between writes. The first write ensures that + // the device is enabled, and the second write starts the VCO + // selection process." + // The first write and the 20ms wait are done here. The second write + // is done when any other function that does a write to the registers + // is called (such as tuning). + _write_all_regs = true; + _delay_after_write = true; + commit(); + _write_all_regs = true; // Next call to commit() writes all regs +} + +template <typename max287x_regs_t> +void max287x<max287x_regs_t>::shutdown(void) +{ + _regs.rf_output_enable = max287x_regs_t::RF_OUTPUT_ENABLE_DISABLED; + _regs.aux_output_enable = max287x_regs_t::AUX_OUTPUT_ENABLE_DISABLED; + _regs.power_down = max287x_regs_t::POWER_DOWN_SHUTDOWN; + commit(); +} + +template <typename max287x_regs_t> +bool max287x<max287x_regs_t>::is_shutdown(void) +{ + return (_regs.power_down == max287x_regs_t::POWER_DOWN_SHUTDOWN); +} + +template <typename max287x_regs_t> +double max287x<max287x_regs_t>::set_frequency( + double target_freq, + double ref_freq, + double target_pfd_freq, + bool is_int_n) +{ + _can_sync = false; + + //map rf divider select output dividers to enums + static const uhd::dict<int, typename max287x_regs_t::rf_divider_select_t> rfdivsel_to_enum = + boost::assign::map_list_of + (1, max287x_regs_t::RF_DIVIDER_SELECT_DIV1) + (2, max287x_regs_t::RF_DIVIDER_SELECT_DIV2) + (4, max287x_regs_t::RF_DIVIDER_SELECT_DIV4) + (8, max287x_regs_t::RF_DIVIDER_SELECT_DIV8) + (16, max287x_regs_t::RF_DIVIDER_SELECT_DIV16) + (32, max287x_regs_t::RF_DIVIDER_SELECT_DIV32) + (64, max287x_regs_t::RF_DIVIDER_SELECT_DIV64) + (128, max287x_regs_t::RF_DIVIDER_SELECT_DIV128); + + //map mode setting to valid integer divider (N) values + static const uhd::range_t int_n_mode_div_range(16,65536,1); + static const uhd::range_t frac_n_mode_div_range(19,4091,1); + + //other ranges and constants from MAX287X datasheets + static const uhd::range_t clock_div_range(1,4095,1); + static const uhd::range_t r_range(1,1023,1); + static const double MIN_VCO_FREQ = 3e9; + static const double BS_FREQ = 50e3; + static const int MAX_BS_VALUE = 1023; + + int T = 0; + int D = ref_freq <= 10.0e6 ? 1 : 0; + int R = 0; + int BS = 0; + int N = 0; + int FRAC = 0; + int MOD = 4095; + int RFdiv = 1; + double pfd_freq = target_pfd_freq; + bool feedback_divided = (_regs.feedback_select == max287x_regs_t::FEEDBACK_SELECT_DIVIDED); + + //increase RF divider until acceptable VCO frequency (MIN freq for MAX287x VCO is 3GHz) + double vco_freq = target_freq; + while (vco_freq < MIN_VCO_FREQ) + { + vco_freq *= 2; + RFdiv *= 2; + } + + // The feedback frequency can be the fundamental VCO frequency or + // divided frequency. The output divider for MAX287x is actually + // 2 dividers, but only the first (1/2/4/8/16) is included in the + // feedback loop. + int fb_divisor = feedback_divided ? (RFdiv > 16 ? 16 : RFdiv) : 1; + + /* + * The goal here is to loop though possible R dividers, + * band select clock dividers, N (int) dividers, and FRAC + * (frac) dividers. + * + * Calculate the N and F dividers for each set of values. + * The loop exits when it meets all of the constraints. + * The resulting loop values are loaded into the registers. + * + * f_pfd = f_ref*(1+D)/(R*(1+T)) + * f_vco = (N + (FRAC/MOD))*f_pfd + * N = f_vco/f_pfd - FRAC/MOD = f_vco*((R*(T+1))/(f_ref*(1+D))) - FRAC/MOD + * f_rf = f_vco/RFdiv + */ + for(R = int(ref_freq*(1+D)/(target_pfd_freq*(1+T))); R <= r_range.stop(); R++) + { + //PFD input frequency = f_ref/R ... ignoring Reference doubler/divide-by-2 (D & T) + pfd_freq = ref_freq*(1+D)/(R*(1+T)); + + //keep the PFD frequency at or below target + if (pfd_freq > target_pfd_freq) + continue; + + //ignore fractional part of tuning + N = int((vco_freq/pfd_freq)/fb_divisor); + + //Fractional-N calculation + FRAC = int(boost::math::round(((vco_freq/pfd_freq)/fb_divisor - N)*MOD)); + + if(is_int_n) + { + if (FRAC > (MOD / 2)) //Round integer such that actual freq is closest to target + N++; + FRAC = 0; + } + + //keep N within int divider requirements + if(is_int_n) + { + if(N < int_n_mode_div_range.start()) continue; + if(N > int_n_mode_div_range.stop()) continue; + } + else + { + if(N < frac_n_mode_div_range.start()) continue; + if(N > frac_n_mode_div_range.stop()) continue; + } + + //keep pfd freq low enough to achieve 50kHz BS clock + BS = std::ceil(pfd_freq / BS_FREQ); + if(BS <= MAX_BS_VALUE) break; + } + UHD_ASSERT_THROW(R <= r_range.stop()); + + //Reference divide-by-2 for 50% duty cycle + // if R even, move one divide by 2 to to regs.reference_divide_by_2 + if(R % 2 == 0) + { + T = 1; + R /= 2; + } + + //actual frequency calculation + double actual_freq = double((N + (double(FRAC)/double(MOD)))*ref_freq*(1+int(D))/(R*(1+int(T)))) * fb_divisor / RFdiv; + + UHD_LOGV(rarely) + << boost::format("MAX287x: Intermediates: ref=%0.2f, outdiv=%f, fbdiv=%f" + ) % ref_freq % double(RFdiv*2) % double(N + double(FRAC)/double(MOD)) << std::endl + << boost::format("MAX287x: tune: R=%d, BS=%d, N=%d, FRAC=%d, MOD=%d, T=%d, D=%d, RFdiv=%d, type=%s" + ) % R % BS % N % FRAC % MOD % T % D % RFdiv % ((is_int_n) ? "Integer-N" : "Fractional") << std::endl + << boost::format("MAX287x: Frequencies (MHz): REQ=%0.2f, ACT=%0.2f, VCO=%0.2f, PFD=%0.2f, BAND=%0.2f" + ) % (target_freq/1e6) % (actual_freq/1e6) % (vco_freq/1e6) % (pfd_freq/1e6) % (pfd_freq/BS/1e6) << std::endl; + + //load the register values + _regs.rf_output_enable = max287x_regs_t::RF_OUTPUT_ENABLE_ENABLED; + + if(is_int_n) { + _regs.cpl = max287x_regs_t::CPL_DISABLED; + _regs.ldf = max287x_regs_t::LDF_INT_N; + _regs.int_n_mode = max287x_regs_t::INT_N_MODE_INT_N; + } else { + _regs.cpl = max287x_regs_t::CPL_ENABLED; + _regs.ldf = max287x_regs_t::LDF_FRAC_N; + _regs.int_n_mode = max287x_regs_t::INT_N_MODE_FRAC_N; + } + + _regs.lds = pfd_freq <= 32e6 ? max287x_regs_t::LDS_SLOW : max287x_regs_t::LDS_FAST; + + _regs.frac_12_bit = FRAC; + _regs.int_16_bit = N; + _regs.mod_12_bit = MOD; + _regs.clock_divider_12_bit = std::max(int(clock_div_range.start()), int(std::ceil(400e-6*pfd_freq/MOD))); + UHD_ASSERT_THROW(_regs.clock_divider_12_bit <= clock_div_range.stop()); + _regs.r_counter_10_bit = R; + _regs.reference_divide_by_2 = T ? + max287x_regs_t::REFERENCE_DIVIDE_BY_2_ENABLED : + max287x_regs_t::REFERENCE_DIVIDE_BY_2_DISABLED; + _regs.reference_doubler = D ? + max287x_regs_t::REFERENCE_DOUBLER_ENABLED : + max287x_regs_t::REFERENCE_DOUBLER_DISABLED; + _regs.band_select_clock_div = BS & 0xFF; + _regs.bs_msb = (BS & 0x300) >> 8; + UHD_ASSERT_THROW(rfdivsel_to_enum.has_key(RFdiv)); + _regs.rf_divider_select = rfdivsel_to_enum[RFdiv]; + + if (_regs.clock_div_mode == max287x_regs_t::CLOCK_DIV_MODE_FAST_LOCK) + { + // Charge pump current needs to be set to lowest value in fast lock mode + _regs.charge_pump_current = max287x_regs_t::CHARGE_PUMP_CURRENT_0_32MA; + // Make sure the register containing the charge pump current is written + _write_all_regs = true; + } + + return actual_freq; +} + +template <typename max287x_regs_t> +void max287x<max287x_regs_t>::set_output_power(output_power_t power) +{ + switch (power) + { + case OUTPUT_POWER_M4DBM: + _regs.output_power = max287x_regs_t::OUTPUT_POWER_M4DBM; + break; + case OUTPUT_POWER_M1DBM: + _regs.output_power = max287x_regs_t::OUTPUT_POWER_M1DBM; + break; + case OUTPUT_POWER_2DBM: + _regs.output_power = max287x_regs_t::OUTPUT_POWER_2DBM; + break; + case OUTPUT_POWER_5DBM: + _regs.output_power = max287x_regs_t::OUTPUT_POWER_5DBM; + break; + default: + UHD_THROW_INVALID_CODE_PATH(); + } +} + +template <typename max287x_regs_t> +void max287x<max287x_regs_t>::set_ld_pin_mode(ld_pin_mode_t mode) +{ + switch(mode) + { + case LD_PIN_MODE_LOW: + _regs.ld_pin_mode = max287x_regs_t::LD_PIN_MODE_LOW; + break; + case LD_PIN_MODE_DLD: + _regs.ld_pin_mode = max287x_regs_t::LD_PIN_MODE_DLD; + break; + case LD_PIN_MODE_ALD: + _regs.ld_pin_mode = max287x_regs_t::LD_PIN_MODE_ALD; + break; + case LD_PIN_MODE_HIGH: + _regs.ld_pin_mode = max287x_regs_t::LD_PIN_MODE_HIGH; + break; + default: + UHD_THROW_INVALID_CODE_PATH(); + } +} + +template <typename max287x_regs_t> +void max287x<max287x_regs_t>::set_muxout_mode(muxout_mode_t mode) +{ + switch(mode) + { + case MUXOUT_TRI_STATE: + _regs.muxout = max287x_regs_t::MUXOUT_TRI_STATE; + break; + case MUXOUT_HIGH: + _regs.muxout = max287x_regs_t::MUXOUT_HIGH; + break; + case MUXOUT_LOW: + _regs.muxout = max287x_regs_t::MUXOUT_LOW; + break; + case MUXOUT_RDIV: + _regs.muxout = max287x_regs_t::MUXOUT_RDIV; + break; + case MUXOUT_NDIV: + _regs.muxout = max287x_regs_t::MUXOUT_NDIV; + break; + case MUXOUT_ALD: + _regs.muxout = max287x_regs_t::MUXOUT_ALD; + break; + case MUXOUT_DLD: + _regs.muxout = max287x_regs_t::MUXOUT_DLD; + break; + default: + UHD_THROW_INVALID_CODE_PATH(); + } +} + +template <typename max287x_regs_t> +void max287x<max287x_regs_t>::set_charge_pump_current(charge_pump_current_t cp_current) +{ + switch(cp_current) + { + case CHARGE_PUMP_CURRENT_0_32MA: + _regs.charge_pump_current = max287x_regs_t::CHARGE_PUMP_CURRENT_0_32MA; + break; + case CHARGE_PUMP_CURRENT_0_64MA: + _regs.charge_pump_current = max287x_regs_t::CHARGE_PUMP_CURRENT_0_64MA; + break; + case CHARGE_PUMP_CURRENT_0_96MA: + _regs.charge_pump_current = max287x_regs_t::CHARGE_PUMP_CURRENT_0_96MA; + break; + case CHARGE_PUMP_CURRENT_1_28MA: + _regs.charge_pump_current = max287x_regs_t::CHARGE_PUMP_CURRENT_1_28MA; + break; + case CHARGE_PUMP_CURRENT_1_60MA: + _regs.charge_pump_current = max287x_regs_t::CHARGE_PUMP_CURRENT_1_60MA; + break; + case CHARGE_PUMP_CURRENT_1_92MA: + _regs.charge_pump_current = max287x_regs_t::CHARGE_PUMP_CURRENT_1_92MA; + break; + case CHARGE_PUMP_CURRENT_2_24MA: + _regs.charge_pump_current = max287x_regs_t::CHARGE_PUMP_CURRENT_2_24MA; + break; + case CHARGE_PUMP_CURRENT_2_56MA: + _regs.charge_pump_current = max287x_regs_t::CHARGE_PUMP_CURRENT_2_56MA; + break; + case CHARGE_PUMP_CURRENT_2_88MA: + _regs.charge_pump_current = max287x_regs_t::CHARGE_PUMP_CURRENT_2_88MA; + break; + case CHARGE_PUMP_CURRENT_3_20MA: + _regs.charge_pump_current = max287x_regs_t::CHARGE_PUMP_CURRENT_3_20MA; + break; + case CHARGE_PUMP_CURRENT_3_52MA: + _regs.charge_pump_current = max287x_regs_t::CHARGE_PUMP_CURRENT_3_52MA; + break; + case CHARGE_PUMP_CURRENT_3_84MA: + _regs.charge_pump_current = max287x_regs_t::CHARGE_PUMP_CURRENT_3_84MA; + break; + case CHARGE_PUMP_CURRENT_4_16MA: + _regs.charge_pump_current = max287x_regs_t::CHARGE_PUMP_CURRENT_4_16MA; + break; + case CHARGE_PUMP_CURRENT_4_48MA: + _regs.charge_pump_current = max287x_regs_t::CHARGE_PUMP_CURRENT_4_48MA; + break; + case CHARGE_PUMP_CURRENT_4_80MA: + _regs.charge_pump_current = max287x_regs_t::CHARGE_PUMP_CURRENT_4_80MA; + break; + case CHARGE_PUMP_CURRENT_5_12MA: + _regs.charge_pump_current = max287x_regs_t::CHARGE_PUMP_CURRENT_5_12MA; + break; + default: + UHD_THROW_INVALID_CODE_PATH(); + } +} + +template <typename max287x_regs_t> +void max287x<max287x_regs_t>::set_auto_retune(bool enabled) +{ + _regs.retune = enabled ? max287x_regs_t::RETUNE_ENABLED : max287x_regs_t::RETUNE_DISABLED; +} + +template <typename max287x_regs_t> +void max287x<max287x_regs_t>::set_clock_divider_mode(clock_divider_mode_t mode) +{ + switch(mode) + { + case CLOCK_DIV_MODE_CLOCK_DIVIDER_OFF: + _regs.clock_div_mode = max287x_regs_t::CLOCK_DIV_MODE_CLOCK_DIVIDER_OFF; + break; + case CLOCK_DIV_MODE_FAST_LOCK: + _regs.clock_div_mode = max287x_regs_t::CLOCK_DIV_MODE_FAST_LOCK; + break; + case CLOCK_DIV_MODE_PHASE: + _regs.clock_div_mode = max287x_regs_t::CLOCK_DIV_MODE_PHASE; + break; + default: + UHD_THROW_INVALID_CODE_PATH(); + } +} + +template <typename max287x_regs_t> +void max287x<max287x_regs_t>::set_cycle_slip_mode(bool enabled) +{ + if (enabled) + throw uhd::runtime_error("Cycle slip mode not supported on this MAX287x synthesizer."); +} + +template <typename max287x_regs_t> +void max287x<max287x_regs_t>::set_low_noise_and_spur(low_noise_and_spur_t mode) +{ + switch(mode) + { + case LOW_NOISE_AND_SPUR_LOW_NOISE: + _regs.low_noise_and_spur = max287x_regs_t::LOW_NOISE_AND_SPUR_LOW_NOISE; + break; + case LOW_NOISE_AND_SPUR_LOW_SPUR_1: + _regs.low_noise_and_spur = max287x_regs_t::LOW_NOISE_AND_SPUR_LOW_SPUR_1; + break; + case LOW_NOISE_AND_SPUR_LOW_SPUR_2: + _regs.low_noise_and_spur = max287x_regs_t::LOW_NOISE_AND_SPUR_LOW_SPUR_2; + break; + default: + UHD_THROW_INVALID_CODE_PATH(); + } +} + +template <typename max287x_regs_t> +void max287x<max287x_regs_t>::set_phase(boost::uint16_t phase) +{ + _regs.phase_12_bit = phase & 0xFFF; +} + +template <typename max287x_regs_t> +void max287x<max287x_regs_t>::commit() +{ + std::vector<boost::uint32_t> regs; + std::set<boost::uint32_t> changed_regs; + + // Get only regs with changes + if (_write_all_regs) + { + for (int addr = 5; addr >= 0; addr--) + regs.push_back(_regs.get_reg(boost::uint32_t(addr))); + } else { + try { + changed_regs = _regs.template get_changed_addrs<boost::uint32_t> (); + for (int addr = 5; addr >= 0; addr--) + { + if (changed_regs.find(boost::uint32_t(addr)) != changed_regs.end()) + regs.push_back(_regs.get_reg(boost::uint32_t(addr))); + } + } catch (uhd::runtime_error& e) { + // No saved state - write all regs + for (int addr = 5; addr >= 0; addr--) + regs.push_back(_regs.get_reg(boost::uint32_t(addr))); + } + } + + _write(regs); + _regs.save_state(); + _write_all_regs = false; + + if (_delay_after_write) + { + boost::this_thread::sleep(boost::posix_time::milliseconds(20)); + _delay_after_write = false; + } +} + + +template <typename max287x_regs_t> +bool max287x<max287x_regs_t>::can_sync(void) +{ + return _can_sync; +} + +#endif // MAX287X_HPP_INCLUDED diff --git a/host/lib/usrp/dboard/db_cbx.cpp b/host/lib/usrp/dboard/db_cbx.cpp index ad255460e..8336117b8 100644 --- a/host/lib/usrp/dboard/db_cbx.cpp +++ b/host/lib/usrp/dboard/db_cbx.cpp @@ -15,8 +15,6 @@ // along with this program. If not, see <http://www.gnu.org/licenses/>. // - -#include "max2870_regs.hpp" #include "db_sbx_common.hpp" #include <boost/algorithm/string.hpp> #include <boost/math/special_functions/round.hpp> @@ -31,6 +29,8 @@ using namespace boost::assign; sbx_xcvr::cbx::cbx(sbx_xcvr *_self_sbx_xcvr) { //register the handle to our base CBX class self_base = _self_sbx_xcvr; + _txlo = max287x_iface::make<max2870>(boost::bind(&sbx_xcvr::cbx::write_lo_regs, this, dboard_iface::UNIT_TX, _1)); + _rxlo = max287x_iface::make<max2870>(boost::bind(&sbx_xcvr::cbx::write_lo_regs, this, dboard_iface::UNIT_RX, _1)); } @@ -38,6 +38,14 @@ sbx_xcvr::cbx::~cbx(void){ /* NOP */ } +void sbx_xcvr::cbx::write_lo_regs(dboard_iface::unit_t unit, std::vector<boost::uint32_t> ®s) +{ + BOOST_FOREACH(boost::uint32_t reg, regs) + { + self_base->get_iface()->write_spi(unit, spi_config_t::EDGE_RISE, reg, 32); + } +} + /*********************************************************************** * Tuning @@ -47,6 +55,13 @@ double sbx_xcvr::cbx::set_lo_freq(dboard_iface::unit_t unit, double target_freq) "CBX tune: target frequency %f MHz" ) % (target_freq/1e6) << std::endl; + //clip the input + target_freq = cbx_freq_range.clip(target_freq); + + double ref_freq = self_base->get_iface()->get_clock_rate(unit); + double target_pfd_freq = 25e6; + double actual_freq = 0.0; + /* * If the user sets 'mode_n=integer' in the tuning args, the user wishes to * tune in Integer-N mode, which can result in better spur @@ -57,174 +72,17 @@ double sbx_xcvr::cbx::set_lo_freq(dboard_iface::unit_t unit, double target_freq) device_addr_t tune_args = subtree->access<device_addr_t>("tune_args").get(); bool is_int_n = boost::iequals(tune_args.get("mode_n",""), "integer"); - //clip the input - target_freq = cbx_freq_range.clip(target_freq); - - //map mode setting to valid integer divider (N) values - static const uhd::range_t int_n_mode_div_range(16,4095,1); - static const uhd::range_t frac_n_mode_div_range(19,4091,1); - - //map rf divider select output dividers to enums - static const uhd::dict<int, max2870_regs_t::rf_divider_select_t> rfdivsel_to_enum = map_list_of - (1, max2870_regs_t::RF_DIVIDER_SELECT_DIV1) - (2, max2870_regs_t::RF_DIVIDER_SELECT_DIV2) - (4, max2870_regs_t::RF_DIVIDER_SELECT_DIV4) - (8, max2870_regs_t::RF_DIVIDER_SELECT_DIV8) - (16, max2870_regs_t::RF_DIVIDER_SELECT_DIV16) - (32, max2870_regs_t::RF_DIVIDER_SELECT_DIV32) - (64, max2870_regs_t::RF_DIVIDER_SELECT_DIV64) - (128, max2870_regs_t::RF_DIVIDER_SELECT_DIV128) - ; - - double actual_freq, pfd_freq; - double ref_freq = self_base->get_iface()->get_clock_rate(unit); - int R=0, BS=0, N=0, FRAC=0, MOD=4095; - int RFdiv = 1; - max2870_regs_t::reference_divide_by_2_t T = max2870_regs_t::REFERENCE_DIVIDE_BY_2_DISABLED; - max2870_regs_t::reference_doubler_t D = max2870_regs_t::REFERENCE_DOUBLER_DISABLED; - - //Reference doubler for 50% duty cycle - // if ref_freq < 12.5MHz enable regs.reference_divide_by_2 - //NOTE: MAX2870 goes down to 10MHz ref vs. 12.5MHz on ADF4351 - if(ref_freq <= 10.0e6) D = max2870_regs_t::REFERENCE_DOUBLER_ENABLED; - - //increase RF divider until acceptable VCO frequency - double vco_freq = target_freq; - //NOTE: MIN freq for MAX2870 VCO is 3GHz vs. 2.2GHz on ADF4351 - while (vco_freq < 3e9) { - vco_freq *= 2; - RFdiv *= 2; - } - - /* - * The goal here is to loop though possible R dividers, - * band select clock dividers, N (int) dividers, and FRAC - * (frac) dividers. - * - * Calculate the N and F dividers for each set of values. - * The loop exits when it meets all of the constraints. - * The resulting loop values are loaded into the registers. - * - * from pg.21 - * - * f_pfd = f_ref*(1+D)/(R*(1+T)) - * f_vco = (N + (FRAC/MOD))*f_pfd - * N = f_vco/f_pfd - FRAC/MOD = f_vco*((R*(T+1))/(f_ref*(1+D))) - FRAC/MOD - * f_rf = f_vco/RFdiv - */ - for(R = 1; R <= 1023; R+=1){ - //PFD input frequency = f_ref/R ... ignoring Reference doubler/divide-by-2 (D & T) - pfd_freq = ref_freq*(1+D)/(R*(1+T)); - - //keep the PFD frequency at or below 25MHz - if (pfd_freq > 25e6) continue; - - //ignore fractional part of tuning - N = int(vco_freq/pfd_freq); - - //Fractional-N calculation - FRAC = int(boost::math::round((vco_freq/pfd_freq - N)*MOD)); - - if(is_int_n) { - if (FRAC > (MOD / 2)) { //Round integer such that actual freq is closest to target - N++; - } - FRAC = 0; - } - - //keep N within int divider requirements - if(is_int_n) { - if(N < int_n_mode_div_range.start()) continue; - if(N > int_n_mode_div_range.stop()) continue; - } else { - if(N < frac_n_mode_div_range.start()) continue; - if(N > frac_n_mode_div_range.stop()) continue; - } - - //keep pfd freq low enough to achieve 50kHz BS clock - BS = int(std::ceil(pfd_freq / 50e3)); - if(BS <= 1023) break; - } - - UHD_ASSERT_THROW(R <= 1023); - - //Reference divide-by-2 for 50% duty cycle - // if R even, move one divide by 2 to to regs.reference_divide_by_2 - if(R % 2 == 0){ - T = max2870_regs_t::REFERENCE_DIVIDE_BY_2_ENABLED; - R /= 2; - } - - //actual frequency calculation - actual_freq = double((N + (double(FRAC)/double(MOD)))*ref_freq*(1+int(D))/(R*(1+int(T)))/RFdiv); - - boost::uint16_t rx_id = self_base->get_rx_id().to_uint16(); - std::string board_name = (rx_id == 0x0085) ? "CBX-120" : "CBX"; - UHD_LOGV(often) - << boost::format("%s Intermediates: ref=%0.2f, outdiv=%f, fbdiv=%f" - ) % board_name.c_str() % (ref_freq*(1+int(D))/(R*(1+int(T)))) % double(RFdiv*2) % double(N + double(FRAC)/double(MOD)) << std::endl - << boost::format("%s tune: R=%d, BS=%d, N=%d, FRAC=%d, MOD=%d, T=%d, D=%d, RFdiv=%d, type=%s" - ) % board_name.c_str() % R % BS % N % FRAC % MOD % T % D % RFdiv % ((is_int_n) ? "Integer-N" : "Fractional") << std::endl - << boost::format("%s Frequencies (MHz): REQ=%0.2f, ACT=%0.2f, VCO=%0.2f, PFD=%0.2f, BAND=%0.2f" - ) % board_name.c_str() % (target_freq/1e6) % (actual_freq/1e6) % (vco_freq/1e6) % (pfd_freq/1e6) % (pfd_freq/BS/1e6) << std::endl; - - //load the register values - max2870_regs_t regs; - - if ((unit == dboard_iface::UNIT_TX) and (actual_freq == sbx_tx_lo_2dbm.clip(actual_freq))) - regs.output_power = max2870_regs_t::OUTPUT_POWER_2DBM; - else - regs.output_power = max2870_regs_t::OUTPUT_POWER_5DBM; - - //set frac/int CPL mode - max2870_regs_t::cpl_t cpl; - max2870_regs_t::ldf_t ldf; - max2870_regs_t::cpoc_t cpoc; - if(is_int_n) { - cpl = max2870_regs_t::CPL_DISABLED; - cpoc = max2870_regs_t::CPOC_ENABLED; - ldf = max2870_regs_t::LDF_INT_N; + if (unit == dboard_iface::UNIT_RX) + { + actual_freq = _rxlo->set_frequency(target_freq, ref_freq, target_pfd_freq, is_int_n); + _rxlo->commit(); } else { - cpl = max2870_regs_t::CPL_ENABLED; - ldf = max2870_regs_t::LDF_FRAC_N; - cpoc = max2870_regs_t::CPOC_DISABLED; - } - - regs.frac_12_bit = FRAC; - regs.int_16_bit = N; - regs.mod_12_bit = MOD; - regs.clock_divider_12_bit = std::max(1, int(std::ceil(400e-6*pfd_freq/MOD))); - regs.feedback_select = (target_freq >= 3.0e9) ? max2870_regs_t::FEEDBACK_SELECT_DIVIDED : max2870_regs_t::FEEDBACK_SELECT_FUNDAMENTAL; - regs.r_counter_10_bit = R; - regs.reference_divide_by_2 = T; - regs.reference_doubler = D; - regs.band_select_clock_div = (BS & 0x0FF); - regs.bs_msb = (BS & 0x300) >>8; - UHD_ASSERT_THROW(rfdivsel_to_enum.has_key(RFdiv)); - regs.rf_divider_select = rfdivsel_to_enum[RFdiv]; - regs.int_n_mode = (is_int_n) ? max2870_regs_t::INT_N_MODE_INT_N : max2870_regs_t::INT_N_MODE_FRAC_N; - regs.cpl = cpl; - regs.ldf = ldf; - regs.cpoc = cpoc; - - //write the registers - //correct power-up sequence to write registers (5, 4, 3, 2, 1, 0) - int addr; - - for(addr=5; addr>=0; addr--){ - UHD_LOGV(often) << boost::format( - "%s SPI Reg (0x%02x): 0x%08x" - ) % board_name.c_str() % addr % regs.get_reg(addr) << std::endl; - self_base->get_iface()->write_spi( - unit, spi_config_t::EDGE_RISE, - regs.get_reg(addr), 32 - ); + actual_freq = _txlo->set_frequency(target_freq, ref_freq, target_pfd_freq, is_int_n); + _txlo->set_output_power((actual_freq == sbx_tx_lo_2dbm.clip(actual_freq)) + ? max287x_iface::OUTPUT_POWER_2DBM + : max287x_iface::OUTPUT_POWER_5DBM); + _txlo->commit(); } - - //return the actual frequency - UHD_LOGV(often) << boost::format( - "%s tune: actual frequency %f MHz" - ) % board_name.c_str() % (actual_freq/1e6) << std::endl; return actual_freq; } diff --git a/host/lib/usrp/dboard/db_sbx_common.hpp b/host/lib/usrp/dboard/db_sbx_common.hpp index 58f79a606..a08d22537 100644 --- a/host/lib/usrp/dboard/db_sbx_common.hpp +++ b/host/lib/usrp/dboard/db_sbx_common.hpp @@ -17,7 +17,8 @@ #include <uhd/types/device_addr.hpp> -#include "../common/adf435x_common.hpp" +#include "adf435x_common.hpp" +#include "max287x.hpp" // Common IO Pins #define LO_LPF_EN (1 << 15) @@ -223,6 +224,10 @@ protected: /*! This is the registered instance of the wrapper class, sbx_base. */ sbx_xcvr *self_base; + private: + void write_lo_regs(dboard_iface::unit_t unit, std::vector<boost::uint32_t> ®s); + max287x_iface::sptr _txlo; + max287x_iface::sptr _rxlo; }; /*! diff --git a/host/lib/usrp/dboard/db_ubx.cpp b/host/lib/usrp/dboard/db_ubx.cpp index 06bfad7d3..1e79c14b0 100644 --- a/host/lib/usrp/dboard/db_ubx.cpp +++ b/host/lib/usrp/dboard/db_ubx.cpp @@ -34,522 +34,11 @@ #include <boost/algorithm/string.hpp> #include <boost/thread/mutex.hpp> #include <map> +#include "max287x.hpp" using namespace uhd; using namespace uhd::usrp; -#define fMHz (1000000.0) -#define UBX_PROTO_V3_TX_ID 0x73 -#define UBX_PROTO_V3_RX_ID 0x74 -#define UBX_PROTO_V4_TX_ID 0x75 -#define UBX_PROTO_V4_RX_ID 0x76 -#define UBX_V1_40MHZ_TX_ID 0x77 -#define UBX_V1_40MHZ_RX_ID 0x78 -#define UBX_V1_160MHZ_TX_ID 0x79 -#define UBX_V1_160MHZ_RX_ID 0x7a - -/*********************************************************************** - * UBX Synthesizers - **********************************************************************/ -#include "max2870_regs.hpp" -#include "max2871_regs.hpp" - -typedef boost::function<void(std::vector<boost::uint32_t>)> max287x_write_fn; - -class max287x_synthesizer_iface -{ -public: - virtual bool is_shutdown(void) = 0; - virtual void shutdown(void) = 0; - virtual void power_up(void) = 0; - virtual double set_freq_and_power(double target_freq, double ref_freq, bool is_int_n, int output_power) = 0; -}; - -class max287x : public max287x_synthesizer_iface -{ -public: - max287x(max287x_write_fn write_fn) : _write_fn(write_fn) {}; - virtual ~max287x() {}; - -protected: - virtual std::set<boost::uint32_t> get_changed_addrs(void) = 0; - virtual boost::uint32_t get_reg(boost::uint32_t addr) = 0; - virtual void save_state(void) = 0; - - void write_regs(void) - { - std::vector<boost::uint32_t> regs; - std::set<boost::uint32_t> changed_regs; - - // Get only regs with changes - try { - changed_regs = get_changed_addrs(); - } catch (uhd::runtime_error&) { - // No saved state - write all regs - for (int addr = 5; addr >= 0; addr--) - changed_regs.insert(boost::uint32_t(addr)); - } - - for (int addr = 5; addr >= 0; addr--) - { - if (changed_regs.find(boost::uint32_t(addr)) != changed_regs.end()) - regs.push_back(get_reg(boost::uint32_t(addr))); - } - - // writing reg 0 initiates VCO auto select, so this makes sure it is written - if (changed_regs.size() and changed_regs.find(0) == changed_regs.end()) - regs.push_back(get_reg(0)); - - _write_fn(regs); - save_state(); - } - - double calculate_freq_settings( - double target_freq, - double ref_freq, - double target_pfd_freq, - bool is_int_n, - double &pfd_freq, - int& T, - int& D, - int& R, - int& BS, - int& N, - int& FRAC, - int& MOD, - int& RFdiv) - { - //map mode setting to valid integer divider (N) values - static const uhd::range_t int_n_mode_div_range(16,4095,1); - static const uhd::range_t frac_n_mode_div_range(19,4091,1); - - double actual_freq = 0.0; - - T = 0; - D = ref_freq <= 10.0e6 ? 1 : 0; - R = 0; - BS = 0; - N = 0; - FRAC = 0; - MOD = 4095; - RFdiv = 1; - - //increase RF divider until acceptable VCO frequency (MIN freq for MAX287x VCO is 3GHz) - double vco_freq = target_freq; - while (vco_freq < 3e9) - { - vco_freq *= 2; - RFdiv *= 2; - } - - /* - * The goal here is to loop though possible R dividers, - * band select clock dividers, N (int) dividers, and FRAC - * (frac) dividers. - * - * Calculate the N and F dividers for each set of values. - * The loop exits when it meets all of the constraints. - * The resulting loop values are loaded into the registers. - * - * f_pfd = f_ref*(1+D)/(R*(1+T)) - * f_vco = (N + (FRAC/MOD))*f_pfd - * N = f_vco/f_pfd - FRAC/MOD = f_vco*((R*(T+1))/(f_ref*(1+D))) - FRAC/MOD - * f_rf = f_vco/RFdiv - */ - for(R = int(ref_freq*(1+D)/(target_pfd_freq*(1+T))); R <= 1023; R++) - { - //PFD input frequency = f_ref/R ... ignoring Reference doubler/divide-by-2 (D & T) - pfd_freq = ref_freq*(1+D)/(R*(1+T)); - - //keep the PFD frequency at or below target - if (pfd_freq > target_pfd_freq) - continue; - - //ignore fractional part of tuning - N = int(vco_freq/pfd_freq); - - //Fractional-N calculation - FRAC = int(boost::math::round((vco_freq/pfd_freq - N)*MOD)); - - if(is_int_n) - { - if (FRAC > (MOD / 2)) //Round integer such that actual freq is closest to target - N++; - FRAC = 0; - } - - //keep N within int divider requirements - if(is_int_n) - { - if(N < int_n_mode_div_range.start()) continue; - if(N > int_n_mode_div_range.stop()) continue; - } - else - { - if(N < frac_n_mode_div_range.start()) continue; - if(N > frac_n_mode_div_range.stop()) continue; - } - - //keep pfd freq low enough to achieve 50kHz BS clock - BS = int(std::ceil(pfd_freq / 50e3)); - if(BS <= 1023) break; - } - UHD_ASSERT_THROW(R <= 1023); - - //Reference divide-by-2 for 50% duty cycle - // if R even, move one divide by 2 to to regs.reference_divide_by_2 - if(R % 2 == 0) - { - T = 1; - R /= 2; - } - - //actual frequency calculation - actual_freq = double((N + (double(FRAC)/double(MOD)))*ref_freq*(1+int(D))/(R*(1+int(T)))/RFdiv); - - UHD_LOGV(rarely) - << boost::format("MAX287x: Intermediates: ref=%0.2f, outdiv=%f, fbdiv=%f" - ) % (ref_freq*(1+int(D))/(R*(1+int(T)))) % double(RFdiv*2) % double(N + double(FRAC)/double(MOD)) << std::endl - << boost::format("MAX287x: tune: R=%d, BS=%d, N=%d, FRAC=%d, MOD=%d, T=%d, D=%d, RFdiv=%d, type=%s" - ) % R % BS % N % FRAC % MOD % T % D % RFdiv % ((is_int_n) ? "Integer-N" : "Fractional") << std::endl - << boost::format("MAX287x: Frequencies (MHz): REQ=%0.2f, ACT=%0.2f, VCO=%0.2f, PFD=%0.2f, BAND=%0.2f" - ) % (pfd_freq/1e6) % (actual_freq/1e6) % (vco_freq/1e6) % (pfd_freq/1e6) % (pfd_freq/BS/1e6) << std::endl; - - return actual_freq; - } - - max287x_write_fn _write_fn; -}; - -class max2870 : public max287x -{ -public: - max2870(max287x_write_fn write_fn) : max287x(write_fn), _first_tune(true) - { - // initialize register values (override defaults) - _regs.retune = max2870_regs_t::RETUNE_DISABLED; - _regs.clock_div_mode = max2870_regs_t::CLOCK_DIV_MODE_FAST_LOCK; - - // MAX2870 data sheet says that all registers must be written twice - // with at least a 20ms delay between writes upon power up. One - // write and a 20ms wait are done in power_up(). The second write - // is done when any other function that does a write to the registers - // is called. To ensure all registers are written the second time, the - // state of the registers is not saved during the first write. - _save_state = false; - power_up(); - _save_state = true; - }; - - ~max2870() - { - shutdown(); - }; - - bool is_shutdown(void) - { - return (_regs.power_down == max2870_regs_t::POWER_DOWN_SHUTDOWN); - }; - - void shutdown(void) - { - _regs.rf_output_enable = max2870_regs_t::RF_OUTPUT_ENABLE_DISABLED; - _regs.aux_output_enable = max2870_regs_t::AUX_OUTPUT_ENABLE_DISABLED; - _regs.power_down = max2870_regs_t::POWER_DOWN_SHUTDOWN; - _regs.muxout = max2870_regs_t::MUXOUT_LOW; - _regs.ld_pin_mode = max2870_regs_t::LD_PIN_MODE_LOW; - write_regs(); - }; - - void power_up(void) - { - _regs.muxout = max2870_regs_t::MUXOUT_DLD; - _regs.ld_pin_mode = max2870_regs_t::LD_PIN_MODE_DLD; - _regs.power_down = max2870_regs_t::POWER_DOWN_NORMAL; - write_regs(); - - // MAX270 data sheet says to wait at least 20 ms after exiting low power mode - // before programming final VCO frequency - boost::this_thread::sleep(boost::posix_time::milliseconds(20)); - - _first_tune = true; - }; - - double set_freq_and_power(double target_freq, double ref_freq, bool is_int_n, int output_power) - { - //map rf divider select output dividers to enums - static const uhd::dict<int, max2870_regs_t::rf_divider_select_t> rfdivsel_to_enum = - boost::assign::map_list_of - (1, max2870_regs_t::RF_DIVIDER_SELECT_DIV1) - (2, max2870_regs_t::RF_DIVIDER_SELECT_DIV2) - (4, max2870_regs_t::RF_DIVIDER_SELECT_DIV4) - (8, max2870_regs_t::RF_DIVIDER_SELECT_DIV8) - (16, max2870_regs_t::RF_DIVIDER_SELECT_DIV16) - (32, max2870_regs_t::RF_DIVIDER_SELECT_DIV32) - (64, max2870_regs_t::RF_DIVIDER_SELECT_DIV64) - (128, max2870_regs_t::RF_DIVIDER_SELECT_DIV128); - - int T = 0; - int D = ref_freq <= 10.0e6 ? 1 : 0; - int R, BS, N, FRAC, MOD, RFdiv; - double pfd_freq = 25e6; - - double actual_freq = calculate_freq_settings( - target_freq, ref_freq, 25e6, is_int_n, pfd_freq, T, D, R, BS, N, FRAC, MOD, RFdiv); - - //load the register values - _regs.rf_output_enable = max2870_regs_t::RF_OUTPUT_ENABLE_ENABLED; - - if(is_int_n) { - _regs.cpl = max2870_regs_t::CPL_DISABLED; - _regs.ldf = max2870_regs_t::LDF_INT_N; - _regs.cpoc = max2870_regs_t::CPOC_ENABLED; - _regs.int_n_mode = max2870_regs_t::INT_N_MODE_INT_N; - } else { - _regs.cpl = max2870_regs_t::CPL_ENABLED; - _regs.ldf = max2870_regs_t::LDF_FRAC_N; - _regs.cpoc = max2870_regs_t::CPOC_DISABLED; - _regs.int_n_mode = max2870_regs_t::INT_N_MODE_FRAC_N; - } - - _regs.lds = pfd_freq <= 32e6 ? max2870_regs_t::LDS_SLOW : max2870_regs_t::LDS_FAST; - - _regs.frac_12_bit = FRAC; - _regs.int_16_bit = N; - _regs.mod_12_bit = MOD; - _regs.clock_divider_12_bit = std::max(1, int(std::ceil(400e-6*pfd_freq/MOD))); - _regs.feedback_select = (target_freq >= 3.0e9) ? - max2870_regs_t::FEEDBACK_SELECT_DIVIDED : - max2870_regs_t::FEEDBACK_SELECT_FUNDAMENTAL; - _regs.r_counter_10_bit = R; - _regs.reference_divide_by_2 = T ? - max2870_regs_t::REFERENCE_DIVIDE_BY_2_ENABLED : - max2870_regs_t::REFERENCE_DIVIDE_BY_2_DISABLED; - _regs.reference_doubler = D ? - max2870_regs_t::REFERENCE_DOUBLER_ENABLED : - max2870_regs_t::REFERENCE_DOUBLER_DISABLED; - _regs.band_select_clock_div = BS; - _regs.bs_msb = (BS & 0x300) >> 8; - UHD_ASSERT_THROW(rfdivsel_to_enum.has_key(RFdiv)); - _regs.rf_divider_select = rfdivsel_to_enum[RFdiv]; - - switch (output_power) - { - case -4: - _regs.output_power = max2870_regs_t::OUTPUT_POWER_M4DBM; - break; - case -1: - _regs.output_power = max2870_regs_t::OUTPUT_POWER_M1DBM; - break; - case 2: - _regs.output_power = max2870_regs_t::OUTPUT_POWER_2DBM; - break; - case 5: - _regs.output_power = max2870_regs_t::OUTPUT_POWER_5DBM; - break; - } - - // Write the register values - write_regs(); - - // MAX2870 needs a 20ms delay after tuning for the first time - // for the lock detect to be reliable. - if (_first_tune) - { - boost::this_thread::sleep(boost::posix_time::milliseconds(20)); - _first_tune = false; - } - - return actual_freq; - }; - -private: - std::set<boost::uint32_t> get_changed_addrs() - { - return _regs.get_changed_addrs<boost::uint32_t>(); - }; - - boost::uint32_t get_reg(boost::uint32_t addr) - { - return _regs.get_reg(addr); - }; - - void save_state() - { - if (_save_state) - _regs.save_state(); - } - - max2870_regs_t _regs; - bool _save_state; - bool _first_tune; -}; - -class max2871 : public max287x -{ -public: - max2871(max287x_write_fn write_fn) : max287x(write_fn), _first_tune(true) - { - // initialize register values (override defaults) - _regs.retune = max2871_regs_t::RETUNE_DISABLED; - //_regs.csm = max2871_regs_t::CSM_ENABLED; // tried it - caused long lock times - _regs.charge_pump_current = max2871_regs_t::CHARGE_PUMP_CURRENT_5_12MA; - - // MAX2871 data sheet says that all registers must be written twice - // with at least a 20ms delay between writes upon power up. One - // write and a 20ms wait are done in power_up(). The second write - // is done when any other function that does a write to the registers - // is called. To ensure all registers are written the second time, the - // state of the registers is not saved during the first write. - _save_state = false; - power_up(); - _save_state = true; - }; - - ~max2871() - { - shutdown(); - }; - - bool is_shutdown(void) - { - return (_regs.power_down == max2871_regs_t::POWER_DOWN_SHUTDOWN); - }; - - void shutdown(void) - { - _regs.rf_output_enable = max2871_regs_t::RF_OUTPUT_ENABLE_DISABLED; - _regs.aux_output_enable = max2871_regs_t::AUX_OUTPUT_ENABLE_DISABLED; - _regs.power_down = max2871_regs_t::POWER_DOWN_SHUTDOWN; - _regs.ld_pin_mode = max2871_regs_t::LD_PIN_MODE_LOW; - _regs.muxout = max2871_regs_t::MUXOUT_TRI_STATE; - write_regs(); - }; - - void power_up(void) - { - _regs.ld_pin_mode = max2871_regs_t::LD_PIN_MODE_DLD; - _regs.power_down = max2871_regs_t::POWER_DOWN_NORMAL; - _regs.muxout = max2871_regs_t::MUXOUT_TRI_STATE; - write_regs(); - - // MAX271 data sheet says to wait at least 20 ms after exiting low power mode - // before programming final VCO frequency - boost::this_thread::sleep(boost::posix_time::milliseconds(20)); - - _first_tune = true; - }; - - double set_freq_and_power(double target_freq, double ref_freq, bool is_int_n, int output_power) - { - //map rf divider select output dividers to enums - static const uhd::dict<int, max2871_regs_t::rf_divider_select_t> rfdivsel_to_enum = - boost::assign::map_list_of - (1, max2871_regs_t::RF_DIVIDER_SELECT_DIV1) - (2, max2871_regs_t::RF_DIVIDER_SELECT_DIV2) - (4, max2871_regs_t::RF_DIVIDER_SELECT_DIV4) - (8, max2871_regs_t::RF_DIVIDER_SELECT_DIV8) - (16, max2871_regs_t::RF_DIVIDER_SELECT_DIV16) - (32, max2871_regs_t::RF_DIVIDER_SELECT_DIV32) - (64, max2871_regs_t::RF_DIVIDER_SELECT_DIV64) - (128, max2871_regs_t::RF_DIVIDER_SELECT_DIV128); - - int T = 0; - int D = ref_freq <= 10.0e6 ? 1 : 0; - int R, BS, N, FRAC, MOD, RFdiv; - double pfd_freq = 50e6; - - double actual_freq = calculate_freq_settings( - target_freq, ref_freq, 50e6, is_int_n, pfd_freq, T, D, R, BS, N, FRAC, MOD, RFdiv); - - //load the register values - _regs.rf_output_enable = max2871_regs_t::RF_OUTPUT_ENABLE_ENABLED; - - if(is_int_n) { - _regs.cpl = max2871_regs_t::CPL_DISABLED; - _regs.ldf = max2871_regs_t::LDF_INT_N; - _regs.int_n_mode = max2871_regs_t::INT_N_MODE_INT_N; - } else { - _regs.cpl = max2871_regs_t::CPL_ENABLED; - _regs.ldf = max2871_regs_t::LDF_FRAC_N; - _regs.int_n_mode = max2871_regs_t::INT_N_MODE_FRAC_N; - } - - _regs.lds = pfd_freq <= 32e6 ? max2871_regs_t::LDS_SLOW : max2871_regs_t::LDS_FAST; - - _regs.frac_12_bit = FRAC; - _regs.int_16_bit = N; - _regs.mod_12_bit = MOD; - _regs.clock_divider_12_bit = std::max(1, int(std::ceil(400e-6*pfd_freq/MOD))); - _regs.feedback_select = (target_freq >= 3.0e9) ? - max2871_regs_t::FEEDBACK_SELECT_DIVIDED : - max2871_regs_t::FEEDBACK_SELECT_FUNDAMENTAL; - _regs.r_counter_10_bit = R; - _regs.reference_divide_by_2 = T ? - max2871_regs_t::REFERENCE_DIVIDE_BY_2_ENABLED : - max2871_regs_t::REFERENCE_DIVIDE_BY_2_DISABLED; - _regs.reference_doubler = D ? - max2871_regs_t::REFERENCE_DOUBLER_ENABLED : - max2871_regs_t::REFERENCE_DOUBLER_DISABLED; - _regs.band_select_clock_div = BS; - _regs.bs_msb = (BS & 0x300) >> 8; - UHD_ASSERT_THROW(rfdivsel_to_enum.has_key(RFdiv)); - _regs.rf_divider_select = rfdivsel_to_enum[RFdiv]; - - switch (output_power) - { - case -4: - _regs.output_power = max2871_regs_t::OUTPUT_POWER_M4DBM; - break; - case -1: - _regs.output_power = max2871_regs_t::OUTPUT_POWER_M1DBM; - break; - case 2: - _regs.output_power = max2871_regs_t::OUTPUT_POWER_2DBM; - break; - case 5: - _regs.output_power = max2871_regs_t::OUTPUT_POWER_5DBM; - break; - default: - UHD_THROW_INVALID_CODE_PATH(); - break; - } - - write_regs(); - - // MAX2871 needs a 20ms delay after tuning for the first time - // for the lock detect to be reliable. - if (_first_tune) - { - boost::this_thread::sleep(boost::posix_time::milliseconds(20)); - _first_tune = false; - } - - return actual_freq; - }; - -private: - std::set<boost::uint32_t> get_changed_addrs() - { - return _regs.get_changed_addrs<boost::uint32_t>(); - }; - - boost::uint32_t get_reg(boost::uint32_t addr) - { - return _regs.get_reg(addr); - }; - - void save_state() - { - if (_save_state) - _regs.save_state(); - } - - max2871_regs_t _regs; - bool _save_state; - bool _first_tune; -}; - /*********************************************************************** * UBX Data Structures **********************************************************************/ @@ -606,7 +95,7 @@ struct ubx_gpio_field_info_t boost::uint32_t offset; boost::uint32_t mask; boost::uint32_t width; - enum direction_t {OUTPUT,INPUT} direction; + enum {OUTPUT,INPUT} direction; bool is_atr_controlled; boost::uint32_t atr_idle; boost::uint32_t atr_tx; @@ -653,7 +142,16 @@ enum spi_dest_t { /*********************************************************************** * UBX Constants **********************************************************************/ -static const freq_range_t ubx_freq_range(1.0e7, 6.0e9); +#define fMHz (1000000.0) +static const dboard_id_t UBX_PROTO_V3_TX_ID(0x73); +static const dboard_id_t UBX_PROTO_V3_RX_ID(0x74); +static const dboard_id_t UBX_PROTO_V4_TX_ID(0x75); +static const dboard_id_t UBX_PROTO_V4_RX_ID(0x76); +static const dboard_id_t UBX_V1_40MHZ_TX_ID(0x77); +static const dboard_id_t UBX_V1_40MHZ_RX_ID(0x78); +static const dboard_id_t UBX_V1_160MHZ_TX_ID(0x79); +static const dboard_id_t UBX_V1_160MHZ_RX_ID(0x7A); +static const freq_range_t ubx_freq_range(10e6, 6.0e9); static const gain_range_t ubx_tx_gain_range(0, 31.5, double(0.5)); static const gain_range_t ubx_rx_gain_range(0, 31.5, double(0.5)); static const std::vector<std::string> ubx_pgas = boost::assign::list_of("PGA-TX")("PGA-RX"); @@ -664,6 +162,7 @@ static const std::vector<std::string> ubx_power_modes = boost::assign::list_of(" static const std::vector<std::string> ubx_xcvr_modes = boost::assign::list_of("FDX")("TX")("TX/RX")("RX"); static const ubx_gpio_field_info_t ubx_proto_gpio_info[] = { + //Field Unit Offset Mask Width Direction ATR IDLE,TX,RX,FDX {SPI_ADDR, dboard_iface::UNIT_TX, 0, 0x7, 3, ubx_gpio_field_info_t::INPUT, false, 0, 0, 0, 0}, {TX_EN_N, dboard_iface::UNIT_TX, 3, 0x1<<3, 1, ubx_gpio_field_info_t::INPUT, true, 1, 0, 1, 0}, {RX_EN_N, dboard_iface::UNIT_TX, 4, 0x1<<4, 1, ubx_gpio_field_info_t::INPUT, true, 1, 1, 0, 0}, @@ -676,6 +175,7 @@ static const ubx_gpio_field_info_t ubx_proto_gpio_info[] = { }; static const ubx_gpio_field_info_t ubx_v1_gpio_info[] = { + //Field Unit Offset Mask Width Direction ATR IDLE,TX,RX,FDX {SPI_ADDR, dboard_iface::UNIT_TX, 0, 0x7, 3, ubx_gpio_field_info_t::INPUT, false, 0, 0, 0, 0}, {CPLD_RST_N, dboard_iface::UNIT_TX, 3, 0x1<<3, 1, ubx_gpio_field_info_t::INPUT, false, 0, 0, 0, 0}, {RX_ANT, dboard_iface::UNIT_TX, 4, 0x1<<4, 1, ubx_gpio_field_info_t::INPUT, false, 0, 0, 0, 0}, @@ -695,7 +195,8 @@ static const ubx_gpio_field_info_t ubx_v1_gpio_info[] = { * Macros for routing and writing SPI registers **********************************************************************/ #define ROUTE_SPI(iface, dest) \ - iface->set_gpio_out(dboard_iface::UNIT_TX, dest, 0x7); + set_gpio_field(SPI_ADDR, dest); \ + write_gpio(); #define WRITE_SPI(iface, val) \ iface->write_spi(dboard_iface::UNIT_TX, spi_config_t::EDGE_RISE, val, 32); @@ -708,6 +209,9 @@ class ubx_xcvr : public xcvr_dboard_base public: ubx_xcvr(ctor_args_t args) : xcvr_dboard_base(args) { + double bw = 40e6; + double pfd_freq_max = 25e6; + //////////////////////////////////////////////////////////////////// // Setup GPIO hardware //////////////////////////////////////////////////////////////////// @@ -716,12 +220,15 @@ public: dboard_id_t tx_id = get_tx_id(); if (rx_id == UBX_PROTO_V3_RX_ID and tx_id == UBX_PROTO_V3_TX_ID) _rev = 0; - if (rx_id == UBX_PROTO_V4_RX_ID and tx_id == UBX_PROTO_V4_TX_ID) + else if (rx_id == UBX_PROTO_V4_RX_ID and tx_id == UBX_PROTO_V4_TX_ID) _rev = 1; else if (rx_id == UBX_V1_40MHZ_RX_ID and tx_id == UBX_V1_40MHZ_TX_ID) _rev = 1; else if (rx_id == UBX_V1_160MHZ_RX_ID and tx_id == UBX_V1_160MHZ_TX_ID) + { + bw = 160e6; _rev = 1; + } else UHD_THROW_INVALID_CODE_PATH(); @@ -730,10 +237,12 @@ public: case 0: for (size_t i = 0; i < sizeof(ubx_proto_gpio_info) / sizeof(ubx_gpio_field_info_t); i++) _gpio_map[ubx_proto_gpio_info[i].id] = ubx_proto_gpio_info[i]; + pfd_freq_max = 25e6; break; case 1: for (size_t i = 0; i < sizeof(ubx_v1_gpio_info) / sizeof(ubx_gpio_field_info_t); i++) _gpio_map[ubx_v1_gpio_info[i].id] = ubx_v1_gpio_info[i]; + pfd_freq_max = 50e6; break; } @@ -757,6 +266,34 @@ public: } // Enable the reference clocks that we need + if (_rev >= 1) + { + // set dboard clock rates to as close to the max PFD freq as possible + if (_iface->get_clock_rate(dboard_iface::UNIT_RX) > pfd_freq_max) + { + std::vector<double> rates = _iface->get_clock_rates(dboard_iface::UNIT_RX); + double highest_rate = 0.0; + BOOST_FOREACH(double rate, rates) + { + if (rate <= pfd_freq_max and rate > highest_rate) + highest_rate = rate; + } + _iface->set_clock_rate(dboard_iface::UNIT_RX, highest_rate); + _rx_target_pfd_freq = highest_rate; + } + if (_iface->get_clock_rate(dboard_iface::UNIT_TX) > pfd_freq_max) + { + std::vector<double> rates = _iface->get_clock_rates(dboard_iface::UNIT_TX); + double highest_rate = 0.0; + BOOST_FOREACH(double rate, rates) + { + if (rate <= pfd_freq_max and rate > highest_rate) + highest_rate = rate; + } + _iface->set_clock_rate(dboard_iface::UNIT_TX, highest_rate); + _tx_target_pfd_freq = highest_rate; + } + } _iface->set_clock_enabled(dboard_iface::UNIT_TX, true); _iface->set_clock_enabled(dboard_iface::UNIT_RX, true); @@ -801,17 +338,37 @@ public: // Initialize LOs if (_rev == 0) { - _txlo1.reset(new max2870(boost::bind(&ubx_xcvr::write_spi_regs, this, TXLO1, _1))); - _txlo2.reset(new max2870(boost::bind(&ubx_xcvr::write_spi_regs, this, TXLO2, _1))); - _rxlo1.reset(new max2870(boost::bind(&ubx_xcvr::write_spi_regs, this, RXLO1, _1))); - _rxlo2.reset(new max2870(boost::bind(&ubx_xcvr::write_spi_regs, this, RXLO2, _1))); + _txlo1 = max287x_iface::make<max2870>(boost::bind(&ubx_xcvr::write_spi_regs, this, TXLO1, _1)); + _txlo2 = max287x_iface::make<max2870>(boost::bind(&ubx_xcvr::write_spi_regs, this, TXLO2, _1)); + _rxlo1 = max287x_iface::make<max2870>(boost::bind(&ubx_xcvr::write_spi_regs, this, RXLO1, _1)); + _rxlo2 = max287x_iface::make<max2870>(boost::bind(&ubx_xcvr::write_spi_regs, this, RXLO2, _1)); + std::vector<max287x_iface::sptr> los = boost::assign::list_of(_txlo1)(_txlo2)(_rxlo1)(_rxlo2); + BOOST_FOREACH(max287x_iface::sptr lo, los) + { + lo->set_auto_retune(false); + lo->set_clock_divider_mode(max287x_iface::CLOCK_DIV_MODE_CLOCK_DIVIDER_OFF); + lo->set_muxout_mode(max287x_iface::MUXOUT_DLD); + lo->set_ld_pin_mode(max287x_iface::LD_PIN_MODE_DLD); + } } else if (_rev == 1) { - _txlo1.reset(new max2871(boost::bind(&ubx_xcvr::write_spi_regs, this, TXLO1, _1))); - _txlo2.reset(new max2871(boost::bind(&ubx_xcvr::write_spi_regs, this, TXLO2, _1))); - _rxlo1.reset(new max2871(boost::bind(&ubx_xcvr::write_spi_regs, this, RXLO1, _1))); - _rxlo2.reset(new max2871(boost::bind(&ubx_xcvr::write_spi_regs, this, RXLO2, _1))); + _txlo1 = max287x_iface::make<max2871>(boost::bind(&ubx_xcvr::write_spi_regs, this, TXLO1, _1)); + _txlo2 = max287x_iface::make<max2871>(boost::bind(&ubx_xcvr::write_spi_regs, this, TXLO2, _1)); + _rxlo1 = max287x_iface::make<max2871>(boost::bind(&ubx_xcvr::write_spi_regs, this, RXLO1, _1)); + _rxlo2 = max287x_iface::make<max2871>(boost::bind(&ubx_xcvr::write_spi_regs, this, RXLO2, _1)); + std::vector<max287x_iface::sptr> los = boost::assign::list_of(_txlo1)(_txlo2)(_rxlo1)(_rxlo2); + BOOST_FOREACH(max287x_iface::sptr lo, los) + { + lo->set_auto_retune(false); + lo->set_clock_divider_mode(max287x_iface::CLOCK_DIV_MODE_CLOCK_DIVIDER_OFF); + //lo->set_cycle_slip_mode(true); // tried it - caused longer lock times + lo->set_charge_pump_current(max287x_iface::CHARGE_PUMP_CURRENT_5_12MA); + lo->set_muxout_mode(max287x_iface::MUXOUT_SYNC); + lo->set_ld_pin_mode(max287x_iface::LD_PIN_MODE_DLD); + lo->set_low_noise_and_spur(max287x_iface::LOW_NOISE_AND_SPUR_LOW_NOISE); + lo->set_phase(0); + } } else { @@ -819,6 +376,7 @@ public: } // Initialize CPLD register + _prev_cpld_value = 0xFFFF; _cpld_reg.value = 0; write_cpld_reg(); @@ -865,9 +423,9 @@ public: get_tx_subtree()->create<bool>("use_lo_offset") .set(false); get_tx_subtree()->create<double>("bandwidth/value") - .set(2*20.0e6); //20MHz low-pass, complex double-sided, so it should be 2x20MHz=40MHz + .set(bw); get_tx_subtree()->create<meta_range_t>("bandwidth/range") - .set(freq_range_t(2*20.0e6, 2*20.0e6)); + .set(freq_range_t(bw, bw)); //////////////////////////////////////////////////////////////////// // Register RX properties @@ -898,9 +456,9 @@ public: get_rx_subtree()->create<bool>("use_lo_offset") .set(false); get_rx_subtree()->create<double>("bandwidth/value") - .set(2*20.0e6); //20MHz low-pass, complex double-sided, so it should be 2x20MHz=40MHz + .set(bw); get_rx_subtree()->create<meta_range_t>("bandwidth/range") - .set(freq_range_t(2*20.0e6, 2*20.0e6)); + .set(freq_range_t(bw, bw)); } ~ubx_xcvr(void) @@ -938,14 +496,14 @@ private: **********************************************************************/ void write_spi_reg(spi_dest_t dest, boost::uint32_t value) { - boost::mutex::scoped_lock lock(_spi_lock); + boost::mutex::scoped_lock lock(_spi_mutex); ROUTE_SPI(_iface, dest); WRITE_SPI(_iface, value); } void write_spi_regs(spi_dest_t dest, std::vector<boost::uint32_t> values) { - boost::mutex::scoped_lock lock(_spi_lock); + boost::mutex::scoped_lock lock(_spi_mutex); ROUTE_SPI(_iface, dest); BOOST_FOREACH(boost::uint32_t value, values) WRITE_SPI(_iface, value); @@ -958,7 +516,11 @@ private: void write_cpld_reg() { - write_spi_reg(CPLD, _cpld_reg.value); + if (_cpld_reg.value != _prev_cpld_value) + { + write_spi_reg(CPLD, _cpld_reg.value); + _prev_cpld_value = _cpld_reg.value; + } } void set_gpio_field(ubx_gpio_field_id_t id, boost::uint32_t value) @@ -996,7 +558,10 @@ private: return 0; ubx_gpio_field_info_t field_info = entry->second; if (field_info.direction == ubx_gpio_field_info_t::INPUT) - return 0; + { + ubx_gpio_reg_t *reg = (field_info.unit == dboard_iface::UNIT_TX ? &_tx_gpio_reg : &_rx_gpio_reg); + return (reg->value >> field_info.offset) & field_info.mask; + } // Read register boost::uint32_t value = _iface->read_gpio(field_info.unit); @@ -1028,6 +593,7 @@ private: **********************************************************************/ sensor_value_t get_locked(const std::string &pll_name) { + boost::mutex::scoped_lock lock(_mutex); assert_has(ubx_plls, pll_name, "ubx pll name"); if(pll_name == "TXLO") @@ -1053,6 +619,7 @@ private: // Set RX antennas void set_rx_ant(const std::string &ant) { + boost::mutex::scoped_lock lock(_mutex); //validate input assert_has(ubx_rx_antennas, ant, "ubx rx antenna name"); @@ -1070,6 +637,7 @@ private: **********************************************************************/ double set_tx_gain(double gain) { + boost::mutex::scoped_lock lock(_mutex); gain = ubx_tx_gain_range.clip(gain); int attn_code = int(std::floor(gain * 2)); _ubx_tx_atten_val = ((attn_code & 0x3F) << 10); @@ -1082,6 +650,7 @@ private: double set_rx_gain(double gain) { + boost::mutex::scoped_lock lock(_mutex); gain = ubx_rx_gain_range.clip(gain); int attn_code = int(std::floor(gain * 2)); _ubx_rx_atten_val = ((attn_code & 0x3F) << 10); @@ -1097,6 +666,7 @@ private: **********************************************************************/ double set_tx_freq(double freq) { + boost::mutex::scoped_lock lock(_mutex); double freq_lo1 = 0.0; double freq_lo2 = 0.0; double ref_freq = _iface->get_clock_rate(dboard_iface::UNIT_TX); @@ -1131,11 +701,12 @@ private: set_cpld_field(TXLO1_FSEL1, 0); set_cpld_field(TXLB_SEL, 1); set_cpld_field(TXHB_SEL, 0); - write_cpld_reg(); // Set LO1 to IF of 2100 MHz (offset from RX IF to reduce leakage) - freq_lo1 = _txlo1->set_freq_and_power(2100*fMHz, ref_freq, is_int_n, 5); + freq_lo1 = _txlo1->set_frequency(2100*fMHz, ref_freq, _tx_target_pfd_freq, is_int_n); + _txlo1->set_output_power(max287x_iface::OUTPUT_POWER_5DBM); // Set LO2 to IF minus desired frequency - freq_lo2 = _txlo2->set_freq_and_power(freq_lo1 - freq, ref_freq, is_int_n, 2); + freq_lo2 = _txlo2->set_frequency(freq_lo1 - freq, ref_freq, _tx_target_pfd_freq, is_int_n); + _txlo2->set_output_power(max287x_iface::OUTPUT_POWER_2DBM); } else if ((freq >= (500*fMHz)) && (freq <= (800*fMHz))) { @@ -1144,8 +715,8 @@ private: set_cpld_field(TXLO1_FSEL1, 1); set_cpld_field(TXLB_SEL, 0); set_cpld_field(TXHB_SEL, 1); - write_cpld_reg(); - freq_lo1 = _txlo1->set_freq_and_power(freq, ref_freq, is_int_n, 2); + freq_lo1 = _txlo1->set_frequency(freq, ref_freq, _tx_target_pfd_freq, is_int_n); + _txlo1->set_output_power(max287x_iface::OUTPUT_POWER_2DBM); } else if ((freq > (800*fMHz)) && (freq <= (1000*fMHz))) { @@ -1154,8 +725,8 @@ private: set_cpld_field(TXLO1_FSEL1, 1); set_cpld_field(TXLB_SEL, 0); set_cpld_field(TXHB_SEL, 1); - write_cpld_reg(); - freq_lo1 = _txlo1->set_freq_and_power(freq, ref_freq, is_int_n, 5); + freq_lo1 = _txlo1->set_frequency(freq, ref_freq, _tx_target_pfd_freq, is_int_n); + _txlo1->set_output_power(max287x_iface::OUTPUT_POWER_5DBM); } else if ((freq > (1000*fMHz)) && (freq <= (2200*fMHz))) { @@ -1164,8 +735,8 @@ private: set_cpld_field(TXLO1_FSEL1, 0); set_cpld_field(TXLB_SEL, 0); set_cpld_field(TXHB_SEL, 1); - write_cpld_reg(); - freq_lo1 = _txlo1->set_freq_and_power(freq, ref_freq, is_int_n, 2); + freq_lo1 = _txlo1->set_frequency(freq, ref_freq, _tx_target_pfd_freq, is_int_n); + _txlo1->set_output_power(max287x_iface::OUTPUT_POWER_2DBM); } else if ((freq > (2200*fMHz)) && (freq <= (2500*fMHz))) { @@ -1174,8 +745,8 @@ private: set_cpld_field(TXLO1_FSEL1, 0); set_cpld_field(TXLB_SEL, 0); set_cpld_field(TXHB_SEL, 1); - write_cpld_reg(); - freq_lo1 = _txlo1->set_freq_and_power(freq, ref_freq, is_int_n, 2); + freq_lo1 = _txlo1->set_frequency(freq, ref_freq, _tx_target_pfd_freq, is_int_n); + _txlo1->set_output_power(max287x_iface::OUTPUT_POWER_2DBM); } else if ((freq > (2500*fMHz)) && (freq <= (6000*fMHz))) { @@ -1184,8 +755,50 @@ private: set_cpld_field(TXLO1_FSEL1, 0); set_cpld_field(TXLB_SEL, 0); set_cpld_field(TXHB_SEL, 1); + freq_lo1 = _txlo1->set_frequency(freq, ref_freq, _tx_target_pfd_freq, is_int_n); + _txlo1->set_output_power(max287x_iface::OUTPUT_POWER_5DBM); + } + + // To reduce the number of commands issued to the device, write to the + // SPI destination already addressed first. This avoids the writes to + // the GPIO registers to route the SPI to the same destination. + switch (get_gpio_field(SPI_ADDR)) + { + case TXLO1: + _txlo1->commit(); + if (freq < (500*fMHz)) _txlo2->commit(); + write_cpld_reg(); + break; + case TXLO2: + if (freq < (500*fMHz)) _txlo2->commit(); + _txlo1->commit(); write_cpld_reg(); - freq_lo1 = _txlo1->set_freq_and_power(freq, ref_freq, is_int_n, 5); + break; + default: + write_cpld_reg(); + _txlo1->commit(); + if (freq < (500*fMHz)) _txlo2->commit(); + break; + } + + if (_txlo1->can_sync()) + { + // Send phase sync signal only if the command time is set + uhd::time_spec_t cmd_time = _iface->get_command_time(); + if (cmd_time != uhd::time_spec_t(0.0)) + { + // Delay 400 microseconds to allow LOs to lock + cmd_time += uhd::time_spec_t(0.0004); + _iface->set_command_time(cmd_time); + set_gpio_field(TXLO1_SYNC, 1); + set_gpio_field(TXLO2_SYNC, 1); + write_gpio(); + // De-assert SYNC + // Head of line blocking means the time does not need to be set. + set_gpio_field(TXLO1_SYNC, 0); + set_gpio_field(TXLO2_SYNC, 0); + write_gpio(); + } } _tx_freq = freq_lo1 - freq_lo2; @@ -1199,6 +812,7 @@ private: double set_rx_freq(double freq) { + boost::mutex::scoped_lock lock(_mutex); double freq_lo1 = 0.0; double freq_lo2 = 0.0; double ref_freq = _iface->get_clock_rate(dboard_iface::UNIT_RX); @@ -1231,11 +845,12 @@ private: set_cpld_field(RXLO1_FSEL1, 0); set_cpld_field(RXLB_SEL, 1); set_cpld_field(RXHB_SEL, 0); - write_cpld_reg(); // Set LO1 to IF of 2380 MHz (2440 MHz filter center minus 60 MHz offset to minimize LO leakage) - freq_lo1 = _rxlo1->set_freq_and_power(2380*fMHz, ref_freq, is_int_n, 5); + freq_lo1 = _rxlo1->set_frequency(2380*fMHz, ref_freq, _rx_target_pfd_freq, is_int_n); + _rxlo1->set_output_power(max287x_iface::OUTPUT_POWER_5DBM); // Set LO2 to IF minus desired frequency - freq_lo2 = _rxlo2->set_freq_and_power(freq_lo1 - freq, ref_freq, is_int_n, 2); + freq_lo2 = _rxlo2->set_frequency(freq_lo1 - freq, ref_freq, _rx_target_pfd_freq, is_int_n); + _rxlo2->set_output_power(max287x_iface::OUTPUT_POWER_2DBM); } else if ((freq >= 100*fMHz) && (freq < 500*fMHz)) { @@ -1246,11 +861,12 @@ private: set_cpld_field(RXLO1_FSEL1, 0); set_cpld_field(RXLB_SEL, 1); set_cpld_field(RXHB_SEL, 0); - write_cpld_reg(); // Set LO1 to IF of 2440 (center of filter) - freq_lo1 = _rxlo1->set_freq_and_power(2440*fMHz, ref_freq, is_int_n, 5); + freq_lo1 = _rxlo1->set_frequency(2440*fMHz, ref_freq, _rx_target_pfd_freq, is_int_n); + _rxlo1->set_output_power(max287x_iface::OUTPUT_POWER_5DBM); // Set LO2 to IF minus desired frequency - freq_lo2 = _rxlo2->set_freq_and_power(freq_lo1 - freq, ref_freq, is_int_n, 2); + freq_lo2 = _rxlo2->set_frequency(freq_lo1 - freq, ref_freq, _rx_target_pfd_freq, is_int_n); + _rxlo1->set_output_power(max287x_iface::OUTPUT_POWER_2DBM); } else if ((freq >= 500*fMHz) && (freq < 800*fMHz)) { @@ -1261,8 +877,8 @@ private: set_cpld_field(RXLO1_FSEL1, 1); set_cpld_field(RXLB_SEL, 0); set_cpld_field(RXHB_SEL, 1); - write_cpld_reg(); - freq_lo1 = _rxlo1->set_freq_and_power(freq, ref_freq, is_int_n, 2); + freq_lo1 = _rxlo1->set_frequency(freq, ref_freq, _rx_target_pfd_freq, is_int_n); + _rxlo1->set_output_power(max287x_iface::OUTPUT_POWER_2DBM); } else if ((freq >= 800*fMHz) && (freq < 1000*fMHz)) { @@ -1273,8 +889,8 @@ private: set_cpld_field(RXLO1_FSEL1, 1); set_cpld_field(RXLB_SEL, 0); set_cpld_field(RXHB_SEL, 1); - write_cpld_reg(); - freq_lo1 = _rxlo1->set_freq_and_power(freq, ref_freq, is_int_n, 5); + freq_lo1 = _rxlo1->set_frequency(freq, ref_freq, _rx_target_pfd_freq, is_int_n); + _rxlo1->set_output_power(max287x_iface::OUTPUT_POWER_5DBM); } else if ((freq >= 1000*fMHz) && (freq < 1500*fMHz)) { @@ -1285,8 +901,8 @@ private: set_cpld_field(RXLO1_FSEL1, 0); set_cpld_field(RXLB_SEL, 0); set_cpld_field(RXHB_SEL, 1); - write_cpld_reg(); - freq_lo1 = _rxlo1->set_freq_and_power(freq, ref_freq, is_int_n, 2); + freq_lo1 = _rxlo1->set_frequency(freq, ref_freq, _rx_target_pfd_freq, is_int_n); + _rxlo1->set_output_power(max287x_iface::OUTPUT_POWER_2DBM); } else if ((freq >= 1500*fMHz) && (freq < 2200*fMHz)) { @@ -1297,8 +913,8 @@ private: set_cpld_field(RXLO1_FSEL1, 0); set_cpld_field(RXLB_SEL, 0); set_cpld_field(RXHB_SEL, 1); - write_cpld_reg(); - freq_lo1 = _rxlo1->set_freq_and_power(freq, ref_freq, is_int_n, 2); + freq_lo1 = _rxlo1->set_frequency(freq, ref_freq, _rx_target_pfd_freq, is_int_n); + _rxlo1->set_output_power(max287x_iface::OUTPUT_POWER_2DBM); } else if ((freq >= 2200*fMHz) && (freq < 2500*fMHz)) { @@ -1309,8 +925,8 @@ private: set_cpld_field(RXLO1_FSEL1, 0); set_cpld_field(RXLB_SEL, 0); set_cpld_field(RXHB_SEL, 1); - write_cpld_reg(); - freq_lo1 = _rxlo1->set_freq_and_power(freq, ref_freq, is_int_n, 2); + freq_lo1 = _rxlo1->set_frequency(freq, ref_freq, _rx_target_pfd_freq, is_int_n); + _rxlo1->set_output_power(max287x_iface::OUTPUT_POWER_2DBM); } else if ((freq >= 2500*fMHz) && (freq <= 6000*fMHz)) { @@ -1321,15 +937,59 @@ private: set_cpld_field(RXLO1_FSEL1, 0); set_cpld_field(RXLB_SEL, 0); set_cpld_field(RXHB_SEL, 1); + freq_lo1 = _rxlo1->set_frequency(freq, ref_freq, _rx_target_pfd_freq, is_int_n); + _rxlo1->set_output_power(max287x_iface::OUTPUT_POWER_5DBM); + } + + // To reduce the number of commands issued to the device, write to the + // SPI destination already addressed first. This avoids the writes to + // the GPIO registers to route the SPI to the same destination. + switch (get_gpio_field(SPI_ADDR)) + { + case RXLO1: + _rxlo1->commit(); + if (freq < (500*fMHz)) _rxlo2->commit(); + write_cpld_reg(); + break; + case RXLO2: + if (freq < (500*fMHz)) _rxlo2->commit(); + _rxlo1->commit(); + write_cpld_reg(); + break; + default: write_cpld_reg(); - freq_lo1 = _rxlo1->set_freq_and_power(freq, ref_freq, is_int_n, 5); + _rxlo1->commit(); + if (freq < (500*fMHz)) _rxlo2->commit(); + break; + } + + if (_rxlo1->can_sync()) + { + // Send phase sync signal only if the command time is set + uhd::time_spec_t cmd_time = _iface->get_command_time(); + if (cmd_time != uhd::time_spec_t(0.0)) + { + // Delay 400 microseconds to allow LOs to lock + cmd_time += uhd::time_spec_t(0.0004); + _iface->set_command_time(cmd_time); + set_gpio_field(RXLO1_SYNC, 1); + set_gpio_field(RXLO2_SYNC, 1); + write_gpio(); + // De-assert SYNC + // Head of line blocking means the time does not need to be set. + set_gpio_field(RXLO1_SYNC, 0); + set_gpio_field(RXLO2_SYNC, 0); + write_gpio(); + } } - freq = freq_lo1 - freq_lo2; + _rx_freq = freq_lo1 - freq_lo2; + _rxlo1_freq = freq_lo1; + _rxlo2_freq = freq_lo2; - UHD_LOGV(rarely) << boost::format("UBX RX: the actual frequency is %f MHz") % (freq/1e6) << std::endl; + UHD_LOGV(rarely) << boost::format("UBX RX: the actual frequency is %f MHz") % (_rx_freq/1e6) << std::endl; - return freq; + return _rx_freq; } /*********************************************************************** @@ -1337,6 +997,7 @@ private: **********************************************************************/ void set_power_mode(std::string mode) { + boost::mutex::scoped_lock lock(_mutex); if (mode == "performance") { // FIXME: Response to ATR change is too slow for some components, @@ -1392,12 +1053,16 @@ private: * Variables **********************************************************************/ dboard_iface::sptr _iface; - boost::mutex _spi_lock; + boost::mutex _spi_mutex; + boost::mutex _mutex; ubx_cpld_reg_t _cpld_reg; - boost::shared_ptr<max287x_synthesizer_iface> _txlo1; - boost::shared_ptr<max287x_synthesizer_iface> _txlo2; - boost::shared_ptr<max287x_synthesizer_iface> _rxlo1; - boost::shared_ptr<max287x_synthesizer_iface> _rxlo2; + boost::uint32_t _prev_cpld_value; + boost::shared_ptr<max287x_iface> _txlo1; + boost::shared_ptr<max287x_iface> _txlo2; + boost::shared_ptr<max287x_iface> _rxlo1; + boost::shared_ptr<max287x_iface> _rxlo2; + double _tx_target_pfd_freq; + double _rx_target_pfd_freq; double _tx_gain; double _rx_gain; double _tx_freq; @@ -1431,8 +1096,8 @@ static dboard_base::sptr make_ubx(dboard_base::ctor_args_t args) UHD_STATIC_BLOCK(reg_ubx_dboards) { - dboard_manager::register_dboard(0x0074, 0x0073, &make_ubx, "UBX v0.3"); - dboard_manager::register_dboard(0x0076, 0x0075, &make_ubx, "UBX v0.4"); - dboard_manager::register_dboard(0x0078, 0x0077, &make_ubx, "UBX-40 v1"); - dboard_manager::register_dboard(0x007a, 0x0079, &make_ubx, "UBX-160 v1"); + dboard_manager::register_dboard(UBX_PROTO_V3_RX_ID, UBX_PROTO_V3_TX_ID, &make_ubx, "UBX v0.3"); + dboard_manager::register_dboard(UBX_PROTO_V4_RX_ID, UBX_PROTO_V4_TX_ID, &make_ubx, "UBX v0.4"); + dboard_manager::register_dboard(UBX_V1_40MHZ_RX_ID, UBX_V1_40MHZ_TX_ID, &make_ubx, "UBX-40 v1"); + dboard_manager::register_dboard(UBX_V1_160MHZ_RX_ID, UBX_V1_160MHZ_TX_ID, &make_ubx, "UBX-160 v1"); } diff --git a/host/lib/usrp/dboard/db_wbx_version4.cpp b/host/lib/usrp/dboard/db_wbx_version4.cpp index f80aeda77..81cdaefac 100644 --- a/host/lib/usrp/dboard/db_wbx_version4.cpp +++ b/host/lib/usrp/dboard/db_wbx_version4.cpp @@ -63,7 +63,7 @@ static int tx_pga0_gain_to_iobits(double &gain){ (attn_code & 8 ? 0 : TX_ATTN_8) | (attn_code & 4 ? 0 : TX_ATTN_4) | (attn_code & 2 ? 0 : TX_ATTN_2) | - (attn_code & 1 ? 0 : TX_ATTN_1) + (attn_code & 1 ? 0 : TX_ATTN_1) ) & TX_ATTN_MASK; UHD_LOGV(often) << boost::format( @@ -220,8 +220,8 @@ double wbx_base::wbx_version4::set_lo_freq(dboard_iface::unit_t unit, double tar //map prescaler setting to mininmum integer divider (N) values (pg.18 prescaler) static const uhd::dict<int, int> prescaler_to_min_int_div = map_list_of - (0,23) //adf4351_regs_t::PRESCALER_4_5 - (1,75) //adf4351_regs_t::PRESCALER_8_9 + (adf4351_regs_t::PRESCALER_4_5, 23) + (adf4351_regs_t::PRESCALER_8_9, 75) ; //map rf divider select output dividers to enums @@ -237,14 +237,13 @@ double wbx_base::wbx_version4::set_lo_freq(dboard_iface::unit_t unit, double tar double reference_freq = self_base->get_iface()->get_clock_rate(unit); //The mixer has a divide-by-2 stage on the LO port so the synthesizer - //frequency must 2x the target frequency + //frequency must 2x the target frequency. This introduces a 180 degree phase + //ambiguity when trying to synchronize the phase of multiple boards. double synth_target_freq = target_freq * 2; - //TODO: Document why the following has to be true - bool div_resync_enabled = (target_freq > reference_freq); adf4351_regs_t::prescaler_t prescaler = - synth_target_freq > 3e9 ? adf4351_regs_t::PRESCALER_8_9 : adf4351_regs_t::PRESCALER_4_5; - + synth_target_freq > 3.6e9 ? adf4351_regs_t::PRESCALER_8_9 : adf4351_regs_t::PRESCALER_4_5; + adf435x_tuning_constraints tuning_constraints; tuning_constraints.force_frac0 = is_int_n; tuning_constraints.band_sel_freq_max = 100e3; @@ -252,9 +251,13 @@ double wbx_base::wbx_version4::set_lo_freq(dboard_iface::unit_t unit, double tar tuning_constraints.int_range = uhd::range_t(prescaler_to_min_int_div[prescaler], 4095); tuning_constraints.pfd_freq_max = 25e6; tuning_constraints.rf_divider_range = uhd::range_t(1, 64); - //When divider resync is enabled, a 180 deg phase error is introduced when syncing - //multiple WBX boards. Switching to fundamental mode works arounds this issue. - tuning_constraints.feedback_after_divider = div_resync_enabled; + //The feedback of the divided frequency must be disabled whenever the target frequency + //divided by the minimum PFD frequency cannot meet the minimum integer divider (N) value. + //If it is disabled, additional phase ambiguity will be introduced. With a minimum PFD + //frequency of 10 MHz, synthesizer frequencies below 230 MHz (LO frequencies below 115 MHz) + //will have too much ambiguity to synchronize. + tuning_constraints.feedback_after_divider = + (int(synth_target_freq / 10e6) >= prescaler_to_min_int_div[prescaler]); double synth_actual_freq = 0; adf435x_tuning_settings tuning_settings = tune_adf435x_synth( @@ -281,7 +284,7 @@ double wbx_base::wbx_version4::set_lo_freq(dboard_iface::unit_t unit, double tar regs.feedback_select = tuning_constraints.feedback_after_divider ? adf4351_regs_t::FEEDBACK_SELECT_DIVIDED : adf4351_regs_t::FEEDBACK_SELECT_FUNDAMENTAL; - regs.clock_div_mode = div_resync_enabled ? + regs.clock_div_mode = tuning_constraints.feedback_after_divider ? adf4351_regs_t::CLOCK_DIV_MODE_RESYNC_ENABLE : adf4351_regs_t::CLOCK_DIV_MODE_FAST_LOCK; regs.prescaler = prescaler; diff --git a/host/lib/usrp/e300/e300_io_impl.cpp b/host/lib/usrp/e300/e300_io_impl.cpp index fa4915ed1..dadfb71e9 100644 --- a/host/lib/usrp/e300/e300_io_impl.cpp +++ b/host/lib/usrp/e300/e300_io_impl.cpp @@ -91,12 +91,21 @@ void e300_impl::_update_tick_rate(const double rate) } } +#define CHECK_BANDWIDTH(dir) \ + if (rate > _codec_ctrl->get_bw_filter_range(dir).stop()) { \ + UHD_MSG(warning) \ + << "Selected " << dir << " bandwidth (" << (rate/1e6) << " MHz) exceeds\n" \ + << "analog frontend filter bandwidth (" << (_codec_ctrl->get_bw_filter_range(dir).stop()/1e6) << " MHz)." \ + << std::endl; \ + } + void e300_impl::_update_rx_samp_rate(const size_t dspno, const double rate) { boost::shared_ptr<sph::recv_packet_streamer> my_streamer = boost::dynamic_pointer_cast<sph::recv_packet_streamer>(_radio_perifs[dspno].rx_streamer.lock()); if (my_streamer) my_streamer->set_samp_rate(rate); + CHECK_BANDWIDTH("Rx"); } void e300_impl::_update_tx_samp_rate(const size_t dspno, const double rate) @@ -105,6 +114,7 @@ void e300_impl::_update_tx_samp_rate(const size_t dspno, const double rate) boost::dynamic_pointer_cast<sph::send_packet_streamer>(_radio_perifs[dspno].tx_streamer.lock()); if (my_streamer) my_streamer->set_samp_rate(rate); + CHECK_BANDWIDTH("Tx"); } /*********************************************************************** diff --git a/host/lib/usrp/usrp1/codec_ctrl.cpp b/host/lib/usrp/usrp1/codec_ctrl.cpp index 2e922a3e2..8ba7b54ca 100644 --- a/host/lib/usrp/usrp1/codec_ctrl.cpp +++ b/host/lib/usrp/usrp1/codec_ctrl.cpp @@ -357,7 +357,7 @@ double usrp1_codec_ctrl_impl::fine_tune(double codec_rate, double target_freq) static const double scale_factor = std::pow(2.0, 24); boost::uint32_t freq_word = boost::uint32_t( - boost::math::round(abs((target_freq / codec_rate) * scale_factor))); + boost::math::round(std::abs((target_freq / codec_rate) * scale_factor))); double actual_freq = freq_word * codec_rate / scale_factor; diff --git a/host/lib/usrp/usrp2/fw_common.h b/host/lib/usrp/usrp2/fw_common.h index 337a1ad6f..cfaee0ddc 100644 --- a/host/lib/usrp/usrp2/fw_common.h +++ b/host/lib/usrp/usrp2/fw_common.h @@ -30,7 +30,7 @@ extern "C" { #endif //fpga and firmware compatibility numbers -#define USRP2_FPGA_COMPAT_NUM 10 +#define USRP2_FPGA_COMPAT_NUM 11 #define USRP2_FW_COMPAT_NUM 12 #define USRP2_FW_VER_MINOR 4 diff --git a/host/lib/usrp/usrp2/usrp2_fifo_ctrl.cpp b/host/lib/usrp/usrp2/usrp2_fifo_ctrl.cpp index 0276a7a66..e0544862d 100644 --- a/host/lib/usrp/usrp2/usrp2_fifo_ctrl.cpp +++ b/host/lib/usrp/usrp2/usrp2_fifo_ctrl.cpp @@ -33,7 +33,7 @@ static const size_t POKE32_CMD = (1 << 8); static const size_t PEEK32_CMD = 0; static const double ACK_TIMEOUT = 0.5; static const double MASSIVE_TIMEOUT = 10.0; //for when we wait on a timed command -static const boost::uint32_t MAX_SEQS_OUT = 15; +static const boost::uint32_t MAX_SEQS_OUT = 63; #define SPI_DIV SR_SPI_CORE + 0 #define SPI_CTRL SR_SPI_CORE + 1 diff --git a/host/lib/usrp/x300/x300_clock_ctrl.cpp b/host/lib/usrp/x300/x300_clock_ctrl.cpp index 3aebfea68..04a9e4bec 100644 --- a/host/lib/usrp/x300/x300_clock_ctrl.cpp +++ b/host/lib/usrp/x300/x300_clock_ctrl.cpp @@ -25,6 +25,8 @@ #include <cstdlib> static const double X300_REF_CLK_OUT_RATE = 10e6; +static const boost::uint16_t X300_MAX_CLKOUT_DIV = 1045; +static const double X300_DEFAULT_DBOARD_CLK_RATE = 50e6; using namespace uhd; @@ -36,375 +38,480 @@ class x300_clock_ctrl_impl : public x300_clock_ctrl { public: -~x300_clock_ctrl_impl(void) {} - -x300_clock_ctrl_impl(uhd::spi_iface::sptr spiface, - const size_t slaveno, - const size_t hw_rev, - const double master_clock_rate, - const double system_ref_rate): - _spiface(spiface), - _slaveno(slaveno), - _hw_rev(hw_rev), - _master_clock_rate(master_clock_rate), - _system_ref_rate(system_ref_rate) -{ -} - -void reset_clocks() { - set_master_clock_rate(_master_clock_rate); -} - -void sync_clocks(void) { - //soft sync: - //put the sync IO into output mode - FPGA must be input - //write low, then write high - this triggers a soft sync - _lmk04816_regs.SYNC_POL_INV = lmk04816_regs_t::SYNC_POL_INV_SYNC_LOW; - this->write_regs(11); - _lmk04816_regs.SYNC_POL_INV = lmk04816_regs_t::SYNC_POL_INV_SYNC_HIGH; - this->write_regs(11); -} - -double get_master_clock_rate(void) { - return _master_clock_rate; -} - -double get_sysref_clock_rate(void) { - return _system_ref_rate; -} + ~x300_clock_ctrl_impl(void) {} -double get_refout_clock_rate(void) { - //We support only one reference output rate - return X300_REF_CLK_OUT_RATE; -} - -void set_dboard_rate(const x300_clock_which_t, double rate) { - if(not doubles_are_equal(rate, get_master_clock_rate())) { - throw uhd::not_implemented_error("x3xx set dboard clock rate does not support setting an arbitrary clock rate"); + x300_clock_ctrl_impl(uhd::spi_iface::sptr spiface, + const size_t slaveno, + const size_t hw_rev, + const double master_clock_rate, + const double system_ref_rate): + _spiface(spiface), + _slaveno(slaveno), + _hw_rev(hw_rev), + _master_clock_rate(master_clock_rate), + _system_ref_rate(system_ref_rate) + { + init(); } -} - -std::vector<double> get_dboard_rates(const x300_clock_which_t) { - /* Right now, the only supported daughterboard clock rate is the master clock - * rate. TODO Implement divider settings for lower clock rates for legacy - * daughterboard support. */ - - std::vector<double> rates; - rates.push_back(get_master_clock_rate()); - return rates; -} -void set_ref_out(const bool enable) { - // TODO Implement divider configuration to allow for configurable output - // rates - if (enable) - _lmk04816_regs.CLKout10_TYPE = lmk04816_regs_t::CLKOUT10_TYPE_LVDS; - else - _lmk04816_regs.CLKout10_TYPE = lmk04816_regs_t::CLKOUT10_TYPE_P_DOWN; - this->write_regs(8); -} - -void write_regs(boost::uint8_t addr) { - boost::uint32_t data = _lmk04816_regs.get_reg(addr); - _spiface->write_spi(_slaveno, spi_config_t::EDGE_RISE, data,32); -} - - -private: - -void set_master_clock_rate(double clock_rate) { - /* The X3xx has two primary rates. The first is the - * _system_ref_rate, which is sourced from the "clock_source"/"value" field - * of the property tree, and whose value can be 10e6, 30.72e6, or 200e6. - * The _system_ref_rate is the input to the clocking system, and - * what comes out is a disciplined master clock running at the - * _master_clock_rate. As such, only certain combinations of - * system reference rates and master clock rates are supported. - * Additionally, a subset of these will operate in "zero delay" mode. */ - - enum opmode_t { INVALID, - m10M_200M_NOZDEL, // used for debug purposes only - m10M_200M_ZDEL, // Normal mode - 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 - - /* The default clocking mode is 10MHz reference generating a 200 MHz master - * clock, in zero-delay mode. */ - opmode_t clocking_mode = INVALID; - - if(doubles_are_equal(_system_ref_rate, 10e6)) { - if(doubles_are_equal(clock_rate, 184.32e6)) { - /* 10MHz reference, 184.32 MHz master clock out, NOT Zero Delay. */ - clocking_mode = m10M_184_32M_NOZDEL; - } else if(doubles_are_equal(clock_rate, 200e6)) { - /* 10MHz reference, 200 MHz master clock out, Zero Delay */ - clocking_mode = m10M_200M_ZDEL; - } else if(doubles_are_equal(clock_rate, 120e6)) { - /* 10MHz reference, 120 MHz master clock rate, Zero Delay */ - clocking_mode = m10M_120M_ZDEL; + void reset_clocks() { + _lmk04816_regs.RESET = lmk04816_regs_t::RESET_RESET; + this->write_regs(0); + _lmk04816_regs.RESET = lmk04816_regs_t::RESET_NO_RESET; + for (size_t i = 0; i <= 16; ++i) { + this->write_regs(i); } - } else if(doubles_are_equal(_system_ref_rate, 30.72e6)) { - if(doubles_are_equal(clock_rate, 184.32e6)) { - /* 30.72MHz reference, 184.32 MHz master clock out, Zero Delay */ - clocking_mode = m30_72M_184_32M_ZDEL; + for (size_t i = 24; i <= 31; ++i) { + this->write_regs(i); } + sync_clocks(); } - if(clocking_mode == INVALID) { - throw uhd::runtime_error(str(boost::format("A master clock rate of %f cannot be derived from a system reference rate of %f") % clock_rate % _system_ref_rate)); + void sync_clocks(void) { + //soft sync: + //put the sync IO into output mode - FPGA must be input + //write low, then write high - this triggers a soft sync + _lmk04816_regs.SYNC_POL_INV = lmk04816_regs_t::SYNC_POL_INV_SYNC_LOW; + this->write_regs(11); + _lmk04816_regs.SYNC_POL_INV = lmk04816_regs_t::SYNC_POL_INV_SYNC_HIGH; + this->write_regs(11); } - // For 200 MHz output, the VCO is run at 2400 MHz - // For the LTE/CPRI rate of 184.32 MHz, the VCO runs at 2580.48 MHz - - int vco_div = 0; - - // Note: PLL2 N2 prescaler is enabled for all cases - // PLL2 reference doubler is enabled for all cases - - /* All LMK04816 settings are from the LMK datasheet for our clocking - * architecture. Please refer to the datasheet for more information. */ - switch (clocking_mode) { - case m10M_200M_NOZDEL: - vco_div = 12; - _lmk04816_regs.MODE = lmk04816_regs_t::MODE_DUAL_INT; + double get_master_clock_rate(void) { + return _master_clock_rate; + } - // PLL1 - 2 MHz 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; + double get_sysref_clock_rate(void) { + return _system_ref_rate; + } - // PLL2 - 48 MHz compare frequency - _lmk04816_regs.PLL2_N_30 = 25; - _lmk04816_regs.PLL2_P_30 = lmk04816_regs_t::PLL2_P_30_DIV_2A; - _lmk04816_regs.PLL2_R_28 = 4; - _lmk04816_regs.PLL2_CP_GAIN_26 = lmk04816_regs_t::PLL2_CP_GAIN_26_3200UA; + double get_refout_clock_rate(void) { + //We support only one reference output rate + return X300_REF_CLK_OUT_RATE; + } + void set_dboard_rate(const x300_clock_which_t which, double rate) { + boost::uint16_t div = boost::uint16_t(_vco_freq / rate); + boost::uint16_t *reg = NULL; + boost::uint8_t addr = 0xFF; + + // Make sure requested rate is an even divisor of the VCO frequency + if (not doubles_are_equal(_vco_freq / div, rate)) + throw uhd::value_error("invalid dboard rate requested"); + + switch (which) + { + case X300_CLOCK_WHICH_DB0_RX: + case X300_CLOCK_WHICH_DB1_RX: + reg = &_lmk04816_regs.CLKout2_3_DIV; + addr = 1; break; + case X300_CLOCK_WHICH_DB0_TX: + case X300_CLOCK_WHICH_DB1_TX: + reg = &_lmk04816_regs.CLKout4_5_DIV; + addr = 2; + break; + default: + UHD_THROW_INVALID_CODE_PATH(); + } - case m10M_200M_ZDEL: - vco_div = 12; - _lmk04816_regs.MODE = lmk04816_regs_t::MODE_DUAL_INT_ZER_DELAY; + if (*reg == div) + return; - // PLL1 - 2 MHz compare frequency - _lmk04816_regs.PLL1_N_28 = 100; - _lmk04816_regs.PLL1_R_27 = 5; - _lmk04816_regs.PLL1_CP_GAIN_27 = lmk04816_regs_t::PLL1_CP_GAIN_27_1600UA; + // Since the clock rate on one daughter board cannot be changed without + // affecting the other daughter board, don't allow it. + throw uhd::not_implemented_error("x3xx set dboard clock rate does not support changing the clock rate"); - // PLL2 - 96 MHz compare frequency - _lmk04816_regs.PLL2_N_30 = 5; - _lmk04816_regs.PLL2_P_30 = lmk04816_regs_t::PLL2_P_30_DIV_5; - _lmk04816_regs.PLL2_R_28 = 2; + // This is open source code and users may need to enable this function + // to support other daughterboards. If so, comment out the line above + // that throws the error and allow the program to reach the code below. - if(_hw_rev <= 4) - _lmk04816_regs.PLL2_CP_GAIN_26 = lmk04816_regs_t::PLL2_CP_GAIN_26_1600UA; - else - _lmk04816_regs.PLL2_CP_GAIN_26 = lmk04816_regs_t::PLL2_CP_GAIN_26_400UA; + // The LMK04816 datasheet says the register must be written twice if SYNC is enabled + *reg = div; + write_regs(addr); + write_regs(addr); + sync_clocks(); + } + double get_dboard_rate(const x300_clock_which_t which) + { + double rate = 0.0; + switch (which) + { + case X300_CLOCK_WHICH_DB0_RX: + case X300_CLOCK_WHICH_DB1_RX: + rate = _vco_freq / _lmk04816_regs.CLKout2_3_DIV; break; + case X300_CLOCK_WHICH_DB0_TX: + case X300_CLOCK_WHICH_DB1_TX: + rate = _vco_freq / _lmk04816_regs.CLKout4_5_DIV; + break; + default: + UHD_THROW_INVALID_CODE_PATH(); + } + return rate; + } - case m30_72M_184_32M_ZDEL: - vco_div=14; - _lmk04816_regs.MODE = lmk04816_regs_t::MODE_DUAL_INT_ZER_DELAY; - - // PLL1 - 2.048 MHz compare frequency - _lmk04816_regs.PLL1_N_28 = 90; - _lmk04816_regs.PLL1_R_27 = 15; - _lmk04816_regs.PLL1_CP_GAIN_27 = lmk04816_regs_t::PLL1_CP_GAIN_27_100UA; + std::vector<double> get_dboard_rates(const x300_clock_which_t) + { + std::vector<double> rates; + for (size_t div = size_t(_vco_freq / _master_clock_rate); div <= X300_MAX_CLKOUT_DIV; div++) + rates.push_back(_vco_freq / div); + return rates; + } - // PLL2 - 7.68 MHz compare frequency - _lmk04816_regs.PLL2_N_30 = 168; - _lmk04816_regs.PLL2_P_30 = lmk04816_regs_t::PLL2_P_30_DIV_2A; - _lmk04816_regs.PLL2_R_28 = 25; - _lmk04816_regs.PLL2_CP_GAIN_26 = lmk04816_regs_t::PLL2_CP_GAIN_26_3200UA; + void enable_dboard_clock(const x300_clock_which_t which, const bool enable) + { + switch (which) + { + case X300_CLOCK_WHICH_DB0_RX: + if (enable != (_lmk04816_regs.CLKout2_TYPE == lmk04816_regs_t::CLKOUT2_TYPE_LVPECL_700MVPP)) + { + _lmk04816_regs.CLKout2_TYPE = enable ? lmk04816_regs_t::CLKOUT2_TYPE_LVPECL_700MVPP : lmk04816_regs_t::CLKOUT2_TYPE_P_DOWN; + write_regs(6); + } + break; + case X300_CLOCK_WHICH_DB1_RX: + if (enable != (_lmk04816_regs.CLKout3_TYPE == lmk04816_regs_t::CLKOUT3_TYPE_LVPECL_700MVPP)) + { + _lmk04816_regs.CLKout3_TYPE = enable ? lmk04816_regs_t::CLKOUT3_TYPE_LVPECL_700MVPP : lmk04816_regs_t::CLKOUT3_TYPE_P_DOWN; + write_regs(6); + } + break; + case X300_CLOCK_WHICH_DB0_TX: + if (enable != (_lmk04816_regs.CLKout5_TYPE == lmk04816_regs_t::CLKOUT5_TYPE_LVPECL_700MVPP)) + { + _lmk04816_regs.CLKout5_TYPE = enable ? lmk04816_regs_t::CLKOUT5_TYPE_LVPECL_700MVPP : lmk04816_regs_t::CLKOUT5_TYPE_P_DOWN; + write_regs(7); + } + break; + case X300_CLOCK_WHICH_DB1_TX: + if (enable != (_lmk04816_regs.CLKout4_TYPE == lmk04816_regs_t::CLKOUT4_TYPE_LVPECL_700MVPP)) + { + _lmk04816_regs.CLKout4_TYPE = enable ? lmk04816_regs_t::CLKOUT4_TYPE_LVPECL_700MVPP : lmk04816_regs_t::CLKOUT4_TYPE_P_DOWN; + write_regs(7); + } + break; + default: + UHD_THROW_INVALID_CODE_PATH(); + } + } - _lmk04816_regs.PLL2_R3_LF = lmk04816_regs_t::PLL2_R3_LF_1KILO_OHM; - _lmk04816_regs.PLL2_C3_LF = lmk04816_regs_t::PLL2_C3_LF_39PF; + void set_ref_out(const bool enable) { + // TODO Implement divider configuration to allow for configurable output + // rates + if (enable) + _lmk04816_regs.CLKout10_TYPE = lmk04816_regs_t::CLKOUT10_TYPE_LVDS; + else + _lmk04816_regs.CLKout10_TYPE = lmk04816_regs_t::CLKOUT10_TYPE_P_DOWN; + this->write_regs(8); + } - _lmk04816_regs.PLL2_R4_LF = lmk04816_regs_t::PLL2_R4_LF_1KILO_OHM; - _lmk04816_regs.PLL2_C4_LF = lmk04816_regs_t::PLL2_C4_LF_34PF; + void write_regs(boost::uint8_t addr) { + boost::uint32_t data = _lmk04816_regs.get_reg(addr); + _spiface->write_spi(_slaveno, spi_config_t::EDGE_RISE, data,32); + } - break; - case m10M_184_32M_NOZDEL: - vco_div=14; - _lmk04816_regs.MODE = lmk04816_regs_t::MODE_DUAL_INT; +private: - // PLL1 - 2 MHz 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; + void init() { + /* The X3xx has two primary rates. The first is the + * _system_ref_rate, which is sourced from the "clock_source"/"value" field + * of the property tree, and whose value can be 10e6, 30.72e6, or 200e6. + * The _system_ref_rate is the input to the clocking system, and + * what comes out is a disciplined master clock running at the + * _master_clock_rate. As such, only certain combinations of + * system reference rates and master clock rates are supported. + * Additionally, a subset of these will operate in "zero delay" mode. */ + + enum opmode_t { INVALID, + m10M_200M_NOZDEL, // used for debug purposes only + m10M_200M_ZDEL, // Normal mode + 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 + + /* The default clocking mode is 10MHz reference generating a 200 MHz master + * clock, in zero-delay mode. */ + opmode_t clocking_mode = INVALID; + + if(doubles_are_equal(_system_ref_rate, 10e6)) { + if(doubles_are_equal(_master_clock_rate, 184.32e6)) { + /* 10MHz reference, 184.32 MHz master clock out, NOT Zero Delay. */ + clocking_mode = m10M_184_32M_NOZDEL; + } else if(doubles_are_equal(_master_clock_rate, 200e6)) { + /* 10MHz reference, 200 MHz master clock out, Zero Delay */ + clocking_mode = m10M_200M_ZDEL; + } else if(doubles_are_equal(_master_clock_rate, 120e6)) { + /* 10MHz reference, 120 MHz master clock rate, Zero Delay */ + clocking_mode = m10M_120M_ZDEL; + } + } else if(doubles_are_equal(_system_ref_rate, 30.72e6)) { + if(doubles_are_equal(_master_clock_rate, 184.32e6)) { + /* 30.72MHz reference, 184.32 MHz master clock out, Zero Delay */ + clocking_mode = m30_72M_184_32M_ZDEL; + } + } - // PLL2 - 7.68 MHz compare frequency - _lmk04816_regs.PLL2_N_30 = 168; - _lmk04816_regs.PLL2_P_30 = lmk04816_regs_t::PLL2_P_30_DIV_2A; - _lmk04816_regs.PLL2_R_28 = 25; - _lmk04816_regs.PLL2_CP_GAIN_26 = lmk04816_regs_t::PLL2_CP_GAIN_26_3200UA; + if(clocking_mode == INVALID) { + throw uhd::runtime_error(str(boost::format("A master clock rate of %f cannot be derived from a system reference rate of %f") % _master_clock_rate % _system_ref_rate)); + } - _lmk04816_regs.PLL2_R3_LF = lmk04816_regs_t::PLL2_R3_LF_4KILO_OHM; - _lmk04816_regs.PLL2_C3_LF = lmk04816_regs_t::PLL2_C3_LF_39PF; + // For 200 MHz output, the VCO is run at 2400 MHz + // For the LTE/CPRI rate of 184.32 MHz, the VCO runs at 2580.48 MHz - _lmk04816_regs.PLL2_R4_LF = lmk04816_regs_t::PLL2_R4_LF_1KILO_OHM; - _lmk04816_regs.PLL2_C4_LF = lmk04816_regs_t::PLL2_C4_LF_71PF; + // Note: PLL2 N2 prescaler is enabled for all cases + // PLL2 reference doubler is enabled for all cases - break; + /* All LMK04816 settings are from the LMK datasheet for our clocking + * architecture. Please refer to the datasheet for more information. */ + switch (clocking_mode) { + case m10M_200M_NOZDEL: + _vco_freq = 2400e6; + _lmk04816_regs.MODE = lmk04816_regs_t::MODE_DUAL_INT; - case m10M_120M_ZDEL: - vco_div = 20; - _lmk04816_regs.MODE = lmk04816_regs_t::MODE_DUAL_INT_ZER_DELAY; + // PLL1 - 2 MHz 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; - // PLL1 - 2 MHz compare frequency - _lmk04816_regs.PLL1_N_28 = 60; - _lmk04816_regs.PLL1_R_27 = 5; - _lmk04816_regs.PLL1_CP_GAIN_27 = lmk04816_regs_t::PLL1_CP_GAIN_27_100UA; + // PLL2 - 48 MHz compare frequency + _lmk04816_regs.PLL2_N_30 = 25; + _lmk04816_regs.PLL2_P_30 = lmk04816_regs_t::PLL2_P_30_DIV_2A; + _lmk04816_regs.PLL2_R_28 = 4; + _lmk04816_regs.PLL2_CP_GAIN_26 = lmk04816_regs_t::PLL2_CP_GAIN_26_3200UA; - // PLL2 - 96 MHz compare frequency - _lmk04816_regs.PLL2_N_30 = 5; - _lmk04816_regs.PLL2_P_30 = lmk04816_regs_t::PLL2_P_30_DIV_5; - _lmk04816_regs.PLL2_R_28 = 2; + break; - if(_hw_rev <= 4) - _lmk04816_regs.PLL2_CP_GAIN_26 = lmk04816_regs_t::PLL2_CP_GAIN_26_1600UA; - else - _lmk04816_regs.PLL2_CP_GAIN_26 = lmk04816_regs_t::PLL2_CP_GAIN_26_400UA; + case m10M_200M_ZDEL: + _vco_freq = 2400e6; + _lmk04816_regs.MODE = lmk04816_regs_t::MODE_DUAL_INT_ZER_DELAY; - break; + // PLL1 - 2 MHz compare frequency + _lmk04816_regs.PLL1_N_28 = 5; + _lmk04816_regs.PLL1_R_27 = 5; + _lmk04816_regs.PLL1_CP_GAIN_27 = lmk04816_regs_t::PLL1_CP_GAIN_27_1600UA; + + // PLL2 - 96 MHz compare frequency + _lmk04816_regs.PLL2_N_30 = 5; + _lmk04816_regs.PLL2_P_30 = lmk04816_regs_t::PLL2_P_30_DIV_5; + _lmk04816_regs.PLL2_R_28 = 2; + + if(_hw_rev <= 4) + _lmk04816_regs.PLL2_CP_GAIN_26 = lmk04816_regs_t::PLL2_CP_GAIN_26_1600UA; + else + _lmk04816_regs.PLL2_CP_GAIN_26 = lmk04816_regs_t::PLL2_CP_GAIN_26_400UA; + + break; + + case m30_72M_184_32M_ZDEL: + _vco_freq = 2580.48e6; + _lmk04816_regs.MODE = lmk04816_regs_t::MODE_DUAL_INT_ZER_DELAY; + + // PLL1 - 2.048 MHz compare frequency + _lmk04816_regs.PLL1_N_28 = 15; + _lmk04816_regs.PLL1_R_27 = 15; + _lmk04816_regs.PLL1_CP_GAIN_27 = lmk04816_regs_t::PLL1_CP_GAIN_27_100UA; + + // PLL2 - 7.68 MHz compare frequency + _lmk04816_regs.PLL2_N_30 = 168; + _lmk04816_regs.PLL2_P_30 = lmk04816_regs_t::PLL2_P_30_DIV_2A; + _lmk04816_regs.PLL2_R_28 = 25; + _lmk04816_regs.PLL2_CP_GAIN_26 = lmk04816_regs_t::PLL2_CP_GAIN_26_3200UA; + + _lmk04816_regs.PLL2_R3_LF = lmk04816_regs_t::PLL2_R3_LF_1KILO_OHM; + _lmk04816_regs.PLL2_C3_LF = lmk04816_regs_t::PLL2_C3_LF_39PF; + + _lmk04816_regs.PLL2_R4_LF = lmk04816_regs_t::PLL2_R4_LF_1KILO_OHM; + _lmk04816_regs.PLL2_C4_LF = lmk04816_regs_t::PLL2_C4_LF_34PF; + + break; + + case m10M_184_32M_NOZDEL: + _vco_freq = 2580.48e6; + _lmk04816_regs.MODE = lmk04816_regs_t::MODE_DUAL_INT; + + // PLL1 - 2 MHz 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 - 7.68 MHz compare frequency + _lmk04816_regs.PLL2_N_30 = 168; + _lmk04816_regs.PLL2_P_30 = lmk04816_regs_t::PLL2_P_30_DIV_2A; + _lmk04816_regs.PLL2_R_28 = 25; + _lmk04816_regs.PLL2_CP_GAIN_26 = lmk04816_regs_t::PLL2_CP_GAIN_26_3200UA; + + _lmk04816_regs.PLL2_R3_LF = lmk04816_regs_t::PLL2_R3_LF_4KILO_OHM; + _lmk04816_regs.PLL2_C3_LF = lmk04816_regs_t::PLL2_C3_LF_39PF; + + _lmk04816_regs.PLL2_R4_LF = lmk04816_regs_t::PLL2_R4_LF_1KILO_OHM; + _lmk04816_regs.PLL2_C4_LF = lmk04816_regs_t::PLL2_C4_LF_71PF; + + break; + + case m10M_120M_ZDEL: + _vco_freq = 2400e6; + _lmk04816_regs.MODE = lmk04816_regs_t::MODE_DUAL_INT_ZER_DELAY; + + // PLL1 - 2 MHz compare frequency + _lmk04816_regs.PLL1_N_28 = 5; + _lmk04816_regs.PLL1_R_27 = 5; + _lmk04816_regs.PLL1_CP_GAIN_27 = lmk04816_regs_t::PLL1_CP_GAIN_27_100UA; + + // PLL2 - 96 MHz compare frequency + _lmk04816_regs.PLL2_N_30 = 5; + _lmk04816_regs.PLL2_P_30 = lmk04816_regs_t::PLL2_P_30_DIV_5; + _lmk04816_regs.PLL2_R_28 = 2; + + if(_hw_rev <= 4) + _lmk04816_regs.PLL2_CP_GAIN_26 = lmk04816_regs_t::PLL2_CP_GAIN_26_1600UA; + else + _lmk04816_regs.PLL2_CP_GAIN_26 = lmk04816_regs_t::PLL2_CP_GAIN_26_400UA; + + break; + + default: + UHD_THROW_INVALID_CODE_PATH(); + break; + }; + + boost::uint16_t master_clock_div = static_cast<boost::uint16_t>( + std::ceil(_vco_freq / _master_clock_rate)); + + boost::uint16_t dboard_div = static_cast<boost::uint16_t>( + std::ceil(_vco_freq / X300_DEFAULT_DBOARD_CLK_RATE)); + + /* Reset the LMK clock controller. */ + _lmk04816_regs.RESET = lmk04816_regs_t::RESET_RESET; + this->write_regs(0); + _lmk04816_regs.RESET = lmk04816_regs_t::RESET_NO_RESET; + this->write_regs(0); + + /* Initial power-up */ + _lmk04816_regs.CLKout0_1_PD = lmk04816_regs_t::CLKOUT0_1_PD_POWER_UP; + this->write_regs(0); + _lmk04816_regs.CLKout0_1_DIV = master_clock_div; + _lmk04816_regs.CLKout0_ADLY_SEL = lmk04816_regs_t::CLKOUT0_ADLY_SEL_D_EV_X; + _lmk04816_regs.CLKout6_ADLY_SEL = lmk04816_regs_t::CLKOUT6_ADLY_SEL_D_BOTH; + _lmk04816_regs.CLKout7_ADLY_SEL = lmk04816_regs_t::CLKOUT7_ADLY_SEL_D_BOTH; + this->write_regs(0); + + // Register 1 + _lmk04816_regs.CLKout2_3_PD = lmk04816_regs_t::CLKOUT2_3_PD_POWER_UP; + _lmk04816_regs.CLKout2_3_DIV = dboard_div; + // Register 2 + _lmk04816_regs.CLKout4_5_PD = lmk04816_regs_t::CLKOUT4_5_PD_POWER_UP; + _lmk04816_regs.CLKout4_5_DIV = dboard_div; + // Register 3 + _lmk04816_regs.CLKout6_7_DIV = master_clock_div; + _lmk04816_regs.CLKout6_7_OSCin_Sel = lmk04816_regs_t::CLKOUT6_7_OSCIN_SEL_VCO; + // Register 4 + _lmk04816_regs.CLKout8_9_DIV = master_clock_div; + // Register 5 + _lmk04816_regs.CLKout10_11_PD = lmk04816_regs_t::CLKOUT10_11_PD_NORMAL; + _lmk04816_regs.CLKout10_11_DIV = + static_cast<boost::uint16_t>(std::ceil(_vco_freq / _system_ref_rate)); + + // Register 6 + _lmk04816_regs.CLKout0_TYPE = lmk04816_regs_t::CLKOUT0_TYPE_LVDS; //FPGA + _lmk04816_regs.CLKout1_TYPE = lmk04816_regs_t::CLKOUT1_TYPE_P_DOWN; //CPRI feedback clock, use LVDS + _lmk04816_regs.CLKout2_TYPE = lmk04816_regs_t::CLKOUT2_TYPE_LVPECL_700MVPP; //DB_0_RX + _lmk04816_regs.CLKout3_TYPE = lmk04816_regs_t::CLKOUT3_TYPE_LVPECL_700MVPP; //DB_1_RX + // Delay the FPGA_CLK by 900ps to ensure a safe ADC_SSCLK -> RADIO_CLK crossing. + // If the FPGA_CLK is delayed, we also need to delay the reference clocks going to the DAC + // because the data interface clock is generated from FPGA_CLK. + // NOTE: This delay value was verified at room temperature only. + _lmk04816_regs.CLKout0_1_ADLY = 0x10; + + // Register 7 + _lmk04816_regs.CLKout4_TYPE = lmk04816_regs_t::CLKOUT4_TYPE_LVPECL_700MVPP; //DB_1_TX + _lmk04816_regs.CLKout5_TYPE = lmk04816_regs_t::CLKOUT5_TYPE_LVPECL_700MVPP; //DB_0_TX + _lmk04816_regs.CLKout6_TYPE = lmk04816_regs_t::CLKOUT6_TYPE_LVPECL_700MVPP; //DB0_DAC + _lmk04816_regs.CLKout7_TYPE = lmk04816_regs_t::CLKOUT7_TYPE_LVPECL_700MVPP; //DB1_DAC + _lmk04816_regs.CLKout8_TYPE = lmk04816_regs_t::CLKOUT8_TYPE_LVPECL_700MVPP; //DB0_ADC + _lmk04816_regs.CLKout6_7_ADLY = _lmk04816_regs.CLKout0_1_ADLY; + + // Register 8 + _lmk04816_regs.CLKout9_TYPE = lmk04816_regs_t::CLKOUT9_TYPE_LVPECL_700MVPP; //DB1_ADC + _lmk04816_regs.CLKout10_TYPE = lmk04816_regs_t::CLKOUT10_TYPE_LVDS; //REF_CLKOUT + _lmk04816_regs.CLKout11_TYPE = lmk04816_regs_t::CLKOUT11_TYPE_P_DOWN; //Debug header, use LVPECL + + + // Register 10 + _lmk04816_regs.EN_OSCout0 = lmk04816_regs_t::EN_OSCOUT0_DISABLED; //Debug header + _lmk04816_regs.FEEDBACK_MUX = 5; //use output 10 (REF OUT) for feedback + _lmk04816_regs.EN_FEEDBACK_MUX = lmk04816_regs_t::EN_FEEDBACK_MUX_ENABLED; + + // Register 11 + // MODE set in individual cases above + _lmk04816_regs.SYNC_QUAL = lmk04816_regs_t::SYNC_QUAL_FB_MUX; + _lmk04816_regs.EN_SYNC = lmk04816_regs_t::EN_SYNC_ENABLE; + _lmk04816_regs.NO_SYNC_CLKout0_1 = lmk04816_regs_t::NO_SYNC_CLKOUT0_1_CLOCK_XY_SYNC; + _lmk04816_regs.NO_SYNC_CLKout2_3 = lmk04816_regs_t::NO_SYNC_CLKOUT2_3_CLOCK_XY_SYNC; + _lmk04816_regs.NO_SYNC_CLKout4_5 = lmk04816_regs_t::NO_SYNC_CLKOUT4_5_CLOCK_XY_SYNC; + _lmk04816_regs.NO_SYNC_CLKout6_7 = lmk04816_regs_t::NO_SYNC_CLKOUT6_7_CLOCK_XY_SYNC; + _lmk04816_regs.NO_SYNC_CLKout8_9 = lmk04816_regs_t::NO_SYNC_CLKOUT8_9_CLOCK_XY_SYNC; + _lmk04816_regs.NO_SYNC_CLKout10_11 = lmk04816_regs_t::NO_SYNC_CLKOUT10_11_CLOCK_XY_SYNC; + _lmk04816_regs.SYNC_TYPE = lmk04816_regs_t::SYNC_TYPE_INPUT; + + // Register 12 + _lmk04816_regs.LD_MUX = lmk04816_regs_t::LD_MUX_BOTH; + + /* Input Clock Configurations */ + // Register 13 + _lmk04816_regs.EN_CLKin0 = lmk04816_regs_t::EN_CLKIN0_NO_VALID_USE; // This is not connected + _lmk04816_regs.EN_CLKin2 = lmk04816_regs_t::EN_CLKIN2_NO_VALID_USE; // Used only for CPRI + _lmk04816_regs.Status_CLKin1_MUX = lmk04816_regs_t::STATUS_CLKIN1_MUX_UWIRE_RB; + _lmk04816_regs.CLKin_Select_MODE = lmk04816_regs_t::CLKIN_SELECT_MODE_CLKIN1_MAN; + _lmk04816_regs.HOLDOVER_MUX = lmk04816_regs_t::HOLDOVER_MUX_PLL1_R; + // Register 14 + _lmk04816_regs.Status_CLKin1_TYPE = lmk04816_regs_t::STATUS_CLKIN1_TYPE_OUT_PUSH_PULL; + _lmk04816_regs.Status_CLKin0_TYPE = lmk04816_regs_t::STATUS_CLKIN0_TYPE_OUT_PUSH_PULL; + + // Register 26 + // PLL2_CP_GAIN_26 set above in individual cases + _lmk04816_regs.PLL2_CP_POL_26 = lmk04816_regs_t::PLL2_CP_POL_26_NEG_SLOPE; + _lmk04816_regs.EN_PLL2_REF_2X = lmk04816_regs_t::EN_PLL2_REF_2X_DOUBLED_FREQ_REF; + + // Register 27 + // PLL1_CP_GAIN_27 set in individual cases above + // PLL1_R_27 set in the individual cases above + + // Register 28 + // PLL1_N_28 and PLL2_R_28 are set in the individual cases above + + // Register 29 + _lmk04816_regs.PLL2_N_CAL_29 = _lmk04816_regs.PLL2_N_30; // N_CAL should always match N + _lmk04816_regs.OSCin_FREQ_29 = lmk04816_regs_t::OSCIN_FREQ_29_63_TO_127MHZ; + + // Register 30 + // PLL2_P_30 set in individual cases above + // PLL2_N_30 set in individual cases above + + /* Write the configuration values into the LMK */ + for (size_t i = 1; i <= 16; ++i) { + this->write_regs(i); + } + for (size_t i = 24; i <= 31; ++i) { + this->write_regs(i); + } - default: - UHD_THROW_INVALID_CODE_PATH(); - break; - }; - - /* Reset the LMK clock controller. */ - _lmk04816_regs.RESET = lmk04816_regs_t::RESET_RESET; - this->write_regs(0); - _lmk04816_regs.RESET = lmk04816_regs_t::RESET_NO_RESET; - this->write_regs(0); - - /* Initial power-up */ - _lmk04816_regs.CLKout0_1_PD = lmk04816_regs_t::CLKOUT0_1_PD_POWER_UP; - this->write_regs(0); - _lmk04816_regs.CLKout0_1_DIV = vco_div; - _lmk04816_regs.CLKout0_ADLY_SEL = lmk04816_regs_t::CLKOUT0_ADLY_SEL_D_EV_X; - _lmk04816_regs.CLKout6_ADLY_SEL = lmk04816_regs_t::CLKOUT6_ADLY_SEL_D_BOTH; - _lmk04816_regs.CLKout7_ADLY_SEL = lmk04816_regs_t::CLKOUT7_ADLY_SEL_D_BOTH; - this->write_regs(0); - - // Register 1 - _lmk04816_regs.CLKout2_3_PD = lmk04816_regs_t::CLKOUT2_3_PD_POWER_UP; - _lmk04816_regs.CLKout2_3_DIV = vco_div; - // Register 2 - _lmk04816_regs.CLKout4_5_PD = lmk04816_regs_t::CLKOUT4_5_PD_POWER_UP; - _lmk04816_regs.CLKout4_5_DIV = vco_div; - // Register 3 - _lmk04816_regs.CLKout6_7_DIV = vco_div; - _lmk04816_regs.CLKout6_7_OSCin_Sel = lmk04816_regs_t::CLKOUT6_7_OSCIN_SEL_VCO; - // Register 4 - _lmk04816_regs.CLKout8_9_DIV = vco_div; - // Register 5 - _lmk04816_regs.CLKout10_11_PD = lmk04816_regs_t::CLKOUT10_11_PD_NORMAL; - _lmk04816_regs.CLKout10_11_DIV = vco_div * static_cast<int>(clock_rate/X300_REF_CLK_OUT_RATE); - - // Register 6 - _lmk04816_regs.CLKout0_TYPE = lmk04816_regs_t::CLKOUT0_TYPE_LVDS; //FPGA - _lmk04816_regs.CLKout1_TYPE = lmk04816_regs_t::CLKOUT1_TYPE_P_DOWN; //CPRI feedback clock, use LVDS - _lmk04816_regs.CLKout2_TYPE = lmk04816_regs_t::CLKOUT2_TYPE_LVPECL_700MVPP; //DB_0_RX - _lmk04816_regs.CLKout3_TYPE = lmk04816_regs_t::CLKOUT3_TYPE_LVPECL_700MVPP; //DB_1_RX - // Delay the FPGA_CLK by 900ps to ensure a safe ADC_SSCLK -> RADIO_CLK crossing. - // If the FPGA_CLK is delayed, we also need to delay the reference clocks going to the DAC - // because the data interface clock is generated from FPGA_CLK. - // This delay may need to vary due to temperature. Tested and verified at room temperature only. - _lmk04816_regs.CLKout0_1_ADLY = 0x10; - _lmk04816_regs.CLKout6_7_ADLY = _lmk04816_regs.CLKout0_1_ADLY; - - // Register 7 - _lmk04816_regs.CLKout4_TYPE = lmk04816_regs_t::CLKOUT4_TYPE_LVPECL_700MVPP; //DB_1_TX - _lmk04816_regs.CLKout5_TYPE = lmk04816_regs_t::CLKOUT5_TYPE_LVPECL_700MVPP; //DB_0_TX - _lmk04816_regs.CLKout6_TYPE = lmk04816_regs_t::CLKOUT6_TYPE_LVPECL_700MVPP; //DB0_DAC - _lmk04816_regs.CLKout7_TYPE = lmk04816_regs_t::CLKOUT7_TYPE_LVPECL_700MVPP; //DB1_DAC - _lmk04816_regs.CLKout8_TYPE = lmk04816_regs_t::CLKOUT8_TYPE_LVPECL_700MVPP; //DB0_ADC - - // Register 8 - _lmk04816_regs.CLKout9_TYPE = lmk04816_regs_t::CLKOUT9_TYPE_LVPECL_700MVPP; //DB1_ADC - _lmk04816_regs.CLKout10_TYPE = lmk04816_regs_t::CLKOUT10_TYPE_LVDS; //REF_CLKOUT - _lmk04816_regs.CLKout11_TYPE = lmk04816_regs_t::CLKOUT11_TYPE_P_DOWN; //Debug header, use LVPECL - - - // Register 10 - _lmk04816_regs.EN_OSCout0 = lmk04816_regs_t::EN_OSCOUT0_DISABLED; //Debug header - _lmk04816_regs.FEEDBACK_MUX = 0; //use output 0 (FPGA clock) for feedback - _lmk04816_regs.EN_FEEDBACK_MUX = lmk04816_regs_t::EN_FEEDBACK_MUX_ENABLED; - - // Register 11 - // MODE set in individual cases above - _lmk04816_regs.SYNC_QUAL = lmk04816_regs_t::SYNC_QUAL_FB_MUX; - _lmk04816_regs.EN_SYNC = lmk04816_regs_t::EN_SYNC_ENABLE; - _lmk04816_regs.NO_SYNC_CLKout0_1 = lmk04816_regs_t::NO_SYNC_CLKOUT0_1_CLOCK_XY_SYNC; - _lmk04816_regs.NO_SYNC_CLKout2_3 = lmk04816_regs_t::NO_SYNC_CLKOUT2_3_CLOCK_XY_SYNC; - _lmk04816_regs.NO_SYNC_CLKout4_5 = lmk04816_regs_t::NO_SYNC_CLKOUT4_5_CLOCK_XY_SYNC; - _lmk04816_regs.NO_SYNC_CLKout6_7 = lmk04816_regs_t::NO_SYNC_CLKOUT6_7_CLOCK_XY_SYNC; - _lmk04816_regs.NO_SYNC_CLKout8_9 = lmk04816_regs_t::NO_SYNC_CLKOUT8_9_CLOCK_XY_SYNC; - _lmk04816_regs.NO_SYNC_CLKout10_11 = lmk04816_regs_t::NO_SYNC_CLKOUT10_11_CLOCK_XY_SYNC; - _lmk04816_regs.SYNC_EN_AUTO = lmk04816_regs_t::SYNC_EN_AUTO_SYNC_INT_GEN; - _lmk04816_regs.SYNC_POL_INV = lmk04816_regs_t::SYNC_POL_INV_SYNC_LOW; - _lmk04816_regs.SYNC_TYPE = lmk04816_regs_t::SYNC_TYPE_INPUT; - - // Register 12 - _lmk04816_regs.LD_MUX = lmk04816_regs_t::LD_MUX_BOTH; - - /* Input Clock Configurations */ - // Register 13 - _lmk04816_regs.EN_CLKin0 = lmk04816_regs_t::EN_CLKIN0_NO_VALID_USE; // This is not connected - _lmk04816_regs.EN_CLKin2 = lmk04816_regs_t::EN_CLKIN2_NO_VALID_USE; // Used only for CPRI - _lmk04816_regs.Status_CLKin1_MUX = lmk04816_regs_t::STATUS_CLKIN1_MUX_UWIRE_RB; - _lmk04816_regs.CLKin_Select_MODE = lmk04816_regs_t::CLKIN_SELECT_MODE_CLKIN1_MAN; - _lmk04816_regs.HOLDOVER_MUX = lmk04816_regs_t::HOLDOVER_MUX_PLL1_R; - // Register 14 - _lmk04816_regs.Status_CLKin1_TYPE = lmk04816_regs_t::STATUS_CLKIN1_TYPE_OUT_PUSH_PULL; - _lmk04816_regs.Status_CLKin0_TYPE = lmk04816_regs_t::STATUS_CLKIN0_TYPE_OUT_PUSH_PULL; - - // Register 26 - // PLL2_CP_GAIN_26 set above in individual cases - _lmk04816_regs.PLL2_CP_POL_26 = lmk04816_regs_t::PLL2_CP_POL_26_NEG_SLOPE; - _lmk04816_regs.EN_PLL2_REF_2X = lmk04816_regs_t::EN_PLL2_REF_2X_DOUBLED_FREQ_REF; - - // Register 27 - // PLL1_CP_GAIN_27 set in individual cases above - // PLL1_R_27 set in the individual cases above - - // Register 28 - // PLL1_N_28 and PLL2_R_28 are set in the individual cases above - - // Register 29 - _lmk04816_regs.PLL2_N_CAL_29 = _lmk04816_regs.PLL2_N_30; // N_CAL should always match N - _lmk04816_regs.OSCin_FREQ_29 = lmk04816_regs_t::OSCIN_FREQ_29_63_TO_127MHZ; - - // Register 30 - // PLL2_P_30 set in individual cases above - // PLL2_N_30 set in individual cases above - - /* Write the configuration values into the LMK */ - for (size_t i = 1; i <= 16; ++i) { - this->write_regs(i); - } - for (size_t i = 24; i <= 31; ++i) { - this->write_regs(i); + this->sync_clocks(); } - this->sync_clocks(); -} - -UHD_INLINE bool doubles_are_equal(double a, double b) { - return (std::fabs(a - b) < std::numeric_limits<double>::epsilon()); -} + UHD_INLINE bool doubles_are_equal(double a, double b) { + return (std::fabs(a - b) < std::numeric_limits<double>::epsilon()); + } -const spi_iface::sptr _spiface; -const size_t _slaveno; -const size_t _hw_rev; -const double _master_clock_rate; -const double _system_ref_rate; -lmk04816_regs_t _lmk04816_regs; + const spi_iface::sptr _spiface; + const size_t _slaveno; + const size_t _hw_rev; + const double _master_clock_rate; + const double _system_ref_rate; + lmk04816_regs_t _lmk04816_regs; + double _vco_freq; }; x300_clock_ctrl::sptr x300_clock_ctrl::make(uhd::spi_iface::sptr spiface, diff --git a/host/lib/usrp/x300/x300_clock_ctrl.hpp b/host/lib/usrp/x300/x300_clock_ctrl.hpp index 40b62b09a..9c08aa356 100644 --- a/host/lib/usrp/x300/x300_clock_ctrl.hpp +++ b/host/lib/usrp/x300/x300_clock_ctrl.hpp @@ -71,11 +71,24 @@ public: */ virtual void set_dboard_rate(const x300_clock_which_t which, double rate) = 0; + /*! Get the clock rate on the given daughterboard clock. + * \throw exception when rate invalid + * \return the clock rate in Hz + */ + virtual double get_dboard_rate(const x300_clock_which_t which) = 0; + /*! Get a list of possible daughterboard clock rates. * \return a list of clock rates in Hz */ virtual std::vector<double> get_dboard_rates(const x300_clock_which_t which) = 0; + /*! Enable or disable daughterboard clock. + * \param which which clock + * \param enable true=enable, false=disable + * \return a list of clock rates in Hz + */ + virtual void enable_dboard_clock(const x300_clock_which_t which, const bool enable) = 0; + /*! Turn the reference output on/off * \param true = on, false = off */ diff --git a/host/lib/usrp/x300/x300_dboard_iface.cpp b/host/lib/usrp/x300/x300_dboard_iface.cpp index c286e805a..502630109 100644 --- a/host/lib/usrp/x300/x300_dboard_iface.cpp +++ b/host/lib/usrp/x300/x300_dboard_iface.cpp @@ -111,12 +111,12 @@ x300_dboard_iface::x300_dboard_iface(const x300_dboard_iface_config_t &config): this->_write_aux_dac(unit); } + _clock_rates[UNIT_RX] = _config.clock->get_dboard_rate(_config.which_rx_clk); + _clock_rates[UNIT_TX] = _config.clock->get_dboard_rate(_config.which_tx_clk); + this->set_clock_enabled(UNIT_RX, false); this->set_clock_enabled(UNIT_TX, false); - this->set_clock_rate(UNIT_RX, _config.clock->get_master_clock_rate()); - this->set_clock_rate(UNIT_TX, _config.clock->get_master_clock_rate()); - //some test code /* @@ -153,16 +153,20 @@ x300_dboard_iface::~x300_dboard_iface(void) **********************************************************************/ void x300_dboard_iface::set_clock_rate(unit_t unit, double rate) { - _clock_rates[unit] = rate; //set to shadow + // Just return if the requested rate is already set + if (std::fabs(_clock_rates[unit] - rate) < std::numeric_limits<double>::epsilon()) + return; + switch(unit) { case UNIT_RX: _config.clock->set_dboard_rate(_config.which_rx_clk, rate); - return; + break; case UNIT_TX: _config.clock->set_dboard_rate(_config.which_tx_clk, rate); - return; + break; } + _clock_rates[unit] = rate; //set to shadow } double x300_dboard_iface::get_clock_rate(unit_t unit) @@ -183,9 +187,17 @@ std::vector<double> x300_dboard_iface::get_clock_rates(unit_t unit) } } -void x300_dboard_iface::set_clock_enabled(UHD_UNUSED(unit_t unit), UHD_UNUSED(bool enb)) +void x300_dboard_iface::set_clock_enabled(unit_t unit, bool enb) { - // TODO Variable DBoard clock control needs to be implemented for X300. + switch(unit) + { + case UNIT_RX: + return _config.clock->enable_dboard_clock(_config.which_rx_clk, enb); + case UNIT_TX: + return _config.clock->enable_dboard_clock(_config.which_tx_clk, enb); + default: + UHD_THROW_INVALID_CODE_PATH(); + } } double x300_dboard_iface::get_codec_rate(unit_t) diff --git a/host/lib/usrp/x300/x300_impl.hpp b/host/lib/usrp/x300/x300_impl.hpp index 9042ad2ca..c27133745 100644 --- a/host/lib/usrp/x300/x300_impl.hpp +++ b/host/lib/usrp/x300/x300_impl.hpp @@ -70,13 +70,13 @@ static const size_t X300_PCIE_RX_DATA_FRAME_SIZE = 8184; //bytes static const size_t X300_PCIE_TX_DATA_FRAME_SIZE = 8192; //bytes static const size_t X300_PCIE_DATA_NUM_FRAMES = 2048; static const size_t X300_PCIE_MSG_FRAME_SIZE = 256; //bytes -static const size_t X300_PCIE_MSG_NUM_FRAMES = 32; +static const size_t X300_PCIE_MSG_NUM_FRAMES = 64; static const size_t X300_10GE_DATA_FRAME_MAX_SIZE = 8000; //bytes static const size_t X300_1GE_DATA_FRAME_MAX_SIZE = 1472; //bytes static const size_t X300_ETH_MSG_FRAME_SIZE = uhd::transport::udp_simple::mtu; //bytes -static const size_t X300_ETH_MSG_NUM_FRAMES = 32; +static const size_t X300_ETH_MSG_NUM_FRAMES = 64; static const size_t X300_ETH_DATA_NUM_FRAMES = 32; static const double X300_DEFAULT_SYSREF_RATE = 10e6; |