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