1 //
2 // Copyright 2014-17 Ettus Research, A National Instruments Company
3 //
4 // SPDX-License-Identifier: GPL-3.0-or-later
5 //
6
7 /***********************************************************************
8 * Included Files and Libraries
9 **********************************************************************/
10 #include "db_ubx.hpp"
11 #include <uhd/types/device_addr.hpp>
12 #include <uhd/types/dict.hpp>
13 #include <uhd/types/direction.hpp>
14 #include <uhd/types/ranges.hpp>
15 #include <uhd/types/sensors.hpp>
16 #include <uhd/usrp/dboard_base.hpp>
17 #include <uhd/usrp/dboard_manager.hpp>
18 #include <uhd/utils/assert_has.hpp>
19 #include <uhd/utils/log.hpp>
20 #include <uhd/utils/safe_call.hpp>
21 #include <uhd/utils/static.hpp>
22 #include <uhdlib/usrp/common/max287x.hpp>
23 #include <boost/algorithm/string.hpp>
24 #include <boost/math/special_functions/round.hpp>
25 #include <boost/thread/mutex.hpp>
26 #include <chrono>
27 #include <functional>
28 #include <map>
29 #include <memory>
30 #include <thread>
31
32 using namespace uhd;
33 using namespace uhd::usrp;
34 using namespace uhd::usrp::dboard::ubx;
35
36 /***********************************************************************
37 * UBX Data Structures
38 **********************************************************************/
39 enum ubx_gpio_field_id_t {
40 SPI_ADDR,
41 TX_EN_N,
42 RX_EN_N,
43 RX_ANT,
44 TX_LO_LOCKED,
45 RX_LO_LOCKED,
46 CPLD_RST_N,
47 TX_GAIN,
48 RX_GAIN,
49 RXLO1_SYNC,
50 RXLO2_SYNC,
51 TXLO1_SYNC,
52 TXLO2_SYNC
53 };
54
55 enum ubx_cpld_field_id_t {
56 TXHB_SEL = 0,
57 TXLB_SEL = 1,
58 TXLO1_FSEL1 = 2,
59 TXLO1_FSEL2 = 3,
60 TXLO1_FSEL3 = 4,
61 RXHB_SEL = 5,
62 RXLB_SEL = 6,
63 RXLO1_FSEL1 = 7,
64 RXLO1_FSEL2 = 8,
65 RXLO1_FSEL3 = 9,
66 SEL_LNA1 = 10,
67 SEL_LNA2 = 11,
68 TXLO1_FORCEON = 12,
69 TXLO2_FORCEON = 13,
70 TXMOD_FORCEON = 14,
71 TXMIXER_FORCEON = 15,
72 TXDRV_FORCEON = 16,
73 RXLO1_FORCEON = 17,
74 RXLO2_FORCEON = 18,
75 RXDEMOD_FORCEON = 19,
76 RXMIXER_FORCEON = 20,
77 RXDRV_FORCEON = 21,
78 RXAMP_FORCEON = 22,
79 RXLNA1_FORCEON = 23,
80 RXLNA2_FORCEON = 24,
81 CAL_ENABLE = 25
82 };
83
84 struct ubx_gpio_field_info_t
85 {
86 ubx_gpio_field_id_t id;
87 dboard_iface::unit_t unit;
88 uint32_t offset;
89 uint32_t mask;
90 uint32_t width;
91 enum { OUTPUT, INPUT } direction;
92 bool is_atr_controlled;
93 uint32_t atr_idle;
94 uint32_t atr_tx;
95 uint32_t atr_rx;
96 uint32_t atr_full_duplex;
97 };
98
99 struct ubx_gpio_reg_t
100 {
101 bool dirty;
102 uint32_t value;
103 uint32_t mask;
104 uint32_t ddr;
105 uint32_t atr_mask;
106 uint32_t atr_idle;
107 uint32_t atr_tx;
108 uint32_t atr_rx;
109 uint32_t atr_full_duplex;
110 };
111
112 struct ubx_cpld_reg_t
113 {
set_fieldubx_cpld_reg_t114 void set_field(ubx_cpld_field_id_t field, uint32_t val)
115 {
116 UHD_ASSERT_THROW(val == (val & 0x1));
117
118 if (val)
119 value |= uint32_t(1) << field;
120 else
121 value &= ~(uint32_t(1) << field);
122 }
123
124 uint32_t value;
125 };
126
127 enum spi_dest_t {
128 TXLO1 = 0x0, // 0x00: TXLO1, the main TXLO from 400MHz to 6000MHz
129 TXLO2 = 0x1, // 0x01: TXLO2, the low band mixer TXLO 10MHz to 400MHz
130 RXLO1 = 0x2, // 0x02: RXLO1, the main RXLO from 400MHz to 6000MHz
131 RXLO2 = 0x3, // 0x03: RXLO2, the low band mixer RXLO 10MHz to 400MHz
132 CPLD = 0x4 // 0x04: CPLD SPI Register
133 };
134
135 /***********************************************************************
136 * UBX Constants
137 **********************************************************************/
138 #define fMHz (1000000.0)
139 static const freq_range_t ubx_freq_range(10e6, 6.0e9);
140 static const gain_range_t ubx_tx_gain_range(0, 31.5, double(0.5));
141 static const gain_range_t ubx_rx_gain_range(0, 31.5, double(0.5));
142 static const std::vector<std::string> ubx_pgas{"PGA-TX", "PGA-RX"};
143 static const std::vector<std::string> ubx_plls{"TXLO", "RXLO"};
144 static const std::vector<std::string> ubx_tx_antennas{"TX/RX", "CAL"};
145 static const std::vector<std::string> ubx_rx_antennas{"TX/RX", "RX2", "CAL"};
146 static const std::vector<std::string> ubx_power_modes{"performance", "powersave"};
147 static const std::vector<std::string> ubx_xcvr_modes{"FDX", "TX", "TX/RX", "RX"};
148 static const std::vector<std::string> ubx_temp_comp_modes{"enabled", "disabled"};
149
150 // clang-format off
151 static const ubx_gpio_field_info_t ubx_proto_gpio_info[] = {
152 //Field Unit Offset Mask Width Direction ATR IDLE,TX,RX,FDX
153 {SPI_ADDR, dboard_iface::UNIT_TX, 0, 0x7, 3, ubx_gpio_field_info_t::INPUT, false, 0, 0, 0, 0},
154 {TX_EN_N, dboard_iface::UNIT_TX, 3, 0x1<<3, 1, ubx_gpio_field_info_t::INPUT, true, 1, 0, 1, 0},
155 {RX_EN_N, dboard_iface::UNIT_TX, 4, 0x1<<4, 1, ubx_gpio_field_info_t::INPUT, true, 1, 1, 0, 0},
156 {RX_ANT, dboard_iface::UNIT_TX, 5, 0x1<<5, 1, ubx_gpio_field_info_t::INPUT, false, 0, 0, 0, 0},
157 {TX_LO_LOCKED, dboard_iface::UNIT_TX, 6, 0x1<<6, 1, ubx_gpio_field_info_t::OUTPUT, false, 0, 0, 0, 0},
158 {RX_LO_LOCKED, dboard_iface::UNIT_TX, 7, 0x1<<7, 1, ubx_gpio_field_info_t::OUTPUT, false, 0, 0, 0, 0},
159 {CPLD_RST_N, dboard_iface::UNIT_TX, 9, 0x1<<9, 1, ubx_gpio_field_info_t::INPUT, false, 0, 0, 0, 0},
160 {TX_GAIN, dboard_iface::UNIT_TX, 10, 0x3F<<10, 10, ubx_gpio_field_info_t::INPUT, false, 0, 0, 0, 0},
161 {RX_GAIN, dboard_iface::UNIT_RX, 10, 0x3F<<10, 10, ubx_gpio_field_info_t::INPUT, false, 0, 0, 0, 0}
162 };
163
164 static const ubx_gpio_field_info_t ubx_v1_gpio_info[] = {
165 //Field Unit Offset Mask Width Direction ATR IDLE,TX,RX,FDX
166 {SPI_ADDR, dboard_iface::UNIT_TX, 0, 0x7, 3, ubx_gpio_field_info_t::INPUT, false, 0, 0, 0, 0},
167 {CPLD_RST_N, dboard_iface::UNIT_TX, 3, 0x1<<3, 1, ubx_gpio_field_info_t::INPUT, false, 0, 0, 0, 0},
168 {RX_ANT, dboard_iface::UNIT_TX, 4, 0x1<<4, 1, ubx_gpio_field_info_t::INPUT, false, 0, 0, 0, 0},
169 {TX_EN_N, dboard_iface::UNIT_TX, 5, 0x1<<5, 1, ubx_gpio_field_info_t::INPUT, true, 1, 0, 1, 0},
170 {RX_EN_N, dboard_iface::UNIT_TX, 6, 0x1<<6, 1, ubx_gpio_field_info_t::INPUT, true, 1, 1, 0, 0},
171 {TXLO1_SYNC, dboard_iface::UNIT_TX, 7, 0x1<<7, 1, ubx_gpio_field_info_t::INPUT, true, 0, 0, 0, 0},
172 {TXLO2_SYNC, dboard_iface::UNIT_TX, 9, 0x1<<9, 1, ubx_gpio_field_info_t::INPUT, true, 0, 0, 0, 0},
173 {TX_GAIN, dboard_iface::UNIT_TX, 10, 0x3F<<10, 10, ubx_gpio_field_info_t::INPUT, false, 0, 0, 0, 0},
174 {RX_LO_LOCKED, dboard_iface::UNIT_RX, 0, 0x1, 1, ubx_gpio_field_info_t::OUTPUT, false, 0, 0, 0, 0},
175 {TX_LO_LOCKED, dboard_iface::UNIT_RX, 1, 0x1<<1, 1, ubx_gpio_field_info_t::OUTPUT, false, 0, 0, 0, 0},
176 {RXLO1_SYNC, dboard_iface::UNIT_RX, 5, 0x1<<5, 1, ubx_gpio_field_info_t::INPUT, true, 0, 0, 0, 0},
177 {RXLO2_SYNC, dboard_iface::UNIT_RX, 7, 0x1<<7, 1, ubx_gpio_field_info_t::INPUT, true, 0, 0, 0, 0},
178 {RX_GAIN, dboard_iface::UNIT_RX, 10, 0x3F<<10, 10, ubx_gpio_field_info_t::INPUT, false, 0, 0, 0, 0}
179 };
180 // clang-format on
181
182 /***********************************************************************
183 * Macros for routing and writing SPI registers
184 **********************************************************************/
185 #define ROUTE_SPI(iface, dest) \
186 set_gpio_field(SPI_ADDR, dest); \
187 write_gpio();
188
189 #define WRITE_SPI(iface, val) \
190 iface->write_spi(dboard_iface::UNIT_TX, spi_config_t::EDGE_RISE, val, 32);
191
192 /***********************************************************************
193 * UBX Class Definition
194 **********************************************************************/
195 class ubx_xcvr : public xcvr_dboard_base
196 {
197 public:
ubx_xcvr(ctor_args_t args)198 ubx_xcvr(ctor_args_t args) : xcvr_dboard_base(args)
199 {
200 double bw = 40e6;
201 double pfd_freq_max = 25e6;
202
203 ////////////////////////////////////////////////////////////////////
204 // Setup GPIO hardware
205 ////////////////////////////////////////////////////////////////////
206 _iface = get_iface();
207 dboard_id_t rx_id = get_rx_id();
208 dboard_id_t tx_id = get_tx_id();
209 size_t revision = 1; // default to rev A
210 // Get revision if programmed
211 const std::string revision_str = get_rx_eeprom().revision;
212 if (not revision_str.empty()) {
213 revision = boost::lexical_cast<size_t>(revision_str);
214 }
215 _high_isolation = false;
216 if (rx_id == UBX_PROTO_V3_RX_ID and tx_id == UBX_PROTO_V3_TX_ID) {
217 _rev = 0;
218 } else if (rx_id == UBX_PROTO_V4_RX_ID and tx_id == UBX_PROTO_V4_TX_ID) {
219 _rev = 1;
220 } else if (rx_id == UBX_V1_40MHZ_RX_ID and tx_id == UBX_V1_40MHZ_TX_ID) {
221 _rev = 1;
222 } else if (rx_id == UBX_V2_40MHZ_RX_ID and tx_id == UBX_V2_40MHZ_TX_ID) {
223 _rev = 2;
224 if (revision >= 4) {
225 _high_isolation = true;
226 }
227 } else if (rx_id == UBX_V1_160MHZ_RX_ID and tx_id == UBX_V1_160MHZ_TX_ID) {
228 bw = 160e6;
229 _rev = 1;
230 } else if (rx_id == UBX_V2_160MHZ_RX_ID and tx_id == UBX_V2_160MHZ_TX_ID) {
231 bw = 160e6;
232 _rev = 2;
233 if (revision >= 4) {
234 _high_isolation = true;
235 }
236 } else if (rx_id == UBX_LP_160MHZ_RX_ID and tx_id == UBX_LP_160MHZ_TX_ID) {
237 // The LP version behaves and looks like a regular UBX-160 v2
238 bw = 160e6;
239 _rev = 2;
240 } else if (rx_id == UBX_TDD_160MHZ_RX_ID and tx_id == UBX_TDD_160MHZ_TX_ID) {
241 bw = 160e6;
242 _rev = 2;
243 _high_isolation = true;
244 } else {
245 UHD_THROW_INVALID_CODE_PATH();
246 }
247
248 switch (_rev) {
249 case 0:
250 for (size_t i = 0;
251 i < sizeof(ubx_proto_gpio_info) / sizeof(ubx_gpio_field_info_t);
252 i++)
253 _gpio_map[ubx_proto_gpio_info[i].id] = ubx_proto_gpio_info[i];
254 pfd_freq_max = 25e6;
255 break;
256 case 1:
257 case 2:
258 for (size_t i = 0;
259 i < sizeof(ubx_v1_gpio_info) / sizeof(ubx_gpio_field_info_t);
260 i++)
261 _gpio_map[ubx_v1_gpio_info[i].id] = ubx_v1_gpio_info[i];
262 pfd_freq_max = 50e6;
263 break;
264 }
265
266 // Initialize GPIO registers
267 memset(&_tx_gpio_reg, 0, sizeof(ubx_gpio_reg_t));
268 memset(&_rx_gpio_reg, 0, sizeof(ubx_gpio_reg_t));
269 for (std::map<ubx_gpio_field_id_t, ubx_gpio_field_info_t>::iterator entry =
270 _gpio_map.begin();
271 entry != _gpio_map.end();
272 entry++) {
273 ubx_gpio_field_info_t info = entry->second;
274 ubx_gpio_reg_t* reg =
275 (info.unit == dboard_iface::UNIT_TX ? &_tx_gpio_reg : &_rx_gpio_reg);
276 if (info.direction == ubx_gpio_field_info_t::INPUT)
277 reg->ddr |= info.mask;
278 if (info.is_atr_controlled) {
279 reg->atr_mask |= info.mask;
280 reg->atr_idle |= (info.atr_idle << info.offset) & info.mask;
281 reg->atr_tx |= (info.atr_tx << info.offset) & info.mask;
282 reg->atr_rx |= (info.atr_rx << info.offset) & info.mask;
283 reg->atr_full_duplex |= (info.atr_full_duplex << info.offset) & info.mask;
284 }
285 }
286
287 // Enable the reference clocks that we need
288 _rx_target_pfd_freq = pfd_freq_max;
289 _tx_target_pfd_freq = pfd_freq_max;
290 if (_rev >= 1) {
291 bool can_set_clock_rate = true;
292 // set dboard clock rates to as close to the max PFD freq as possible
293 if (_iface->get_clock_rate(dboard_iface::UNIT_RX) > pfd_freq_max) {
294 std::vector<double> rates =
295 _iface->get_clock_rates(dboard_iface::UNIT_RX);
296 double highest_rate = 0.0;
297 for (double rate : rates) {
298 if (rate <= pfd_freq_max and rate > highest_rate)
299 highest_rate = rate;
300 }
301 try {
302 _iface->set_clock_rate(dboard_iface::UNIT_RX, highest_rate);
303 } catch (const uhd::not_implemented_error&) {
304 UHD_LOG_WARNING(
305 "UBX", "Unable to set dboard clock rate - phase will vary");
306 can_set_clock_rate = false;
307 }
308 _rx_target_pfd_freq = highest_rate;
309 }
310 if (can_set_clock_rate
311 and _iface->get_clock_rate(dboard_iface::UNIT_TX) > pfd_freq_max) {
312 std::vector<double> rates =
313 _iface->get_clock_rates(dboard_iface::UNIT_TX);
314 double highest_rate = 0.0;
315 for (double rate : rates) {
316 if (rate <= pfd_freq_max and rate > highest_rate)
317 highest_rate = rate;
318 }
319 try {
320 _iface->set_clock_rate(dboard_iface::UNIT_TX, highest_rate);
321 } catch (const uhd::not_implemented_error&) {
322 UHD_LOG_WARNING(
323 "UBX", "Unable to set dboard clock rate - phase will vary");
324 }
325 _tx_target_pfd_freq = highest_rate;
326 }
327 }
328 _iface->set_clock_enabled(dboard_iface::UNIT_TX, true);
329 _iface->set_clock_enabled(dboard_iface::UNIT_RX, true);
330
331 // Set direction of GPIO pins (1 is input to UBX, 0 is output)
332 _iface->set_gpio_ddr(dboard_iface::UNIT_TX, _tx_gpio_reg.ddr);
333 _iface->set_gpio_ddr(dboard_iface::UNIT_RX, _rx_gpio_reg.ddr);
334
335 // Set default GPIO values
336 set_gpio_field(TX_GAIN, 0);
337 set_gpio_field(CPLD_RST_N, 0);
338 set_gpio_field(RX_ANT, 1);
339 set_gpio_field(TX_EN_N, 1);
340 set_gpio_field(RX_EN_N, 1);
341 set_gpio_field(SPI_ADDR, 0x7);
342 set_gpio_field(RX_GAIN, 0);
343 set_gpio_field(TXLO1_SYNC, 0);
344 set_gpio_field(TXLO2_SYNC, 0);
345 set_gpio_field(RXLO1_SYNC, 0);
346 set_gpio_field(RXLO1_SYNC, 0);
347 write_gpio();
348
349 // Configure ATR
350 _iface->set_atr_reg(
351 dboard_iface::UNIT_TX, gpio_atr::ATR_REG_IDLE, _tx_gpio_reg.atr_idle);
352 _iface->set_atr_reg(
353 dboard_iface::UNIT_TX, gpio_atr::ATR_REG_TX_ONLY, _tx_gpio_reg.atr_tx);
354 _iface->set_atr_reg(
355 dboard_iface::UNIT_TX, gpio_atr::ATR_REG_RX_ONLY, _tx_gpio_reg.atr_rx);
356 _iface->set_atr_reg(dboard_iface::UNIT_TX,
357 gpio_atr::ATR_REG_FULL_DUPLEX,
358 _tx_gpio_reg.atr_full_duplex);
359 _iface->set_atr_reg(
360 dboard_iface::UNIT_RX, gpio_atr::ATR_REG_IDLE, _rx_gpio_reg.atr_idle);
361 _iface->set_atr_reg(
362 dboard_iface::UNIT_RX, gpio_atr::ATR_REG_TX_ONLY, _rx_gpio_reg.atr_tx);
363 _iface->set_atr_reg(
364 dboard_iface::UNIT_RX, gpio_atr::ATR_REG_RX_ONLY, _rx_gpio_reg.atr_rx);
365 _iface->set_atr_reg(dboard_iface::UNIT_RX,
366 gpio_atr::ATR_REG_FULL_DUPLEX,
367 _rx_gpio_reg.atr_full_duplex);
368
369 // Engage ATR control (1 is ATR control, 0 is manual control)
370 _iface->set_pin_ctrl(dboard_iface::UNIT_TX, _tx_gpio_reg.atr_mask);
371 _iface->set_pin_ctrl(dboard_iface::UNIT_RX, _rx_gpio_reg.atr_mask);
372
373 // bring CPLD out of reset
374 std::this_thread::sleep_for(
375 std::chrono::milliseconds(20)); // hold CPLD reset for minimum of 20 ms
376
377 set_gpio_field(CPLD_RST_N, 1);
378 write_gpio();
379
380 // Initialize LOs
381 if (_rev == 0) {
382 _txlo1 = max287x_iface::make<max2870>(
383 std::bind(&ubx_xcvr::write_spi_regs, this, TXLO1, std::placeholders::_1));
384 _txlo2 = max287x_iface::make<max2870>(
385 std::bind(&ubx_xcvr::write_spi_regs, this, TXLO2, std::placeholders::_1));
386 _rxlo1 = max287x_iface::make<max2870>(
387 std::bind(&ubx_xcvr::write_spi_regs, this, RXLO1, std::placeholders::_1));
388 _rxlo2 = max287x_iface::make<max2870>(
389 std::bind(&ubx_xcvr::write_spi_regs, this, RXLO2, std::placeholders::_1));
390 std::vector<max287x_iface::sptr> los{_txlo1, _txlo2, _rxlo1, _rxlo2};
391 for (max287x_iface::sptr lo : los) {
392 lo->set_auto_retune(false);
393 lo->set_muxout_mode(max287x_iface::MUXOUT_DLD);
394 lo->set_ld_pin_mode(max287x_iface::LD_PIN_MODE_DLD);
395 }
396 } else if (_rev == 1 or _rev == 2) {
397 _txlo1 = max287x_iface::make<max2871>(
398 std::bind(&ubx_xcvr::write_spi_regs, this, TXLO1, std::placeholders::_1));
399 _txlo2 = max287x_iface::make<max2871>(
400 std::bind(&ubx_xcvr::write_spi_regs, this, TXLO2, std::placeholders::_1));
401 _rxlo1 = max287x_iface::make<max2871>(
402 std::bind(&ubx_xcvr::write_spi_regs, this, RXLO1, std::placeholders::_1));
403 _rxlo2 = max287x_iface::make<max2871>(
404 std::bind(&ubx_xcvr::write_spi_regs, this, RXLO2, std::placeholders::_1));
405 std::vector<max287x_iface::sptr> los{_txlo1, _txlo2, _rxlo1, _rxlo2};
406 for (max287x_iface::sptr lo : los) {
407 lo->set_auto_retune(false);
408 // lo->set_cycle_slip_mode(true); // tried it - caused longer lock times
409 lo->set_charge_pump_current(max287x_iface::CHARGE_PUMP_CURRENT_5_12MA);
410 lo->set_muxout_mode(max287x_iface::MUXOUT_SYNC);
411 lo->set_ld_pin_mode(max287x_iface::LD_PIN_MODE_DLD);
412 }
413 } else {
414 UHD_THROW_INVALID_CODE_PATH();
415 }
416
417 // Initialize CPLD register
418 _prev_cpld_value = 0xFFFF;
419 _cpld_reg.value = 0;
420 write_cpld_reg();
421
422 ////////////////////////////////////////////////////////////////////
423 // Register power save properties
424 ////////////////////////////////////////////////////////////////////
425 get_rx_subtree()
426 ->create<std::vector<std::string>>("power_mode/options")
427 .set(ubx_power_modes);
428 get_rx_subtree()
429 ->create<std::string>("power_mode/value")
430 .add_coerced_subscriber(
431 std::bind(&ubx_xcvr::set_power_mode, this, std::placeholders::_1))
432 .set("performance");
433 get_rx_subtree()
434 ->create<std::vector<std::string>>("xcvr_mode/options")
435 .set(ubx_xcvr_modes);
436 get_rx_subtree()
437 ->create<std::string>("xcvr_mode/value")
438 .add_coerced_subscriber(
439 std::bind(&ubx_xcvr::set_xcvr_mode, this, std::placeholders::_1))
440 .set("FDX");
441 get_rx_subtree()
442 ->create<std::vector<std::string>>("temp_comp_mode/options")
443 .set(ubx_temp_comp_modes);
444 get_rx_subtree()
445 ->create<std::string>("temp_comp_mode/value")
446 .add_coerced_subscriber(
447 [this](std::string mode) { this->set_temp_comp_mode(mode); })
448 .set("disabled");
449 get_tx_subtree()
450 ->create<std::vector<std::string>>("power_mode/options")
451 .set(ubx_power_modes);
452 get_tx_subtree()
453 ->create<std::string>("power_mode/value")
454 .add_coerced_subscriber(std::bind(&uhd::property<std::string>::set,
455 &get_rx_subtree()->access<std::string>("power_mode/value"),
456 std::placeholders::_1))
457 .set_publisher(std::bind(&uhd::property<std::string>::get,
458 &get_rx_subtree()->access<std::string>("power_mode/value")));
459 get_tx_subtree()
460 ->create<std::vector<std::string>>("xcvr_mode/options")
461 .set(ubx_xcvr_modes);
462 get_tx_subtree()
463 ->create<std::string>("xcvr_mode/value")
464 .add_coerced_subscriber(std::bind(&uhd::property<std::string>::set,
465 &get_rx_subtree()->access<std::string>("xcvr_mode/value"),
466 std::placeholders::_1))
467 .set_publisher(std::bind(&uhd::property<std::string>::get,
468 &get_rx_subtree()->access<std::string>("xcvr_mode/value")));
469 get_tx_subtree()
470 ->create<std::vector<std::string>>("temp_comp_mode/options")
471 .set(ubx_temp_comp_modes);
472 get_tx_subtree()
473 ->create<std::string>("temp_comp_mode/value")
474 .add_coerced_subscriber([this](std::string mode) {
475 this->get_rx_subtree()
476 ->access<std::string>("temp_comp_mode/value")
477 .set(mode);
478 })
479 .set_publisher([this]() {
480 return this->get_rx_subtree()
481 ->access<std::string>("temp_comp_mode/value")
482 .get();
483 });
484
485
486 ////////////////////////////////////////////////////////////////////
487 // Register TX properties
488 ////////////////////////////////////////////////////////////////////
489 get_tx_subtree()->create<std::string>("name").set("UBX TX");
490 get_tx_subtree()->create<device_addr_t>("tune_args").set(device_addr_t());
491 get_tx_subtree()
492 ->create<sensor_value_t>("sensors/lo_locked")
493 .set_publisher(std::bind(&ubx_xcvr::get_locked, this, "TXLO"));
494 get_tx_subtree()
495 ->create<double>("gains/PGA0/value")
496 .set_coercer(std::bind(&ubx_xcvr::set_tx_gain, this, std::placeholders::_1))
497 .set(0);
498 get_tx_subtree()->create<meta_range_t>("gains/PGA0/range").set(ubx_tx_gain_range);
499 get_tx_subtree()
500 ->create<double>("freq/value")
501 .set_coercer(std::bind(&ubx_xcvr::set_tx_freq, this, std::placeholders::_1))
502 .set(ubx_freq_range.start());
503 get_tx_subtree()->create<meta_range_t>("freq/range").set(ubx_freq_range);
504 get_tx_subtree()
505 ->create<std::vector<std::string>>("antenna/options")
506 .set(ubx_tx_antennas);
507 get_tx_subtree()
508 ->create<std::string>("antenna/value")
509 .set_coercer(std::bind(&ubx_xcvr::set_tx_ant, this, std::placeholders::_1))
510 .set(ubx_tx_antennas.at(0));
511 get_tx_subtree()->create<std::string>("connection").set("QI");
512 get_tx_subtree()->create<bool>("enabled").set(true); // always enabled
513 get_tx_subtree()->create<bool>("use_lo_offset").set(false);
514 get_tx_subtree()->create<double>("bandwidth/value").set(bw);
515 get_tx_subtree()
516 ->create<meta_range_t>("bandwidth/range")
517 .set(freq_range_t(bw, bw));
518 get_tx_subtree()
519 ->create<int64_t>("sync_delay")
520 .add_coerced_subscriber(
521 std::bind(&ubx_xcvr::set_sync_delay, this, true, std::placeholders::_1))
522 .set(0);
523
524 ////////////////////////////////////////////////////////////////////
525 // Register RX properties
526 ////////////////////////////////////////////////////////////////////
527 get_rx_subtree()->create<std::string>("name").set("UBX RX");
528 get_rx_subtree()->create<device_addr_t>("tune_args").set(device_addr_t());
529 get_rx_subtree()
530 ->create<sensor_value_t>("sensors/lo_locked")
531 .set_publisher(std::bind(&ubx_xcvr::get_locked, this, "RXLO"));
532 get_rx_subtree()
533 ->create<double>("gains/PGA0/value")
534 .set_coercer(std::bind(&ubx_xcvr::set_rx_gain, this, std::placeholders::_1))
535 .set(0);
536 get_rx_subtree()->create<meta_range_t>("gains/PGA0/range").set(ubx_rx_gain_range);
537 get_rx_subtree()
538 ->create<double>("freq/value")
539 .set_coercer(std::bind(&ubx_xcvr::set_rx_freq, this, std::placeholders::_1))
540 .set(ubx_freq_range.start());
541 get_rx_subtree()->create<meta_range_t>("freq/range").set(ubx_freq_range);
542 get_rx_subtree()
543 ->create<std::vector<std::string>>("antenna/options")
544 .set(ubx_rx_antennas);
545 get_rx_subtree()
546 ->create<std::string>("antenna/value")
547 .set_coercer(std::bind(&ubx_xcvr::set_rx_ant, this, std::placeholders::_1))
548 .set("RX2");
549 get_rx_subtree()->create<std::string>("connection").set("IQ");
550 get_rx_subtree()->create<bool>("enabled").set(true); // always enabled
551 get_rx_subtree()->create<bool>("use_lo_offset").set(false);
552 get_rx_subtree()->create<double>("bandwidth/value").set(bw);
553 get_rx_subtree()
554 ->create<meta_range_t>("bandwidth/range")
555 .set(freq_range_t(bw, bw));
556 get_rx_subtree()
557 ->create<int64_t>("sync_delay")
558 .add_coerced_subscriber(
559 std::bind(&ubx_xcvr::set_sync_delay, this, false, std::placeholders::_1))
560 .set(0);
561 }
562
~ubx_xcvr(void)563 virtual ~ubx_xcvr(void)
564 {
565 UHD_SAFE_CALL(
566 // Shutdown synthesizers
567 _txlo1->shutdown(); _txlo2->shutdown(); _rxlo1->shutdown();
568 _rxlo2->shutdown();
569
570 // Reset CPLD values
571 _cpld_reg.value = 0;
572 write_cpld_reg();
573
574 // Reset GPIO values
575 set_gpio_field(TX_GAIN, 0);
576 set_gpio_field(CPLD_RST_N, 0);
577 set_gpio_field(RX_ANT, 1);
578 set_gpio_field(TX_EN_N, 1);
579 set_gpio_field(RX_EN_N, 1);
580 set_gpio_field(SPI_ADDR, 0x7);
581 set_gpio_field(RX_GAIN, 0);
582 set_gpio_field(TXLO1_SYNC, 0);
583 set_gpio_field(TXLO2_SYNC, 0);
584 set_gpio_field(RXLO1_SYNC, 0);
585 set_gpio_field(RXLO1_SYNC, 0);
586 write_gpio();)
587 }
588
589 private:
590 enum power_mode_t { PERFORMANCE, POWERSAVE };
591 enum xcvr_mode_t { FDX, TDD, TX, RX, FAST_TDD };
592
593 /***********************************************************************
594 * Helper Functions
595 **********************************************************************/
write_spi_reg(spi_dest_t dest,uint32_t value)596 void write_spi_reg(spi_dest_t dest, uint32_t value)
597 {
598 boost::mutex::scoped_lock lock(_spi_mutex);
599 ROUTE_SPI(_iface, dest);
600 WRITE_SPI(_iface, value);
601 }
602
write_spi_regs(spi_dest_t dest,std::vector<uint32_t> values)603 void write_spi_regs(spi_dest_t dest, std::vector<uint32_t> values)
604 {
605 boost::mutex::scoped_lock lock(_spi_mutex);
606 ROUTE_SPI(_iface, dest);
607 for (uint32_t value : values)
608 WRITE_SPI(_iface, value);
609 }
610
set_cpld_field(ubx_cpld_field_id_t id,uint32_t value)611 void set_cpld_field(ubx_cpld_field_id_t id, uint32_t value)
612 {
613 _cpld_reg.set_field(id, value);
614 }
615
write_cpld_reg()616 void write_cpld_reg()
617 {
618 if (_cpld_reg.value != _prev_cpld_value) {
619 write_spi_reg(CPLD, _cpld_reg.value);
620 _prev_cpld_value = _cpld_reg.value;
621 }
622 }
623
set_gpio_field(ubx_gpio_field_id_t id,uint32_t value)624 void set_gpio_field(ubx_gpio_field_id_t id, uint32_t value)
625 {
626 // Look up field info
627 std::map<ubx_gpio_field_id_t, ubx_gpio_field_info_t>::iterator entry =
628 _gpio_map.find(id);
629 if (entry == _gpio_map.end())
630 return;
631 ubx_gpio_field_info_t field_info = entry->second;
632 if (field_info.direction == ubx_gpio_field_info_t::OUTPUT)
633 return;
634 ubx_gpio_reg_t* reg =
635 (field_info.unit == dboard_iface::UNIT_TX ? &_tx_gpio_reg : &_rx_gpio_reg);
636 uint32_t _value = reg->value;
637 uint32_t _mask = reg->mask;
638
639 // Set field and mask
640 _value &= ~field_info.mask;
641 _value |= (value << field_info.offset) & field_info.mask;
642 _mask |= field_info.mask;
643
644 // Mark whether register is dirty or not
645 if (_value != reg->value) {
646 reg->value = _value;
647 reg->mask = _mask;
648 reg->dirty = true;
649 }
650 }
651
get_gpio_field(ubx_gpio_field_id_t id)652 uint32_t get_gpio_field(ubx_gpio_field_id_t id)
653 {
654 // Look up field info
655 std::map<ubx_gpio_field_id_t, ubx_gpio_field_info_t>::iterator entry =
656 _gpio_map.find(id);
657 if (entry == _gpio_map.end())
658 return 0;
659 ubx_gpio_field_info_t field_info = entry->second;
660 if (field_info.direction == ubx_gpio_field_info_t::INPUT) {
661 ubx_gpio_reg_t* reg = (field_info.unit == dboard_iface::UNIT_TX
662 ? &_tx_gpio_reg
663 : &_rx_gpio_reg);
664 return (reg->value >> field_info.offset) & field_info.mask;
665 }
666
667 // Read register
668 uint32_t value = _iface->read_gpio(field_info.unit);
669 value &= field_info.mask;
670 value >>= field_info.offset;
671
672 // Return field value
673 return value;
674 }
675
write_gpio()676 void write_gpio()
677 {
678 if (_tx_gpio_reg.dirty) {
679 _iface->set_gpio_out(
680 dboard_iface::UNIT_TX, _tx_gpio_reg.value, _tx_gpio_reg.mask);
681 _tx_gpio_reg.dirty = false;
682 _tx_gpio_reg.mask = 0;
683 }
684 if (_rx_gpio_reg.dirty) {
685 _iface->set_gpio_out(
686 dboard_iface::UNIT_RX, _rx_gpio_reg.value, _rx_gpio_reg.mask);
687 _rx_gpio_reg.dirty = false;
688 _rx_gpio_reg.mask = 0;
689 }
690 }
691
sync_phase(uhd::time_spec_t cmd_time,uhd::direction_t dir)692 void sync_phase(uhd::time_spec_t cmd_time, uhd::direction_t dir)
693 {
694 // Send phase sync signal only if the command time is set
695 if (cmd_time != uhd::time_spec_t(0.0)) {
696 // Delay 400 microseconds to allow LOs to lock
697 cmd_time += uhd::time_spec_t(0.0004);
698
699 // Phase synchronization for MAX2871 requires that the sync signal
700 // is at least 4/(N*PFD_freq) + 2.6ns before the rising edge of the
701 // ref clock and 4/(N*PFD_freq) after the rising edge of the ref clock.
702 // Since the MAX2871 requires the ref freq and PFD freq be the same
703 // for phase synchronization, the dboard clock rate is used as the PFD
704 // freq and the sync signal is aligned to the falling edge to meet
705 // the setup and hold requirements. Since the command time ticks
706 // at the radio clock rate, this only works if the radio clock is
707 // an even multiple of the dboard clock, the dboard clock is a
708 // multiple of the system reference, and the device time has been
709 // set on a PPS edge sampled by the system reference clock.
710
711 const double pfd_freq = _iface->get_clock_rate(
712 dir == TX_DIRECTION ? dboard_iface::UNIT_TX : dboard_iface::UNIT_RX);
713 const double tick_rate = _iface->get_codec_rate(
714 dir == TX_DIRECTION ? dboard_iface::UNIT_TX : dboard_iface::UNIT_RX);
715 const int64_t ticks_per_pfd_cycle = (int64_t)(tick_rate / pfd_freq);
716
717 // Convert time to ticks
718 int64_t ticks = cmd_time.to_ticks(tick_rate);
719 // Align time to next falling edge of dboard clock
720 ticks += ticks_per_pfd_cycle - (ticks % ticks_per_pfd_cycle)
721 + (ticks_per_pfd_cycle / 2);
722 // Add any user specified delay
723 ticks += dir == TX_DIRECTION ? _tx_sync_delay : _rx_sync_delay;
724 // Set the command time
725 cmd_time = uhd::time_spec_t::from_ticks(ticks, tick_rate);
726 _iface->set_command_time(cmd_time);
727
728 // Assert SYNC
729 ubx_gpio_field_info_t lo1_field_info =
730 _gpio_map.find(dir == TX_DIRECTION ? TXLO1_SYNC : RXLO1_SYNC)->second;
731 ubx_gpio_field_info_t lo2_field_info =
732 _gpio_map.find(dir == TX_DIRECTION ? TXLO2_SYNC : RXLO2_SYNC)->second;
733 uint16_t value = (1 << lo1_field_info.offset) | (1 << lo2_field_info.offset);
734 uint16_t mask = lo1_field_info.mask | lo2_field_info.mask;
735 dboard_iface::unit_t unit = lo1_field_info.unit;
736 UHD_ASSERT_THROW(lo1_field_info.unit == lo2_field_info.unit);
737 _iface->set_atr_reg(unit, gpio_atr::ATR_REG_IDLE, value, mask);
738 cmd_time += uhd::time_spec_t(1 / pfd_freq);
739 _iface->set_command_time(cmd_time);
740 _iface->set_atr_reg(unit, gpio_atr::ATR_REG_TX_ONLY, value, mask);
741 cmd_time += uhd::time_spec_t(1 / pfd_freq);
742 _iface->set_command_time(cmd_time);
743 _iface->set_atr_reg(unit, gpio_atr::ATR_REG_RX_ONLY, value, mask);
744 cmd_time += uhd::time_spec_t(1 / pfd_freq);
745 _iface->set_command_time(cmd_time);
746 _iface->set_atr_reg(unit, gpio_atr::ATR_REG_FULL_DUPLEX, value, mask);
747
748 // De-assert SYNC
749 // Head of line blocking means the command time does not need to be set.
750 _iface->set_atr_reg(unit, gpio_atr::ATR_REG_IDLE, 0, mask);
751 _iface->set_atr_reg(unit, gpio_atr::ATR_REG_TX_ONLY, 0, mask);
752 _iface->set_atr_reg(unit, gpio_atr::ATR_REG_RX_ONLY, 0, mask);
753 _iface->set_atr_reg(unit, gpio_atr::ATR_REG_FULL_DUPLEX, 0, mask);
754 }
755 }
756
757 /***********************************************************************
758 * Board Control Handling
759 **********************************************************************/
get_locked(const std::string & pll_name)760 sensor_value_t get_locked(const std::string& pll_name)
761 {
762 boost::mutex::scoped_lock lock(_mutex);
763 assert_has(ubx_plls, pll_name, "ubx pll name");
764
765 if (pll_name == "TXLO") {
766 _txlo_locked = (get_gpio_field(TX_LO_LOCKED) != 0);
767 return sensor_value_t("TXLO", _txlo_locked, "locked", "unlocked");
768 } else if (pll_name == "RXLO") {
769 _rxlo_locked = (get_gpio_field(RX_LO_LOCKED) != 0);
770 return sensor_value_t("RXLO", _rxlo_locked, "locked", "unlocked");
771 }
772
773 return sensor_value_t("Unknown", false, "locked", "unlocked");
774 }
775
set_tx_ant(const std::string & ant)776 std::string set_tx_ant(const std::string& ant)
777 {
778 // validate input
779 assert_has(ubx_tx_antennas, ant, "ubx tx antenna name");
780 set_cpld_field(CAL_ENABLE, (ant == "CAL"));
781 write_cpld_reg();
782 return ant;
783 }
784
785 // Set RX antennas
set_rx_ant(const std::string & ant)786 std::string set_rx_ant(const std::string& ant)
787 {
788 boost::mutex::scoped_lock lock(_mutex);
789 // validate input
790 assert_has(ubx_rx_antennas, ant, "ubx rx antenna name");
791
792 // There can be long transients on TX, so force on the TX PA
793 // except when in powersave mode (to save power) or on early
794 // boards that had lower TX-RX isolation when the RX antenna
795 // is set to TX/RX (to prevent higher noise floor on RX).
796 // Setting the xcvr_mode to TDD will force on the PA when
797 // not in powersave mode regardless of the board revision.
798 if (ant == "TX/RX") {
799 set_gpio_field(RX_ANT, 0);
800 // Force on TX PA for boards with high isolation or if the user sets the TDD
801 // mode
802 set_cpld_field(TXDRV_FORCEON,
803 (_power_mode == POWERSAVE
804 ? 0
805 : _high_isolation or _xcvr_mode == TDD ? 1 : 0));
806 } else {
807 set_gpio_field(RX_ANT, 1);
808 set_cpld_field(
809 TXDRV_FORCEON, (_power_mode == POWERSAVE ? 0 : 1)); // Keep PA on
810 }
811 write_gpio();
812 write_cpld_reg();
813
814 return ant;
815 }
816
817 /***********************************************************************
818 * Gain Handling
819 **********************************************************************/
set_tx_gain(double gain)820 double set_tx_gain(double gain)
821 {
822 boost::mutex::scoped_lock lock(_mutex);
823 gain = ubx_tx_gain_range.clip(gain);
824 int attn_code = int(std::floor(gain * 2));
825 _ubx_tx_atten_val = ((attn_code & 0x3F) << 10);
826 set_gpio_field(TX_GAIN, attn_code);
827 write_gpio();
828 UHD_LOGGER_TRACE("UBX")
829 << boost::format("UBX TX Gain: %f dB, Code: %d, IO Bits 0x%04x") % gain
830 % attn_code % _ubx_tx_atten_val;
831 _tx_gain = gain;
832 return gain;
833 }
834
set_rx_gain(double gain)835 double set_rx_gain(double gain)
836 {
837 boost::mutex::scoped_lock lock(_mutex);
838 gain = ubx_rx_gain_range.clip(gain);
839 int attn_code = int(std::floor(gain * 2));
840 _ubx_rx_atten_val = ((attn_code & 0x3F) << 10);
841 set_gpio_field(RX_GAIN, attn_code);
842 write_gpio();
843 UHD_LOGGER_TRACE("UBX")
844 << boost::format("UBX RX Gain: %f dB, Code: %d, IO Bits 0x%04x") % gain
845 % attn_code % _ubx_rx_atten_val;
846 _rx_gain = gain;
847 return gain;
848 }
849
850 /***********************************************************************
851 * Frequency Handling
852 **********************************************************************/
set_tx_freq(double freq)853 double set_tx_freq(double freq)
854 {
855 boost::mutex::scoped_lock lock(_mutex);
856 double freq_lo1 = 0.0;
857 double freq_lo2 = 0.0;
858 double ref_freq = _iface->get_clock_rate(dboard_iface::UNIT_TX);
859 bool is_int_n = false;
860
861 /*
862 * If the user sets 'mode_n=integer' in the tuning args, the user wishes to
863 * tune in Integer-N mode, which can result in better spur
864 * performance on some mixers. The default is fractional tuning.
865 */
866 property_tree::sptr subtree = this->get_tx_subtree();
867 device_addr_t tune_args = subtree->access<device_addr_t>("tune_args").get();
868 is_int_n = boost::iequals(tune_args.get("mode_n", ""), "integer");
869 UHD_LOGGER_TRACE("UBX")
870 << boost::format("UBX TX: the requested frequency is %f MHz") % (freq / 1e6);
871 double target_pfd_freq = _tx_target_pfd_freq;
872 if (is_int_n and tune_args.has_key("int_n_step")) {
873 target_pfd_freq = tune_args.cast<double>("int_n_step", _tx_target_pfd_freq);
874 if (target_pfd_freq > _tx_target_pfd_freq) {
875 UHD_LOGGER_WARNING("UBX")
876 << boost::format(
877 "Requested int_n_step of %f MHz too large, clipping to %f MHz")
878 % (target_pfd_freq / 1e6) % (_tx_target_pfd_freq / 1e6);
879 target_pfd_freq = _tx_target_pfd_freq;
880 }
881 }
882
883 // Clip the frequency to the valid range
884 freq = ubx_freq_range.clip(freq);
885
886 // Power up/down LOs
887 if (_txlo1->is_shutdown())
888 _txlo1->power_up();
889 if (_txlo2->is_shutdown() and (_power_mode == PERFORMANCE or freq < (500 * fMHz)))
890 _txlo2->power_up();
891 else if (freq >= 500 * fMHz and _power_mode == POWERSAVE)
892 _txlo2->shutdown();
893
894 // Set up LOs for phase sync if command time is set
895 uhd::time_spec_t cmd_time = _iface->get_command_time();
896 if (cmd_time != uhd::time_spec_t(0.0)) {
897 _txlo1->config_for_sync(true);
898 if (not _txlo2->is_shutdown())
899 _txlo2->config_for_sync(true);
900 } else {
901 _txlo1->config_for_sync(false);
902 if (not _txlo2->is_shutdown())
903 _txlo2->config_for_sync(false);
904 }
905
906 // Set up registers for the requested frequency
907 if (freq < (500 * fMHz)) {
908 set_cpld_field(TXLO1_FSEL3, 0);
909 set_cpld_field(TXLO1_FSEL2, 1);
910 set_cpld_field(TXLO1_FSEL1, 0);
911 set_cpld_field(TXLB_SEL, 1);
912 set_cpld_field(TXHB_SEL, 0);
913 // Set LO1 to IF of 2100 MHz (offset from RX IF to reduce leakage)
914 freq_lo1 =
915 _txlo1->set_frequency(2100 * fMHz, ref_freq, target_pfd_freq, is_int_n);
916 _txlo1->set_output_power(max287x_iface::OUTPUT_POWER_5DBM);
917 // Set LO2 to IF minus desired frequency
918 freq_lo2 = _txlo2->set_frequency(
919 freq_lo1 - freq, ref_freq, target_pfd_freq, is_int_n);
920 _txlo2->set_output_power(max287x_iface::OUTPUT_POWER_2DBM);
921 } else if ((freq >= (500 * fMHz)) && (freq <= (800 * fMHz))) {
922 set_cpld_field(TXLO1_FSEL3, 0);
923 set_cpld_field(TXLO1_FSEL2, 0);
924 set_cpld_field(TXLO1_FSEL1, 1);
925 set_cpld_field(TXLB_SEL, 0);
926 set_cpld_field(TXHB_SEL, 1);
927 freq_lo1 = _txlo1->set_frequency(freq, ref_freq, target_pfd_freq, is_int_n);
928 _txlo1->set_output_power(max287x_iface::OUTPUT_POWER_2DBM);
929 } else if ((freq > (800 * fMHz)) && (freq <= (1000 * fMHz))) {
930 set_cpld_field(TXLO1_FSEL3, 0);
931 set_cpld_field(TXLO1_FSEL2, 0);
932 set_cpld_field(TXLO1_FSEL1, 1);
933 set_cpld_field(TXLB_SEL, 0);
934 set_cpld_field(TXHB_SEL, 1);
935 freq_lo1 = _txlo1->set_frequency(freq, ref_freq, target_pfd_freq, is_int_n);
936 _txlo1->set_output_power(max287x_iface::OUTPUT_POWER_5DBM);
937 } else if ((freq > (1000 * fMHz)) && (freq <= (2200 * fMHz))) {
938 set_cpld_field(TXLO1_FSEL3, 0);
939 set_cpld_field(TXLO1_FSEL2, 1);
940 set_cpld_field(TXLO1_FSEL1, 0);
941 set_cpld_field(TXLB_SEL, 0);
942 set_cpld_field(TXHB_SEL, 1);
943 freq_lo1 = _txlo1->set_frequency(freq, ref_freq, target_pfd_freq, is_int_n);
944 _txlo1->set_output_power(max287x_iface::OUTPUT_POWER_2DBM);
945 } else if ((freq > (2200 * fMHz)) && (freq <= (2500 * fMHz))) {
946 set_cpld_field(TXLO1_FSEL3, 0);
947 set_cpld_field(TXLO1_FSEL2, 1);
948 set_cpld_field(TXLO1_FSEL1, 0);
949 set_cpld_field(TXLB_SEL, 0);
950 set_cpld_field(TXHB_SEL, 1);
951 freq_lo1 = _txlo1->set_frequency(freq, ref_freq, target_pfd_freq, is_int_n);
952 _txlo1->set_output_power(max287x_iface::OUTPUT_POWER_2DBM);
953 } else if ((freq > (2500 * fMHz)) && (freq <= (6000 * fMHz))) {
954 set_cpld_field(TXLO1_FSEL3, 1);
955 set_cpld_field(TXLO1_FSEL2, 0);
956 set_cpld_field(TXLO1_FSEL1, 0);
957 set_cpld_field(TXLB_SEL, 0);
958 set_cpld_field(TXHB_SEL, 1);
959 freq_lo1 = _txlo1->set_frequency(freq, ref_freq, target_pfd_freq, is_int_n);
960 _txlo1->set_output_power(max287x_iface::OUTPUT_POWER_5DBM);
961 }
962
963 // To reduce the number of commands issued to the device, write to the
964 // SPI destination already addressed first. This avoids the writes to
965 // the GPIO registers to route the SPI to the same destination.
966 switch (get_gpio_field(SPI_ADDR)) {
967 case TXLO1:
968 _txlo1->commit();
969 if (freq < (500 * fMHz))
970 _txlo2->commit();
971 write_cpld_reg();
972 break;
973 case TXLO2:
974 if (freq < (500 * fMHz))
975 _txlo2->commit();
976 _txlo1->commit();
977 write_cpld_reg();
978 break;
979 default:
980 write_cpld_reg();
981 _txlo1->commit();
982 if (freq < (500 * fMHz))
983 _txlo2->commit();
984 break;
985 }
986
987 if (cmd_time != uhd::time_spec_t(0.0) and _txlo1->can_sync()) {
988 sync_phase(cmd_time, TX_DIRECTION);
989 }
990
991 _tx_freq = freq_lo1 - freq_lo2;
992 _txlo1_freq = freq_lo1;
993 _txlo2_freq = freq_lo2;
994
995 UHD_LOGGER_TRACE("UBX")
996 << boost::format("UBX TX: the actual frequency is %f MHz") % (_tx_freq / 1e6);
997
998 return _tx_freq;
999 }
1000
set_rx_freq(double freq)1001 double set_rx_freq(double freq)
1002 {
1003 boost::mutex::scoped_lock lock(_mutex);
1004 double freq_lo1 = 0.0;
1005 double freq_lo2 = 0.0;
1006 double ref_freq = _iface->get_clock_rate(dboard_iface::UNIT_RX);
1007 bool is_int_n = false;
1008
1009 UHD_LOGGER_TRACE("UBX")
1010 << boost::format("UBX RX: the requested frequency is %f MHz") % (freq / 1e6);
1011
1012 property_tree::sptr subtree = this->get_rx_subtree();
1013 device_addr_t tune_args = subtree->access<device_addr_t>("tune_args").get();
1014 is_int_n = boost::iequals(tune_args.get("mode_n", ""), "integer");
1015 double target_pfd_freq = _rx_target_pfd_freq;
1016 if (is_int_n and tune_args.has_key("int_n_step")) {
1017 target_pfd_freq = tune_args.cast<double>("int_n_step", _rx_target_pfd_freq);
1018 if (target_pfd_freq > _rx_target_pfd_freq) {
1019 UHD_LOGGER_WARNING("UBX")
1020 << boost::format(
1021 "Requested int_n_step of %f Mhz too large, clipping to %f MHz")
1022 % (target_pfd_freq / 1e6) % (_rx_target_pfd_freq / 1e6);
1023 target_pfd_freq = _rx_target_pfd_freq;
1024 }
1025 }
1026
1027 // Clip the frequency to the valid range
1028 freq = ubx_freq_range.clip(freq);
1029
1030 // Power up/down LOs
1031 if (_rxlo1->is_shutdown())
1032 _rxlo1->power_up();
1033 if (_rxlo2->is_shutdown() and (_power_mode == PERFORMANCE or freq < 500 * fMHz))
1034 _rxlo2->power_up();
1035 else if (freq >= 500 * fMHz and _power_mode == POWERSAVE)
1036 _rxlo2->shutdown();
1037
1038 // Set up LOs for phase sync if command time is set
1039 uhd::time_spec_t cmd_time = _iface->get_command_time();
1040 if (cmd_time != uhd::time_spec_t(0.0)) {
1041 _rxlo1->config_for_sync(true);
1042 if (not _rxlo2->is_shutdown())
1043 _rxlo2->config_for_sync(true);
1044 } else {
1045 _rxlo1->config_for_sync(false);
1046 if (not _rxlo2->is_shutdown())
1047 _rxlo2->config_for_sync(false);
1048 }
1049
1050 // Work with frequencies
1051 if (freq < 100 * fMHz) {
1052 set_cpld_field(SEL_LNA1, 0);
1053 set_cpld_field(SEL_LNA2, 1);
1054 set_cpld_field(RXLO1_FSEL3, 1);
1055 set_cpld_field(RXLO1_FSEL2, 0);
1056 set_cpld_field(RXLO1_FSEL1, 0);
1057 set_cpld_field(RXLB_SEL, 1);
1058 set_cpld_field(RXHB_SEL, 0);
1059 // Set LO1 to IF of 2380 MHz (2440 MHz filter center minus 60 MHz offset to
1060 // minimize LO leakage)
1061 freq_lo1 =
1062 _rxlo1->set_frequency(2380 * fMHz, ref_freq, target_pfd_freq, is_int_n);
1063 _rxlo1->set_output_power(max287x_iface::OUTPUT_POWER_5DBM);
1064 // Set LO2 to IF minus desired frequency
1065 freq_lo2 = _rxlo2->set_frequency(
1066 freq_lo1 - freq, ref_freq, target_pfd_freq, is_int_n);
1067 _rxlo2->set_output_power(max287x_iface::OUTPUT_POWER_2DBM);
1068 } else if ((freq >= 100 * fMHz) && (freq < 500 * fMHz)) {
1069 set_cpld_field(SEL_LNA1, 0);
1070 set_cpld_field(SEL_LNA2, 1);
1071 set_cpld_field(RXLO1_FSEL3, 1);
1072 set_cpld_field(RXLO1_FSEL2, 0);
1073 set_cpld_field(RXLO1_FSEL1, 0);
1074 set_cpld_field(RXLB_SEL, 1);
1075 set_cpld_field(RXHB_SEL, 0);
1076 // Set LO1 to IF of 2440 (center of filter)
1077 freq_lo1 =
1078 _rxlo1->set_frequency(2440 * fMHz, ref_freq, target_pfd_freq, is_int_n);
1079 _rxlo1->set_output_power(max287x_iface::OUTPUT_POWER_5DBM);
1080 // Set LO2 to IF minus desired frequency
1081 freq_lo2 = _rxlo2->set_frequency(
1082 freq_lo1 - freq, ref_freq, target_pfd_freq, is_int_n);
1083 _rxlo1->set_output_power(max287x_iface::OUTPUT_POWER_2DBM);
1084 } else if ((freq >= 500 * fMHz) && (freq < 800 * fMHz)) {
1085 set_cpld_field(SEL_LNA1, 0);
1086 set_cpld_field(SEL_LNA2, 1);
1087 set_cpld_field(RXLO1_FSEL3, 0);
1088 set_cpld_field(RXLO1_FSEL2, 0);
1089 set_cpld_field(RXLO1_FSEL1, 1);
1090 set_cpld_field(RXLB_SEL, 0);
1091 set_cpld_field(RXHB_SEL, 1);
1092 freq_lo1 = _rxlo1->set_frequency(freq, ref_freq, target_pfd_freq, is_int_n);
1093 _rxlo1->set_output_power(max287x_iface::OUTPUT_POWER_2DBM);
1094 } else if ((freq >= 800 * fMHz) && (freq < 1000 * fMHz)) {
1095 set_cpld_field(SEL_LNA1, 0);
1096 set_cpld_field(SEL_LNA2, 1);
1097 set_cpld_field(RXLO1_FSEL3, 0);
1098 set_cpld_field(RXLO1_FSEL2, 0);
1099 set_cpld_field(RXLO1_FSEL1, 1);
1100 set_cpld_field(RXLB_SEL, 0);
1101 set_cpld_field(RXHB_SEL, 1);
1102 freq_lo1 = _rxlo1->set_frequency(freq, ref_freq, target_pfd_freq, is_int_n);
1103 _rxlo1->set_output_power(max287x_iface::OUTPUT_POWER_5DBM);
1104 } else if ((freq >= 1000 * fMHz) && (freq < 1500 * fMHz)) {
1105 set_cpld_field(SEL_LNA1, 0);
1106 set_cpld_field(SEL_LNA2, 1);
1107 set_cpld_field(RXLO1_FSEL3, 0);
1108 set_cpld_field(RXLO1_FSEL2, 1);
1109 set_cpld_field(RXLO1_FSEL1, 0);
1110 set_cpld_field(RXLB_SEL, 0);
1111 set_cpld_field(RXHB_SEL, 1);
1112 freq_lo1 = _rxlo1->set_frequency(freq, ref_freq, target_pfd_freq, is_int_n);
1113 _rxlo1->set_output_power(max287x_iface::OUTPUT_POWER_2DBM);
1114 } else if ((freq >= 1500 * fMHz) && (freq < 2200 * fMHz)) {
1115 set_cpld_field(SEL_LNA1, 1);
1116 set_cpld_field(SEL_LNA2, 0);
1117 set_cpld_field(RXLO1_FSEL3, 0);
1118 set_cpld_field(RXLO1_FSEL2, 1);
1119 set_cpld_field(RXLO1_FSEL1, 0);
1120 set_cpld_field(RXLB_SEL, 0);
1121 set_cpld_field(RXHB_SEL, 1);
1122 freq_lo1 = _rxlo1->set_frequency(freq, ref_freq, target_pfd_freq, is_int_n);
1123 _rxlo1->set_output_power(max287x_iface::OUTPUT_POWER_2DBM);
1124 } else if ((freq >= 2200 * fMHz) && (freq < 2500 * fMHz)) {
1125 set_cpld_field(SEL_LNA1, 1);
1126 set_cpld_field(SEL_LNA2, 0);
1127 set_cpld_field(RXLO1_FSEL3, 0);
1128 set_cpld_field(RXLO1_FSEL2, 1);
1129 set_cpld_field(RXLO1_FSEL1, 0);
1130 set_cpld_field(RXLB_SEL, 0);
1131 set_cpld_field(RXHB_SEL, 1);
1132 freq_lo1 = _rxlo1->set_frequency(freq, ref_freq, target_pfd_freq, is_int_n);
1133 _rxlo1->set_output_power(max287x_iface::OUTPUT_POWER_2DBM);
1134 } else if ((freq >= 2500 * fMHz) && (freq <= 6000 * fMHz)) {
1135 set_cpld_field(SEL_LNA1, 1);
1136 set_cpld_field(SEL_LNA2, 0);
1137 set_cpld_field(RXLO1_FSEL3, 1);
1138 set_cpld_field(RXLO1_FSEL2, 0);
1139 set_cpld_field(RXLO1_FSEL1, 0);
1140 set_cpld_field(RXLB_SEL, 0);
1141 set_cpld_field(RXHB_SEL, 1);
1142 freq_lo1 = _rxlo1->set_frequency(freq, ref_freq, target_pfd_freq, is_int_n);
1143 _rxlo1->set_output_power(max287x_iface::OUTPUT_POWER_5DBM);
1144 }
1145
1146 // To reduce the number of commands issued to the device, write to the
1147 // SPI destination already addressed first. This avoids the writes to
1148 // the GPIO registers to route the SPI to the same destination.
1149 switch (get_gpio_field(SPI_ADDR)) {
1150 case RXLO1:
1151 _rxlo1->commit();
1152 if (freq < (500 * fMHz))
1153 _rxlo2->commit();
1154 write_cpld_reg();
1155 break;
1156 case RXLO2:
1157 if (freq < (500 * fMHz))
1158 _rxlo2->commit();
1159 _rxlo1->commit();
1160 write_cpld_reg();
1161 break;
1162 default:
1163 write_cpld_reg();
1164 _rxlo1->commit();
1165 if (freq < (500 * fMHz))
1166 _rxlo2->commit();
1167 break;
1168 }
1169
1170 if (cmd_time != uhd::time_spec_t(0.0) and _rxlo1->can_sync()) {
1171 sync_phase(cmd_time, RX_DIRECTION);
1172 }
1173
1174 _rx_freq = freq_lo1 - freq_lo2;
1175 _rxlo1_freq = freq_lo1;
1176 _rxlo2_freq = freq_lo2;
1177
1178 UHD_LOGGER_TRACE("UBX")
1179 << boost::format("UBX RX: the actual frequency is %f MHz") % (_rx_freq / 1e6);
1180
1181 return _rx_freq;
1182 }
1183
1184 /***********************************************************************
1185 * Setting Modes
1186 **********************************************************************/
set_power_mode(std::string mode)1187 void set_power_mode(std::string mode)
1188 {
1189 boost::mutex::scoped_lock lock(_mutex);
1190 if (mode == "performance") {
1191 // performance mode attempts to reduce tuning and settling time
1192 // as much as possible without adding noise.
1193
1194 // RXLNA2 has a ~100ms warm up time, so the LNAs are forced on
1195 // here to reduce the settling time as much as possible. The
1196 // force on signals are gated by the LNA selection so the LNAs
1197 // are turned on/off during tuning. Unfortunately, that means
1198 // there is still a long settling time when tuning from the high
1199 // band (>1.5 GHz) to the low band (<1.5 GHz).
1200 set_cpld_field(RXLNA1_FORCEON, 1);
1201 set_cpld_field(RXLNA2_FORCEON, 1);
1202
1203 // Placeholders in case some components need to be forced on to
1204 // reduce settling time. Note that some FORCEON lines are still gated
1205 // by other bits in the CPLD register and are asserted during
1206 // frequency tuning.
1207 set_cpld_field(RXAMP_FORCEON, 1);
1208 set_cpld_field(RXDEMOD_FORCEON, 1);
1209 set_cpld_field(RXDRV_FORCEON, 1);
1210 set_cpld_field(RXMIXER_FORCEON, 0);
1211 set_cpld_field(RXLO1_FORCEON, 1);
1212 set_cpld_field(RXLO2_FORCEON, 1);
1213 /*
1214 //set_cpld_field(TXDRV_FORCEON, 1); // controlled by RX antenna selection
1215 set_cpld_field(TXMOD_FORCEON, 0);
1216 set_cpld_field(TXMIXER_FORCEON, 0);
1217 set_cpld_field(TXLO1_FORCEON, 0);
1218 set_cpld_field(TXLO2_FORCEON, 0);
1219 */
1220 write_cpld_reg();
1221
1222 _power_mode = PERFORMANCE;
1223 } else if (mode == "powersave") {
1224 // powersave mode attempts to use the least amount of power possible
1225 // by powering on components only when needed. Longer tuning and
1226 // settling times are expected.
1227
1228 // Clear the LNA force on bits.
1229 set_cpld_field(RXLNA1_FORCEON, 0);
1230 set_cpld_field(RXLNA2_FORCEON, 0);
1231
1232 /*
1233 // Placeholders in case other force on bits need to be set or cleared.
1234 set_cpld_field(RXAMP_FORCEON, 0);
1235 set_cpld_field(RXDEMOD_FORCEON, 0);
1236 set_cpld_field(RXDRV_FORCEON, 0);
1237 set_cpld_field(RXMIXER_FORCEON, 0);
1238 set_cpld_field(RXLO1_FORCEON, 0);
1239 set_cpld_field(RXLO2_FORCEON, 0);
1240 //set_cpld_field(TXDRV_FORCEON, 1); // controlled by RX antenna selection
1241 set_cpld_field(TXMOD_FORCEON, 0);
1242 set_cpld_field(TXMIXER_FORCEON, 0);
1243 set_cpld_field(TXLO1_FORCEON, 0);
1244 set_cpld_field(TXLO2_FORCEON, 0);
1245 */
1246
1247 write_cpld_reg();
1248
1249 _power_mode = POWERSAVE;
1250 }
1251 }
1252
set_xcvr_mode(std::string mode)1253 void set_xcvr_mode(std::string mode)
1254 {
1255 // TO DO: Add implementation
1256 // The intent is to add behavior based on whether
1257 // the board is in TX, RX, or full duplex mode
1258 // to reduce power consumption and RF noise.
1259 boost::to_upper(mode);
1260 if (mode == "FDX") {
1261 _xcvr_mode = FDX;
1262 } else if (mode == "TDD") {
1263 _xcvr_mode = TDD;
1264 set_cpld_field(TXDRV_FORCEON, 1);
1265 write_cpld_reg();
1266 } else if (mode == "TX") {
1267 _xcvr_mode = TX;
1268 } else if (mode == "RX") {
1269 _xcvr_mode = RX;
1270 } else {
1271 throw uhd::value_error("invalid xcvr_mode");
1272 }
1273 }
1274
set_sync_delay(bool is_tx,int64_t value)1275 void set_sync_delay(bool is_tx, int64_t value)
1276 {
1277 if (is_tx)
1278 _tx_sync_delay = value;
1279 else
1280 _rx_sync_delay = value;
1281 }
1282
set_temp_comp_mode(std::string mode)1283 void set_temp_comp_mode(std::string mode)
1284 {
1285 const bool enabled = [mode]() {
1286 if (mode == "enabled") {
1287 return true;
1288 } else if (mode == "disabled") {
1289 return false;
1290 } else {
1291 throw uhd::value_error("invalid temperature_compensation_mode");
1292 }
1293 }();
1294
1295 boost::mutex::scoped_lock lock(_mutex);
1296 for (const auto& lo : {_txlo1, _txlo2, _rxlo1, _rxlo2}) {
1297 lo->set_auto_retune(enabled);
1298 }
1299 }
1300
1301 /***********************************************************************
1302 * Variables
1303 **********************************************************************/
1304 dboard_iface::sptr _iface;
1305 boost::mutex _spi_mutex;
1306 boost::mutex _mutex;
1307 ubx_cpld_reg_t _cpld_reg;
1308 uint32_t _prev_cpld_value;
1309 std::map<ubx_gpio_field_id_t, ubx_gpio_field_info_t> _gpio_map;
1310 std::shared_ptr<max287x_iface> _txlo1;
1311 std::shared_ptr<max287x_iface> _txlo2;
1312 std::shared_ptr<max287x_iface> _rxlo1;
1313 std::shared_ptr<max287x_iface> _rxlo2;
1314 double _tx_target_pfd_freq;
1315 double _rx_target_pfd_freq;
1316 double _tx_gain;
1317 double _rx_gain;
1318 double _tx_freq;
1319 double _txlo1_freq;
1320 double _txlo2_freq;
1321 double _rx_freq;
1322 double _rxlo1_freq;
1323 double _rxlo2_freq;
1324 bool _rxlo_locked;
1325 bool _txlo_locked;
1326 std::string _rx_ant;
1327 int _ubx_tx_atten_val;
1328 int _ubx_rx_atten_val;
1329 power_mode_t _power_mode;
1330 xcvr_mode_t _xcvr_mode;
1331 size_t _rev;
1332 ubx_gpio_reg_t _tx_gpio_reg;
1333 ubx_gpio_reg_t _rx_gpio_reg;
1334 int64_t _tx_sync_delay;
1335 int64_t _rx_sync_delay;
1336 bool _high_isolation;
1337 };
1338
1339 /***********************************************************************
1340 * Register the UBX dboard (min freq, max freq, rx div2, tx div2)
1341 **********************************************************************/
make_ubx(dboard_base::ctor_args_t args)1342 static dboard_base::sptr make_ubx(dboard_base::ctor_args_t args)
1343 {
1344 return dboard_base::sptr(new ubx_xcvr(args));
1345 }
1346
UHD_STATIC_BLOCK(reg_ubx_dboards)1347 UHD_STATIC_BLOCK(reg_ubx_dboards)
1348 {
1349 dboard_manager::register_dboard(
1350 UBX_PROTO_V3_RX_ID, UBX_PROTO_V3_TX_ID, &make_ubx, "UBX v0.3");
1351 dboard_manager::register_dboard(
1352 UBX_PROTO_V4_RX_ID, UBX_PROTO_V4_TX_ID, &make_ubx, "UBX v0.4");
1353 dboard_manager::register_dboard(
1354 UBX_V1_40MHZ_RX_ID, UBX_V1_40MHZ_TX_ID, &make_ubx, "UBX-40 v1");
1355 dboard_manager::register_dboard(
1356 UBX_V1_160MHZ_RX_ID, UBX_V1_160MHZ_TX_ID, &make_ubx, "UBX-160 v1");
1357 dboard_manager::register_dboard(
1358 UBX_V2_40MHZ_RX_ID, UBX_V2_40MHZ_TX_ID, &make_ubx, "UBX-40 v2");
1359 dboard_manager::register_dboard(
1360 UBX_V2_160MHZ_RX_ID, UBX_V2_160MHZ_TX_ID, &make_ubx, "UBX-160 v2");
1361 dboard_manager::register_dboard(
1362 UBX_LP_160MHZ_RX_ID, UBX_LP_160MHZ_TX_ID, &make_ubx, "UBX-160-LP");
1363 dboard_manager::register_dboard(
1364 UBX_TDD_160MHZ_RX_ID, UBX_TDD_160MHZ_TX_ID, &make_ubx, "UBX-TDD");
1365 }
1366