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 #ifndef INCLUDED_DBOARD_TWINRX_EXPERTS_HPP
9 #define INCLUDED_DBOARD_TWINRX_EXPERTS_HPP
10 
11 #include "twinrx_ctrl.hpp"
12 #include <uhd/utils/math.hpp>
13 #include <uhdlib/experts/expert_nodes.hpp>
14 
15 namespace uhd { namespace usrp { namespace dboard { namespace twinrx {
16 
17 //---------------------------------------------------------
18 // Misc types and definitions
19 //---------------------------------------------------------
20 
21 struct rf_freq_abs_t : public uhd::math::fp_compare::fp_compare_delta<double>
22 {
rf_freq_abs_tuhd::usrp::dboard::twinrx::rf_freq_abs_t23     rf_freq_abs_t(double freq = 0.0, double epsilon = 1.0 /* 1Hz epsilon */)
24         : uhd::math::fp_compare::fp_compare_delta<double>(freq, epsilon)
25     {
26     }
getuhd::usrp::dboard::twinrx::rf_freq_abs_t27     inline double get() const
28     {
29         return _value;
30     }
31 };
32 
33 struct rf_freq_ppm_t : public rf_freq_abs_t
34 {
rf_freq_ppm_tuhd::usrp::dboard::twinrx::rf_freq_ppm_t35     rf_freq_ppm_t(double freq = 0.0, double epsilon_ppm = 0.1 /* 1PPM epsilon */)
36         : rf_freq_abs_t(freq, 1e-6 * freq * epsilon_ppm)
37     {
38     }
39 };
40 
41 enum lo_stage_t { STAGE_LO1, STAGE_LO2 };
42 enum lo_inj_side_t { INJ_LOW_SIDE, INJ_HIGH_SIDE };
43 enum lo_synth_mapping_t { MAPPING_NONE, MAPPING_CH0, MAPPING_CH1, MAPPING_SHARED };
44 
prepend_ch(std::string name,const std::string & ch)45 static const std::string prepend_ch(std::string name, const std::string& ch)
46 {
47     return ch + "/" + name;
48 }
49 
lo_stage_str(lo_stage_t stage,bool lower=false)50 static const std::string lo_stage_str(lo_stage_t stage, bool lower = false)
51 {
52     std::string prefix = lower ? "lo" : "LO";
53     return prefix + ((stage == STAGE_LO1) ? "1" : "2");
54 }
55 
56 
57 /*!---------------------------------------------------------
58  * twinrx_scheduling_expert
59  *
60  * This expert is responsible for scheduling time sensitive actions
61  * in other experts. It responds to changes in the command time and
62  * selectively causes experts to run in order to ensure a synchronized
63  * system.
64  *
65  * ---------------------------------------------------------
66  */
67 class twinrx_scheduling_expert : public experts::worker_node_t
68 {
69 public:
twinrx_scheduling_expert(const experts::node_retriever_t & db,std::string ch)70     twinrx_scheduling_expert(const experts::node_retriever_t& db, std::string ch)
71         : experts::worker_node_t(prepend_ch("twinrx_scheduling_expert", ch))
72         , _command_time(db, prepend_ch("time/cmd", ch))
73         , _rx_frontend_time(db, prepend_ch("time/rx_frontend", ch))
74     {
75         bind_accessor(_command_time);
76         bind_accessor(_rx_frontend_time);
77     }
78 
79 private:
80     virtual void resolve();
81 
82     // Inputs
83     experts::data_reader_t<time_spec_t> _command_time;
84 
85     // Outputs
86     experts::data_writer_t<time_spec_t> _rx_frontend_time;
87 };
88 
89 /*!---------------------------------------------------------
90  * twinrx_freq_path_expert
91  *
92  * This expert is responsble for translating a user-specified
93  * RF and IF center frequency into TwinRX specific settings
94  * like band, preselector path, LO frequency and injection
95  * sides for both the LO stages.
96  *
97  * One instance of this expert is required for each channel
98  * ---------------------------------------------------------
99  */
100 class twinrx_freq_path_expert : public experts::worker_node_t
101 {
102 public:
twinrx_freq_path_expert(const experts::node_retriever_t & db,std::string ch)103     twinrx_freq_path_expert(const experts::node_retriever_t& db, std::string ch)
104         : experts::worker_node_t(prepend_ch("twinrx_freq_path_expert", ch))
105         , _rf_freq_d(db, prepend_ch("freq/desired", ch))
106         , _if_freq_d(db, prepend_ch("if_freq/desired", ch))
107         , _signal_path(db, prepend_ch("ch/signal_path", ch))
108         , _lb_presel(db, prepend_ch("ch/lb_presel", ch))
109         , _hb_presel(db, prepend_ch("ch/hb_presel", ch))
110         , _lb_preamp_presel(db, prepend_ch("ch/lb_preamp_presel", ch))
111         , _lo1_freq_d(db, prepend_ch("los/LO1/freq/desired", ch))
112         , _lo2_freq_d(db, prepend_ch("los/LO2/freq/desired", ch))
113         , _lo1_inj_side(db, prepend_ch("ch/LO1/inj_side", ch))
114         , _lo2_inj_side(db, prepend_ch("ch/LO2/inj_side", ch))
115     {
116         bind_accessor(_rf_freq_d);
117         bind_accessor(_if_freq_d);
118         bind_accessor(_signal_path);
119         bind_accessor(_lb_presel);
120         bind_accessor(_hb_presel);
121         bind_accessor(_lb_preamp_presel);
122         bind_accessor(_lo1_freq_d);
123         bind_accessor(_lo2_freq_d);
124         bind_accessor(_lo1_inj_side);
125         bind_accessor(_lo2_inj_side);
126     }
127 
128 private:
129     virtual void resolve();
130     static lo_inj_side_t _compute_lo2_inj_side(
131         double lo1_freq, double if1_freq, double if2_freq, double bandwidth);
132     static bool _has_mixer_spurs(double lo1_freq,
133         double lo2_freq,
134         double if2_freq,
135         double bandwidth,
136         int spur_order);
137 
138     // Inputs
139     experts::data_reader_t<double> _rf_freq_d;
140     experts::data_reader_t<double> _if_freq_d;
141     // Outputs
142     experts::data_writer_t<twinrx_ctrl::signal_path_t> _signal_path;
143     experts::data_writer_t<twinrx_ctrl::preselector_path_t> _lb_presel;
144     experts::data_writer_t<twinrx_ctrl::preselector_path_t> _hb_presel;
145     experts::data_writer_t<bool> _lb_preamp_presel;
146     experts::data_writer_t<double> _lo1_freq_d;
147     experts::data_writer_t<double> _lo2_freq_d;
148     experts::data_writer_t<lo_inj_side_t> _lo1_inj_side;
149     experts::data_writer_t<lo_inj_side_t> _lo2_inj_side;
150 };
151 
152 /*!---------------------------------------------------------
153  * twinrx_lo_config_expert
154  *
155  * This expert is responsible for translating high level
156  * channel-scoped  LO source and export settings to low-level
157  * channel-scoped settings. The expert only deals with
158  * the source and export attributes, not frequency.
159  *
160  * One instance of this expert is required for all channels
161  * ---------------------------------------------------------
162  */
163 class twinrx_lo_config_expert : public experts::worker_node_t
164 {
165 public:
twinrx_lo_config_expert(const experts::node_retriever_t & db)166     twinrx_lo_config_expert(const experts::node_retriever_t& db)
167         : experts::worker_node_t("twinrx_lo_config_expert")
168         , _lo_source_ch0(db, prepend_ch("los/all/source", "0"))
169         , _lo_source_ch1(db, prepend_ch("los/all/source", "1"))
170         , _lo_export_ch0(db, prepend_ch("los/all/export", "0"))
171         , _lo_export_ch1(db, prepend_ch("los/all/export", "1"))
172         , _lo1_src_ch0(db, prepend_ch("ch/LO1/source", "0"))
173         , _lo1_src_ch1(db, prepend_ch("ch/LO1/source", "1"))
174         , _lo2_src_ch0(db, prepend_ch("ch/LO2/source", "0"))
175         , _lo2_src_ch1(db, prepend_ch("ch/LO2/source", "1"))
176         , _lo1_export_src(db, "com/LO1/export_source")
177         , _lo2_export_src(db, "com/LO2/export_source")
178     {
179         bind_accessor(_lo_source_ch0);
180         bind_accessor(_lo_source_ch1);
181         bind_accessor(_lo_export_ch0);
182         bind_accessor(_lo_export_ch1);
183         bind_accessor(_lo1_src_ch0);
184         bind_accessor(_lo1_src_ch1);
185         bind_accessor(_lo2_src_ch0);
186         bind_accessor(_lo2_src_ch1);
187         bind_accessor(_lo1_export_src);
188         bind_accessor(_lo2_export_src);
189     }
190 
191 private:
192     virtual void resolve();
193 
194     // Inputs
195     experts::data_reader_t<std::string> _lo_source_ch0;
196     experts::data_reader_t<std::string> _lo_source_ch1;
197     experts::data_reader_t<bool> _lo_export_ch0;
198     experts::data_reader_t<bool> _lo_export_ch1;
199     // Outputs
200     experts::data_writer_t<twinrx_ctrl::lo_source_t> _lo1_src_ch0;
201     experts::data_writer_t<twinrx_ctrl::lo_source_t> _lo1_src_ch1;
202     experts::data_writer_t<twinrx_ctrl::lo_source_t> _lo2_src_ch0;
203     experts::data_writer_t<twinrx_ctrl::lo_source_t> _lo2_src_ch1;
204     experts::data_writer_t<twinrx_ctrl::lo_export_source_t> _lo1_export_src;
205     experts::data_writer_t<twinrx_ctrl::lo_export_source_t> _lo2_export_src;
206 };
207 
208 /*!---------------------------------------------------------
209  * twinrx_lo_mapping_expert
210  *
211  * This expert is responsible for translating low-level
212  * channel-scoped  LO source and export settings to low-level
213  * synthesizer-scoped settings. The expert deals with the
214  * extremely flexible channel->synthesizer mapping and handles
215  * frequency hopping modes.
216  *
217  * One instance of this expert is required for each LO stage
218  * ---------------------------------------------------------
219  */
220 class twinrx_lo_mapping_expert : public experts::worker_node_t
221 {
222 public:
twinrx_lo_mapping_expert(const experts::node_retriever_t & db,lo_stage_t stage)223     twinrx_lo_mapping_expert(const experts::node_retriever_t& db, lo_stage_t stage)
224         : experts::worker_node_t(
225               "twinrx_" + lo_stage_str(stage, true) + "_mapping_expert")
226         , _lox_src_ch0(db, prepend_ch("ch/" + lo_stage_str(stage) + "/source", "0"))
227         , _lox_src_ch1(db, prepend_ch("ch/" + lo_stage_str(stage) + "/source", "1"))
228         , _lox_mapping_synth0(
229               db, prepend_ch("synth/" + lo_stage_str(stage) + "/mapping", "0"))
230         , _lox_mapping_synth1(
231               db, prepend_ch("synth/" + lo_stage_str(stage) + "/mapping", "1"))
232         , _lox_hopping_enabled(
233               db, "com/synth/" + lo_stage_str(stage) + "/hopping_enabled")
234     {
235         bind_accessor(_lox_src_ch0);
236         bind_accessor(_lox_src_ch1);
237         bind_accessor(_lox_mapping_synth0);
238         bind_accessor(_lox_mapping_synth1);
239         bind_accessor(_lox_hopping_enabled);
240     }
241 
242 private:
243     virtual void resolve();
244 
245     // Inputs
246     experts::data_reader_t<twinrx_ctrl::lo_source_t> _lox_src_ch0;
247     experts::data_reader_t<twinrx_ctrl::lo_source_t> _lox_src_ch1;
248     // Outputs
249     experts::data_writer_t<lo_synth_mapping_t> _lox_mapping_synth0;
250     experts::data_writer_t<lo_synth_mapping_t> _lox_mapping_synth1;
251     experts::data_writer_t<bool> _lox_hopping_enabled;
252 };
253 
254 /*!---------------------------------------------------------
255  * twinrx_freq_coercion_expert
256  *
257  * This expert is responsible for calculating the coerced
258  * RF frequency after most settings and modes have been
259  * resolved.
260  *
261  * One instance of this expert is required for each channel
262  * ---------------------------------------------------------
263  */
264 class twinrx_freq_coercion_expert : public experts::worker_node_t
265 {
266 public:
twinrx_freq_coercion_expert(const experts::node_retriever_t & db,std::string ch)267     twinrx_freq_coercion_expert(const experts::node_retriever_t& db, std::string ch)
268         : experts::worker_node_t(prepend_ch("twinrx_freq_coercion_expert", ch))
269         , _lo1_freq_c(db, prepend_ch("los/LO1/freq/coerced", ch))
270         , _lo2_freq_c(db, prepend_ch("los/LO2/freq/coerced", ch))
271         , _if_freq_d(db, prepend_ch("if_freq/desired", ch))
272         , _lo1_inj_side(db, prepend_ch("ch/LO1/inj_side", ch))
273         , _lo2_inj_side(db, prepend_ch("ch/LO2/inj_side", ch))
274         , _rf_freq_c(db, prepend_ch("freq/coerced", ch))
275     {
276         bind_accessor(_lo1_freq_c);
277         bind_accessor(_lo2_freq_c);
278         bind_accessor(_if_freq_d);
279         bind_accessor(_lo1_inj_side);
280         bind_accessor(_lo2_inj_side);
281         bind_accessor(_rf_freq_c);
282     }
283 
284 private:
285     virtual void resolve();
286 
287     // Inputs
288     experts::data_reader_t<double> _lo1_freq_c;
289     experts::data_reader_t<double> _lo2_freq_c;
290     experts::data_reader_t<double> _if_freq_d;
291     experts::data_reader_t<lo_inj_side_t> _lo1_inj_side;
292     experts::data_reader_t<lo_inj_side_t> _lo2_inj_side;
293     // Outputs
294     experts::data_writer_t<double> _rf_freq_c;
295 };
296 
297 /*!---------------------------------------------------------
298  * twinrx_nyquist_expert
299  *
300  * This expert is responsible for figuring out the DSP
301  * front-end settings required for each channel
302  *
303  * One instance of this expert is required for each channel
304  * ---------------------------------------------------------
305  */
306 class twinrx_nyquist_expert : public experts::worker_node_t
307 {
308 public:
twinrx_nyquist_expert(const experts::node_retriever_t & db,std::string ch,dboard_iface::sptr db_iface)309     twinrx_nyquist_expert(
310         const experts::node_retriever_t& db, std::string ch, dboard_iface::sptr db_iface)
311         : experts::worker_node_t(prepend_ch("twinrx_nyquist_expert", ch))
312         , _channel(ch)
313         , _codec_conn(ch == "0" ? "II" : "QQ")
314         , // Ch->ADC Port mapping
315         _lo1_freq_d(db, prepend_ch("los/LO1/freq/desired", ch))
316         , _lo2_freq_d(db, prepend_ch("los/LO2/freq/desired", ch))
317         , _if_freq_d(db, prepend_ch("if_freq/desired", ch))
318         , _lo1_inj_side(db, prepend_ch("ch/LO1/inj_side", ch))
319         , _lo2_inj_side(db, prepend_ch("ch/LO2/inj_side", ch))
320         , _rx_frontend_time(db, prepend_ch("time/rx_frontend", ch))
321         , _if_freq_c(db, prepend_ch("if_freq/coerced", ch))
322         , _db_iface(db_iface)
323     {
324         bind_accessor(_lo1_freq_d);
325         bind_accessor(_lo2_freq_d);
326         bind_accessor(_if_freq_d);
327         bind_accessor(_lo1_inj_side);
328         bind_accessor(_lo2_inj_side);
329         bind_accessor(_if_freq_c);
330         bind_accessor(_rx_frontend_time);
331     }
332 
333 private:
334     virtual void resolve();
335 
336     // Inputs
337     const std::string _channel;
338     const std::string _codec_conn;
339     experts::data_reader_t<double> _lo1_freq_d;
340     experts::data_reader_t<double> _lo2_freq_d;
341     experts::data_reader_t<double> _if_freq_d;
342     experts::data_reader_t<lo_inj_side_t> _lo1_inj_side;
343     experts::data_reader_t<lo_inj_side_t> _lo2_inj_side;
344     experts::data_reader_t<time_spec_t> _rx_frontend_time;
345 
346     // Outputs
347     experts::data_writer_t<double> _if_freq_c;
348     dboard_iface::sptr _db_iface;
349 
350     // Misc
351     time_spec_t _cached_cmd_time;
352 };
353 
354 /*!---------------------------------------------------------
355  * twinrx_antenna_expert
356  *
357  * This expert is responsible for translating high-level
358  * antenna selection settings and channel enables to low-level
359  * switch configurations.
360  *
361  * One instance of this expert is required for all channels
362  * ---------------------------------------------------------
363  */
364 class twinrx_antenna_expert : public experts::worker_node_t
365 {
366 public:
twinrx_antenna_expert(const experts::node_retriever_t & db)367     twinrx_antenna_expert(const experts::node_retriever_t& db)
368         : experts::worker_node_t("twinrx_antenna_expert")
369         , _antenna_ch0(db, prepend_ch("antenna", "0"))
370         , _antenna_ch1(db, prepend_ch("antenna", "1"))
371         , _enabled_ch0(db, prepend_ch("enabled", "0"))
372         , _enabled_ch1(db, prepend_ch("enabled", "1"))
373         , _lo_export_ch0(db, prepend_ch("los/all/export", "0"))
374         , _lo_export_ch1(db, prepend_ch("los/all/export", "1"))
375         , _ant_mapping(db, "com/ant_mapping")
376         , _cal_mode(db, "com/cal_mode")
377         , _id_ch0(db, prepend_ch("id", "0"))
378         , _id_ch1(db, prepend_ch("id", "1"))
379     {
380         bind_accessor(_antenna_ch0);
381         bind_accessor(_antenna_ch1);
382         bind_accessor(_enabled_ch0);
383         bind_accessor(_enabled_ch1);
384         bind_accessor(_lo_export_ch0);
385         bind_accessor(_lo_export_ch1);
386         bind_accessor(_ant_mapping);
387         bind_accessor(_cal_mode);
388         bind_accessor(_id_ch0);
389         bind_accessor(_id_ch1);
390     }
391 
392 private:
393     virtual void resolve();
394 
395     // Inputs
396     experts::data_reader_t<std::string> _antenna_ch0;
397     experts::data_reader_t<std::string> _antenna_ch1;
398     experts::data_reader_t<bool> _enabled_ch0;
399     experts::data_reader_t<bool> _enabled_ch1;
400     experts::data_reader_t<bool> _lo_export_ch0;
401     experts::data_reader_t<bool> _lo_export_ch1;
402     // Outputs
403     experts::data_writer_t<twinrx_ctrl::antenna_mapping_t> _ant_mapping;
404     experts::data_writer_t<twinrx_ctrl::cal_mode_t> _cal_mode;
405 
406     experts::data_writer_t<std::string> _id_ch0;
407     experts::data_writer_t<std::string> _id_ch1;
408 };
409 
410 /*!---------------------------------------------------------
411  * twinrx_chan_gain_expert
412  *
413  * This expert is responsible for mapping high-level channel
414  * gain settings to individual attenuator and amp configurations
415  * that are also channel-scoped. This expert will implement
416  * the gain distribution strategy.
417  *
418  * One instance of this expert is required for each channel
419  * ---------------------------------------------------------
420  */
421 class twinrx_chan_gain_expert : public experts::worker_node_t
422 {
423 public:
twinrx_chan_gain_expert(const experts::node_retriever_t & db,std::string ch)424     twinrx_chan_gain_expert(const experts::node_retriever_t& db, std::string ch)
425         : experts::worker_node_t(prepend_ch("twinrx_chan_gain_expert", ch))
426         , _gain(db, prepend_ch("gain", ch))
427         , _gain_profile(db, prepend_ch("gain_profile", ch))
428         , _signal_path(db, prepend_ch("ch/signal_path", ch))
429         , _lb_presel(db, prepend_ch("ch/lb_presel", ch))
430         , _hb_presel(db, prepend_ch("ch/hb_presel", ch))
431         , _ant_mapping(db, "com/ant_mapping")
432         , _input_atten(db, prepend_ch("ch/input_atten", ch))
433         , _lb_atten(db, prepend_ch("ch/lb_atten", ch))
434         , _hb_atten(db, prepend_ch("ch/hb_atten", ch))
435         , _preamp1(db, prepend_ch("ch/preamp1", ch))
436         , _preamp2(db, prepend_ch("ch/preamp2", ch))
437     {
438         bind_accessor(_gain);
439         bind_accessor(_gain_profile);
440         bind_accessor(_signal_path);
441         bind_accessor(_lb_presel);
442         bind_accessor(_hb_presel);
443         bind_accessor(_ant_mapping);
444         bind_accessor(_input_atten);
445         bind_accessor(_lb_atten);
446         bind_accessor(_hb_atten);
447         bind_accessor(_preamp1);
448         bind_accessor(_preamp2);
449     }
450 
451 private:
452     virtual void resolve();
453 
454     // Inputs
455     experts::data_reader_t<double> _gain;
456     experts::data_reader_t<std::string> _gain_profile;
457     experts::data_reader_t<twinrx_ctrl::signal_path_t> _signal_path;
458     experts::data_reader_t<twinrx_ctrl::preselector_path_t> _lb_presel;
459     experts::data_reader_t<twinrx_ctrl::preselector_path_t> _hb_presel;
460     experts::data_reader_t<twinrx_ctrl::antenna_mapping_t> _ant_mapping;
461     // Outputs
462     experts::data_writer_t<uint8_t> _input_atten;
463     experts::data_writer_t<uint8_t> _lb_atten;
464     experts::data_writer_t<uint8_t> _hb_atten;
465     experts::data_writer_t<twinrx_ctrl::preamp_state_t> _preamp1;
466     experts::data_writer_t<bool> _preamp2;
467 };
468 
469 /*!---------------------------------------------------------
470  * twinrx_ant_gain_expert
471  *
472  * This expert is responsible for translating between the
473  * channel-scoped low-level gain settings to antenna-scoped
474  * gain settings.
475  *
476  * One instance of this expert is required for all channels
477  * ---------------------------------------------------------
478  */
479 class twinrx_ant_gain_expert : public experts::worker_node_t
480 {
481 public:
twinrx_ant_gain_expert(const experts::node_retriever_t & db)482     twinrx_ant_gain_expert(const experts::node_retriever_t& db)
483         : experts::worker_node_t("twinrx_ant_gain_expert")
484         , _ant_mapping(db, "com/ant_mapping")
485         , _ch0_input_atten(db, prepend_ch("ch/input_atten", "0"))
486         , _ch0_preamp1(db, prepend_ch("ch/preamp1", "0"))
487         , _ch0_preamp2(db, prepend_ch("ch/preamp2", "0"))
488         , _ch0_lb_preamp_presel(db, prepend_ch("ch/lb_preamp_presel", "0"))
489         , _ch1_input_atten(db, prepend_ch("ch/input_atten", "1"))
490         , _ch1_preamp1(db, prepend_ch("ch/preamp1", "1"))
491         , _ch1_preamp2(db, prepend_ch("ch/preamp2", "1"))
492         , _ch1_lb_preamp_presel(db, prepend_ch("ch/lb_preamp_presel", "1"))
493         , _ant0_input_atten(db, prepend_ch("ant/input_atten", "0"))
494         , _ant0_preamp1(db, prepend_ch("ant/preamp1", "0"))
495         , _ant0_preamp2(db, prepend_ch("ant/preamp2", "0"))
496         , _ant0_lb_preamp_presel(db, prepend_ch("ant/lb_preamp_presel", "0"))
497         , _ant1_input_atten(db, prepend_ch("ant/input_atten", "1"))
498         , _ant1_preamp1(db, prepend_ch("ant/preamp1", "1"))
499         , _ant1_preamp2(db, prepend_ch("ant/preamp2", "1"))
500         , _ant1_lb_preamp_presel(db, prepend_ch("ant/lb_preamp_presel", "1"))
501     {
502         bind_accessor(_ant_mapping);
503         bind_accessor(_ch0_input_atten);
504         bind_accessor(_ch0_preamp1);
505         bind_accessor(_ch0_preamp2);
506         bind_accessor(_ch0_lb_preamp_presel);
507         bind_accessor(_ch1_input_atten);
508         bind_accessor(_ch1_preamp1);
509         bind_accessor(_ch1_preamp2);
510         bind_accessor(_ch1_lb_preamp_presel);
511         bind_accessor(_ant0_input_atten);
512         bind_accessor(_ant0_preamp1);
513         bind_accessor(_ant0_preamp2);
514         bind_accessor(_ant0_lb_preamp_presel);
515         bind_accessor(_ant1_input_atten);
516         bind_accessor(_ant1_preamp1);
517         bind_accessor(_ant1_preamp2);
518         bind_accessor(_ant1_lb_preamp_presel);
519     }
520 
521 private:
522     virtual void resolve();
523 
524     // Inputs
525     experts::data_reader_t<twinrx_ctrl::antenna_mapping_t> _ant_mapping;
526     experts::data_reader_t<uint8_t> _ch0_input_atten;
527     experts::data_reader_t<twinrx_ctrl::preamp_state_t> _ch0_preamp1;
528     experts::data_reader_t<bool> _ch0_preamp2;
529     experts::data_reader_t<bool> _ch0_lb_preamp_presel;
530     experts::data_reader_t<uint8_t> _ch1_input_atten;
531     experts::data_reader_t<twinrx_ctrl::preamp_state_t> _ch1_preamp1;
532     experts::data_reader_t<bool> _ch1_preamp2;
533     experts::data_reader_t<bool> _ch1_lb_preamp_presel;
534 
535     // Outputs
536     experts::data_writer_t<uint8_t> _ant0_input_atten;
537     experts::data_writer_t<twinrx_ctrl::preamp_state_t> _ant0_preamp1;
538     experts::data_writer_t<bool> _ant0_preamp2;
539     experts::data_writer_t<bool> _ant0_lb_preamp_presel;
540     experts::data_writer_t<uint8_t> _ant1_input_atten;
541     experts::data_writer_t<twinrx_ctrl::preamp_state_t> _ant1_preamp1;
542     experts::data_writer_t<bool> _ant1_preamp2;
543     experts::data_writer_t<bool> _ant1_lb_preamp_presel;
544 };
545 
546 /*!---------------------------------------------------------
547  * twinrx_settings_expert
548  *
549  * This expert is responsible for gathering all low-level
550  * settings and writing them to hardware. All LO frequency
551  * settings are cached with a hysteresis. All other settings
552  * are always written to twinrx_ctrl and rely on register
553  * level caching.
554  *
555  * One instance of this expert is required for all channels
556  * ---------------------------------------------------------
557  */
558 class twinrx_settings_expert : public experts::worker_node_t
559 {
560 public:
twinrx_settings_expert(const experts::node_retriever_t & db,twinrx_ctrl::sptr ctrl)561     twinrx_settings_expert(const experts::node_retriever_t& db, twinrx_ctrl::sptr ctrl)
562         : experts::worker_node_t("twinrx_settings_expert")
563         , _ctrl(ctrl)
564         , _ch0(db, "0")
565         , _ch1(db, "1")
566         , _lo1_synth0_mapping(db, "0/synth/LO1/mapping")
567         , _lo1_synth1_mapping(db, "1/synth/LO1/mapping")
568         , _lo2_synth0_mapping(db, "0/synth/LO2/mapping")
569         , _lo2_synth1_mapping(db, "1/synth/LO2/mapping")
570         , _lo1_hopping_enabled(db, "com/synth/LO1/hopping_enabled")
571         , _lo2_hopping_enabled(db, "com/synth/LO2/hopping_enabled")
572         , _lo1_export_src(db, "com/LO1/export_source")
573         , _lo2_export_src(db, "com/LO2/export_source")
574         , _ant_mapping(db, "com/ant_mapping")
575         , _cal_mode(db, "com/cal_mode")
576     {
577         for (size_t i = 0; i < 2; i++) {
578             ch_settings& ch = (i == 1) ? _ch1 : _ch0;
579             bind_accessor(ch.chan_enabled);
580             bind_accessor(ch.preamp1);
581             bind_accessor(ch.preamp2);
582             bind_accessor(ch.lb_preamp_presel);
583             bind_accessor(ch.signal_path);
584             bind_accessor(ch.lb_presel);
585             bind_accessor(ch.hb_presel);
586             bind_accessor(ch.input_atten);
587             bind_accessor(ch.lb_atten);
588             bind_accessor(ch.hb_atten);
589             bind_accessor(ch.lo1_source);
590             bind_accessor(ch.lo2_source);
591             bind_accessor(ch.lo1_freq_d);
592             bind_accessor(ch.lo2_freq_d);
593             bind_accessor(ch.lo1_freq_c);
594             bind_accessor(ch.lo2_freq_c);
595             bind_accessor(ch.lo1_charge_pump_c);
596             bind_accessor(ch.lo2_charge_pump_c);
597             bind_accessor(ch.lo1_charge_pump_d);
598             bind_accessor(ch.lo2_charge_pump_d);
599         }
600         bind_accessor(_lo1_synth0_mapping);
601         bind_accessor(_lo1_synth1_mapping);
602         bind_accessor(_lo2_synth0_mapping);
603         bind_accessor(_lo2_synth1_mapping);
604         bind_accessor(_lo1_hopping_enabled);
605         bind_accessor(_lo2_hopping_enabled);
606         bind_accessor(_lo1_export_src);
607         bind_accessor(_lo2_export_src);
608         bind_accessor(_ant_mapping);
609         bind_accessor(_cal_mode);
610     }
611 
612 private:
613     virtual void resolve();
614     void _resolve_lox_freq(lo_stage_t lo_stage,
615         experts::data_reader_t<double>& ch0_freq_d,
616         experts::data_reader_t<double>& ch1_freq_d,
617         experts::data_writer_t<double>& ch0_freq_c,
618         experts::data_writer_t<double>& ch1_freq_c,
619         twinrx_ctrl::lo_source_t ch0_lo_source,
620         twinrx_ctrl::lo_source_t ch1_lo_source,
621         lo_synth_mapping_t synth0_mapping,
622         lo_synth_mapping_t synth1_mapping,
623         bool hopping_enabled);
624     double _set_lox_synth_freq(lo_stage_t stage, twinrx_ctrl::channel_t ch, double freq);
625 
626     class ch_settings
627     {
628     public:
ch_settings(const experts::node_retriever_t & db,const std::string & ch)629         ch_settings(const experts::node_retriever_t& db, const std::string& ch)
630             : chan_enabled(db, prepend_ch("enabled", ch))
631             , preamp1(db, prepend_ch("ant/preamp1", ch))
632             , preamp2(db, prepend_ch("ant/preamp2", ch))
633             , lb_preamp_presel(db, prepend_ch("ant/lb_preamp_presel", ch))
634             , signal_path(db, prepend_ch("ch/signal_path", ch))
635             , lb_presel(db, prepend_ch("ch/lb_presel", ch))
636             , hb_presel(db, prepend_ch("ch/hb_presel", ch))
637             , input_atten(db, prepend_ch("ant/input_atten", ch))
638             , lb_atten(db, prepend_ch("ch/lb_atten", ch))
639             , hb_atten(db, prepend_ch("ch/hb_atten", ch))
640             , lo1_source(db, prepend_ch("ch/LO1/source", ch))
641             , lo2_source(db, prepend_ch("ch/LO2/source", ch))
642             , lo1_freq_d(db, prepend_ch("los/LO1/freq/desired", ch))
643             , lo2_freq_d(db, prepend_ch("los/LO2/freq/desired", ch))
644             , lo1_charge_pump_d(db, prepend_ch("los/LO1/charge_pump/desired", ch))
645             , lo2_charge_pump_d(db, prepend_ch("los/LO2/charge_pump/desired", ch))
646             , lo1_freq_c(db, prepend_ch("los/LO1/freq/coerced", ch))
647             , lo2_freq_c(db, prepend_ch("los/LO2/freq/coerced", ch))
648             , lo1_charge_pump_c(db, prepend_ch("los/LO1/charge_pump/coerced", ch))
649             , lo2_charge_pump_c(db, prepend_ch("los/LO2/charge_pump/coerced", ch))
650         {
651         }
652 
653         // Inputs (channel specific)
654         experts::data_reader_t<bool> chan_enabled;
655         experts::data_reader_t<twinrx_ctrl::preamp_state_t> preamp1;
656         experts::data_reader_t<bool> preamp2;
657         experts::data_reader_t<bool> lb_preamp_presel;
658         experts::data_reader_t<twinrx_ctrl::signal_path_t> signal_path;
659         experts::data_reader_t<twinrx_ctrl::preselector_path_t> lb_presel;
660         experts::data_reader_t<twinrx_ctrl::preselector_path_t> hb_presel;
661         experts::data_reader_t<uint8_t> input_atten;
662         experts::data_reader_t<uint8_t> lb_atten;
663         experts::data_reader_t<uint8_t> hb_atten;
664         experts::data_reader_t<twinrx_ctrl::lo_source_t> lo1_source;
665         experts::data_reader_t<twinrx_ctrl::lo_source_t> lo2_source;
666         experts::data_reader_t<double> lo1_freq_d;
667         experts::data_reader_t<double> lo2_freq_d;
668         experts::data_reader_t<double> lo1_charge_pump_d;
669         experts::data_reader_t<double> lo2_charge_pump_d;
670 
671         // Output (channel specific)
672         experts::data_writer_t<double> lo1_freq_c;
673         experts::data_writer_t<double> lo2_freq_c;
674         experts::data_writer_t<double> lo1_charge_pump_c;
675         experts::data_writer_t<double> lo2_charge_pump_c;
676     };
677 
678     // External interface
679     twinrx_ctrl::sptr _ctrl;
680 
681     // Inputs (channel agnostic)
682     ch_settings _ch0;
683     ch_settings _ch1;
684     experts::data_reader_t<lo_synth_mapping_t> _lo1_synth0_mapping;
685     experts::data_reader_t<lo_synth_mapping_t> _lo1_synth1_mapping;
686     experts::data_reader_t<lo_synth_mapping_t> _lo2_synth0_mapping;
687     experts::data_reader_t<lo_synth_mapping_t> _lo2_synth1_mapping;
688     experts::data_reader_t<bool> _lo1_hopping_enabled;
689     experts::data_reader_t<bool> _lo2_hopping_enabled;
690     experts::data_reader_t<twinrx_ctrl::lo_export_source_t> _lo1_export_src;
691     experts::data_reader_t<twinrx_ctrl::lo_export_source_t> _lo2_export_src;
692     experts::data_reader_t<twinrx_ctrl::antenna_mapping_t> _ant_mapping;
693     experts::data_reader_t<twinrx_ctrl::cal_mode_t> _cal_mode;
694 
695     // Outputs (channel agnostic)
696     // None
697 
698     // Misc
699     struct lo_freq_cache_t
700     {
701         rf_freq_ppm_t desired;
702         double coerced;
703     };
704     lo_freq_cache_t _cached_lo1_synth0_freq;
705     lo_freq_cache_t _cached_lo2_synth0_freq;
706     lo_freq_cache_t _cached_lo1_synth1_freq;
707     lo_freq_cache_t _cached_lo2_synth1_freq;
708 
709     static const bool FORCE_COMMIT;
710 };
711 
712 
713 }}}} // namespace uhd::usrp::dboard::twinrx
714 
715 #endif /* INCLUDED_DBOARD_TWINRX_EXPERTS_HPP */
716