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