Statistics
| Branch: | Tag: | Revision:

root / host / lib / usrp / usrp2 / usrp2_impl.cpp @ 76930c03

History | View | Annotate | Download (40.7 KB)

1
//
2
// Copyright 2010-2012 Ettus Research LLC
3
//
4
// This program is free software: you can redistribute it and/or modify
5
// it under the terms of the GNU General Public License as published by
6
// the Free Software Foundation, either version 3 of the License, or
7
// (at your option) any later version.
8
//
9
// This program is distributed in the hope that it will be useful,
10
// but WITHOUT ANY WARRANTY; without even the implied warranty of
11
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12
// GNU General Public License for more details.
13
//
14
// You should have received a copy of the GNU General Public License
15
// along with this program.  If not, see <http://www.gnu.org/licenses/>.
16
//
17

    
18
#include "usrp2_impl.hpp"
19
#include "fw_common.h"
20
#include "apply_corrections.hpp"
21
#include <uhd/utils/log.hpp>
22
#include <uhd/utils/msg.hpp>
23
#include <uhd/exception.hpp>
24
#include <uhd/transport/if_addrs.hpp>
25
#include <uhd/transport/udp_zero_copy.hpp>
26
#include <uhd/types/ranges.hpp>
27
#include <uhd/exception.hpp>
28
#include <uhd/utils/static.hpp>
29
#include <uhd/utils/byteswap.hpp>
30
#include <uhd/utils/safe_call.hpp>
31
#include <boost/format.hpp>
32
#include <boost/foreach.hpp>
33
#include <boost/lexical_cast.hpp>
34
#include <boost/bind.hpp>
35
#include <boost/assign/list_of.hpp>
36
#include <boost/asio/ip/address_v4.hpp>
37
#include <boost/asio.hpp> //used for htonl and ntohl
38

    
39
using namespace uhd;
40
using namespace uhd::usrp;
41
using namespace uhd::transport;
42
namespace asio = boost::asio;
43

    
44
/***********************************************************************
45
 * Discovery over the udp transport
46
 **********************************************************************/
47
static device_addrs_t usrp2_find(const device_addr_t &hint_){
48
    //handle the multi-device discovery
49
    device_addrs_t hints = separate_device_addr(hint_);
50
    if (hints.size() > 1){
51
        device_addrs_t found_devices;
52
        BOOST_FOREACH(const device_addr_t &hint_i, hints){
53
            device_addrs_t found_devices_i = usrp2_find(hint_i);
54
            if (found_devices_i.size() != 1) throw uhd::value_error(str(boost::format(
55
                "Could not resolve device hint \"%s\" to a single device."
56
            ) % hint_i.to_string()));
57
            found_devices.push_back(found_devices_i[0]);
58
        }
59
        return device_addrs_t(1, combine_device_addrs(found_devices));
60
    }
61

    
62
    //initialize the hint for a single device case
63
    UHD_ASSERT_THROW(hints.size() <= 1);
64
    hints.resize(1); //in case it was empty
65
    device_addr_t hint = hints[0];
66
    device_addrs_t usrp2_addrs;
67

    
68
    //return an empty list of addresses when type is set to non-usrp2
69
    if (hint.has_key("type") and hint["type"] != "usrp2") return usrp2_addrs;
70

    
71
    //if no address was specified, send a broadcast on each interface
72
    if (not hint.has_key("addr")){
73
        BOOST_FOREACH(const if_addrs_t &if_addrs, get_if_addrs()){
74
            //avoid the loopback device
75
            if (if_addrs.inet == asio::ip::address_v4::loopback().to_string()) continue;
76

    
77
            //create a new hint with this broadcast address
78
            device_addr_t new_hint = hint;
79
            new_hint["addr"] = if_addrs.bcast;
80

    
81
            //call discover with the new hint and append results
82
            device_addrs_t new_usrp2_addrs = usrp2_find(new_hint);
83
            usrp2_addrs.insert(usrp2_addrs.begin(),
84
                new_usrp2_addrs.begin(), new_usrp2_addrs.end()
85
            );
86
        }
87
        return usrp2_addrs;
88
    }
89

    
90
    //Create a UDP transport to communicate:
91
    //Some devices will cause a throw when opened for a broadcast address.
92
    //We print and recover so the caller can loop through all bcast addrs.
93
    udp_simple::sptr udp_transport;
94
    try{
95
        udp_transport = udp_simple::make_broadcast(hint["addr"], BOOST_STRINGIZE(USRP2_UDP_CTRL_PORT));
96
    }
97
    catch(const std::exception &e){
98
        UHD_MSG(error) << boost::format("Cannot open UDP transport on %s\n%s") % hint["addr"] % e.what() << std::endl;
99
        return usrp2_addrs; //dont throw, but return empty address so caller can insert
100
    }
101

    
102
    //send a hello control packet
103
    usrp2_ctrl_data_t ctrl_data_out = usrp2_ctrl_data_t();
104
    ctrl_data_out.proto_ver = uhd::htonx<boost::uint32_t>(USRP2_FW_COMPAT_NUM);
105
    ctrl_data_out.id = uhd::htonx<boost::uint32_t>(USRP2_CTRL_ID_WAZZUP_BRO);
106
    udp_transport->send(boost::asio::buffer(&ctrl_data_out, sizeof(ctrl_data_out)));
107

    
108
    //loop and recieve until the timeout
109
    boost::uint8_t usrp2_ctrl_data_in_mem[udp_simple::mtu]; //allocate max bytes for recv
110
    const usrp2_ctrl_data_t *ctrl_data_in = reinterpret_cast<const usrp2_ctrl_data_t *>(usrp2_ctrl_data_in_mem);
111
    while(true){
112
        size_t len = udp_transport->recv(asio::buffer(usrp2_ctrl_data_in_mem));
113
        if (len > offsetof(usrp2_ctrl_data_t, data) and ntohl(ctrl_data_in->id) == USRP2_CTRL_ID_WAZZUP_DUDE){
114

    
115
            //make a boost asio ipv4 with the raw addr in host byte order
116
            device_addr_t new_addr;
117
            new_addr["type"] = "usrp2";
118
            //We used to get the address from the control packet.
119
            //Now now uses the socket itself to yield the address.
120
            //boost::asio::ip::address_v4 ip_addr(ntohl(ctrl_data_in->data.ip_addr));
121
            //new_addr["addr"] = ip_addr.to_string();
122
            new_addr["addr"] = udp_transport->get_recv_addr();
123

    
124
            //Attempt a simple 2-way communication with a connected socket.
125
            //Reason: Although the USRP will respond the broadcast above,
126
            //we may not be able to communicate directly (non-broadcast).
127
            udp_simple::sptr ctrl_xport = udp_simple::make_connected(
128
                new_addr["addr"], BOOST_STRINGIZE(USRP2_UDP_CTRL_PORT)
129
            );
130
            ctrl_xport->send(boost::asio::buffer(&ctrl_data_out, sizeof(ctrl_data_out)));
131
            size_t len = ctrl_xport->recv(asio::buffer(usrp2_ctrl_data_in_mem));
132
            if (len > offsetof(usrp2_ctrl_data_t, data) and ntohl(ctrl_data_in->id) == USRP2_CTRL_ID_WAZZUP_DUDE){
133
                //found the device, open up for communication!
134
            }
135
            else{
136
                //otherwise we don't find it...
137
                continue;
138
            }
139

    
140
            //Attempt to read the name from the EEPROM and perform filtering.
141
            //This operation can throw due to compatibility mismatch.
142
            try{
143
                usrp2_iface::sptr iface = usrp2_iface::make(ctrl_xport);
144
                if (iface->is_device_locked()) continue; //ignore locked devices
145
                mboard_eeprom_t mb_eeprom = iface->mb_eeprom;
146
                new_addr["name"] = mb_eeprom["name"];
147
                new_addr["serial"] = mb_eeprom["serial"];
148
            }
149
            catch(const std::exception &){
150
                //set these values as empty string so the device may still be found
151
                //and the filter's below can still operate on the discovered device
152
                new_addr["name"] = "";
153
                new_addr["serial"] = "";
154
            }
155

    
156
            //filter the discovered device below by matching optional keys
157
            if (
158
                (not hint.has_key("name")   or hint["name"]   == new_addr["name"]) and
159
                (not hint.has_key("serial") or hint["serial"] == new_addr["serial"])
160
            ){
161
                usrp2_addrs.push_back(new_addr);
162
            }
163

    
164
            //dont break here, it will exit the while loop
165
            //just continue on to the next loop iteration
166
        }
167
        if (len == 0) break; //timeout
168
    }
169

    
170
    return usrp2_addrs;
171
}
172

    
173
/***********************************************************************
174
 * Make
175
 **********************************************************************/
176
static device::sptr usrp2_make(const device_addr_t &device_addr){
177
    return device::sptr(new usrp2_impl(device_addr));
178
}
179

    
180
UHD_STATIC_BLOCK(register_usrp2_device){
181
    device::register_device(&usrp2_find, &usrp2_make);
182
}
183

    
184
/***********************************************************************
185
 * MTU Discovery
186
 **********************************************************************/
187
struct mtu_result_t{
188
    size_t recv_mtu, send_mtu;
189
};
190

    
191
static mtu_result_t determine_mtu(const std::string &addr, const mtu_result_t &user_mtu){
192
    udp_simple::sptr udp_sock = udp_simple::make_connected(
193
        addr, BOOST_STRINGIZE(USRP2_UDP_CTRL_PORT)
194
    );
195

    
196
    //The FPGA offers 4K buffers, and the user may manually request this.
197
    //However, multiple simultaneous receives (2DSP slave + 2DSP master),
198
    //require that buffering to be used internally, and this is a safe setting.
199
    std::vector<boost::uint8_t> buffer(std::max(user_mtu.recv_mtu, user_mtu.send_mtu));
200
    usrp2_ctrl_data_t *ctrl_data = reinterpret_cast<usrp2_ctrl_data_t *>(&buffer.front());
201
    static const double echo_timeout = 0.020; //20 ms
202

    
203
    //test holler - check if its supported in this fw version
204
    ctrl_data->id = htonl(USRP2_CTRL_ID_HOLLER_AT_ME_BRO);
205
    ctrl_data->proto_ver = htonl(USRP2_FW_COMPAT_NUM);
206
    ctrl_data->data.echo_args.len = htonl(sizeof(usrp2_ctrl_data_t));
207
    udp_sock->send(boost::asio::buffer(buffer, sizeof(usrp2_ctrl_data_t)));
208
    udp_sock->recv(boost::asio::buffer(buffer), echo_timeout);
209
    if (ntohl(ctrl_data->id) != USRP2_CTRL_ID_HOLLER_BACK_DUDE)
210
        throw uhd::not_implemented_error("holler protocol not implemented");
211

    
212
    size_t min_recv_mtu = sizeof(usrp2_ctrl_data_t), max_recv_mtu = user_mtu.recv_mtu;
213
    size_t min_send_mtu = sizeof(usrp2_ctrl_data_t), max_send_mtu = user_mtu.send_mtu;
214

    
215
    while (min_recv_mtu < max_recv_mtu){
216

    
217
        size_t test_mtu = (max_recv_mtu/2 + min_recv_mtu/2 + 3) & ~3;
218

    
219
        ctrl_data->id = htonl(USRP2_CTRL_ID_HOLLER_AT_ME_BRO);
220
        ctrl_data->proto_ver = htonl(USRP2_FW_COMPAT_NUM);
221
        ctrl_data->data.echo_args.len = htonl(test_mtu);
222
        udp_sock->send(boost::asio::buffer(buffer, sizeof(usrp2_ctrl_data_t)));
223

    
224
        size_t len = udp_sock->recv(boost::asio::buffer(buffer), echo_timeout);
225

    
226
        if (len >= test_mtu) min_recv_mtu = test_mtu;
227
        else                 max_recv_mtu = test_mtu - 4;
228

    
229
    }
230

    
231
    while (min_send_mtu < max_send_mtu){
232

    
233
        size_t test_mtu = (max_send_mtu/2 + min_send_mtu/2 + 3) & ~3;
234

    
235
        ctrl_data->id = htonl(USRP2_CTRL_ID_HOLLER_AT_ME_BRO);
236
        ctrl_data->proto_ver = htonl(USRP2_FW_COMPAT_NUM);
237
        ctrl_data->data.echo_args.len = htonl(sizeof(usrp2_ctrl_data_t));
238
        udp_sock->send(boost::asio::buffer(buffer, test_mtu));
239

    
240
        size_t len = udp_sock->recv(boost::asio::buffer(buffer), echo_timeout);
241
        if (len >= sizeof(usrp2_ctrl_data_t)) len = ntohl(ctrl_data->data.echo_args.len);
242

    
243
        if (len >= test_mtu) min_send_mtu = test_mtu;
244
        else                 max_send_mtu = test_mtu - 4;
245
    }
246

    
247
    mtu_result_t mtu;
248
    mtu.recv_mtu = min_recv_mtu;
249
    mtu.send_mtu = min_send_mtu;
250
    return mtu;
251
}
252

    
253
/***********************************************************************
254
 * Helpers
255
 **********************************************************************/
256
static zero_copy_if::sptr make_xport(
257
    const std::string &addr,
258
    const std::string &port,
259
    const device_addr_t &hints,
260
    const std::string &filter
261
){
262

    
263
    //only copy hints that contain the filter word
264
    device_addr_t filtered_hints;
265
    BOOST_FOREACH(const std::string &key, hints.keys()){
266
        if (key.find(filter) == std::string::npos) continue;
267
        filtered_hints[key] = hints[key];
268
    }
269

    
270
    //make the transport object with the filtered hints
271
    zero_copy_if::sptr xport = udp_zero_copy::make(addr, port, filtered_hints);
272

    
273
    //Send a small data packet so the usrp2 knows the udp source port.
274
    //This setup must happen before further initialization occurs
275
    //or the async update packets will cause ICMP destination unreachable.
276
    static const boost::uint32_t data[2] = {
277
        uhd::htonx(boost::uint32_t(0 /* don't care seq num */)),
278
        uhd::htonx(boost::uint32_t(USRP2_INVALID_VRT_HEADER))
279
    };
280
    transport::managed_send_buffer::sptr send_buff = xport->get_send_buff();
281
    std::memcpy(send_buff->cast<void*>(), &data, sizeof(data));
282
    send_buff->commit(sizeof(data));
283

    
284
    return xport;
285
}
286

    
287
/***********************************************************************
288
 * Structors
289
 **********************************************************************/
290
usrp2_impl::usrp2_impl(const device_addr_t &_device_addr){
291
    UHD_MSG(status) << "Opening a USRP2/N-Series device..." << std::endl;
292
    device_addr_t device_addr = _device_addr;
293

    
294
    //setup the dsp transport hints (default to a large recv buff)
295
    if (not device_addr.has_key("recv_buff_size")){
296
        #if defined(UHD_PLATFORM_MACOS) || defined(UHD_PLATFORM_BSD)
297
            //limit buffer resize on macos or it will error
298
            device_addr["recv_buff_size"] = "1e6";
299
        #elif defined(UHD_PLATFORM_LINUX) || defined(UHD_PLATFORM_WIN32)
300
            //set to half-a-second of buffering at max rate
301
            device_addr["recv_buff_size"] = "50e6";
302
        #endif
303
    }
304
    if (not device_addr.has_key("send_buff_size")){
305
        //The buffer should be the size of the SRAM on the device,
306
        //because we will never commit more than the SRAM can hold.
307
        device_addr["send_buff_size"] = boost::lexical_cast<std::string>(USRP2_SRAM_BYTES);
308
    }
309

    
310
    device_addrs_t device_args = separate_device_addr(device_addr);
311

    
312
    //extract the user's requested MTU size or default
313
    mtu_result_t user_mtu;
314
    user_mtu.recv_mtu = size_t(device_addr.cast<double>("recv_frame_size", udp_simple::mtu));
315
    user_mtu.send_mtu = size_t(device_addr.cast<double>("send_frame_size", udp_simple::mtu));
316

    
317
    try{
318
        //calculate the minimum send and recv mtu of all devices
319
        mtu_result_t mtu = determine_mtu(device_args[0]["addr"], user_mtu);
320
        for (size_t i = 1; i < device_args.size(); i++){
321
            mtu_result_t mtu_i = determine_mtu(device_args[i]["addr"], user_mtu);
322
            mtu.recv_mtu = std::min(mtu.recv_mtu, mtu_i.recv_mtu);
323
            mtu.send_mtu = std::min(mtu.send_mtu, mtu_i.send_mtu);
324
        }
325

    
326
        device_addr["recv_frame_size"] = boost::lexical_cast<std::string>(mtu.recv_mtu);
327
        device_addr["send_frame_size"] = boost::lexical_cast<std::string>(mtu.send_mtu);
328

    
329
        UHD_MSG(status) << boost::format("Current recv frame size: %d bytes") % mtu.recv_mtu << std::endl;
330
        UHD_MSG(status) << boost::format("Current send frame size: %d bytes") % mtu.send_mtu << std::endl;
331
    }
332
    catch(const uhd::not_implemented_error &){
333
        //just ignore this error, makes older fw work...
334
    }
335

    
336
    device_args = separate_device_addr(device_addr); //update args for new frame sizes
337

    
338
    ////////////////////////////////////////////////////////////////////
339
    // create controller objects and initialize the properties tree
340
    ////////////////////////////////////////////////////////////////////
341
    _tree = property_tree::make();
342
    _tree->create<std::string>("/name").set("USRP2 / N-Series Device");
343

    
344
    for (size_t mbi = 0; mbi < device_args.size(); mbi++){
345
        const device_addr_t device_args_i = device_args[mbi];
346
        const std::string mb = boost::lexical_cast<std::string>(mbi);
347
        const std::string addr = device_args_i["addr"];
348
        const fs_path mb_path = "/mboards/" + mb;
349

    
350
        ////////////////////////////////////////////////////////////////
351
        // create the iface that controls i2c, spi, uart, and wb
352
        ////////////////////////////////////////////////////////////////
353
        _mbc[mb].iface = usrp2_iface::make(udp_simple::make_connected(
354
            addr, BOOST_STRINGIZE(USRP2_UDP_CTRL_PORT)
355
        ));
356
        _tree->create<std::string>(mb_path / "name").set(_mbc[mb].iface->get_cname());
357
        _tree->create<std::string>(mb_path / "fw_version").set(_mbc[mb].iface->get_fw_version_string());
358

    
359
        //check the fpga compatibility number
360
        const boost::uint32_t fpga_compat_num = _mbc[mb].iface->peek32(U2_REG_COMPAT_NUM_RB);
361
        boost::uint16_t fpga_major = fpga_compat_num >> 16, fpga_minor = fpga_compat_num & 0xffff;
362
        if (fpga_major == 0){ //old version scheme
363
            fpga_major = fpga_minor;
364
            fpga_minor = 0;
365
        }
366
        if (fpga_major != USRP2_FPGA_COMPAT_NUM){
367
            throw uhd::runtime_error(str(boost::format(
368
                "\nPlease update the firmware and FPGA images for your device.\n"
369
                "See the application notes for USRP2/N-Series for instructions.\n"
370
                "Expected FPGA compatibility number %d, but got %d:\n"
371
                "The FPGA build is not compatible with the host code build.\n"
372
                "%s\n"
373
            ) % int(USRP2_FPGA_COMPAT_NUM) % fpga_major % _mbc[mb].iface->images_warn_help_message()));
374
        }
375
        _tree->create<std::string>(mb_path / "fpga_version").set(str(boost::format("%u.%u") % fpga_major % fpga_minor));
376

    
377
        //lock the device/motherboard to this process
378
        _mbc[mb].iface->lock_device(true);
379

    
380
        ////////////////////////////////////////////////////////////////
381
        // construct transports for RX and TX DSPs
382
        ////////////////////////////////////////////////////////////////
383
        UHD_LOG << "Making transport for RX DSP0..." << std::endl;
384
        _mbc[mb].rx_dsp_xports.push_back(make_xport(
385
            addr, BOOST_STRINGIZE(USRP2_UDP_RX_DSP0_PORT), device_args_i, "recv"
386
        ));
387
        UHD_LOG << "Making transport for RX DSP1..." << std::endl;
388
        _mbc[mb].rx_dsp_xports.push_back(make_xport(
389
            addr, BOOST_STRINGIZE(USRP2_UDP_RX_DSP1_PORT), device_args_i, "recv"
390
        ));
391
        UHD_LOG << "Making transport for TX DSP0..." << std::endl;
392
        _mbc[mb].tx_dsp_xport = make_xport(
393
            addr, BOOST_STRINGIZE(USRP2_UDP_TX_DSP0_PORT), device_args_i, "send"
394
        );
395
        UHD_LOG << "Making transport for Control..." << std::endl;
396
        _mbc[mb].fifo_ctrl_xport = make_xport(
397
            addr, BOOST_STRINGIZE(USRP2_UDP_FIFO_CRTL_PORT), device_addr_t(), ""
398
        );
399
        //set the filter on the router to take dsp data from this port
400
        _mbc[mb].iface->poke32(U2_REG_ROUTER_CTRL_PORTS, (USRP2_UDP_FIFO_CRTL_PORT << 16) | USRP2_UDP_TX_DSP0_PORT);
401

    
402
        //create the fifo control interface for high speed register access
403
        _mbc[mb].fifo_ctrl = usrp2_fifo_ctrl::make(_mbc[mb].fifo_ctrl_xport);
404
        switch(_mbc[mb].iface->get_rev()){
405
        case usrp2_iface::USRP_N200:
406
        case usrp2_iface::USRP_N210:
407
        case usrp2_iface::USRP_N200_R4:
408
        case usrp2_iface::USRP_N210_R4:
409
            _mbc[mb].wbiface = _mbc[mb].fifo_ctrl;
410
            _mbc[mb].spiface = _mbc[mb].fifo_ctrl;
411
            break;
412
        default:
413
            _mbc[mb].wbiface = _mbc[mb].iface;
414
            _mbc[mb].spiface = _mbc[mb].iface;
415
            break;
416
        }
417

    
418
        ////////////////////////////////////////////////////////////////
419
        // setup the mboard eeprom
420
        ////////////////////////////////////////////////////////////////
421
        _tree->create<mboard_eeprom_t>(mb_path / "eeprom")
422
            .set(_mbc[mb].iface->mb_eeprom)
423
            .subscribe(boost::bind(&usrp2_impl::set_mb_eeprom, this, mb, _1));
424

    
425
        ////////////////////////////////////////////////////////////////
426
        // create clock control objects
427
        ////////////////////////////////////////////////////////////////
428
        _mbc[mb].clock = usrp2_clock_ctrl::make(_mbc[mb].iface, _mbc[mb].spiface);
429
        _tree->create<double>(mb_path / "tick_rate")
430
            .publish(boost::bind(&usrp2_clock_ctrl::get_master_clock_rate, _mbc[mb].clock))
431
            .subscribe(boost::bind(&usrp2_impl::update_tick_rate, this, _1));
432

    
433
        ////////////////////////////////////////////////////////////////
434
        // create codec control objects
435
        ////////////////////////////////////////////////////////////////
436
        const fs_path rx_codec_path = mb_path / "rx_codecs/A";
437
        const fs_path tx_codec_path = mb_path / "tx_codecs/A";
438
        _tree->create<int>(rx_codec_path / "gains"); //phony property so this dir exists
439
        _tree->create<int>(tx_codec_path / "gains"); //phony property so this dir exists
440
        _mbc[mb].codec = usrp2_codec_ctrl::make(_mbc[mb].iface, _mbc[mb].spiface);
441
        switch(_mbc[mb].iface->get_rev()){
442
        case usrp2_iface::USRP_N200:
443
        case usrp2_iface::USRP_N210:
444
        case usrp2_iface::USRP_N200_R4:
445
        case usrp2_iface::USRP_N210_R4:{
446
            _tree->create<std::string>(rx_codec_path / "name").set("ads62p44");
447
            _tree->create<meta_range_t>(rx_codec_path / "gains/digital/range").set(meta_range_t(0, 6.0, 0.5));
448
            _tree->create<double>(rx_codec_path / "gains/digital/value")
449
                .subscribe(boost::bind(&usrp2_codec_ctrl::set_rx_digital_gain, _mbc[mb].codec, _1)).set(0);
450
            _tree->create<meta_range_t>(rx_codec_path / "gains/fine/range").set(meta_range_t(0, 0.5, 0.05));
451
            _tree->create<double>(rx_codec_path / "gains/fine/value")
452
                .subscribe(boost::bind(&usrp2_codec_ctrl::set_rx_digital_fine_gain, _mbc[mb].codec, _1)).set(0);
453
        }break;
454

    
455
        case usrp2_iface::USRP2_REV3:
456
        case usrp2_iface::USRP2_REV4:
457
            _tree->create<std::string>(rx_codec_path / "name").set("ltc2284");
458
            break;
459

    
460
        case usrp2_iface::USRP_NXXX:
461
            _tree->create<std::string>(rx_codec_path / "name").set("??????");
462
            break;
463
        }
464
        _tree->create<std::string>(tx_codec_path / "name").set("ad9777");
465

    
466
        ////////////////////////////////////////////////////////////////////
467
        // Create the GPSDO control
468
        ////////////////////////////////////////////////////////////////////
469
        static const boost::uint32_t dont_look_for_gpsdo = 0x1234abcdul;
470

    
471
        //disable check for internal GPSDO when not the following:
472
        switch(_mbc[mb].iface->get_rev()){
473
        case usrp2_iface::USRP_N200:
474
        case usrp2_iface::USRP_N210:
475
        case usrp2_iface::USRP_N200_R4:
476
        case usrp2_iface::USRP_N210_R4:
477
            break;
478
        default:
479
            _mbc[mb].iface->pokefw(U2_FW_REG_HAS_GPSDO, dont_look_for_gpsdo);
480
        }
481

    
482
        //otherwise if not disabled, look for the internal GPSDO
483
        if (_mbc[mb].iface->peekfw(U2_FW_REG_HAS_GPSDO) != dont_look_for_gpsdo)
484
        {
485
            UHD_MSG(status) << "Detecting internal GPSDO.... " << std::flush;
486
            try{
487
                _mbc[mb].gps = gps_ctrl::make(udp_simple::make_uart(udp_simple::make_connected(
488
                    addr, BOOST_STRINGIZE(USRP2_UDP_UART_GPS_PORT)
489
                )));
490
            }
491
            catch(std::exception &e){
492
                UHD_MSG(error) << "An error occurred making GPSDO control: " << e.what() << std::endl;
493
            }
494
            if (_mbc[mb].gps and _mbc[mb].gps->gps_detected())
495
            {
496
                UHD_MSG(status) << "found" << std::endl;
497
                BOOST_FOREACH(const std::string &name, _mbc[mb].gps->get_sensors())
498
                {
499
                    _tree->create<sensor_value_t>(mb_path / "sensors" / name)
500
                        .publish(boost::bind(&gps_ctrl::get_sensor, _mbc[mb].gps, name));
501
                }
502
            }
503
            else
504
            {
505
                UHD_MSG(status) << "not found" << std::endl;
506
                _mbc[mb].iface->pokefw(U2_FW_REG_HAS_GPSDO, dont_look_for_gpsdo);
507
            }
508
        }
509

    
510
        ////////////////////////////////////////////////////////////////
511
        // and do the misc mboard sensors
512
        ////////////////////////////////////////////////////////////////
513
        _tree->create<sensor_value_t>(mb_path / "sensors/mimo_locked")
514
            .publish(boost::bind(&usrp2_impl::get_mimo_locked, this, mb));
515
        _tree->create<sensor_value_t>(mb_path / "sensors/ref_locked")
516
            .publish(boost::bind(&usrp2_impl::get_ref_locked, this, mb));
517

    
518
        ////////////////////////////////////////////////////////////////
519
        // create frontend control objects
520
        ////////////////////////////////////////////////////////////////
521
        _mbc[mb].rx_fe = rx_frontend_core_200::make(
522
            _mbc[mb].wbiface, U2_REG_SR_ADDR(SR_RX_FRONT)
523
        );
524
        _mbc[mb].tx_fe = tx_frontend_core_200::make(
525
            _mbc[mb].wbiface, U2_REG_SR_ADDR(SR_TX_FRONT)
526
        );
527

    
528
        _tree->create<subdev_spec_t>(mb_path / "rx_subdev_spec")
529
            .subscribe(boost::bind(&usrp2_impl::update_rx_subdev_spec, this, mb, _1));
530
        _tree->create<subdev_spec_t>(mb_path / "tx_subdev_spec")
531
            .subscribe(boost::bind(&usrp2_impl::update_tx_subdev_spec, this, mb, _1));
532

    
533
        const fs_path rx_fe_path = mb_path / "rx_frontends" / "A";
534
        const fs_path tx_fe_path = mb_path / "tx_frontends" / "A";
535

    
536
        _tree->create<std::complex<double> >(rx_fe_path / "dc_offset" / "value")
537
            .coerce(boost::bind(&rx_frontend_core_200::set_dc_offset, _mbc[mb].rx_fe, _1))
538
            .set(std::complex<double>(0.0, 0.0));
539
        _tree->create<bool>(rx_fe_path / "dc_offset" / "enable")
540
            .subscribe(boost::bind(&rx_frontend_core_200::set_dc_offset_auto, _mbc[mb].rx_fe, _1))
541
            .set(true);
542
        _tree->create<std::complex<double> >(rx_fe_path / "iq_balance" / "value")
543
            .subscribe(boost::bind(&rx_frontend_core_200::set_iq_balance, _mbc[mb].rx_fe, _1))
544
            .set(std::complex<double>(0.0, 0.0));
545
        _tree->create<std::complex<double> >(tx_fe_path / "dc_offset" / "value")
546
            .coerce(boost::bind(&tx_frontend_core_200::set_dc_offset, _mbc[mb].tx_fe, _1))
547
            .set(std::complex<double>(0.0, 0.0));
548
        _tree->create<std::complex<double> >(tx_fe_path / "iq_balance" / "value")
549
            .subscribe(boost::bind(&tx_frontend_core_200::set_iq_balance, _mbc[mb].tx_fe, _1))
550
            .set(std::complex<double>(0.0, 0.0));
551

    
552
        ////////////////////////////////////////////////////////////////
553
        // create rx dsp control objects
554
        ////////////////////////////////////////////////////////////////
555
        _mbc[mb].rx_dsps.push_back(rx_dsp_core_200::make(
556
            _mbc[mb].wbiface, U2_REG_SR_ADDR(SR_RX_DSP0), U2_REG_SR_ADDR(SR_RX_CTRL0), USRP2_RX_SID_BASE + 0, true
557
        ));
558
        _mbc[mb].rx_dsps.push_back(rx_dsp_core_200::make(
559
            _mbc[mb].wbiface, U2_REG_SR_ADDR(SR_RX_DSP1), U2_REG_SR_ADDR(SR_RX_CTRL1), USRP2_RX_SID_BASE + 1, true
560
        ));
561
        for (size_t dspno = 0; dspno < _mbc[mb].rx_dsps.size(); dspno++){
562
            _mbc[mb].rx_dsps[dspno]->set_link_rate(USRP2_LINK_RATE_BPS);
563
            _tree->access<double>(mb_path / "tick_rate")
564
                .subscribe(boost::bind(&rx_dsp_core_200::set_tick_rate, _mbc[mb].rx_dsps[dspno], _1));
565
            fs_path rx_dsp_path = mb_path / str(boost::format("rx_dsps/%u") % dspno);
566
            _tree->create<meta_range_t>(rx_dsp_path / "rate/range")
567
                .publish(boost::bind(&rx_dsp_core_200::get_host_rates, _mbc[mb].rx_dsps[dspno]));
568
            _tree->create<double>(rx_dsp_path / "rate/value")
569
                .set(1e6) //some default
570
                .coerce(boost::bind(&rx_dsp_core_200::set_host_rate, _mbc[mb].rx_dsps[dspno], _1))
571
                .subscribe(boost::bind(&usrp2_impl::update_rx_samp_rate, this, mb, dspno, _1));
572
            _tree->create<double>(rx_dsp_path / "freq/value")
573
                .coerce(boost::bind(&rx_dsp_core_200::set_freq, _mbc[mb].rx_dsps[dspno], _1));
574
            _tree->create<meta_range_t>(rx_dsp_path / "freq/range")
575
                .publish(boost::bind(&rx_dsp_core_200::get_freq_range, _mbc[mb].rx_dsps[dspno]));
576
            _tree->create<stream_cmd_t>(rx_dsp_path / "stream_cmd")
577
                .subscribe(boost::bind(&rx_dsp_core_200::issue_stream_command, _mbc[mb].rx_dsps[dspno], _1));
578
        }
579

    
580
        ////////////////////////////////////////////////////////////////
581
        // create tx dsp control objects
582
        ////////////////////////////////////////////////////////////////
583
        _mbc[mb].tx_dsp = tx_dsp_core_200::make(
584
            _mbc[mb].wbiface, U2_REG_SR_ADDR(SR_TX_DSP), U2_REG_SR_ADDR(SR_TX_CTRL), USRP2_TX_ASYNC_SID
585
        );
586
        _mbc[mb].tx_dsp->set_link_rate(USRP2_LINK_RATE_BPS);
587
        _tree->access<double>(mb_path / "tick_rate")
588
            .subscribe(boost::bind(&tx_dsp_core_200::set_tick_rate, _mbc[mb].tx_dsp, _1));
589
        _tree->create<meta_range_t>(mb_path / "tx_dsps/0/rate/range")
590
            .publish(boost::bind(&tx_dsp_core_200::get_host_rates, _mbc[mb].tx_dsp));
591
        _tree->create<double>(mb_path / "tx_dsps/0/rate/value")
592
            .set(1e6) //some default
593
            .coerce(boost::bind(&tx_dsp_core_200::set_host_rate, _mbc[mb].tx_dsp, _1))
594
            .subscribe(boost::bind(&usrp2_impl::update_tx_samp_rate, this, mb, 0, _1));
595
        _tree->create<double>(mb_path / "tx_dsps/0/freq/value")
596
            .coerce(boost::bind(&usrp2_impl::set_tx_dsp_freq, this, mb, _1));
597
        _tree->create<meta_range_t>(mb_path / "tx_dsps/0/freq/range")
598
            .publish(boost::bind(&usrp2_impl::get_tx_dsp_freq_range, this, mb));
599

    
600
        //setup dsp flow control
601
        const double ups_per_sec = device_args_i.cast<double>("ups_per_sec", 20);
602
        const size_t send_frame_size = _mbc[mb].tx_dsp_xport->get_send_frame_size();
603
        const double ups_per_fifo = device_args_i.cast<double>("ups_per_fifo", 8.0);
604
        _mbc[mb].tx_dsp->set_updates(
605
            (ups_per_sec > 0.0)? size_t(100e6/*approx tick rate*//ups_per_sec) : 0,
606
            (ups_per_fifo > 0.0)? size_t(USRP2_SRAM_BYTES/ups_per_fifo/send_frame_size) : 0
607
        );
608

    
609
        ////////////////////////////////////////////////////////////////
610
        // create time control objects
611
        ////////////////////////////////////////////////////////////////
612
        time64_core_200::readback_bases_type time64_rb_bases;
613
        time64_rb_bases.rb_hi_now = U2_REG_TIME64_HI_RB_IMM;
614
        time64_rb_bases.rb_lo_now = U2_REG_TIME64_LO_RB_IMM;
615
        time64_rb_bases.rb_hi_pps = U2_REG_TIME64_HI_RB_PPS;
616
        time64_rb_bases.rb_lo_pps = U2_REG_TIME64_LO_RB_PPS;
617
        _mbc[mb].time64 = time64_core_200::make(
618
            _mbc[mb].wbiface, U2_REG_SR_ADDR(SR_TIME64), time64_rb_bases, mimo_clock_sync_delay_cycles
619
        );
620
        _tree->access<double>(mb_path / "tick_rate")
621
            .subscribe(boost::bind(&time64_core_200::set_tick_rate, _mbc[mb].time64, _1));
622
        _tree->create<time_spec_t>(mb_path / "time/now")
623
            .publish(boost::bind(&time64_core_200::get_time_now, _mbc[mb].time64))
624
            .subscribe(boost::bind(&time64_core_200::set_time_now, _mbc[mb].time64, _1));
625
        _tree->create<time_spec_t>(mb_path / "time/pps")
626
            .publish(boost::bind(&time64_core_200::get_time_last_pps, _mbc[mb].time64))
627
            .subscribe(boost::bind(&time64_core_200::set_time_next_pps, _mbc[mb].time64, _1));
628
        //setup time source props
629
        _tree->create<std::string>(mb_path / "time_source/value")
630
            .subscribe(boost::bind(&time64_core_200::set_time_source, _mbc[mb].time64, _1));
631
        _tree->create<std::vector<std::string> >(mb_path / "time_source/options")
632
            .publish(boost::bind(&time64_core_200::get_time_sources, _mbc[mb].time64));
633
        //setup reference source props
634
        _tree->create<std::string>(mb_path / "clock_source/value")
635
            .subscribe(boost::bind(&usrp2_impl::update_clock_source, this, mb, _1));
636
        std::vector<std::string> clock_sources = boost::assign::list_of("internal")("external")("mimo");
637
        if (_mbc[mb].gps and _mbc[mb].gps->gps_detected()) clock_sources.push_back("gpsdo");
638
        _tree->create<std::vector<std::string> >(mb_path / "clock_source/options").set(clock_sources);
639
        //plug timed commands into tree here
640
        switch(_mbc[mb].iface->get_rev()){
641
        case usrp2_iface::USRP_N200:
642
        case usrp2_iface::USRP_N210:
643
        case usrp2_iface::USRP_N200_R4:
644
        case usrp2_iface::USRP_N210_R4:
645
            _tree->create<time_spec_t>(mb_path / "time/cmd")
646
                .subscribe(boost::bind(&usrp2_fifo_ctrl::set_time, _mbc[mb].fifo_ctrl, _1));
647
        default: break; //otherwise, do not register
648
        }
649
        _tree->access<double>(mb_path / "tick_rate")
650
            .subscribe(boost::bind(&usrp2_fifo_ctrl::set_tick_rate, _mbc[mb].fifo_ctrl, _1));
651

    
652
        ////////////////////////////////////////////////////////////////////
653
        // create user-defined control objects
654
        ////////////////////////////////////////////////////////////////////
655
        _mbc[mb].user = user_settings_core_200::make(_mbc[mb].wbiface, U2_REG_SR_ADDR(SR_USER_REGS));
656
        _tree->create<user_settings_core_200::user_reg_t>(mb_path / "user/regs")
657
            .subscribe(boost::bind(&user_settings_core_200::set_reg, _mbc[mb].user, _1));
658

    
659
        ////////////////////////////////////////////////////////////////
660
        // create dboard control objects
661
        ////////////////////////////////////////////////////////////////
662

    
663
        //read the dboard eeprom to extract the dboard ids
664
        dboard_eeprom_t rx_db_eeprom, tx_db_eeprom, gdb_eeprom;
665
        rx_db_eeprom.load(*_mbc[mb].iface, USRP2_I2C_ADDR_RX_DB);
666
        tx_db_eeprom.load(*_mbc[mb].iface, USRP2_I2C_ADDR_TX_DB);
667
        gdb_eeprom.load(*_mbc[mb].iface, USRP2_I2C_ADDR_TX_DB ^ 5);
668

    
669
        //create the properties and register subscribers
670
        _tree->create<dboard_eeprom_t>(mb_path / "dboards/A/rx_eeprom")
671
            .set(rx_db_eeprom)
672
            .subscribe(boost::bind(&usrp2_impl::set_db_eeprom, this, mb, "rx", _1));
673
        _tree->create<dboard_eeprom_t>(mb_path / "dboards/A/tx_eeprom")
674
            .set(tx_db_eeprom)
675
            .subscribe(boost::bind(&usrp2_impl::set_db_eeprom, this, mb, "tx", _1));
676
        _tree->create<dboard_eeprom_t>(mb_path / "dboards/A/gdb_eeprom")
677
            .set(gdb_eeprom)
678
            .subscribe(boost::bind(&usrp2_impl::set_db_eeprom, this, mb, "gdb", _1));
679

    
680
        //create a new dboard interface and manager
681
        _mbc[mb].dboard_iface = make_usrp2_dboard_iface(_mbc[mb].wbiface, _mbc[mb].iface/*i2c*/, _mbc[mb].spiface, _mbc[mb].clock);
682
        _tree->create<dboard_iface::sptr>(mb_path / "dboards/A/iface").set(_mbc[mb].dboard_iface);
683
        _mbc[mb].dboard_manager = dboard_manager::make(
684
            rx_db_eeprom.id, tx_db_eeprom.id, gdb_eeprom.id,
685
            _mbc[mb].dboard_iface, _tree->subtree(mb_path / "dboards/A")
686
        );
687

    
688
        //bind frontend corrections to the dboard freq props
689
        const fs_path db_tx_fe_path = mb_path / "dboards" / "A" / "tx_frontends";
690
        BOOST_FOREACH(const std::string &name, _tree->list(db_tx_fe_path)){
691
            _tree->access<double>(db_tx_fe_path / name / "freq" / "value")
692
                .subscribe(boost::bind(&usrp2_impl::set_tx_fe_corrections, this, mb, _1));
693
        }
694
        const fs_path db_rx_fe_path = mb_path / "dboards" / "A" / "rx_frontends";
695
        BOOST_FOREACH(const std::string &name, _tree->list(db_rx_fe_path)){
696
            _tree->access<double>(db_rx_fe_path / name / "freq" / "value")
697
                .subscribe(boost::bind(&usrp2_impl::set_rx_fe_corrections, this, mb, _1));
698
        }
699
    }
700

    
701
    //initialize io handling
702
    this->io_init();
703

    
704
    //do some post-init tasks
705
    this->update_rates();
706
    BOOST_FOREACH(const std::string &mb, _mbc.keys()){
707
        fs_path root = "/mboards/" + mb;
708

    
709
        //reset cordic rates and their properties to zero
710
        BOOST_FOREACH(const std::string &name, _tree->list(root / "rx_dsps")){
711
            _tree->access<double>(root / "rx_dsps" / name / "freq" / "value").set(0.0);
712
        }
713
        BOOST_FOREACH(const std::string &name, _tree->list(root / "tx_dsps")){
714
            _tree->access<double>(root / "tx_dsps" / name / "freq" / "value").set(0.0);
715
        }
716

    
717
        _tree->access<subdev_spec_t>(root / "rx_subdev_spec").set(subdev_spec_t("A:" + _tree->list(root / "dboards/A/rx_frontends").at(0)));
718
        _tree->access<subdev_spec_t>(root / "tx_subdev_spec").set(subdev_spec_t("A:" + _tree->list(root / "dboards/A/tx_frontends").at(0)));
719
        _tree->access<std::string>(root / "clock_source/value").set("internal");
720
        _tree->access<std::string>(root / "time_source/value").set("none");
721

    
722
        //GPS installed: use external ref, time, and init time spec
723
        if (_mbc[mb].gps and _mbc[mb].gps->gps_detected()){
724
            _mbc[mb].time64->enable_gpsdo();
725
            UHD_MSG(status) << "Setting references to the internal GPSDO" << std::endl;
726
            _tree->access<std::string>(root / "time_source/value").set("gpsdo");
727
            _tree->access<std::string>(root / "clock_source/value").set("gpsdo");
728
            UHD_MSG(status) << "Initializing time to the internal GPSDO" << std::endl;
729
            _mbc[mb].time64->set_time_next_pps(time_spec_t(time_t(_mbc[mb].gps->get_sensor("gps_time").to_int()+1)));
730
        }
731
    }
732

    
733
}
734

    
735
usrp2_impl::~usrp2_impl(void){UHD_SAFE_CALL(
736
    BOOST_FOREACH(const std::string &mb, _mbc.keys()){
737
        _mbc[mb].tx_dsp->set_updates(0, 0);
738
    }
739
)}
740

    
741
void usrp2_impl::set_mb_eeprom(const std::string &mb, const uhd::usrp::mboard_eeprom_t &mb_eeprom){
742
    mb_eeprom.commit(*(_mbc[mb].iface), USRP2_EEPROM_MAP_KEY);
743
}
744

    
745
void usrp2_impl::set_db_eeprom(const std::string &mb, const std::string &type, const uhd::usrp::dboard_eeprom_t &db_eeprom){
746
    if (type == "rx") db_eeprom.store(*_mbc[mb].iface, USRP2_I2C_ADDR_RX_DB);
747
    if (type == "tx") db_eeprom.store(*_mbc[mb].iface, USRP2_I2C_ADDR_TX_DB);
748
    if (type == "gdb") db_eeprom.store(*_mbc[mb].iface, USRP2_I2C_ADDR_TX_DB ^ 5);
749
}
750

    
751
sensor_value_t usrp2_impl::get_mimo_locked(const std::string &mb){
752
    const bool lock = (_mbc[mb].wbiface->peek32(U2_REG_IRQ_RB) & (1<<10)) != 0;
753
    return sensor_value_t("MIMO", lock, "locked", "unlocked");
754
}
755

    
756
sensor_value_t usrp2_impl::get_ref_locked(const std::string &mb){
757
    const bool lock = (_mbc[mb].wbiface->peek32(U2_REG_IRQ_RB) & (1<<11)) != 0;
758
    return sensor_value_t("Ref", lock, "locked", "unlocked");
759
}
760

    
761
void usrp2_impl::set_rx_fe_corrections(const std::string &mb, const double lo_freq){
762
    apply_rx_fe_corrections(this->get_tree()->subtree("/mboards/" + mb), "A", lo_freq);
763
}
764

    
765
void usrp2_impl::set_tx_fe_corrections(const std::string &mb, const double lo_freq){
766
    apply_tx_fe_corrections(this->get_tree()->subtree("/mboards/" + mb), "A", lo_freq);
767
}
768

    
769
#include <boost/math/special_functions/round.hpp>
770
#include <boost/math/special_functions/sign.hpp>
771

    
772
double usrp2_impl::set_tx_dsp_freq(const std::string &mb, const double freq_){
773
    double new_freq = freq_;
774
    const double tick_rate = _tree->access<double>("/mboards/"+mb+"/tick_rate").get();
775

    
776
    //calculate the DAC shift (multiples of rate)
777
    const int sign = boost::math::sign(new_freq);
778
    const int zone = std::min(boost::math::iround(new_freq/tick_rate), 2);
779
    const double dac_shift = sign*zone*tick_rate;
780
    new_freq -= dac_shift; //update FPGA DSP target freq
781

    
782
    //set the DAC shift (modulation mode)
783
    if (zone == 0) _mbc[mb].codec->set_tx_mod_mode(0); //no shift
784
    else _mbc[mb].codec->set_tx_mod_mode(sign*4/zone); //DAC interp = 4
785

    
786
    return _mbc[mb].tx_dsp->set_freq(new_freq) + dac_shift; //actual freq
787
}
788

    
789
meta_range_t usrp2_impl::get_tx_dsp_freq_range(const std::string &mb){
790
    const double tick_rate = _tree->access<double>("/mboards/"+mb+"/tick_rate").get();
791
    const meta_range_t dsp_range = _mbc[mb].tx_dsp->get_freq_range();
792
    return meta_range_t(dsp_range.start() - tick_rate*2, dsp_range.stop() + tick_rate*2, dsp_range.step());
793
}
794

    
795
void usrp2_impl::update_clock_source(const std::string &mb, const std::string &source){
796
    //NOTICE: U2_REG_MISC_CTRL_CLOCK is on the wb clock, and cannot be set from fifo_ctrl
797
    //clock source ref 10mhz
798
    switch(_mbc[mb].iface->get_rev()){
799
    case usrp2_iface::USRP_N200:
800
    case usrp2_iface::USRP_N210:
801
    case usrp2_iface::USRP_N200_R4:
802
    case usrp2_iface::USRP_N210_R4:
803
        if      (source == "internal")  _mbc[mb].iface->poke32(U2_REG_MISC_CTRL_CLOCK, 0x12);
804
        else if (source == "external")  _mbc[mb].iface->poke32(U2_REG_MISC_CTRL_CLOCK, 0x1C);
805
        else if (source == "gpsdo")     _mbc[mb].iface->poke32(U2_REG_MISC_CTRL_CLOCK, 0x1C);
806
        else if (source == "mimo")      _mbc[mb].iface->poke32(U2_REG_MISC_CTRL_CLOCK, 0x15);
807
        else throw uhd::value_error("unhandled clock configuration reference source: " + source);
808
        _mbc[mb].clock->enable_external_ref(true); //USRP2P has an internal 10MHz TCXO
809
        break;
810

    
811
    case usrp2_iface::USRP2_REV3:
812
    case usrp2_iface::USRP2_REV4:
813
        if      (source == "internal")  _mbc[mb].iface->poke32(U2_REG_MISC_CTRL_CLOCK, 0x10);
814
        else if (source == "external")  _mbc[mb].iface->poke32(U2_REG_MISC_CTRL_CLOCK, 0x1C);
815
        else if (source == "mimo")      _mbc[mb].iface->poke32(U2_REG_MISC_CTRL_CLOCK, 0x15);
816
        else throw uhd::value_error("unhandled clock configuration reference source: " + source);
817
        _mbc[mb].clock->enable_external_ref(source != "internal");
818
        break;
819

    
820
    case usrp2_iface::USRP_NXXX: break;
821
    }
822

    
823
    //always drive the clock over serdes if not locking to it
824
    _mbc[mb].clock->enable_mimo_clock_out(source != "mimo");
825

    
826
    //set the mimo clock delay over the serdes
827
    if (source != "mimo"){
828
        switch(_mbc[mb].iface->get_rev()){
829
        case usrp2_iface::USRP_N200:
830
        case usrp2_iface::USRP_N210:
831
        case usrp2_iface::USRP_N200_R4:
832
        case usrp2_iface::USRP_N210_R4:
833
            _mbc[mb].clock->set_mimo_clock_delay(mimo_clock_delay_usrp_n2xx);
834
            break;
835

    
836
        case usrp2_iface::USRP2_REV4:
837
            _mbc[mb].clock->set_mimo_clock_delay(mimo_clock_delay_usrp2_rev4);
838
            break;
839

    
840
        default: break; //not handled
841
        }
842
    }
843
}