1 //
2 // Copyright 2015-2017 Ettus Research, A National Instruments Company
3 // Copyright 2019 Ettus Research, A National Instruments Brand
4 //
5 // SPDX-License-Identifier: GPL-3.0-or-later
6 //
7 
8 #include "twinrx_ctrl.hpp"
9 #include "twinrx_ids.hpp"
10 #include <uhd/utils/math.hpp>
11 #include <uhd/utils/safe_call.hpp>
12 #include <uhdlib/usrp/common/adf435x.hpp>
13 #include <uhdlib/usrp/common/adf535x.hpp>
14 #include <uhdlib/utils/narrow.hpp>
15 #include <chrono>
16 #include <cmath>
17 #include <thread>
18 
19 using namespace uhd;
20 using namespace usrp;
21 using namespace dboard::twinrx;
22 
23 namespace {
24 typedef twinrx_cpld_regmap rm;
25 
26 typedef enum { LO1, LO2 } lo_t;
27 
bool2bin(bool x)28 inline uint32_t bool2bin(bool x)
29 {
30     return x ? 1 : 0;
31 }
32 
33 const double TWINRX_REV_AB_PFD_FREQ = 6.25e6;
34 const double TWINRX_REV_C_PFD_FREQ  = 12.5e6;
35 const double TWINRX_SPI_CLOCK_FREQ  = 3e6;
36 } // namespace
37 
38 class twinrx_ctrl_impl : public twinrx_ctrl
39 {
40 public:
twinrx_ctrl_impl(dboard_iface::sptr db_iface,twinrx_gpio::sptr gpio_iface,twinrx_cpld_regmap::sptr cpld_regmap,const dboard_id_t rx_id)41     twinrx_ctrl_impl(dboard_iface::sptr db_iface,
42         twinrx_gpio::sptr gpio_iface,
43         twinrx_cpld_regmap::sptr cpld_regmap,
44         const dboard_id_t rx_id)
45         : _db_iface(db_iface), _gpio_iface(gpio_iface), _cpld_regs(cpld_regmap)
46     {
47         // SPI configuration
48         _spi_config.use_custom_divider = true;
49         _spi_config.divider            = uhd::narrow_cast<size_t>(std::ceil(
50             _db_iface->get_codec_rate(dboard_iface::UNIT_TX) / TWINRX_SPI_CLOCK_FREQ));
51 
52         // Daughterboard clock rates must be a multiple of the pfd frequency
53         if (rx_id == twinrx::TWINRX_REV_C_ID) {
54             if (fmod(_db_iface->get_clock_rate(dboard_iface::UNIT_RX),
55                     TWINRX_REV_C_PFD_FREQ)
56                 != 0) {
57                 throw uhd::value_error(
58                     str(boost::format(
59                             "TwinRX clock rate %f is not a multiple of the pfd freq %f.")
60                         % _db_iface->get_clock_rate(dboard_iface::UNIT_RX)
61                         % TWINRX_REV_C_PFD_FREQ));
62             }
63         } else {
64             if (fmod(_db_iface->get_clock_rate(dboard_iface::UNIT_RX),
65                     TWINRX_REV_AB_PFD_FREQ)
66                 != 0) {
67                 throw uhd::value_error(
68                     str(boost::format(
69                             "TwinRX clock rate %f is not a multiple of the pfd freq %f.")
70                         % _db_iface->get_clock_rate(dboard_iface::UNIT_RX)
71                         % TWINRX_REV_AB_PFD_FREQ));
72             }
73         }
74         // Initialize dboard clocks
75         _db_iface->set_clock_enabled(dboard_iface::UNIT_TX, true);
76         _db_iface->set_clock_enabled(dboard_iface::UNIT_RX, true);
77 
78         // Initialize default switch and attenuator states
79         set_chan_enabled(BOTH, false, false);
80         set_preamp1(BOTH, PREAMP_BYPASS, false);
81         set_preamp2(BOTH, false, false);
82         set_lb_preamp_preselector(BOTH, false, false);
83         set_signal_path(BOTH, PATH_LOWBAND, false);
84         set_lb_preselector(BOTH, PRESEL_PATH3, false);
85         set_hb_preselector(BOTH, PRESEL_PATH1, false);
86         set_input_atten(BOTH, 31, false);
87         set_lb_atten(BOTH, 31, false);
88         set_hb_atten(BOTH, 31, false);
89         set_lo1_source(BOTH, LO_INTERNAL, false);
90         set_lo2_source(BOTH, LO_INTERNAL, false);
91         set_lo1_export_source(LO_EXPORT_DISABLED, false);
92         set_lo2_export_source(LO_EXPORT_DISABLED, false);
93         set_antenna_mapping(ANTX_NATIVE, false);
94         set_crossover_cal_mode(CAL_DISABLED, false);
95         _cpld_regs->flush();
96 
97         // Turn on power and wait for power good
98         _gpio_iface->set_field(twinrx_gpio::FIELD_SWPS_EN, 1);
99         size_t timeout_ms = 100;
100         while (_gpio_iface->get_field(twinrx_gpio::FIELD_SWPS_PWR_GOOD) == 0) {
101             std::this_thread::sleep_for(std::chrono::microseconds(1000));
102             if (--timeout_ms == 0) {
103                 throw uhd::runtime_error("power supply failure");
104             }
105         }
106 
107         // Assert synthesizer chip enables
108         _gpio_iface->set_field(twinrx_gpio::FIELD_LO1_CE_CH1, 1);
109         _gpio_iface->set_field(twinrx_gpio::FIELD_LO1_CE_CH2, 1);
110         _gpio_iface->set_field(twinrx_gpio::FIELD_LO2_CE_CH1, 1);
111         _gpio_iface->set_field(twinrx_gpio::FIELD_LO2_CE_CH2, 1);
112 
113         // Initialize synthesizers
114         for (size_t i = 0; i < NUM_CHANS; i++) {
115             // LO1
116             if (rx_id == twinrx::TWINRX_REV_C_ID) {
117                 _lo1_iface[i] = adf535x_iface::make_adf5356(
118                     [this](const std::vector<uint32_t>& regs) {
119                         _write_lo_spi(dboard_iface::UNIT_TX, regs);
120                     },
121                     [this](uint32_t microseconds) {
122                         _db_iface->sleep(boost::chrono::microseconds(microseconds));
123                     });
124                 _lo1_iface[i]->set_pfd_freq(TWINRX_REV_C_PFD_FREQ);
125             } else {
126                 _lo1_iface[i] = adf535x_iface::make_adf5355(
127                     [this](const std::vector<uint32_t>& regs) {
128                         _write_lo_spi(dboard_iface::UNIT_TX, regs);
129                     },
130                     [this](uint32_t microseconds) {
131                         _db_iface->sleep(boost::chrono::microseconds(microseconds));
132                     });
133                 _lo1_iface[i]->set_pfd_freq(TWINRX_REV_AB_PFD_FREQ);
134             }
135             _lo1_iface[i]->set_output_power(adf535x_iface::OUTPUT_POWER_5DBM);
136             _lo1_iface[i]->set_reference_freq(
137                 _db_iface->get_clock_rate(dboard_iface::UNIT_TX));
138             _lo1_iface[i]->set_muxout_mode(adf535x_iface::MUXOUT_DLD);
139             _lo1_iface[i]->set_frequency(3e9, 1.0e3);
140 
141             // LO2
142             _lo2_iface[i] =
143                 adf435x_iface::make_adf4351([this](const std::vector<uint32_t>& regs) {
144                     _write_lo_spi(dboard_iface::UNIT_RX, regs);
145                 });
146             _lo2_iface[i]->set_feedback_select(adf435x_iface::FB_SEL_DIVIDED);
147             _lo2_iface[i]->set_output_power(adf435x_iface::OUTPUT_POWER_5DBM);
148             _lo2_iface[i]->set_reference_freq(
149                 _db_iface->get_clock_rate(dboard_iface::UNIT_RX));
150             _lo2_iface[i]->set_muxout_mode(adf435x_iface::MUXOUT_DLD);
151             _lo2_iface[i]->set_tuning_mode(adf435x_iface::TUNING_MODE_LOW_SPUR);
152             _lo2_iface[i]->set_prescaler(adf435x_iface::PRESCALER_8_9);
153         }
154         commit();
155     }
156 
~twinrx_ctrl_impl()157     ~twinrx_ctrl_impl()
158     {
159         UHD_SAFE_CALL(boost::lock_guard<boost::mutex> lock(_mutex);
160                       _gpio_iface->set_field(twinrx_gpio::FIELD_SWPS_EN, 0);)
161     }
162 
commit()163     void commit()
164     {
165         boost::lock_guard<boost::mutex> lock(_mutex);
166         _commit();
167     }
168 
set_chan_enabled(channel_t ch,bool enabled,bool commit=true)169     void set_chan_enabled(channel_t ch, bool enabled, bool commit = true)
170     {
171         boost::lock_guard<boost::mutex> lock(_mutex);
172         if (ch == CH1 or ch == BOTH) {
173             _cpld_regs->if0_reg3.set(rm::if0_reg3_t::IF1_IF2_EN_CH1, bool2bin(enabled));
174             _cpld_regs->if0_reg0.set(rm::if0_reg0_t::AMP_LO2_EN_CH1, bool2bin(enabled));
175             _chan_enabled[size_t(CH1)] = enabled;
176         }
177         if (ch == CH2 or ch == BOTH) {
178             _cpld_regs->rf1_reg5.set(rm::rf1_reg5_t::AMP_LO1_EN_CH2, bool2bin(enabled));
179             _cpld_regs->if0_reg4.set(rm::if0_reg4_t::IF1_IF2_EN_CH2, bool2bin(enabled));
180             _cpld_regs->if0_reg0.set(rm::if0_reg0_t::AMP_LO2_EN_CH2, bool2bin(enabled));
181             _chan_enabled[size_t(CH2)] = enabled;
182         }
183         _set_lo1_amp(_chan_enabled[size_t(CH1)],
184             _chan_enabled[size_t(CH2)],
185             _lo1_src[size_t(CH2)]);
186         if (commit)
187             _commit();
188     }
189 
set_preamp1(channel_t ch,preamp_state_t value,bool commit=true)190     void set_preamp1(channel_t ch, preamp_state_t value, bool commit = true)
191     {
192         boost::lock_guard<boost::mutex> lock(_mutex);
193         if (ch == CH1 or ch == BOTH) {
194             _cpld_regs->rf0_reg1.set(
195                 rm::rf0_reg1_t::SWPA1_CTL_CH1, bool2bin(value == PREAMP_HIGHBAND));
196             _cpld_regs->rf2_reg2.set(
197                 rm::rf2_reg2_t::SWPA2_CTRL_CH1, bool2bin(value == PREAMP_BYPASS));
198             _cpld_regs->rf0_reg1.set(
199                 rm::rf0_reg1_t::HB_PREAMP_EN_CH1, bool2bin(value == PREAMP_HIGHBAND));
200             _cpld_regs->rf0_reg1.set(
201                 rm::rf0_reg1_t::LB_PREAMP_EN_CH1, bool2bin(value == PREAMP_LOWBAND));
202         }
203         if (ch == CH2 or ch == BOTH) {
204             _cpld_regs->rf0_reg7.set(
205                 rm::rf0_reg7_t::SWPA1_CTRL_CH2, bool2bin(value == PREAMP_HIGHBAND));
206             _cpld_regs->rf2_reg5.set(
207                 rm::rf2_reg5_t::SWPA2_CTRL_CH2, bool2bin(value == PREAMP_BYPASS));
208             _cpld_regs->rf0_reg5.set(
209                 rm::rf0_reg5_t::HB_PREAMP_EN_CH2, bool2bin(value == PREAMP_HIGHBAND));
210             _cpld_regs->rf2_reg6.set(
211                 rm::rf2_reg6_t::LB_PREAMP_EN_CH2, bool2bin(value == PREAMP_LOWBAND));
212         }
213         if (commit)
214             _commit();
215     }
216 
set_preamp2(channel_t ch,bool enabled,bool commit=true)217     void set_preamp2(channel_t ch, bool enabled, bool commit = true)
218     {
219         boost::lock_guard<boost::mutex> lock(_mutex);
220         if (ch == CH1 or ch == BOTH) {
221             _cpld_regs->rf2_reg7.set(
222                 rm::rf2_reg7_t::SWPA4_CTRL_CH1, bool2bin(not enabled));
223             _cpld_regs->rf2_reg3.set(rm::rf2_reg3_t::PREAMP2_EN_CH1, bool2bin(enabled));
224         }
225         if (ch == CH2 or ch == BOTH) {
226             _cpld_regs->rf0_reg6.set(
227                 rm::rf0_reg6_t::SWPA4_CTRL_CH2, bool2bin(not enabled));
228             _cpld_regs->rf1_reg6.set(rm::rf1_reg6_t::PREAMP2_EN_CH2, bool2bin(enabled));
229         }
230         if (commit)
231             _commit();
232     }
233 
set_lb_preamp_preselector(channel_t ch,bool enabled,bool commit=true)234     void set_lb_preamp_preselector(channel_t ch, bool enabled, bool commit = true)
235     {
236         boost::lock_guard<boost::mutex> lock(_mutex);
237         if (ch == CH1 or ch == BOTH) {
238             _cpld_regs->rf0_reg7.set(
239                 rm::rf0_reg7_t::SWPA3_CTRL_CH1, bool2bin(not enabled));
240         }
241         if (ch == CH2 or ch == BOTH) {
242             _cpld_regs->rf0_reg1.set(
243                 rm::rf0_reg1_t::SWPA3_CTRL_CH2, bool2bin(not enabled));
244         }
245         if (commit)
246             _commit();
247     }
248 
set_signal_path(channel_t ch,signal_path_t path,bool commit=true)249     void set_signal_path(channel_t ch, signal_path_t path, bool commit = true)
250     {
251         boost::lock_guard<boost::mutex> lock(_mutex);
252         if (ch == CH1 or ch == BOTH) {
253             _cpld_regs->rf2_reg2.set(
254                 rm::rf2_reg2_t::SW11_CTRL_CH1, bool2bin(path == PATH_LOWBAND));
255             _cpld_regs->rf1_reg2.set(
256                 rm::rf1_reg2_t::SW12_CTRL_CH1, bool2bin(path == PATH_LOWBAND));
257             _cpld_regs->rf1_reg6.set(
258                 rm::rf1_reg6_t::HB_PRESEL_PGA_EN_CH1, bool2bin(path == PATH_HIGHBAND));
259             _cpld_regs->rf0_reg2.set(
260                 rm::rf0_reg2_t::SW6_CTRL_CH1, bool2bin(path == PATH_LOWBAND));
261             _cpld_regs->if0_reg3.set(
262                 rm::if0_reg3_t::SW13_CTRL_CH1, bool2bin(path == PATH_LOWBAND));
263             _cpld_regs->if0_reg2.set(
264                 rm::if0_reg2_t::AMP_LB_IF1_EN_CH1, bool2bin(path == PATH_LOWBAND));
265             _cpld_regs->if0_reg0.set(
266                 rm::if0_reg0_t::AMP_HB_IF1_EN_CH1, bool2bin(path == PATH_HIGHBAND));
267             _cpld_regs->rf1_reg2.set(
268                 rm::rf1_reg2_t::AMP_HB_EN_CH1, bool2bin(path == PATH_HIGHBAND));
269             _cpld_regs->rf2_reg2.set(
270                 rm::rf2_reg2_t::AMP_LB_EN_CH1, bool2bin(path == PATH_LOWBAND));
271         }
272         if (ch == CH2 or ch == BOTH) {
273             _cpld_regs->rf2_reg7.set(
274                 rm::rf2_reg7_t::SW11_CTRL_CH2, bool2bin(path == PATH_LOWBAND));
275             _cpld_regs->rf1_reg7.set(
276                 rm::rf1_reg7_t::SW12_CTRL_CH2, bool2bin(path == PATH_LOWBAND));
277             _cpld_regs->rf1_reg2.set(
278                 rm::rf1_reg2_t::HB_PRESEL_PGA_EN_CH2, bool2bin(path == PATH_HIGHBAND));
279             _cpld_regs->rf0_reg6.set(
280                 rm::rf0_reg6_t::SW6_CTRL_CH2, bool2bin(path == PATH_HIGHBAND));
281             _cpld_regs->if0_reg6.set(
282                 rm::if0_reg6_t::SW13_CTRL_CH2, bool2bin(path == PATH_HIGHBAND));
283             _cpld_regs->if0_reg2.set(
284                 rm::if0_reg2_t::AMP_LB_IF1_EN_CH2, bool2bin(path == PATH_LOWBAND));
285             _cpld_regs->if0_reg6.set(
286                 rm::if0_reg6_t::AMP_HB_IF1_EN_CH2, bool2bin(path == PATH_HIGHBAND));
287             _cpld_regs->rf1_reg7.set(
288                 rm::rf1_reg7_t::AMP_HB_EN_CH2, bool2bin(path == PATH_HIGHBAND));
289             _cpld_regs->rf2_reg7.set(
290                 rm::rf2_reg7_t::AMP_LB_EN_CH2, bool2bin(path == PATH_LOWBAND));
291         }
292         if (commit)
293             _commit();
294     }
295 
set_lb_preselector(channel_t ch,preselector_path_t path,bool commit=true)296     void set_lb_preselector(channel_t ch, preselector_path_t path, bool commit = true)
297     {
298         boost::lock_guard<boost::mutex> lock(_mutex);
299         uint32_t sw7val = 0, sw8val = 0;
300         switch (path) {
301             case PRESEL_PATH1:
302                 sw7val = 3;
303                 sw8val = 1;
304                 break;
305             case PRESEL_PATH2:
306                 sw7val = 2;
307                 sw8val = 0;
308                 break;
309             case PRESEL_PATH3:
310                 sw7val = 0;
311                 sw8val = 2;
312                 break;
313             case PRESEL_PATH4:
314                 sw7val = 1;
315                 sw8val = 3;
316                 break;
317             default:
318                 UHD_THROW_INVALID_CODE_PATH();
319         }
320         if (ch == CH1 or ch == BOTH) {
321             _cpld_regs->rf0_reg3.set(rm::rf0_reg3_t::SW7_CTRL_CH1, sw7val);
322             _cpld_regs->rf2_reg3.set(rm::rf2_reg3_t::SW8_CTRL_CH1, sw8val);
323         }
324         if (ch == CH2 or ch == BOTH) {
325             _cpld_regs->rf0_reg7.set(rm::rf0_reg7_t::SW7_CTRL_CH2, sw7val);
326             _cpld_regs->rf2_reg7.set(rm::rf2_reg7_t::SW8_CTRL_CH2, sw8val);
327         }
328         if (commit)
329             _commit();
330     }
331 
set_hb_preselector(channel_t ch,preselector_path_t path,bool commit=true)332     void set_hb_preselector(channel_t ch, preselector_path_t path, bool commit = true)
333     {
334         boost::lock_guard<boost::mutex> lock(_mutex);
335         uint32_t sw9ch1val = 0, sw10ch1val = 0, sw9ch2val = 0, sw10ch2val = 0;
336         switch (path) {
337             case PRESEL_PATH1:
338                 sw9ch1val  = 3;
339                 sw10ch1val = 0;
340                 sw9ch2val  = 0;
341                 sw10ch2val = 3;
342                 break;
343             case PRESEL_PATH2:
344                 sw9ch1val  = 1;
345                 sw10ch1val = 2;
346                 sw9ch2val  = 1;
347                 sw10ch2val = 1;
348                 break;
349             case PRESEL_PATH3:
350                 sw9ch1val  = 2;
351                 sw10ch1val = 1;
352                 sw9ch2val  = 2;
353                 sw10ch2val = 2;
354                 break;
355             case PRESEL_PATH4:
356                 sw9ch1val  = 0;
357                 sw10ch1val = 3;
358                 sw9ch2val  = 3;
359                 sw10ch2val = 0;
360                 break;
361             default:
362                 UHD_THROW_INVALID_CODE_PATH();
363         }
364         if (ch == CH1 or ch == BOTH) {
365             _cpld_regs->rf0_reg5.set(rm::rf0_reg5_t::SW9_CTRL_CH1, sw9ch1val);
366             _cpld_regs->rf1_reg3.set(rm::rf1_reg3_t::SW10_CTRL_CH1, sw10ch1val);
367         }
368         if (ch == CH2 or ch == BOTH) {
369             _cpld_regs->rf0_reg3.set(rm::rf0_reg3_t::SW9_CTRL_CH2, sw9ch2val);
370             _cpld_regs->rf1_reg7.set(rm::rf1_reg7_t::SW10_CTRL_CH2, sw10ch2val);
371         }
372         if (commit)
373             _commit();
374     }
375 
set_input_atten(channel_t ch,uint8_t atten,bool commit=true)376     void set_input_atten(channel_t ch, uint8_t atten, bool commit = true)
377     {
378         boost::lock_guard<boost::mutex> lock(_mutex);
379         if (ch == CH1 or ch == BOTH) {
380             _cpld_regs->rf0_reg0.set(rm::rf0_reg0_t::ATTEN_IN_CH1, atten & 0x1F);
381         }
382         if (ch == CH2 or ch == BOTH) {
383             _cpld_regs->rf0_reg4.set(rm::rf0_reg4_t::ATTEN_IN_CH2, atten & 0x1F);
384         }
385         if (commit)
386             _commit();
387     }
388 
set_lb_atten(channel_t ch,uint8_t atten,bool commit=true)389     void set_lb_atten(channel_t ch, uint8_t atten, bool commit = true)
390     {
391         boost::lock_guard<boost::mutex> lock(_mutex);
392         if (ch == CH1 or ch == BOTH) {
393             _cpld_regs->rf2_reg0.set(rm::rf2_reg0_t::ATTEN_LB_CH1, atten & 0x1F);
394         }
395         if (ch == CH2 or ch == BOTH) {
396             _cpld_regs->rf2_reg4.set(rm::rf2_reg4_t::ATTEN_LB_CH2, atten & 0x1F);
397         }
398         if (commit)
399             _commit();
400     }
401 
set_hb_atten(channel_t ch,uint8_t atten,bool commit=true)402     void set_hb_atten(channel_t ch, uint8_t atten, bool commit = true)
403     {
404         boost::lock_guard<boost::mutex> lock(_mutex);
405         if (ch == CH1 or ch == BOTH) {
406             _cpld_regs->rf1_reg0.set(rm::rf1_reg0_t::ATTEN_HB_CH1, atten & 0x1F);
407         }
408         if (ch == CH2 or ch == BOTH) {
409             _cpld_regs->rf1_reg4.set(rm::rf1_reg4_t::ATTEN_HB_CH2, atten & 0x1F);
410         }
411         if (commit)
412             _commit();
413     }
414 
set_lo1_source(channel_t ch,lo_source_t source,bool commit=true)415     void set_lo1_source(channel_t ch, lo_source_t source, bool commit = true)
416     {
417         boost::lock_guard<boost::mutex> lock(_mutex);
418         if (ch == CH1 or ch == BOTH) {
419             _cpld_regs->rf1_reg5.set(
420                 rm::rf1_reg5_t::SW14_CTRL_CH2, bool2bin(source != LO_COMPANION));
421             _cpld_regs->rf1_reg1.set(rm::rf1_reg1_t::SW15_CTRL_CH1,
422                 bool2bin(source == LO_EXTERNAL || source == LO_REIMPORT));
423             _cpld_regs->rf1_reg1.set(
424                 rm::rf1_reg1_t::SW16_CTRL_CH1, bool2bin(source != LO_INTERNAL));
425             _lo1_src[size_t(CH1)] = source;
426         }
427         if (ch == CH2 or ch == BOTH) {
428             _cpld_regs->rf1_reg1.set(
429                 rm::rf1_reg1_t::SW14_CTRL_CH1, bool2bin(source == LO_COMPANION));
430             _cpld_regs->rf1_reg5.set(
431                 rm::rf1_reg5_t::SW15_CTRL_CH2, bool2bin(source != LO_INTERNAL));
432             _cpld_regs->rf1_reg6.set(
433                 rm::rf1_reg6_t::SW16_CTRL_CH2, bool2bin(source == LO_INTERNAL));
434             _lo1_src[size_t(CH2)] = source;
435             _set_lo1_amp(_chan_enabled[size_t(CH1)],
436                 _chan_enabled[size_t(CH2)],
437                 _lo1_src[size_t(CH2)]);
438         }
439         if (commit)
440             _commit();
441     }
442 
set_lo2_source(channel_t ch,lo_source_t source,bool commit=true)443     void set_lo2_source(channel_t ch, lo_source_t source, bool commit = true)
444     {
445         boost::lock_guard<boost::mutex> lock(_mutex);
446         if (ch == CH1 or ch == BOTH) {
447             _cpld_regs->if0_reg0.set(
448                 rm::if0_reg0_t::SW19_CTRL_CH2, bool2bin(source == LO_COMPANION));
449             _cpld_regs->if0_reg1.set(
450                 rm::if0_reg1_t::SW20_CTRL_CH1, bool2bin(source == LO_COMPANION));
451             _cpld_regs->if0_reg4.set(
452                 rm::if0_reg4_t::SW21_CTRL_CH1, bool2bin(source == LO_INTERNAL));
453             _lo2_src[size_t(CH1)] = source;
454         }
455         if (ch == CH2 or ch == BOTH) {
456             _cpld_regs->if0_reg4.set(rm::if0_reg4_t::SW19_CTRL_CH1,
457                 bool2bin(source == LO_EXTERNAL || source == LO_REIMPORT));
458             _cpld_regs->if0_reg0.set(rm::if0_reg0_t::SW20_CTRL_CH2,
459                 bool2bin(source == LO_INTERNAL || source == LO_DISABLED));
460             _cpld_regs->if0_reg4.set(
461                 rm::if0_reg4_t::SW21_CTRL_CH2, bool2bin(source == LO_INTERNAL));
462             _lo2_src[size_t(CH2)] = source;
463         }
464         if (commit)
465             _commit();
466     }
467 
set_lo1_export_source(lo_export_source_t source,bool commit=true)468     void set_lo1_export_source(lo_export_source_t source, bool commit = true)
469     {
470         boost::lock_guard<boost::mutex> lock(_mutex);
471         // SW22 may conflict with the cal switch but this attr takes priority and we
472         // assume that the cal switch is disabled (by disabling it!)
473         _set_cal_mode(CAL_DISABLED, source);
474         _cpld_regs->rf1_reg3.set(
475             rm::rf1_reg3_t::SW23_CTRL, bool2bin(source != LO_CH1_SYNTH));
476         _lo1_export = source;
477 
478         if (commit)
479             _commit();
480     }
481 
set_lo2_export_source(lo_export_source_t source,bool commit=true)482     void set_lo2_export_source(lo_export_source_t source, bool commit = true)
483     {
484         boost::lock_guard<boost::mutex> lock(_mutex);
485         _cpld_regs->if0_reg7.set(
486             rm::if0_reg7_t::SW24_CTRL_CH2, bool2bin(source == LO_CH2_SYNTH));
487         _cpld_regs->if0_reg4.set(
488             rm::if0_reg4_t::SW25_CTRL, bool2bin(source != LO_CH1_SYNTH));
489         _cpld_regs->if0_reg3.set(
490             rm::if0_reg3_t::SW24_CTRL_CH1, bool2bin(source != LO_CH1_SYNTH));
491         _lo2_export = source;
492 
493         if (commit)
494             _commit();
495     }
496 
set_antenna_mapping(antenna_mapping_t mapping,bool commit=true)497     void set_antenna_mapping(antenna_mapping_t mapping, bool commit = true)
498     {
499         boost::lock_guard<boost::mutex> lock(_mutex);
500 
501         enum switch_path_t { CONNECT, TERM, EXPORT, IMPORT, SWAP };
502         switch_path_t path1, path2;
503 
504         switch (mapping) {
505             case ANTX_NATIVE:
506                 path1 = CONNECT;
507                 path2 = CONNECT;
508                 break;
509             case ANT1_SHARED:
510                 path1 = EXPORT;
511                 path2 = IMPORT;
512                 break;
513             case ANT2_SHARED:
514                 path1 = IMPORT;
515                 path2 = EXPORT;
516                 break;
517             case ANTX_SWAPPED:
518                 path1 = SWAP;
519                 path2 = SWAP;
520                 break;
521             default:
522                 path1 = TERM;
523                 path2 = TERM;
524                 break;
525         }
526 
527         _cpld_regs->rf0_reg5.set(
528             rm::rf0_reg5_t::SW3_CTRL_CH1, bool2bin(path1 == EXPORT || path1 == SWAP));
529         _cpld_regs->rf0_reg2.set(
530             rm::rf0_reg2_t::SW4_CTRL_CH1, bool2bin(!(path1 == IMPORT || path1 == SWAP)));
531         _cpld_regs->rf0_reg2.set(
532             rm::rf0_reg2_t::SW5_CTRL_CH1, bool2bin(path1 == CONNECT));
533         _cpld_regs->rf0_reg7.set(
534             rm::rf0_reg7_t::SW3_CTRL_CH2, bool2bin(path2 == EXPORT || path2 == SWAP));
535         _cpld_regs->rf0_reg6.set(
536             rm::rf0_reg6_t::SW4_CTRL_CH2, bool2bin(path2 == IMPORT || path2 == SWAP));
537         _cpld_regs->rf0_reg6.set(
538             rm::rf0_reg6_t::SW5_CTRL_CH2, bool2bin(path2 == CONNECT));
539 
540         if (commit)
541             _commit();
542     }
543 
set_crossover_cal_mode(cal_mode_t cal_mode,bool commit=true)544     void set_crossover_cal_mode(cal_mode_t cal_mode, bool commit = true)
545     {
546         boost::lock_guard<boost::mutex> lock(_mutex);
547         if (_lo1_export == LO_CH1_SYNTH && cal_mode == CAL_CH2) {
548             throw uhd::runtime_error(
549                 "cannot enable cal crossover on CH2 when LO1 in CH1 is exported");
550         }
551         if (_lo1_export == LO_CH2_SYNTH && cal_mode == CAL_CH1) {
552             throw uhd::runtime_error(
553                 "cannot enable cal crossover on CH1 when LO1 in CH2 is exported");
554         }
555         _set_cal_mode(cal_mode, _lo1_export);
556 
557         if (commit)
558             _commit();
559     }
560 
set_lo1_synth_freq(channel_t ch,double freq,bool commit=true)561     double set_lo1_synth_freq(channel_t ch, double freq, bool commit = true)
562     {
563         boost::lock_guard<boost::mutex> lock(_mutex);
564         static const double RESOLUTION = 1e3;
565 
566         double coerced_freq = 0.0;
567         if (ch == CH1 or ch == BOTH) {
568             coerced_freq =
569                 _lo1_iface[size_t(CH1)]->set_frequency(freq, RESOLUTION, false);
570             _lo1_freq[size_t(CH1)] = tune_freq_t(freq);
571         }
572         if (ch == CH2 or ch == BOTH) {
573             coerced_freq =
574                 _lo1_iface[size_t(CH2)]->set_frequency(freq, RESOLUTION, false);
575             _lo1_freq[size_t(CH2)] = tune_freq_t(freq);
576         }
577 
578         if (commit)
579             _commit();
580         return coerced_freq;
581     }
582 
set_lo2_synth_freq(channel_t ch,double freq,bool commit=true)583     double set_lo2_synth_freq(channel_t ch, double freq, bool commit = true)
584     {
585         boost::lock_guard<boost::mutex> lock(_mutex);
586 
587         double coerced_freq = 0.0;
588         if (ch == CH1 or ch == BOTH) {
589             coerced_freq = _lo2_iface[size_t(CH1)]->set_frequency(freq, false, false);
590             _lo2_freq[size_t(CH1)] = tune_freq_t(freq);
591         }
592         if (ch == CH2 or ch == BOTH) {
593             coerced_freq = _lo2_iface[size_t(CH2)]->set_frequency(freq, false, false);
594             _lo2_freq[size_t(CH2)] = tune_freq_t(freq);
595         }
596 
597         if (commit)
598             _commit();
599         return coerced_freq;
600     }
601 
set_lo1_charge_pump(channel_t ch,double current,bool commit=true)602     double set_lo1_charge_pump(channel_t ch, double current, bool commit = true)
603     {
604         boost::lock_guard<boost::mutex> lock(_mutex);
605         double coerced_current = 0.0;
606         if (ch == CH1 or ch == BOTH) {
607             coerced_current =
608                 _lo1_iface[size_t(CH1)]->set_charge_pump_current(current, false);
609         }
610         if (ch == CH2 or ch == BOTH) {
611             coerced_current =
612                 _lo1_iface[size_t(CH2)]->set_charge_pump_current(current, false);
613         }
614 
615         if (commit) {
616             _commit();
617         }
618         return coerced_current;
619     }
620 
set_lo2_charge_pump(channel_t ch,double current,bool commit=true)621     double set_lo2_charge_pump(channel_t ch, double current, bool commit = true)
622     {
623         boost::lock_guard<boost::mutex> lock(_mutex);
624         double coerced_current = 0.0;
625         if (ch == CH1 or ch == BOTH) {
626             coerced_current =
627                 _lo2_iface[size_t(CH1)]->set_charge_pump_current(current, false);
628         }
629         if (ch == CH2 or ch == BOTH) {
630             coerced_current =
631                 _lo2_iface[size_t(CH2)]->set_charge_pump_current(current, false);
632         }
633 
634         if (commit) {
635             _commit();
636         }
637         return coerced_current;
638     }
639 
get_lo1_charge_pump_range()640     uhd::meta_range_t get_lo1_charge_pump_range()
641     {
642         // assume that both channels have the same range
643         return _lo1_iface[size_t(CH1)]->get_charge_pump_current_range();
644     }
645 
get_lo2_charge_pump_range()646     uhd::meta_range_t get_lo2_charge_pump_range()
647     {
648         // assume that both channels have the same range
649         return _lo2_iface[size_t(CH1)]->get_charge_pump_current_range();
650     }
651 
read_lo1_locked(channel_t ch)652     bool read_lo1_locked(channel_t ch)
653     {
654         boost::lock_guard<boost::mutex> lock(_mutex);
655 
656         bool locked = true;
657         if (ch == CH1 or ch == BOTH) {
658             locked = locked
659                      && (_gpio_iface->get_field(twinrx_gpio::FIELD_LO1_MUXOUT_CH1) == 1);
660         }
661         if (ch == CH2 or ch == BOTH) {
662             locked = locked
663                      && (_gpio_iface->get_field(twinrx_gpio::FIELD_LO1_MUXOUT_CH2) == 1);
664         }
665         return locked;
666     }
667 
read_lo2_locked(channel_t ch)668     bool read_lo2_locked(channel_t ch)
669     {
670         boost::lock_guard<boost::mutex> lock(_mutex);
671 
672         bool locked = true;
673         if (ch == CH1 or ch == BOTH) {
674             locked = locked
675                      && (_gpio_iface->get_field(twinrx_gpio::FIELD_LO2_MUXOUT_CH1) == 1);
676         }
677         if (ch == CH2 or ch == BOTH) {
678             locked = locked
679                      && (_gpio_iface->get_field(twinrx_gpio::FIELD_LO2_MUXOUT_CH2) == 1);
680         }
681         return locked;
682     }
683 
684 private: // Functions
_set_cal_mode(cal_mode_t cal_mode,lo_export_source_t lo1_export_src)685     void _set_cal_mode(cal_mode_t cal_mode, lo_export_source_t lo1_export_src)
686     {
687         _cpld_regs->rf1_reg1.set(
688             rm::rf1_reg1_t::SW17_CTRL_CH1, bool2bin(cal_mode != CAL_CH1));
689         _cpld_regs->rf1_reg6.set(
690             rm::rf1_reg6_t::SW17_CTRL_CH2, bool2bin(cal_mode != CAL_CH2));
691         _cpld_regs->rf1_reg5.set(
692             rm::rf1_reg5_t::SW18_CTRL_CH1, bool2bin(cal_mode != CAL_CH1));
693         _cpld_regs->rf2_reg3.set(
694             rm::rf2_reg3_t::SW18_CTRL_CH2, bool2bin(cal_mode != CAL_CH2));
695         _cpld_regs->rf1_reg3.set(rm::rf1_reg3_t::SW22_CTRL_CH1,
696             bool2bin((lo1_export_src != LO_CH1_SYNTH) || (cal_mode == CAL_CH1)));
697         _cpld_regs->rf1_reg7.set(rm::rf1_reg7_t::SW22_CTRL_CH2,
698             bool2bin((lo1_export_src != LO_CH2_SYNTH) || (cal_mode == CAL_CH2)));
699     }
700 
_set_lo1_amp(bool ch1_enabled,bool ch2_enabled,lo_source_t ch2_lo1_src)701     void _set_lo1_amp(bool ch1_enabled, bool ch2_enabled, lo_source_t ch2_lo1_src)
702     {
703         // AMP_LO1_EN_CH1 also controls the amp for the external LO1 port,
704         // which could be in use by ch2
705         _cpld_regs->rf1_reg1.set(rm::rf1_reg1_t::AMP_LO1_EN_CH1,
706             bool2bin(
707                 ch1_enabled
708                 || (ch2_enabled
709                        && (ch2_lo1_src == LO_EXTERNAL || ch2_lo1_src == LO_REIMPORT))));
710     }
711 
_config_lo_route(lo_t lo,channel_t channel)712     void _config_lo_route(lo_t lo, channel_t channel)
713     {
714         // Route SPI LEs through CPLD (will not assert them)
715         _cpld_regs->rf0_reg2.set(rm::rf0_reg2_t::LO1_LE_CH1,
716             bool2bin(lo == LO1 and (channel == CH1 or channel == BOTH)));
717         _cpld_regs->rf0_reg2.set(rm::rf0_reg2_t::LO1_LE_CH2,
718             bool2bin(lo == LO1 and (channel == CH2 or channel == BOTH)));
719         _cpld_regs->rf0_reg2.flush();
720         _cpld_regs->if0_reg2.set(rm::if0_reg2_t::LO2_LE_CH1,
721             bool2bin(lo == LO2 and (channel == CH1 or channel == BOTH)));
722         _cpld_regs->if0_reg2.set(rm::if0_reg2_t::LO2_LE_CH2,
723             bool2bin(lo == LO2 and (channel == CH2 or channel == BOTH)));
724         _cpld_regs->if0_reg2.flush();
725     }
726 
_write_lo_spi(dboard_iface::unit_t unit,const std::vector<uint32_t> & regs)727     void _write_lo_spi(dboard_iface::unit_t unit, const std::vector<uint32_t>& regs)
728     {
729         for (uint32_t reg : regs) {
730             _db_iface->write_spi(unit, _spi_config, reg, 32);
731         }
732     }
733 
_commit()734     void _commit()
735     {
736         // Commit everything except the LO synthesizers
737         _cpld_regs->flush();
738 
739         // Disable unused LO synthesizers
740         _lo1_enable[size_t(CH1)] = _lo1_src[size_t(CH1)] == LO_INTERNAL
741                                    || _lo1_src[size_t(CH2)] == LO_COMPANION
742                                    || _lo1_export == LO_CH1_SYNTH;
743 
744         _lo1_enable[size_t(CH2)] = _lo1_src[size_t(CH2)] == LO_INTERNAL
745                                    || _lo1_src[size_t(CH1)] == LO_COMPANION
746                                    || _lo1_export == LO_CH2_SYNTH;
747         _lo2_enable[size_t(CH1)] = _lo2_src[size_t(CH1)] == LO_INTERNAL
748                                    || _lo2_src[size_t(CH2)] == LO_COMPANION
749                                    || _lo2_export == LO_CH1_SYNTH;
750 
751         _lo2_enable[size_t(CH2)] = _lo2_src[size_t(CH2)] == LO_INTERNAL
752                                    || _lo2_src[size_t(CH1)] == LO_COMPANION
753                                    || _lo2_export == LO_CH2_SYNTH;
754 
755         _lo1_iface[size_t(CH1)]->set_output_enable(
756             adf535x_iface::RF_OUTPUT_A, _lo1_enable[size_t(CH1)].get());
757         _lo1_iface[size_t(CH2)]->set_output_enable(
758             adf535x_iface::RF_OUTPUT_A, _lo1_enable[size_t(CH2)].get());
759 
760         _lo2_iface[size_t(CH1)]->set_output_enable(
761             adf435x_iface::RF_OUTPUT_A, _lo2_enable[size_t(CH1)].get());
762         _lo2_iface[size_t(CH2)]->set_output_enable(
763             adf435x_iface::RF_OUTPUT_A, _lo2_enable[size_t(CH2)].get());
764 
765         // Commit LO1 frequency
766         // Commit Channel 1's settings to both channels simultaneously if the frequency is
767         // the same.
768         bool simultaneous_commit_lo1 =
769             _lo1_freq[size_t(CH1)].is_dirty() and _lo1_freq[size_t(CH2)].is_dirty()
770             and _lo1_freq[size_t(CH1)].get() == _lo1_freq[size_t(CH2)].get()
771             and _lo1_enable[size_t(CH1)].get() == _lo1_enable[size_t(CH2)].get();
772 
773         if (simultaneous_commit_lo1) {
774             _config_lo_route(LO1, BOTH);
775             // Only commit one of the channels. The route LO_CONFIG_BOTH
776             // will ensure that the LEs for both channels are enabled
777             _lo1_iface[size_t(CH1)]->commit();
778             _lo1_freq[size_t(CH1)].mark_clean();
779             _lo1_freq[size_t(CH2)].mark_clean();
780             _lo1_enable[size_t(CH1)].mark_clean();
781             _lo1_enable[size_t(CH2)].mark_clean();
782         } else {
783             if (_lo1_freq[size_t(CH1)].is_dirty()
784                 || _lo1_enable[size_t(CH1)].is_dirty()) {
785                 _config_lo_route(LO1, CH1);
786                 _lo1_iface[size_t(CH1)]->commit();
787                 _lo1_freq[size_t(CH1)].mark_clean();
788                 _lo1_enable[size_t(CH1)].mark_clean();
789             }
790             if (_lo1_freq[size_t(CH2)].is_dirty()
791                 || _lo1_enable[size_t(CH2)].is_dirty()) {
792                 _config_lo_route(LO1, CH2);
793                 _lo1_iface[size_t(CH2)]->commit();
794                 _lo1_freq[size_t(CH2)].mark_clean();
795                 _lo1_enable[size_t(CH2)].mark_clean();
796             }
797         }
798 
799         // Commit LO2 frequency
800         bool simultaneous_commit_lo2 =
801             _lo2_freq[size_t(CH1)].is_dirty() and _lo2_freq[size_t(CH2)].is_dirty()
802             and _lo2_freq[size_t(CH1)].get() == _lo2_freq[size_t(CH2)].get()
803             and _lo2_enable[size_t(CH1)].get() == _lo2_enable[size_t(CH2)].get();
804 
805         if (simultaneous_commit_lo2) {
806             _config_lo_route(LO2, BOTH);
807             // Only commit one of the channels. The route LO_CONFIG_BOTH
808             // will ensure that the LEs for both channels are enabled
809             _lo2_iface[size_t(CH1)]->commit();
810             _lo2_freq[size_t(CH1)].mark_clean();
811             _lo2_freq[size_t(CH2)].mark_clean();
812             _lo2_enable[size_t(CH1)].mark_clean();
813             _lo2_enable[size_t(CH2)].mark_clean();
814         } else {
815             if (_lo2_freq[size_t(CH1)].is_dirty()
816                 || _lo2_enable[size_t(CH1)].is_dirty()) {
817                 _config_lo_route(LO2, CH1);
818                 _lo2_iface[size_t(CH1)]->commit();
819                 _lo2_freq[size_t(CH1)].mark_clean();
820                 _lo2_enable[size_t(CH1)].mark_clean();
821             }
822             if (_lo2_freq[size_t(CH2)].is_dirty()
823                 || _lo2_enable[size_t(CH2)].is_dirty()) {
824                 _config_lo_route(LO2, CH2);
825                 _lo2_iface[size_t(CH2)]->commit();
826                 _lo2_freq[size_t(CH2)].mark_clean();
827                 _lo2_enable[size_t(CH2)].mark_clean();
828             }
829         }
830     }
831 
832 private: // Members
833     static const size_t NUM_CHANS = 2;
834 
835     struct tune_freq_t : public uhd::math::fp_compare::fp_compare_delta<double>
836     {
tune_freq_ttwinrx_ctrl_impl::tune_freq_t837         tune_freq_t()
838             : uhd::math::fp_compare::fp_compare_delta<double>(
839                   0.0, uhd::math::FREQ_COMPARISON_DELTA_HZ)
840         {
841         }
842 
tune_freq_ttwinrx_ctrl_impl::tune_freq_t843         tune_freq_t(double freq)
844             : uhd::math::fp_compare::fp_compare_delta<double>(
845                   freq, uhd::math::FREQ_COMPARISON_DELTA_HZ)
846         {
847         }
848     };
849 
850     boost::mutex _mutex;
851     dboard_iface::sptr _db_iface;
852     twinrx_gpio::sptr _gpio_iface;
853     twinrx_cpld_regmap::sptr _cpld_regs;
854     spi_config_t _spi_config;
855     adf535x_iface::sptr _lo1_iface[NUM_CHANS];
856     adf435x_iface::sptr _lo2_iface[NUM_CHANS];
857     lo_source_t _lo1_src[NUM_CHANS];
858     lo_source_t _lo2_src[NUM_CHANS];
859     dirty_tracked<tune_freq_t> _lo1_freq[NUM_CHANS];
860     dirty_tracked<tune_freq_t> _lo2_freq[NUM_CHANS];
861     dirty_tracked<bool> _lo1_enable[NUM_CHANS];
862     dirty_tracked<bool> _lo2_enable[NUM_CHANS];
863     lo_export_source_t _lo1_export;
864     lo_export_source_t _lo2_export;
865     bool _chan_enabled[NUM_CHANS];
866 };
867 
make(dboard_iface::sptr db_iface,twinrx_gpio::sptr gpio_iface,twinrx_cpld_regmap::sptr cpld_regmap,const dboard_id_t rx_id)868 twinrx_ctrl::sptr twinrx_ctrl::make(dboard_iface::sptr db_iface,
869     twinrx_gpio::sptr gpio_iface,
870     twinrx_cpld_regmap::sptr cpld_regmap,
871     const dboard_id_t rx_id)
872 {
873     return std::make_shared<twinrx_ctrl_impl>(db_iface, gpio_iface, cpld_regmap, rx_id);
874 }
875