1 //
2 // Copyright 2016 Ettus Research LLC
3 // Copyright 2018 Ettus Research, a National Instruments Company
4 //
5 // SPDX-License-Identifier: GPL-3.0-or-later
6 //
7 
8 #include "twinrx_experts.hpp"
9 #include "twinrx_gain_tables.hpp"
10 #include <uhd/exception.hpp>
11 #include <uhd/types/dict.hpp>
12 #include <uhd/types/ranges.hpp>
13 #include <uhd/utils/log.hpp>
14 #include <uhd/utils/math.hpp>
15 #include <boost/assign/list_of.hpp>
16 #include <boost/math/special_functions/round.hpp>
17 
18 using namespace uhd::experts;
19 using namespace uhd::math;
20 using namespace uhd::usrp::dboard::twinrx;
21 
22 /*!---------------------------------------------------------
23  * twinrx_scheduling_expert::resolve
24  * ---------------------------------------------------------
25  */
resolve()26 void twinrx_scheduling_expert::resolve()
27 {
28     // Currently a straight pass-through. To be expanded as needed
29     // when more advanced scheduling is needed
30     _rx_frontend_time = _command_time;
31 }
32 
33 /*!---------------------------------------------------------
34  * twinrx_freq_path_expert::resolve
35  * ---------------------------------------------------------
36  */
resolve()37 void twinrx_freq_path_expert::resolve()
38 {
39     // Lowband/highband switch point
40     static const double LB_HB_THRESHOLD_FREQ    = 1.8e9;
41     static const double LB_TARGET_IF1_FREQ      = 2.345e9;
42     static const double HB_TARGET_IF1_FREQ      = 1.25e9;
43     static const double INJ_SIDE_THRESHOLD_FREQ = 5.1e9;
44 
45     static const double FIXED_LO1_THRESHOLD_FREQ = 50e6;
46 
47     // Preselector filter switch point
48     static const double LB_FILT1_THRESHOLD_FREQ = 0.5e9;
49     static const double LB_FILT2_THRESHOLD_FREQ = 0.8e9;
50     static const double LB_FILT3_THRESHOLD_FREQ = 1.2e9;
51     static const double LB_FILT4_THRESHOLD_FREQ = 1.8e9;
52     static const double HB_FILT1_THRESHOLD_FREQ = 3.0e9;
53     static const double HB_FILT2_THRESHOLD_FREQ = 4.1e9;
54     static const double HB_FILT3_THRESHOLD_FREQ = 5.1e9;
55     static const double HB_FILT4_THRESHOLD_FREQ = 6.0e9;
56 
57     static const double LB_PREAMP_PRESEL_THRESHOLD_FREQ = 0.8e9;
58 
59     // Misc
60     static const double INST_BANDWIDTH           = 80e6;
61     static const double MANUAL_LO_HYSTERESIS_PPM = 1.0;
62 
63     static const freq_range_t FREQ_RANGE(10e6, 6e9);
64     rf_freq_abs_t rf_freq(FREQ_RANGE.clip(_rf_freq_d));
65 
66     // Choose low-band vs high-band depending on frequency
67     _signal_path = (rf_freq > LB_HB_THRESHOLD_FREQ) ? twinrx_ctrl::PATH_HIGHBAND
68                                                     : twinrx_ctrl::PATH_LOWBAND;
69     if (_signal_path == twinrx_ctrl::PATH_LOWBAND) {
70         // Choose low-band preselector filter
71         if (rf_freq < LB_FILT1_THRESHOLD_FREQ) {
72             _lb_presel = twinrx_ctrl::PRESEL_PATH1;
73         } else if (rf_freq < LB_FILT2_THRESHOLD_FREQ) {
74             _lb_presel = twinrx_ctrl::PRESEL_PATH2;
75         } else if (rf_freq < LB_FILT3_THRESHOLD_FREQ) {
76             _lb_presel = twinrx_ctrl::PRESEL_PATH3;
77         } else if (rf_freq < LB_FILT4_THRESHOLD_FREQ) {
78             _lb_presel = twinrx_ctrl::PRESEL_PATH4;
79         } else {
80             _lb_presel = twinrx_ctrl::PRESEL_PATH4;
81         }
82     } else if (_signal_path == twinrx_ctrl::PATH_HIGHBAND) {
83         // Choose high-band preselector filter
84         if (rf_freq < HB_FILT1_THRESHOLD_FREQ) {
85             _hb_presel = twinrx_ctrl::PRESEL_PATH1;
86         } else if (rf_freq < HB_FILT2_THRESHOLD_FREQ) {
87             _hb_presel = twinrx_ctrl::PRESEL_PATH2;
88         } else if (rf_freq < HB_FILT3_THRESHOLD_FREQ) {
89             _hb_presel = twinrx_ctrl::PRESEL_PATH3;
90         } else if (rf_freq < HB_FILT4_THRESHOLD_FREQ) {
91             _hb_presel = twinrx_ctrl::PRESEL_PATH4;
92         } else {
93             _hb_presel = twinrx_ctrl::PRESEL_PATH4;
94         }
95     } else {
96         UHD_THROW_INVALID_CODE_PATH();
97     }
98 
99     // Choose low-band preamp preselector
100     _lb_preamp_presel = (rf_freq > LB_PREAMP_PRESEL_THRESHOLD_FREQ);
101 
102     // Choose LO frequencies
103     const double target_if1_freq = (_signal_path == twinrx_ctrl::PATH_HIGHBAND)
104                                        ? HB_TARGET_IF1_FREQ
105                                        : LB_TARGET_IF1_FREQ;
106     const double target_if2_freq = _if_freq_d;
107 
108     // LO1
109     double lo1_freq_ideal = 0.0, lo2_freq_ideal = 0.0;
110     if (rf_freq <= FIXED_LO1_THRESHOLD_FREQ) {
111         // LO1 Freq static
112         lo1_freq_ideal = target_if1_freq + FIXED_LO1_THRESHOLD_FREQ;
113     } else if (rf_freq <= INJ_SIDE_THRESHOLD_FREQ) {
114         // High-side LO1 Injection
115         lo1_freq_ideal = rf_freq.get() + target_if1_freq;
116     } else {
117         // Low-side LO1 Injection
118         lo1_freq_ideal = rf_freq.get() - target_if1_freq;
119     }
120 
121     if (_lo1_freq_d.get_author() == experts::AUTHOR_USER) {
122         if (_lo1_freq_d.is_dirty()) { // Are we here because the LO frequency was set?
123             // The user explicitly requested to set the LO freq so don't touch it!
124         } else {
125             // Something else changed which may cause the LO frequency to update.
126             // Only commit if the frequency is stale. If the user's value is stale
127             // reset the author to expert.
128             if (rf_freq_ppm_t(lo1_freq_ideal, MANUAL_LO_HYSTERESIS_PPM)
129                 != _lo1_freq_d.get()) {
130                 _lo1_freq_d = lo1_freq_ideal; // Reset author
131             }
132         }
133     } else {
134         // The LO frequency was never set by the user. Let the expert take care of it
135         _lo1_freq_d = lo1_freq_ideal; // Reset author
136     }
137 
138     // LO2
139     lo_inj_side_t lo2_inj_side_ideal = _compute_lo2_inj_side(
140         lo1_freq_ideal, target_if1_freq, target_if2_freq, INST_BANDWIDTH);
141     if (lo2_inj_side_ideal == INJ_HIGH_SIDE) {
142         lo2_freq_ideal = target_if1_freq + target_if2_freq;
143     } else {
144         lo2_freq_ideal = target_if1_freq - target_if2_freq;
145     }
146 
147     if (_lo2_freq_d.get_author() == experts::AUTHOR_USER) {
148         if (_lo2_freq_d.is_dirty()) { // Are we here because the LO frequency was set?
149             // The user explicitly requested to set the LO freq so don't touch it!
150         } else {
151             // Something else changed which may cause the LO frequency to update.
152             // Only commit if the frequency is stale. If the user's value is stale
153             // reset the author to expert.
154             if (rf_freq_ppm_t(lo2_freq_ideal, MANUAL_LO_HYSTERESIS_PPM)
155                 != _lo2_freq_d.get()) {
156                 _lo2_freq_d = lo2_freq_ideal; // Reset author
157             }
158         }
159     } else {
160         // The LO frequency was never set by the user. Let the expert take care of it
161         _lo2_freq_d = lo2_freq_ideal; // Reset author
162     }
163 
164     // Determine injection side using the final LO frequency
165     _lo1_inj_side = (_lo1_freq_d > rf_freq.get()) ? INJ_HIGH_SIDE : INJ_LOW_SIDE;
166     _lo2_inj_side = (_lo2_freq_d > target_if1_freq) ? INJ_HIGH_SIDE : INJ_LOW_SIDE;
167 }
168 
_compute_lo2_inj_side(double lo1_freq,double if1_freq,double if2_freq,double bandwidth)169 lo_inj_side_t twinrx_freq_path_expert::_compute_lo2_inj_side(
170     double lo1_freq, double if1_freq, double if2_freq, double bandwidth)
171 {
172     static const int MAX_SPUR_ORDER = 5;
173     for (int ord = MAX_SPUR_ORDER; ord >= 1; ord--) {
174         // Check high-side injection first
175         if (not _has_mixer_spurs(
176                 lo1_freq, if1_freq + if2_freq, if2_freq, bandwidth, ord)) {
177             return INJ_HIGH_SIDE;
178         }
179         // Check low-side injection second
180         if (not _has_mixer_spurs(
181                 lo1_freq, if1_freq - if2_freq, if2_freq, bandwidth, ord)) {
182             return INJ_LOW_SIDE;
183         }
184     }
185     // If we reached here, then there are spurs everywhere. Pick high-side as the default
186     return INJ_HIGH_SIDE;
187 }
188 
_has_mixer_spurs(double lo1_freq,double lo2_freq,double if2_freq,double bandwidth,int spur_order)189 bool twinrx_freq_path_expert::_has_mixer_spurs(
190     double lo1_freq, double lo2_freq, double if2_freq, double bandwidth, int spur_order)
191 {
192     // Iterate through all N-th order harmomic combinations
193     // of LOs...
194     for (int lo1h_i = 1; lo1h_i <= spur_order; lo1h_i++) {
195         double lo1harm_freq = lo1_freq * lo1h_i;
196         for (int lo2h_i = 1; lo2h_i <= spur_order; lo2h_i++) {
197             double lo2harm_freq = lo2_freq * lo2h_i;
198             double hdelta       = lo1harm_freq - lo2harm_freq;
199             // .. and check if there is a mixer spur in the IF band
200             if (std::abs(hdelta + if2_freq) < bandwidth / 2
201                 or std::abs(hdelta - if2_freq) < bandwidth / 2) {
202                 return true;
203             }
204         }
205     }
206     // No spurs were found after NxN search
207     return false;
208 }
209 
210 /*!---------------------------------------------------------
211  * twinrx_freq_coercion_expert::resolve
212  * ---------------------------------------------------------
213  */
resolve()214 void twinrx_freq_coercion_expert::resolve()
215 {
216     const double actual_if2_freq = _if_freq_d;
217     const double actual_if1_freq = (_lo2_inj_side == INJ_LOW_SIDE)
218                                        ? (_lo2_freq_c + actual_if2_freq)
219                                        : (_lo2_freq_c - actual_if2_freq);
220 
221     _rf_freq_c = (_lo1_inj_side == INJ_LOW_SIDE) ? (_lo1_freq_c + actual_if1_freq)
222                                                  : (_lo1_freq_c - actual_if1_freq);
223 }
224 
225 /*!---------------------------------------------------------
226  * twinrx_nyquist_expert::resolve
227  * ---------------------------------------------------------
228  */
resolve()229 void twinrx_nyquist_expert::resolve()
230 {
231     // Do not execute when clear_command_time is called.
232     // This is a transition of the command time from non-zero to zero.
233     if (_rx_frontend_time == time_spec_t(0.0) and _cached_cmd_time != time_spec_t(0.0)) {
234         _cached_cmd_time = _rx_frontend_time;
235         return;
236     }
237 
238     // Do not execute twice for the same command time unless untimed
239     if (_rx_frontend_time == _cached_cmd_time and _rx_frontend_time != time_spec_t(0.0)) {
240         return;
241     }
242     _cached_cmd_time = _rx_frontend_time;
243 
244     double if_freq_sign = 1.0;
245     if (_lo1_inj_side == INJ_HIGH_SIDE)
246         if_freq_sign *= -1.0;
247     if (_lo2_inj_side == INJ_HIGH_SIDE)
248         if_freq_sign *= -1.0;
249     _if_freq_c = _if_freq_d * if_freq_sign;
250 
251     _db_iface->set_fe_connection(
252         dboard_iface::UNIT_RX, _channel, usrp::fe_connection_t(_codec_conn, _if_freq_c));
253 }
254 
255 /*!---------------------------------------------------------
256  * twinrx_chan_gain_expert::resolve
257  * ---------------------------------------------------------
258  */
resolve()259 void twinrx_chan_gain_expert::resolve()
260 {
261     if (_gain_profile != "default") {
262         // TODO: Implement me!
263         throw uhd::not_implemented_error("custom gain strategies not implemeted yet");
264     }
265 
266     // Lookup table using settings
267     const twinrx_gain_table table = twinrx_gain_table::lookup_table(_signal_path,
268         (_signal_path == twinrx_ctrl::PATH_HIGHBAND) ? _hb_presel : _lb_presel,
269         _gain_profile);
270 
271     // Compute minimum gain. The user-specified gain value will be interpreted as
272     // the gain applied on top of the minimum gain state.
273     // If antennas are shared or swapped, the switch has 6dB of loss
274     size_t gain_index = std::min(static_cast<size_t>(boost::math::round(_gain.get())),
275         table.get_num_entries() - 1);
276 
277     // Translate gain to an index in the gain table
278     const twinrx_gain_config_t& config = table.find_by_index(gain_index);
279 
280     _input_atten = config.atten1;
281     if (_signal_path == twinrx_ctrl::PATH_HIGHBAND) {
282         _hb_atten = config.atten2;
283     } else {
284         _lb_atten = config.atten2;
285     }
286 
287     // Preamp 1 should use the Highband amp for frequencies above 3 GHz
288     if (_signal_path == twinrx_ctrl::PATH_HIGHBAND
289         && _hb_presel != twinrx_ctrl::PRESEL_PATH1) {
290         _preamp1 = config.amp1 ? twinrx_ctrl::PREAMP_HIGHBAND
291                                : twinrx_ctrl::PREAMP_BYPASS;
292     } else {
293         _preamp1 = config.amp1 ? twinrx_ctrl::PREAMP_LOWBAND : twinrx_ctrl::PREAMP_BYPASS;
294     }
295 
296     _preamp2 = config.amp2;
297 }
298 
299 /*!---------------------------------------------------------
300  * twinrx_lo_config_expert::resolve
301  * ---------------------------------------------------------
302  */
resolve()303 void twinrx_lo_config_expert::resolve()
304 {
305     static const uhd::dict<std::string, twinrx_ctrl::lo_source_t> src_lookup =
306         boost::assign::map_list_of("internal", twinrx_ctrl::LO_INTERNAL)(
307             "external", twinrx_ctrl::LO_EXTERNAL)("companion", twinrx_ctrl::LO_COMPANION)(
308             "disabled", twinrx_ctrl::LO_DISABLED)("reimport", twinrx_ctrl::LO_REIMPORT);
309 
310     if (src_lookup.has_key(_lo_source_ch0)) {
311         _lo1_src_ch0 = _lo2_src_ch0 = src_lookup[_lo_source_ch0];
312     } else {
313         throw uhd::value_error("Invalid LO source for channel 0.Choose from {internal, "
314                                "external, companion, reimport}");
315     }
316     if (src_lookup.has_key(_lo_source_ch1)) {
317         _lo1_src_ch1 = _lo2_src_ch1 = src_lookup[_lo_source_ch1];
318     } else {
319         throw uhd::value_error("Invalid LO source for channel 1.Choose from {internal, "
320                                "external, companion, reimport}");
321     }
322 
323     twinrx_ctrl::lo_export_source_t export_src = twinrx_ctrl::LO_EXPORT_DISABLED;
324     if (_lo_export_ch0 and (_lo_source_ch0 == "external")) {
325         throw uhd::value_error("Cannot export an external LO for channel 0");
326     }
327     if (_lo_export_ch1 and (_lo_source_ch1 == "external")) {
328         throw uhd::value_error("Cannot export an external LO for channel 1");
329     }
330 
331     // Determine which channel will provide the exported LO
332     if (_lo_export_ch0 and _lo_export_ch1) {
333         throw uhd::value_error("Cannot export LOs for both channels");
334     } else if (_lo_export_ch0) {
335         export_src = (_lo1_src_ch0 == twinrx_ctrl::LO_COMPANION)
336                          ? twinrx_ctrl::LO_CH2_SYNTH
337                          : twinrx_ctrl::LO_CH1_SYNTH;
338     } else if (_lo_export_ch1) {
339         export_src = (_lo1_src_ch1 == twinrx_ctrl::LO_COMPANION)
340                          ? twinrx_ctrl::LO_CH1_SYNTH
341                          : twinrx_ctrl::LO_CH2_SYNTH;
342     }
343     _lo1_export_src = _lo2_export_src = export_src;
344 }
345 
346 /*!---------------------------------------------------------
347  * twinrx_lo_freq_expert::resolve
348  * ---------------------------------------------------------
349  */
resolve()350 void twinrx_lo_mapping_expert::resolve()
351 {
352     static const size_t CH0_MSK = 0x1;
353     static const size_t CH1_MSK = 0x2;
354 
355     // Determine which channels are "driving" each synthesizer
356     // First check for explicit requests
357     // "internal" or "reimport" -> this channel
358     // "companion" -> other channel
359     size_t synth_map[] = {0, 0};
360     if (_lox_src_ch0 == twinrx_ctrl::LO_INTERNAL
361         or _lox_src_ch0 == twinrx_ctrl::LO_REIMPORT) {
362         synth_map[0] = synth_map[0] | CH0_MSK;
363     } else if (_lox_src_ch0 == twinrx_ctrl::LO_COMPANION) {
364         synth_map[1] = synth_map[1] | CH0_MSK;
365     }
366     if (_lox_src_ch1 == twinrx_ctrl::LO_INTERNAL
367         or _lox_src_ch1 == twinrx_ctrl::LO_REIMPORT) {
368         synth_map[1] = synth_map[1] | CH1_MSK;
369     } else if (_lox_src_ch1 == twinrx_ctrl::LO_COMPANION) {
370         synth_map[0] = synth_map[0] | CH1_MSK;
371     }
372 
373     // If a particular channel has its LO source disabled then the other
374     // channel is automatically put in hop mode i.e. the synthesizer that
375     // belongs to the disabled channel can be re-purposed as a redundant LO
376     // to overlap tuning with signal dwell time.
377     bool hopping_enabled = false;
378     if (_lox_src_ch0 == twinrx_ctrl::LO_DISABLED) {
379         if (_lox_src_ch1 == twinrx_ctrl::LO_INTERNAL
380             or _lox_src_ch1 == twinrx_ctrl::LO_REIMPORT) {
381             synth_map[0]    = synth_map[0] | CH0_MSK;
382             hopping_enabled = true;
383         } else if (_lox_src_ch1 == twinrx_ctrl::LO_COMPANION) {
384             synth_map[1]    = synth_map[1] | CH0_MSK;
385             hopping_enabled = true;
386         }
387     }
388     if (_lox_src_ch1 == twinrx_ctrl::LO_DISABLED) {
389         if (_lox_src_ch0 == twinrx_ctrl::LO_INTERNAL
390             or _lox_src_ch0 == twinrx_ctrl::LO_REIMPORT) {
391             synth_map[1]    = synth_map[1] | CH1_MSK;
392             hopping_enabled = true;
393         } else if (_lox_src_ch0 == twinrx_ctrl::LO_COMPANION) {
394             synth_map[0]    = synth_map[0] | CH1_MSK;
395             hopping_enabled = true;
396         }
397     }
398 
399     // For each synthesizer come up with the final mapping
400     for (size_t synth = 0; synth < 2; synth++) {
401         experts::data_writer_t<lo_synth_mapping_t>& lox_mapping =
402             (synth == 0) ? _lox_mapping_synth0 : _lox_mapping_synth1;
403         if (synth_map[synth] == (CH0_MSK | CH1_MSK)) {
404             lox_mapping = MAPPING_SHARED;
405         } else if (synth_map[synth] == CH0_MSK) {
406             lox_mapping = MAPPING_CH0;
407         } else if (synth_map[synth] == CH1_MSK) {
408             lox_mapping = MAPPING_CH1;
409         } else {
410             lox_mapping = MAPPING_NONE;
411         }
412     }
413     _lox_hopping_enabled = hopping_enabled;
414 }
415 
416 /*!---------------------------------------------------------
417  * twinrx_antenna_expert::resolve
418  * ---------------------------------------------------------
419  */
resolve()420 void twinrx_antenna_expert::resolve()
421 {
422     static const std::string ANT0 = "RX1", ANT1 = "RX2";
423 
424     if (_antenna_ch0 == ANT0 and _antenna_ch1 == ANT1) {
425         _ant_mapping = twinrx_ctrl::ANTX_NATIVE;
426     } else if (_antenna_ch0 == ANT0 and _antenna_ch1 == ANT0) {
427         if (_enabled_ch0 and _enabled_ch1) {
428             _ant_mapping = twinrx_ctrl::ANT1_SHARED;
429         } else if (_enabled_ch0) {
430             _ant_mapping = twinrx_ctrl::ANTX_NATIVE;
431         } else if (_enabled_ch1) {
432             _ant_mapping = twinrx_ctrl::ANTX_SWAPPED;
433         }
434     } else if (_antenna_ch0 == ANT1 and _antenna_ch1 == ANT1) {
435         if (_enabled_ch0 and _enabled_ch1) {
436             _ant_mapping = twinrx_ctrl::ANT2_SHARED;
437         } else if (_enabled_ch0) {
438             _ant_mapping = twinrx_ctrl::ANTX_SWAPPED;
439         } else if (_enabled_ch1) {
440             _ant_mapping = twinrx_ctrl::ANTX_NATIVE;
441         }
442     } else if (_antenna_ch0 == ANT1 and _antenna_ch1 == ANT0) {
443         _ant_mapping = twinrx_ctrl::ANTX_SWAPPED;
444     } else if (_antenna_ch0 != ANT0 and _antenna_ch0 != ANT1) {
445         throw uhd::value_error("Invalid antenna selection " + _antenna_ch0.get()
446                                + " for channel 0. Must be " + ANT0 + " or " + ANT1);
447     } else if (_antenna_ch1 != ANT0 and _antenna_ch1 != ANT1) {
448         throw uhd::value_error("Invalid antenna selection " + _antenna_ch1.get()
449                                + " for channel 1. Must be " + ANT0 + " or " + ANT1);
450     }
451 
452     // TODO: Implement hooks for the calibration switch
453     _cal_mode = twinrx_ctrl::CAL_DISABLED;
454 
455     if (_cal_mode == twinrx_ctrl::CAL_CH1 and _lo_export_ch1) {
456         throw uhd::value_error(
457             "Cannot calibrate channel 0 and export the LO for channel 1.");
458     } else if (_cal_mode == twinrx_ctrl::CAL_CH2 and _lo_export_ch0) {
459         throw uhd::value_error(
460             "Cannot calibrate channel 1 and export the LO for channel 0.");
461     }
462 
463     // Set ID for power cal
464     if (_enabled_ch0 and _enabled_ch1) {
465         _id_ch0 = "twinrx2";
466         _id_ch1 = "twinrx2";
467     } else {
468         _id_ch0 = "twinrx";
469         _id_ch1 = "twinrx";
470     }
471 }
472 
473 /*!---------------------------------------------------------
474  * twinrx_ant_gain_expert::resolve
475  * ---------------------------------------------------------
476  */
resolve()477 void twinrx_ant_gain_expert::resolve()
478 {
479     switch (_ant_mapping) {
480         case twinrx_ctrl::ANTX_NATIVE:
481             _ant0_input_atten      = _ch0_input_atten;
482             _ant0_preamp1          = _ch0_preamp1;
483             _ant0_preamp2          = _ch0_preamp2;
484             _ant0_lb_preamp_presel = _ch0_lb_preamp_presel;
485             _ant1_input_atten      = _ch1_input_atten;
486             _ant1_preamp1          = _ch1_preamp1;
487             _ant1_preamp2          = _ch1_preamp2;
488             _ant1_lb_preamp_presel = _ch1_lb_preamp_presel;
489             break;
490         case twinrx_ctrl::ANTX_SWAPPED:
491             _ant0_input_atten      = _ch1_input_atten;
492             _ant0_preamp1          = _ch1_preamp1;
493             _ant0_preamp2          = _ch1_preamp2;
494             _ant0_lb_preamp_presel = _ch1_lb_preamp_presel;
495             _ant1_input_atten      = _ch0_input_atten;
496             _ant1_preamp1          = _ch0_preamp1;
497             _ant1_preamp2          = _ch0_preamp2;
498             _ant1_lb_preamp_presel = _ch0_lb_preamp_presel;
499             break;
500         case twinrx_ctrl::ANT1_SHARED:
501             if ((_ch0_input_atten != _ch1_input_atten) or (_ch0_preamp1 != _ch1_preamp1)
502                 or (_ch0_preamp2 != _ch1_preamp2)
503                 or (_ch0_lb_preamp_presel != _ch1_lb_preamp_presel)) {
504                 UHD_LOGGER_WARNING("TWINRX")
505                     << "incompatible gain settings for antenna sharing. temporarily "
506                        "using Ch0 settings for Ch1.";
507             }
508             _ant0_input_atten      = _ch0_input_atten;
509             _ant0_preamp1          = _ch0_preamp1;
510             _ant0_preamp2          = _ch0_preamp2;
511             _ant0_lb_preamp_presel = _ch0_lb_preamp_presel;
512 
513             _ant1_input_atten      = 0;
514             _ant1_preamp1          = twinrx_ctrl::PREAMP_BYPASS;
515             _ant1_preamp2          = false;
516             _ant1_lb_preamp_presel = false;
517             break;
518         case twinrx_ctrl::ANT2_SHARED:
519             if ((_ch0_input_atten != _ch1_input_atten) or (_ch0_preamp1 != _ch1_preamp1)
520                 or (_ch0_preamp2 != _ch1_preamp2)
521                 or (_ch0_lb_preamp_presel != _ch1_lb_preamp_presel)) {
522                 UHD_LOGGER_WARNING("TWINRX")
523                     << "incompatible gain settings for antenna sharing. temporarily "
524                        "using Ch0 settings for Ch1.";
525             }
526             _ant1_input_atten      = _ch0_input_atten;
527             _ant1_preamp1          = _ch0_preamp1;
528             _ant1_preamp2          = _ch0_preamp2;
529             _ant1_lb_preamp_presel = _ch0_lb_preamp_presel;
530 
531             _ant0_input_atten      = 0;
532             _ant0_preamp1          = twinrx_ctrl::PREAMP_BYPASS;
533             _ant0_preamp2          = false;
534             _ant0_lb_preamp_presel = false;
535             break;
536         default:
537             _ant0_input_atten      = 0;
538             _ant0_preamp1          = twinrx_ctrl::PREAMP_BYPASS;
539             _ant0_preamp2          = false;
540             _ant0_lb_preamp_presel = false;
541             _ant1_input_atten      = 0;
542             _ant1_preamp1          = twinrx_ctrl::PREAMP_BYPASS;
543             _ant1_preamp2          = false;
544             _ant1_lb_preamp_presel = false;
545             break;
546     }
547 }
548 
549 /*!---------------------------------------------------------
550  * twinrx_settings_expert::resolve
551  * ---------------------------------------------------------
552  */
553 const bool twinrx_settings_expert::FORCE_COMMIT = false;
554 
resolve()555 void twinrx_settings_expert::resolve()
556 {
557     for (size_t i = 0; i < 2; i++) {
558         ch_settings& ch_set       = (i == 1) ? _ch1 : _ch0;
559         twinrx_ctrl::channel_t ch = (i == 1) ? twinrx_ctrl::CH2 : twinrx_ctrl::CH1;
560         _ctrl->set_chan_enabled(ch, ch_set.chan_enabled, FORCE_COMMIT);
561         _ctrl->set_preamp1(ch, ch_set.preamp1, FORCE_COMMIT);
562         _ctrl->set_preamp2(ch, ch_set.preamp2, FORCE_COMMIT);
563         _ctrl->set_lb_preamp_preselector(ch, ch_set.lb_preamp_presel, FORCE_COMMIT);
564         _ctrl->set_signal_path(ch, ch_set.signal_path, FORCE_COMMIT);
565         _ctrl->set_lb_preselector(ch, ch_set.lb_presel, FORCE_COMMIT);
566         _ctrl->set_hb_preselector(ch, ch_set.hb_presel, FORCE_COMMIT);
567         _ctrl->set_input_atten(ch, ch_set.input_atten, FORCE_COMMIT);
568         _ctrl->set_lb_atten(ch, ch_set.lb_atten, FORCE_COMMIT);
569         _ctrl->set_hb_atten(ch, ch_set.hb_atten, FORCE_COMMIT);
570         _ctrl->set_lo1_source(ch, ch_set.lo1_source, FORCE_COMMIT);
571         _ctrl->set_lo2_source(ch, ch_set.lo2_source, FORCE_COMMIT);
572         ch_set.lo1_charge_pump_c =
573             _ctrl->set_lo1_charge_pump(ch, ch_set.lo1_charge_pump_d, FORCE_COMMIT);
574         ch_set.lo2_charge_pump_c =
575             _ctrl->set_lo2_charge_pump(ch, ch_set.lo2_charge_pump_d, FORCE_COMMIT);
576     }
577 
578     _resolve_lox_freq(STAGE_LO1,
579         _ch0.lo1_freq_d,
580         _ch1.lo1_freq_d,
581         _ch0.lo1_freq_c,
582         _ch1.lo1_freq_c,
583         _ch0.lo1_source,
584         _ch1.lo1_source,
585         _lo1_synth0_mapping,
586         _lo1_synth1_mapping,
587         _lo1_hopping_enabled);
588     _resolve_lox_freq(STAGE_LO2,
589         _ch0.lo2_freq_d,
590         _ch1.lo2_freq_d,
591         _ch0.lo2_freq_c,
592         _ch1.lo2_freq_c,
593         _ch0.lo2_source,
594         _ch1.lo2_source,
595         _lo2_synth0_mapping,
596         _lo2_synth1_mapping,
597         _lo2_hopping_enabled);
598 
599     _ctrl->set_lo1_export_source(_lo1_export_src, FORCE_COMMIT);
600     _ctrl->set_lo2_export_source(_lo2_export_src, FORCE_COMMIT);
601     _ctrl->set_antenna_mapping(_ant_mapping, FORCE_COMMIT);
602     // TODO: Re-enable this when we support this mode
603     //_ctrl->set_crossover_cal_mode(_cal_mode, FORCE_COMMIT);
604 
605     _ctrl->commit();
606 }
607 
_resolve_lox_freq(lo_stage_t lo_stage,uhd::experts::data_reader_t<double> & ch0_freq_d,uhd::experts::data_reader_t<double> & ch1_freq_d,uhd::experts::data_writer_t<double> & ch0_freq_c,uhd::experts::data_writer_t<double> & ch1_freq_c,twinrx_ctrl::lo_source_t ch0_lo_source,twinrx_ctrl::lo_source_t ch1_lo_source,lo_synth_mapping_t synth0_mapping,lo_synth_mapping_t synth1_mapping,bool hopping_enabled)608 void twinrx_settings_expert::_resolve_lox_freq(lo_stage_t lo_stage,
609     uhd::experts::data_reader_t<double>& ch0_freq_d,
610     uhd::experts::data_reader_t<double>& ch1_freq_d,
611     uhd::experts::data_writer_t<double>& ch0_freq_c,
612     uhd::experts::data_writer_t<double>& ch1_freq_c,
613     twinrx_ctrl::lo_source_t ch0_lo_source,
614     twinrx_ctrl::lo_source_t ch1_lo_source,
615     lo_synth_mapping_t synth0_mapping,
616     lo_synth_mapping_t synth1_mapping,
617     bool hopping_enabled)
618 {
619     if (ch0_lo_source == twinrx_ctrl::LO_EXTERNAL) {
620         // If the LO is external then we don't need to program any synthesizers
621         ch0_freq_c = ch0_freq_d;
622     } else {
623         // When in hopping mode, only attempt to write the LO frequency if it is actually
624         // dirty to avoid reconfiguring the LO if it is being "double-buffered". If not
625         // hopping, then always write the frequency because other inputs might require
626         // an LO re-commit
627         const bool freq_update_request = (not hopping_enabled) or ch0_freq_d.is_dirty();
628         if (synth0_mapping == MAPPING_CH0 and freq_update_request) {
629             ch0_freq_c = _set_lox_synth_freq(lo_stage, twinrx_ctrl::CH1, ch0_freq_d);
630         } else if (synth1_mapping == MAPPING_CH0 and freq_update_request) {
631             ch0_freq_c = _set_lox_synth_freq(lo_stage, twinrx_ctrl::CH2, ch0_freq_d);
632         } else if (synth0_mapping == MAPPING_SHARED or synth1_mapping == MAPPING_SHARED) {
633             // If any synthesizer is being shared then we are not in hopping mode
634             twinrx_ctrl::channel_t ch =
635                 (synth0_mapping == MAPPING_SHARED) ? twinrx_ctrl::CH1 : twinrx_ctrl::CH2;
636             ch0_freq_c = _set_lox_synth_freq(lo_stage, ch, ch0_freq_d);
637             ch1_freq_c = ch0_freq_c;
638         }
639     }
640 
641     if (ch1_lo_source == twinrx_ctrl::LO_EXTERNAL) {
642         // If the LO is external then we don't need to program any synthesizers
643         ch1_freq_c = ch1_freq_d;
644     } else {
645         // When in hopping mode, only attempt to write the LO frequency if it is actually
646         // dirty to avoid reconfiguring the LO if it is being "double-buffered". If not
647         // hopping, then always write the frequency because other inputs might require
648         // an LO re-commit
649         const bool freq_update_request = (not hopping_enabled) or ch1_freq_d.is_dirty();
650         // As an additional layer of protection from unnecessarily committing the LO,
651         // check if the frequency has actually changed.
652         if (synth0_mapping == MAPPING_CH1 and freq_update_request) {
653             ch1_freq_c = _set_lox_synth_freq(lo_stage, twinrx_ctrl::CH1, ch1_freq_d);
654         } else if (synth1_mapping == MAPPING_CH1 and freq_update_request) {
655             ch1_freq_c = _set_lox_synth_freq(lo_stage, twinrx_ctrl::CH2, ch1_freq_d);
656         } else if (synth0_mapping == MAPPING_SHARED or synth1_mapping == MAPPING_SHARED) {
657             // If any synthesizer is being shared then we are not in hopping mode
658             twinrx_ctrl::channel_t ch =
659                 (synth0_mapping == MAPPING_SHARED) ? twinrx_ctrl::CH1 : twinrx_ctrl::CH2;
660             ch0_freq_c = _set_lox_synth_freq(lo_stage, ch, ch0_freq_d);
661             ch1_freq_c = ch0_freq_c;
662         }
663     }
664 }
665 
_set_lox_synth_freq(lo_stage_t stage,twinrx_ctrl::channel_t ch,double freq)666 double twinrx_settings_expert::_set_lox_synth_freq(
667     lo_stage_t stage, twinrx_ctrl::channel_t ch, double freq)
668 {
669     lo_freq_cache_t* freq_cache = NULL;
670     if (stage == STAGE_LO1) {
671         freq_cache = (ch == twinrx_ctrl::CH1) ? &_cached_lo1_synth0_freq
672                                               : &_cached_lo1_synth1_freq;
673     } else if (stage == STAGE_LO2) {
674         freq_cache = (ch == twinrx_ctrl::CH1) ? &_cached_lo2_synth0_freq
675                                               : &_cached_lo2_synth1_freq;
676     } else {
677         throw uhd::assertion_error("Invalid LO stage");
678     }
679 
680     // Check if the frequency has actually changed before configuring synthesizers
681     double coerced_freq = 0.0;
682     if (freq_cache->desired != freq) {
683         if (stage == STAGE_LO1) {
684             coerced_freq = _ctrl->set_lo1_synth_freq(ch, freq, FORCE_COMMIT);
685         } else {
686             coerced_freq = _ctrl->set_lo2_synth_freq(ch, freq, FORCE_COMMIT);
687         }
688         freq_cache->desired = rf_freq_ppm_t(freq);
689         freq_cache->coerced = coerced_freq;
690     } else {
691         coerced_freq = freq_cache->coerced;
692     }
693     return coerced_freq;
694 }
695