aboutsummaryrefslogtreecommitdiffstats
path: root/host/lib/usrp/b200/b200_impl.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'host/lib/usrp/b200/b200_impl.cpp')
-rw-r--r--host/lib/usrp/b200/b200_impl.cpp630
1 files changed, 412 insertions, 218 deletions
diff --git a/host/lib/usrp/b200/b200_impl.cpp b/host/lib/usrp/b200/b200_impl.cpp
index 0409adf30..6bd49e013 100644
--- a/host/lib/usrp/b200/b200_impl.cpp
+++ b/host/lib/usrp/b200/b200_impl.cpp
@@ -1,5 +1,5 @@
//
-// Copyright 2012-2014 Ettus Research LLC
+// Copyright 2012-2015 Ettus Research LLC
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
@@ -37,20 +37,23 @@
#include <ctime>
#include <cmath>
+#include "../../transport/libusb1_base.hpp"
+
using namespace uhd;
using namespace uhd::usrp;
using namespace uhd::transport;
static const boost::posix_time::milliseconds REENUMERATION_TIMEOUT_MS(3000);
+// B200 + B210:
class b200_ad9361_client_t : public ad9361_params {
public:
~b200_ad9361_client_t() {}
double get_band_edge(frequency_band_t band) {
switch (band) {
- case AD9361_RX_BAND0: return 2.2e9;
- case AD9361_RX_BAND1: return 4.0e9;
- case AD9361_TX_BAND0: return 2.5e9;
+ case AD9361_RX_BAND0: return 2.2e9; // Port C
+ case AD9361_RX_BAND1: return 4.0e9; // Port B
+ case AD9361_TX_BAND0: return 2.5e9; // Port B
default: return 0;
}
}
@@ -70,25 +73,92 @@ public:
}
};
+// B205
+class b205_ad9361_client_t : public ad9361_params {
+public:
+ ~b205_ad9361_client_t() {}
+ double get_band_edge(frequency_band_t band) {
+ switch (band) {
+ case AD9361_RX_BAND0: return 0; // Set these all to
+ case AD9361_RX_BAND1: return 0; // zero, so RF port A
+ case AD9361_TX_BAND0: return 0; // is used all the time
+ default: return 0; // On both Rx and Tx
+ }
+ }
+ clocking_mode_t get_clocking_mode() {
+ return AD9361_XTAL_N_CLK_PATH;
+ }
+ digital_interface_mode_t get_digital_interface_mode() {
+ return AD9361_DDR_FDD_LVCMOS;
+ }
+ digital_interface_delays_t get_digital_interface_timing() {
+ digital_interface_delays_t delays;
+ delays.rx_clk_delay = 0;
+ delays.rx_data_delay = 0x6;
+ delays.tx_clk_delay = 0;
+ delays.tx_data_delay = 0xF;
+ return delays;
+ }
+};
+
+/***********************************************************************
+ * Helpers
+ **********************************************************************/
+std::string check_option_valid(
+ const std::string &name,
+ const std::vector<std::string> &valid_options,
+ const std::string &option
+) {
+ if (std::find(valid_options.begin(), valid_options.end(), option) == valid_options.end()) {
+ throw uhd::runtime_error(str(
+ boost::format("Invalid option chosen for: %s")
+ % name
+ ));
+ }
+
+ return option;
+}
+
/***********************************************************************
* Discovery
**********************************************************************/
//! Look up the type of B-Series device we're currently running.
-// If the product ID stored in mb_eeprom is invalid, throws a
-// uhd::runtime_error.
-static b200_type_t get_b200_type(const mboard_eeprom_t &mb_eeprom)
+// Throws a uhd::runtime_error if the USB PID and the product ID stored
+// in the MB EEPROM are invalid,
+b200_product_t get_b200_product(const usb_device_handle::sptr& handle, const mboard_eeprom_t &mb_eeprom)
{
+ // Try USB PID first
+ boost::uint16_t product_id = handle->get_product_id();
+ if (B2XX_PID_TO_PRODUCT.has_key(product_id))
+ return B2XX_PID_TO_PRODUCT[product_id];
+
+ // Try EEPROM product ID code second
if (mb_eeprom["product"].empty()) {
throw uhd::runtime_error("B200: Missing product ID on EEPROM.");
}
- boost::uint16_t product_id = boost::lexical_cast<boost::uint16_t>(mb_eeprom["product"]);
- if (not B2X0_PRODUCT_ID.has_key(product_id)) {
+ product_id = boost::lexical_cast<boost::uint16_t>(mb_eeprom["product"]);
+ if (not B2XX_PRODUCT_ID.has_key(product_id)) {
throw uhd::runtime_error(str(
boost::format("B200 unknown product code: 0x%04x")
% product_id
));
}
- return B2X0_PRODUCT_ID[product_id];
+ return B2XX_PRODUCT_ID[product_id];
+}
+
+std::vector<usb_device_handle::sptr> get_b200_device_handles(const device_addr_t &hint)
+{
+ std::vector<usb_device_handle::vid_pid_pair_t> vid_pid_pair_list;
+
+ if(hint.has_key("vid") && hint.has_key("pid") && hint.has_key("type") && hint["type"] == "b200") {
+ vid_pid_pair_list.push_back(usb_device_handle::vid_pid_pair_t(uhd::cast::hexstr_cast<boost::uint16_t>(hint.get("vid")),
+ uhd::cast::hexstr_cast<boost::uint16_t>(hint.get("pid"))));
+ } else {
+ vid_pid_pair_list = b200_vid_pid_pairs;
+ }
+
+ //find the usrps and load firmware
+ return usb_device_handle::get_device_list(vid_pid_pair_list);
}
static device_addrs_t b200_find(const device_addr_t &hint)
@@ -104,25 +174,13 @@ static device_addrs_t b200_find(const device_addr_t &hint)
if (hint_i.has_key("addr") || hint_i.has_key("resource")) return b200_addrs;
}
- boost::uint16_t vid, pid;
-
- if(hint.has_key("vid") && hint.has_key("pid") && hint.has_key("type") && hint["type"] == "b200") {
- vid = uhd::cast::hexstr_cast<boost::uint16_t>(hint.get("vid"));
- pid = uhd::cast::hexstr_cast<boost::uint16_t>(hint.get("pid"));
- } else {
- vid = B200_VENDOR_ID;
- pid = B200_PRODUCT_ID;
- }
-
// Important note:
// The get device list calls are nested inside the for loop.
// This allows the usb guts to decontruct when not in use,
// so that re-enumeration after fw load can occur successfully.
// This requirement is a courtesy of libusb1.0 on windows.
-
- //find the usrps and load firmware
size_t found = 0;
- BOOST_FOREACH(usb_device_handle::sptr handle, usb_device_handle::get_device_list(vid, pid)) {
+ BOOST_FOREACH(usb_device_handle::sptr handle, get_b200_device_handles(hint)) {
//extract the firmware path for the b200
std::string b200_fw_image;
try{
@@ -153,7 +211,7 @@ static device_addrs_t b200_find(const device_addr_t &hint)
//search for the device until found or timeout
while (boost::get_system_time() < timeout_time and b200_addrs.empty() and found != 0)
{
- BOOST_FOREACH(usb_device_handle::sptr handle, usb_device_handle::get_device_list(vid, pid))
+ BOOST_FOREACH(usb_device_handle::sptr handle, get_b200_device_handles(hint))
{
usb_control::sptr control;
try{control = usb_control::make(handle, 0);}
@@ -168,9 +226,10 @@ static device_addrs_t b200_find(const device_addr_t &hint)
new_addr["serial"] = handle->get_serial();
try {
// Turn the 16-Bit product ID into a string representation
- new_addr["product"] = B2X0_STR_NAMES[get_b200_type(mb_eeprom)];
+ new_addr["product"] = B2XX_STR_NAMES[get_b200_product(handle, mb_eeprom)];
} catch (const uhd::runtime_error &e) {
// No problem if this fails -- this is just device discovery, after all.
+ new_addr["product"] = "B2??";
}
//this is a found b200 when the hint serial and name match or blank
@@ -191,7 +250,24 @@ static device_addrs_t b200_find(const device_addr_t &hint)
**********************************************************************/
static device::sptr b200_make(const device_addr_t &device_addr)
{
- return device::sptr(new b200_impl(device_addr));
+ uhd::transport::usb_device_handle::sptr handle;
+
+ // We try twice, because the first time, the link might be in a bad state
+ // and we might need to reset the link, but if that didn't help, trying
+ // a third time is pointless.
+ try {
+ return device::sptr(new b200_impl(device_addr, handle));
+ }
+ catch (const uhd::usb_error &e) {
+ libusb::device_handle::sptr dev_handle(libusb::device_handle::get_cached_handle(
+ boost::static_pointer_cast<libusb::special_handle>(handle)->get_device()
+ ));
+ dev_handle->clear_endpoints(B200_USB_CTRL_RECV_ENDPOINT, B200_USB_CTRL_SEND_ENDPOINT);
+ dev_handle->clear_endpoints(B200_USB_DATA_RECV_ENDPOINT, B200_USB_DATA_SEND_ENDPOINT);
+ dev_handle->reset_device();
+ }
+
+ return device::sptr(new b200_impl(device_addr, handle));
}
UHD_STATIC_BLOCK(register_b200_device)
@@ -202,8 +278,10 @@ UHD_STATIC_BLOCK(register_b200_device)
/***********************************************************************
* Structors
**********************************************************************/
-b200_impl::b200_impl(const device_addr_t &device_addr) :
+b200_impl::b200_impl(const uhd::device_addr_t& device_addr, usb_device_handle::sptr &handle) :
+ _product(B200), // Some safe value
_revision(0),
+ _time_source(UNKNOWN),
_tick_rate(0.0) // Forces a clock initialization at startup
{
_tree = property_tree::make();
@@ -213,16 +291,54 @@ b200_impl::b200_impl(const device_addr_t &device_addr) :
//try to match the given device address with something on the USB bus
boost::uint16_t vid = B200_VENDOR_ID;
boost::uint16_t pid = B200_PRODUCT_ID;
+ bool specified_vid = false;
+ bool specified_pid = false;
+
if (device_addr.has_key("vid"))
+ {
vid = uhd::cast::hexstr_cast<boost::uint16_t>(device_addr.get("vid"));
+ specified_vid = true;
+ }
+
if (device_addr.has_key("pid"))
+ {
pid = uhd::cast::hexstr_cast<boost::uint16_t>(device_addr.get("pid"));
+ specified_pid = true;
+ }
- std::vector<usb_device_handle::sptr> device_list =
- usb_device_handle::get_device_list(vid, pid);
+ std::vector<usb_device_handle::vid_pid_pair_t> vid_pid_pair_list;//search list for devices.
+
+ // Search only for specified VID and PID if both specified
+ if (specified_vid && specified_pid)
+ {
+ vid_pid_pair_list.push_back(usb_device_handle::vid_pid_pair_t(vid,pid));
+ }
+ // Search for all supported PIDs limited to specified VID if only VID specified
+ else if (specified_vid)
+ {
+ vid_pid_pair_list.push_back(usb_device_handle::vid_pid_pair_t(vid, B200_PRODUCT_ID));
+ vid_pid_pair_list.push_back(usb_device_handle::vid_pid_pair_t(vid, B205_PRODUCT_ID));
+ vid_pid_pair_list.push_back(usb_device_handle::vid_pid_pair_t(vid, B200_PRODUCT_NI_ID));
+ vid_pid_pair_list.push_back(usb_device_handle::vid_pid_pair_t(vid, B210_PRODUCT_NI_ID));
+ }
+ // Search for all supported VIDs limited to specified PID if only PID specified
+ else if (specified_pid)
+ {
+ vid_pid_pair_list.push_back(usb_device_handle::vid_pid_pair_t(B200_VENDOR_ID,pid));
+ vid_pid_pair_list.push_back(usb_device_handle::vid_pid_pair_t(B200_VENDOR_NI_ID,pid));
+ }
+ // Search for all supported devices if neither VID nor PID specified
+ else
+ {
+ vid_pid_pair_list.push_back(usb_device_handle::vid_pid_pair_t(B200_VENDOR_ID, B200_PRODUCT_ID));
+ vid_pid_pair_list.push_back(usb_device_handle::vid_pid_pair_t(B200_VENDOR_ID, B205_PRODUCT_ID));
+ vid_pid_pair_list.push_back(usb_device_handle::vid_pid_pair_t(B200_VENDOR_NI_ID, B200_PRODUCT_NI_ID));
+ vid_pid_pair_list.push_back(usb_device_handle::vid_pid_pair_t(B200_VENDOR_NI_ID, B210_PRODUCT_NI_ID));
+ }
+
+ std::vector<usb_device_handle::sptr> device_list = usb_device_handle::get_device_list(vid_pid_pair_list);
//locate the matching handle in the device list
- usb_device_handle::sptr handle;
BOOST_FOREACH(usb_device_handle::sptr dev_handle, device_list) {
if (dev_handle->get_serial() == device_addr["serial"]){
handle = dev_handle;
@@ -251,9 +367,9 @@ b200_impl::b200_impl(const device_addr_t &device_addr) :
std::string product_name;
try {
// This will throw if the product ID is invalid:
- _b200_type = get_b200_type(mb_eeprom);
- default_file_name = B2X0_FPGA_FILE_NAME.get(_b200_type);
- product_name = B2X0_STR_NAMES.get(_b200_type);
+ _product = get_b200_product(handle, mb_eeprom);
+ default_file_name = B2XX_FPGA_FILE_NAME.get(_product);
+ product_name = B2XX_STR_NAMES.get(_product);
} catch (const uhd::runtime_error &e) {
// The only reason we may let this pass is if the user specified
// the FPGA file name:
@@ -262,12 +378,15 @@ b200_impl::b200_impl(const device_addr_t &device_addr) :
}
// In this case, we must provide a default product name:
product_name = "B200?";
- _b200_type = B200;
}
if (not mb_eeprom["revision"].empty()) {
_revision = boost::lexical_cast<size_t>(mb_eeprom["revision"]);
}
+ UHD_MSG(status) << "Detected Device: " << B2XX_STR_NAMES[_product] << std::endl;
+
+ _gpsdo_capable = (_product != B205);
+
////////////////////////////////////////////////////////////////////
// Set up frontend mapping
////////////////////////////////////////////////////////////////////
@@ -285,7 +404,7 @@ b200_impl::b200_impl(const device_addr_t &device_addr) :
_fe2 = 0;
_gpio_state.swap_atr = 1;
// Unswapped setup:
- if (_b200_type == B200 and _revision >= 5) {
+ if (_product == B205 or (_product == B200 and _revision >= 5)) {
_fe1 = 0; //map radio0 to FE1
_fe2 = 1; //map radio1 to FE2
_gpio_state.swap_atr = 0; // ATRs for radio0 are mapped to FE1
@@ -320,10 +439,11 @@ b200_impl::b200_impl(const device_addr_t &device_addr) :
ctrl_xport_args["send_frame_size"] = min_frame_size;
ctrl_xport_args["num_send_frames"] = "16";
+ // This may throw a uhd::usb_error, which will be caught by b200_make().
_ctrl_transport = usb_zero_copy::make(
handle,
- 4, 8, //interface, endpoint
- 3, 4, //interface, endpoint
+ B200_USB_CTRL_RECV_INTERFACE, B200_USB_CTRL_RECV_ENDPOINT, //interface, endpoint
+ B200_USB_CTRL_SEND_INTERFACE, B200_USB_CTRL_SEND_ENDPOINT, //interface, endpoint
ctrl_xport_args
);
while (_ctrl_transport->get_recv_buff(0.0)){} //flush ctrl xport
@@ -352,34 +472,37 @@ b200_impl::b200_impl(const device_addr_t &device_addr) :
////////////////////////////////////////////////////////////////////
// Create the GPSDO control
////////////////////////////////////////////////////////////////////
- _async_task_data->gpsdo_uart = b200_uart::make(_ctrl_transport, B200_TX_GPS_UART_SID);
- _async_task_data->gpsdo_uart->set_baud_divider(B200_BUS_CLOCK_RATE/115200);
- _async_task_data->gpsdo_uart->write_uart("\n"); //cause the baud and response to be setup
- boost::this_thread::sleep(boost::posix_time::seconds(1)); //allow for a little propagation
-
- if ((_local_ctrl->peek32(RB32_CORE_STATUS) & 0xff) != B200_GPSDO_ST_NONE)
+ if (_gpsdo_capable)
{
- UHD_MSG(status) << "Detecting internal GPSDO.... " << std::flush;
- try
- {
- _gps = gps_ctrl::make(_async_task_data->gpsdo_uart);
- }
- catch(std::exception &e)
- {
- UHD_MSG(error) << "An error occurred making GPSDO control: " << e.what() << std::endl;
- }
- if (_gps and _gps->gps_detected())
+ _async_task_data->gpsdo_uart = b200_uart::make(_ctrl_transport, B200_TX_GPS_UART_SID);
+ _async_task_data->gpsdo_uart->set_baud_divider(B200_BUS_CLOCK_RATE/115200);
+ _async_task_data->gpsdo_uart->write_uart("\n"); //cause the baud and response to be setup
+ boost::this_thread::sleep(boost::posix_time::seconds(1)); //allow for a little propagation
+
+ if ((_local_ctrl->peek32(RB32_CORE_STATUS) & 0xff) != B200_GPSDO_ST_NONE)
{
- //UHD_MSG(status) << "found" << std::endl;
- BOOST_FOREACH(const std::string &name, _gps->get_sensors())
+ UHD_MSG(status) << "Detecting internal GPSDO.... " << std::flush;
+ try
{
- _tree->create<sensor_value_t>(mb_path / "sensors" / name)
- .publish(boost::bind(&gps_ctrl::get_sensor, _gps, name));
+ _gps = gps_ctrl::make(_async_task_data->gpsdo_uart);
+ }
+ catch(std::exception &e)
+ {
+ UHD_MSG(error) << "An error occurred making GPSDO control: " << e.what() << std::endl;
+ }
+ if (_gps and _gps->gps_detected())
+ {
+ //UHD_MSG(status) << "found" << std::endl;
+ BOOST_FOREACH(const std::string &name, _gps->get_sensors())
+ {
+ _tree->create<sensor_value_t>(mb_path / "sensors" / name)
+ .publish(boost::bind(&gps_ctrl::get_sensor, _gps, name));
+ }
+ }
+ else
+ {
+ _local_ctrl->poke32(TOREG(SR_CORE_GPSDO_ST), B200_GPSDO_ST_NONE);
}
- }
- else
- {
- _local_ctrl->poke32(TOREG(SR_CORE_GPSDO_ST), B200_GPSDO_ST_NONE);
}
}
@@ -388,7 +511,7 @@ b200_impl::b200_impl(const device_addr_t &device_addr) :
////////////////////////////////////////////////////////////////////
_tree->create<std::string>("/name").set("B-Series Device");
_tree->create<std::string>(mb_path / "name").set(product_name);
- _tree->create<std::string>(mb_path / "codename").set("Sasquatch");
+ _tree->create<std::string>(mb_path / "codename").set((_product == B205) ? "Pixie" : "Sasquatch");
////////////////////////////////////////////////////////////////////
// Create data transport
@@ -402,10 +525,11 @@ b200_impl::b200_impl(const device_addr_t &device_addr) :
data_xport_args["send_frame_size"] = device_addr.get("send_frame_size", "8192");
data_xport_args["num_send_frames"] = device_addr.get("num_send_frames", "16");
+ // This may throw a uhd::usb_error, which will be caught by b200_make().
_data_transport = usb_zero_copy::make(
handle, // identifier
- 2, 6, // IN interface, endpoint
- 1, 2, // OUT interface, endpoint
+ B200_USB_DATA_RECV_INTERFACE, B200_USB_DATA_RECV_ENDPOINT, //interface, endpoint
+ B200_USB_DATA_SEND_INTERFACE, B200_USB_DATA_SEND_ENDPOINT, //interface, endpoint
data_xport_args // param hints
);
while (_data_transport->get_recv_buff(0.0)){} //flush ctrl xport
@@ -415,13 +539,20 @@ b200_impl::b200_impl(const device_addr_t &device_addr) :
// create time and clock control objects
////////////////////////////////////////////////////////////////////
_spi_iface = b200_local_spi_core::make(_local_ctrl);
- _adf4001_iface = boost::make_shared<b200_ref_pll_ctrl>(_spi_iface);
+ if (_product != B205) {
+ _adf4001_iface = boost::make_shared<b200_ref_pll_ctrl>(_spi_iface);
+ }
////////////////////////////////////////////////////////////////////
// Init codec - turns on clocks
////////////////////////////////////////////////////////////////////
UHD_MSG(status) << "Initialize CODEC control..." << std::endl;
- ad9361_params::sptr client_settings = boost::make_shared<b200_ad9361_client_t>();
+ ad9361_params::sptr client_settings;
+ if (_product == B205) {
+ client_settings = boost::make_shared<b205_ad9361_client_t>();
+ } else {
+ client_settings = boost::make_shared<b200_ad9361_client_t>();
+ }
_codec_ctrl = ad9361_ctrl::make_spi(client_settings, _spi_iface, AD9361_SLAVENO);
this->reset_codec_dcm();
@@ -447,6 +578,7 @@ b200_impl::b200_impl(const device_addr_t &device_addr) :
.publish(boost::bind(&b200_impl::get_tick_rate, this))
.subscribe(boost::bind(&b200_impl::update_tick_rate, this, _1));
_tree->create<time_spec_t>(mb_path / "time" / "cmd");
+ _tree->create<bool>(mb_path / "auto_tick_rate").set(false);
////////////////////////////////////////////////////////////////////
// and do the misc mboard sensors
@@ -477,15 +609,17 @@ b200_impl::b200_impl(const device_addr_t &device_addr) :
UHD_ASSERT_THROW(num_radio_chains > 0);
UHD_ASSERT_THROW(num_radio_chains <= 2);
_radio_perifs.resize(num_radio_chains);
- for (size_t i = 0; i < _radio_perifs.size(); i++) this->setup_radio(i);
+ _codec_mgr = ad936x_manager::make(_codec_ctrl, num_radio_chains);
+ _codec_mgr->init_codec();
+ for (size_t i = 0; i < _radio_perifs.size(); i++)
+ this->setup_radio(i);
+
//now test each radio module's connection to the codec interface
- _codec_ctrl->data_port_loopback(true);
BOOST_FOREACH(radio_perifs_t &perif, _radio_perifs)
{
- this->codec_loopback_self_test(perif.ctrl);
+ _codec_mgr->loopback_self_test(perif.ctrl, TOREG(SR_CODEC_IDLE), RB64_CODEC_READBACK);
}
- _codec_ctrl->data_port_loopback(false);
//register time now and pps onto available radio cores
_tree->create<time_spec_t>(mb_path / "time" / "now")
@@ -501,15 +635,36 @@ b200_impl::b200_impl(const device_addr_t &device_addr) :
}
//setup time source props
+ static const std::vector<std::string> time_sources = (_gpsdo_capable) ?
+ boost::assign::list_of("none")("internal")("external")("gpsdo") :
+ boost::assign::list_of("none")("internal")("external") ;
+ _tree->create<std::vector<std::string> >(mb_path / "time_source" / "options")
+ .set(time_sources);
_tree->create<std::string>(mb_path / "time_source" / "value")
+ .coerce(boost::bind(&check_option_valid, "time source", time_sources, _1))
.subscribe(boost::bind(&b200_impl::update_time_source, this, _1));
- static const std::vector<std::string> time_sources = boost::assign::list_of("none")("internal")("external")("gpsdo");
- _tree->create<std::vector<std::string> >(mb_path / "time_source" / "options").set(time_sources);
//setup reference source props
+ static const std::vector<std::string> clock_sources = (_gpsdo_capable) ?
+ boost::assign::list_of("internal")("external")("gpsdo") :
+ boost::assign::list_of("internal")("external") ;
+ _tree->create<std::vector<std::string> >(mb_path / "clock_source" / "options")
+ .set(clock_sources);
_tree->create<std::string>(mb_path / "clock_source" / "value")
+ .coerce(boost::bind(&check_option_valid, "clock source", clock_sources, _1))
.subscribe(boost::bind(&b200_impl::update_clock_source, this, _1));
- static const std::vector<std::string> clock_sources = boost::assign::list_of("internal")("external")("gpsdo");
- _tree->create<std::vector<std::string> >(mb_path / "clock_source" / "options").set(clock_sources);
+
+ ////////////////////////////////////////////////////////////////////
+ // front panel gpio
+ ////////////////////////////////////////////////////////////////////
+ _radio_perifs[0].fp_gpio = gpio_core_200::make(_radio_perifs[0].ctrl, TOREG(SR_FP_GPIO), RB32_FP_GPIO);
+ BOOST_FOREACH(const gpio_attr_map_t::value_type attr, gpio_attr_map)
+ {
+ _tree->create<boost::uint32_t>(mb_path / "gpio" / "FP0" / attr.second)
+ .set(0)
+ .subscribe(boost::bind(&b200_impl::set_fp_gpio, this, _radio_perifs[0].fp_gpio, attr.first, _1));
+ }
+ _tree->create<boost::uint32_t>(mb_path / "gpio" / "FP0" / "READBACK")
+ .publish(boost::bind(&b200_impl::get_fp_gpio, this, _radio_perifs[0].fp_gpio));
////////////////////////////////////////////////////////////////////
// dboard eeproms but not really
@@ -524,7 +679,7 @@ b200_impl::b200_impl(const device_addr_t &device_addr) :
////////////////////////////////////////////////////////////////////
//init the clock rate to something reasonable
- double default_tick_rate = device_addr.cast<double>("master_clock_rate", B200_DEFAULT_TICK_RATE);
+ double default_tick_rate = device_addr.cast<double>("master_clock_rate", ad936x_manager::DEFAULT_TICK_RATE);
_tree->access<double>(mb_path / "tick_rate").set(default_tick_rate);
//subdev spec contains full width of selections
@@ -542,27 +697,17 @@ b200_impl::b200_impl(const device_addr_t &device_addr) :
//init to internal clock and time source
_tree->access<std::string>(mb_path / "clock_source/value").set("internal");
- _tree->access<std::string>(mb_path / "time_source/value").set("none");
+ _tree->access<std::string>(mb_path / "time_source/value").set("internal");
// Set the DSP chains to some safe value
for (size_t i = 0; i < _radio_perifs.size(); i++) {
- _radio_perifs[i].ddc->set_host_rate(default_tick_rate / B200_DEFAULT_DECIM);
- _radio_perifs[i].duc->set_host_rate(default_tick_rate / B200_DEFAULT_INTERP);
+ _radio_perifs[i].ddc->set_host_rate(default_tick_rate / ad936x_manager::DEFAULT_DECIM);
+ _radio_perifs[i].duc->set_host_rate(default_tick_rate / ad936x_manager::DEFAULT_INTERP);
}
-
- //GPS installed: use external ref, time, and init time spec
- if (_gps and _gps->gps_detected())
- {
- UHD_MSG(status) << "Setting references to the internal GPSDO" << std::endl;
- _tree->access<std::string>(mb_path / "time_source" / "value").set("gpsdo");
- _tree->access<std::string>(mb_path / "clock_source" / "value").set("gpsdo");
- UHD_MSG(status) << "Initializing time to the internal GPSDO" << std::endl;
- const time_t tp = time_t(_gps->get_sensor("gps_time").to_int()+1);
- _tree->access<time_spec_t>(mb_path / "time" / "pps").set(time_spec_t(tp));
- } else {
- //init to internal clock and time source
- _tree->access<std::string>(mb_path / "clock_source/value").set("internal");
- _tree->access<std::string>(mb_path / "time_source/value").set("internal");
+ // We can automatically choose a master clock rate, but not if the user specifies one
+ _tree->access<bool>(mb_path / "auto_tick_rate").set(not device_addr.has_key("master_clock_rate"));
+ if (not device_addr.has_key("master_clock_rate")) {
+ UHD_MSG(status) << "Setting master clock rate selection to 'automatic'." << std::endl;
}
}
@@ -585,10 +730,18 @@ void b200_impl::setup_radio(const size_t dspno)
const fs_path mb_path = "/mboards/0";
////////////////////////////////////////////////////////////////////
+ // Set up transport
+ ////////////////////////////////////////////////////////////////////
+ const boost::uint32_t sid = (dspno == 0) ? B200_CTRL0_MSG_SID : B200_CTRL1_MSG_SID;
+
+ ////////////////////////////////////////////////////////////////////
// radio control
////////////////////////////////////////////////////////////////////
- const boost::uint32_t sid = (dspno == 0)? B200_CTRL0_MSG_SID : B200_CTRL1_MSG_SID;
- perif.ctrl = radio_ctrl_core_3000::make(false/*lilE*/, _ctrl_transport, zero_copy_if::sptr()/*null*/, sid);
+ perif.ctrl = radio_ctrl_core_3000::make(
+ false/*lilE*/,
+ _ctrl_transport,
+ zero_copy_if::sptr()/*null*/,
+ sid);
perif.ctrl->hold_task(_async_task);
_async_task_data->radio_ctrl[dspno] = perif.ctrl; //weak
_tree->access<time_spec_t>(mb_path / "time" / "cmd")
@@ -596,121 +749,96 @@ void b200_impl::setup_radio(const size_t dspno)
_tree->access<double>(mb_path / "tick_rate")
.subscribe(boost::bind(&radio_ctrl_core_3000::set_tick_rate, perif.ctrl, _1));
this->register_loopback_self_test(perif.ctrl);
- perif.atr = gpio_core_200_32wo::make(perif.ctrl, TOREG(SR_ATR));
////////////////////////////////////////////////////////////////////
- // create rx dsp control objects
+ // Set up peripherals
////////////////////////////////////////////////////////////////////
+ perif.atr = gpio_core_200_32wo::make(perif.ctrl, TOREG(SR_ATR));
+ // create rx dsp control objects
perif.framer = rx_vita_core_3000::make(perif.ctrl, TOREG(SR_RX_CTRL));
perif.ddc = rx_dsp_core_3000::make(perif.ctrl, TOREG(SR_RX_DSP), true /*is_b200?*/);
perif.ddc->set_link_rate(10e9/8); //whatever
perif.ddc->set_mux("IQ", false, dspno == 1 ? true : false, dspno == 1 ? true : false);
+ perif.ddc->set_freq(rx_dsp_core_3000::DEFAULT_CORDIC_FREQ);
+ perif.deframer = tx_vita_core_3000::make(perif.ctrl, TOREG(SR_TX_CTRL));
+ perif.duc = tx_dsp_core_3000::make(perif.ctrl, TOREG(SR_TX_DSP));
+ perif.duc->set_link_rate(10e9/8); //whatever
+ perif.duc->set_freq(tx_dsp_core_3000::DEFAULT_CORDIC_FREQ);
+
+ ////////////////////////////////////////////////////////////////////
+ // create time control objects
+ ////////////////////////////////////////////////////////////////////
+ time_core_3000::readback_bases_type time64_rb_bases;
+ time64_rb_bases.rb_now = RB64_TIME_NOW;
+ time64_rb_bases.rb_pps = RB64_TIME_PPS;
+ perif.time64 = time_core_3000::make(perif.ctrl, TOREG(SR_TIME), time64_rb_bases);
+
+ ////////////////////////////////////////////////////////////////////
+ // connect rx dsp control objects
+ ////////////////////////////////////////////////////////////////////
_tree->access<double>(mb_path / "tick_rate")
.subscribe(boost::bind(&rx_vita_core_3000::set_tick_rate, perif.framer, _1))
.subscribe(boost::bind(&rx_dsp_core_3000::set_tick_rate, perif.ddc, _1));
const fs_path rx_dsp_path = mb_path / "rx_dsps" / dspno;
- _tree->create<meta_range_t>(rx_dsp_path / "rate" / "range")
- .publish(boost::bind(&rx_dsp_core_3000::get_host_rates, perif.ddc));
- _tree->create<double>(rx_dsp_path / "rate" / "value")
- .set(0.0) // We can only load a sensible value after the tick rate was set
- .coerce(boost::bind(&rx_dsp_core_3000::set_host_rate, perif.ddc, _1))
+ perif.ddc->populate_subtree(_tree->subtree(rx_dsp_path));
+ _tree->access<double>(rx_dsp_path / "rate" / "value")
+ .coerce(boost::bind(&b200_impl::coerce_rx_samp_rate, this, perif.ddc, dspno, _1))
.subscribe(boost::bind(&b200_impl::update_rx_samp_rate, this, dspno, _1))
;
- _tree->create<double>(rx_dsp_path / "freq" / "value")
- .coerce(boost::bind(&rx_dsp_core_3000::set_freq, perif.ddc, _1))
- .set(0.0);
- _tree->create<meta_range_t>(rx_dsp_path / "freq" / "range")
- .publish(boost::bind(&rx_dsp_core_3000::get_freq_range, perif.ddc));
_tree->create<stream_cmd_t>(rx_dsp_path / "stream_cmd")
.subscribe(boost::bind(&rx_vita_core_3000::issue_stream_command, perif.framer, _1));
////////////////////////////////////////////////////////////////////
// create tx dsp control objects
////////////////////////////////////////////////////////////////////
- perif.deframer = tx_vita_core_3000::make(perif.ctrl, TOREG(SR_TX_CTRL));
- perif.duc = tx_dsp_core_3000::make(perif.ctrl, TOREG(SR_TX_DSP));
- perif.duc->set_link_rate(10e9/8); //whatever
_tree->access<double>(mb_path / "tick_rate")
.subscribe(boost::bind(&tx_vita_core_3000::set_tick_rate, perif.deframer, _1))
.subscribe(boost::bind(&tx_dsp_core_3000::set_tick_rate, perif.duc, _1));
const fs_path tx_dsp_path = mb_path / "tx_dsps" / dspno;
- _tree->create<meta_range_t>(tx_dsp_path / "rate" / "range")
- .publish(boost::bind(&tx_dsp_core_3000::get_host_rates, perif.duc));
- _tree->create<double>(tx_dsp_path / "rate" / "value")
- .set(0.0) // We can only load a sensible value after the tick rate was set
- .coerce(boost::bind(&tx_dsp_core_3000::set_host_rate, perif.duc, _1))
+ perif.duc->populate_subtree(_tree->subtree(tx_dsp_path));
+ _tree->access<double>(tx_dsp_path / "rate" / "value")
+ .coerce(boost::bind(&b200_impl::coerce_tx_samp_rate, this, perif.duc, dspno, _1))
.subscribe(boost::bind(&b200_impl::update_tx_samp_rate, this, dspno, _1))
;
- _tree->create<double>(tx_dsp_path / "freq" / "value")
- .coerce(boost::bind(&tx_dsp_core_3000::set_freq, perif.duc, _1))
- .set(0.0);
- _tree->create<meta_range_t>(tx_dsp_path / "freq" / "range")
- .publish(boost::bind(&tx_dsp_core_3000::get_freq_range, perif.duc));
-
- ////////////////////////////////////////////////////////////////////
- // create time control objects
- ////////////////////////////////////////////////////////////////////
- time_core_3000::readback_bases_type time64_rb_bases;
- time64_rb_bases.rb_now = RB64_TIME_NOW;
- time64_rb_bases.rb_pps = RB64_TIME_PPS;
- perif.time64 = time_core_3000::make(perif.ctrl, TOREG(SR_TIME), time64_rb_bases);
////////////////////////////////////////////////////////////////////
// create RF frontend interfacing
////////////////////////////////////////////////////////////////////
- for(size_t direction = 0; direction < 2; direction++)
- {
- const std::string x = direction? "rx" : "tx";
- const std::string key = std::string((direction? "RX" : "TX")) + std::string(((dspno == _fe1)? "1" : "2"));
- const fs_path rf_fe_path = mb_path / "dboards" / "A" / (x+"_frontends") / (dspno? "B" : "A");
-
- _tree->create<std::string>(rf_fe_path / "name").set("FE-"+key);
- _tree->create<int>(rf_fe_path / "sensors");
+ static const std::vector<direction_t> dirs = boost::assign::list_of(RX_DIRECTION)(TX_DIRECTION);
+ BOOST_FOREACH(direction_t dir, dirs) {
+ const std::string x = (dir == RX_DIRECTION) ? "rx" : "tx";
+ const std::string key = std::string(((dir == RX_DIRECTION) ? "RX" : "TX")) + std::string(((dspno == _fe1) ? "1" : "2"));
+ const fs_path rf_fe_path
+ = mb_path / "dboards" / "A" / (x + "_frontends") / (dspno ? "B" : "A");
+
+ // This will connect all the AD936x-specific items
+ _codec_mgr->populate_frontend_subtree(
+ _tree->subtree(rf_fe_path), key, dir
+ );
+
+ // Now connect all the b200_impl-specific items
_tree->create<sensor_value_t>(rf_fe_path / "sensors" / "lo_locked")
- .publish(boost::bind(&b200_impl::get_fe_pll_locked, this, x == "tx"));
- BOOST_FOREACH(const std::string &name, ad9361_ctrl::get_gain_names(key))
- {
- _tree->create<meta_range_t>(rf_fe_path / "gains" / name / "range")
- .set(ad9361_ctrl::get_gain_range(key));
-
- _tree->create<double>(rf_fe_path / "gains" / name / "value")
- .coerce(boost::bind(&ad9361_ctrl::set_gain, _codec_ctrl, key, _1))
- .set(x == "rx" ? B200_DEFAULT_RX_GAIN : B200_DEFAULT_TX_GAIN);
- }
- _tree->create<std::string>(rf_fe_path / "connection").set("IQ");
- _tree->create<bool>(rf_fe_path / "enabled").set(true);
- _tree->create<bool>(rf_fe_path / "use_lo_offset").set(false);
- _tree->create<double>(rf_fe_path / "bandwidth" / "value")
- .coerce(boost::bind(&ad9361_ctrl::set_bw_filter, _codec_ctrl, key, _1))
- .set(40e6);
- _tree->create<meta_range_t>(rf_fe_path / "bandwidth" / "range")
- .publish(boost::bind(&ad9361_ctrl::get_bw_filter_range, key));
- _tree->create<double>(rf_fe_path / "freq" / "value")
- .publish(boost::bind(&ad9361_ctrl::get_freq, _codec_ctrl, key))
- .coerce(boost::bind(&ad9361_ctrl::tune, _codec_ctrl, key, _1))
+ .publish(boost::bind(&b200_impl::get_fe_pll_locked, this, dir == TX_DIRECTION))
+ ;
+ _tree->access<double>(rf_fe_path / "freq" / "value")
.subscribe(boost::bind(&b200_impl::update_bandsel, this, key, _1))
- .set(B200_DEFAULT_FREQ);
- _tree->create<meta_range_t>(rf_fe_path / "freq" / "range")
- .publish(boost::bind(&ad9361_ctrl::get_rf_freq_range));
-
- //setup RX related stuff
- if (key[0] == 'R')
+ ;
+ if (dir == RX_DIRECTION)
{
static const std::vector<std::string> ants = boost::assign::list_of("TX/RX")("RX2");
_tree->create<std::vector<std::string> >(rf_fe_path / "antenna" / "options").set(ants);
_tree->create<std::string>(rf_fe_path / "antenna" / "value")
.subscribe(boost::bind(&b200_impl::update_antenna_sel, this, dspno, _1))
- .set("RX2");
- _tree->create<sensor_value_t>(rf_fe_path / "sensors" / "rssi")
- .publish(boost::bind(&ad9361_ctrl::get_rssi, _codec_ctrl, key));
+ .set("RX2")
+ ;
+
}
- if (key[0] == 'T')
+ else if (dir == TX_DIRECTION)
{
static const std::vector<std::string> ants(1, "TX/RX");
_tree->create<std::vector<std::string> >(rf_fe_path / "antenna" / "options").set(ants);
_tree->create<std::string>(rf_fe_path / "antenna" / "value").set("TX/RX");
}
-
}
}
@@ -733,34 +861,10 @@ void b200_impl::register_loopback_self_test(wb_iface::sptr iface)
UHD_MSG(status) << ((test_fail)? "fail" : "pass") << std::endl;
}
-void b200_impl::codec_loopback_self_test(wb_iface::sptr iface)
-{
- UHD_MSG(status) << "Performing CODEC loopback test... " << std::flush;
- size_t hash = size_t(time(NULL));
- for (size_t i = 0; i < 100; i++)
- {
- boost::hash_combine(hash, i);
- const boost::uint32_t word32 = boost::uint32_t(hash) & 0xfff0fff0;
- iface->poke32(TOREG(SR_CODEC_IDLE), word32);
- iface->peek64(RB64_CODEC_READBACK); //enough idleness for loopback to propagate
- const boost::uint64_t rb_word64 = iface->peek64(RB64_CODEC_READBACK);
- const boost::uint32_t rb_tx = boost::uint32_t(rb_word64 >> 32);
- const boost::uint32_t rb_rx = boost::uint32_t(rb_word64 & 0xffffffff);
- bool test_fail = word32 != rb_tx or word32 != rb_rx;
- if (test_fail) {
- UHD_MSG(status) << "fail" << std::endl;
- throw uhd::runtime_error("CODEC loopback test failed.");
- }
- }
- UHD_MSG(status) << "pass" << std::endl;
- /* Zero out the idle data. */
- iface->poke32(TOREG(SR_CODEC_IDLE), 0);
-}
-
/***********************************************************************
* Sample and tick rate comprehension below
**********************************************************************/
-void b200_impl::enforce_tick_rate_limits(size_t chan_count, double tick_rate, const char* direction /*= NULL*/)
+void b200_impl::enforce_tick_rate_limits(size_t chan_count, double tick_rate, const std::string &direction /*= ""*/)
{
const size_t max_chans = 2;
if (chan_count > max_chans)
@@ -768,7 +872,7 @@ void b200_impl::enforce_tick_rate_limits(size_t chan_count, double tick_rate, co
throw uhd::value_error(boost::str(
boost::format("cannot not setup %d %s channels (maximum is %d)")
% chan_count
- % (direction ? direction : "data")
+ % (direction.empty() ? "data" : direction)
% max_chans
));
}
@@ -782,20 +886,26 @@ void b200_impl::enforce_tick_rate_limits(size_t chan_count, double tick_rate, co
% (tick_rate/1e6)
% (max_tick_rate/1e6)
% chan_count
- % (direction ? direction : "data")
+ % (direction.empty() ? "data" : direction)
));
}
}
}
-double b200_impl::set_tick_rate(const double rate)
+double b200_impl::set_tick_rate(const double new_tick_rate)
{
- UHD_MSG(status) << (boost::format("Asking for clock rate %.6f MHz\n") % (rate/1e6));
-
- check_tick_rate_with_current_streamers(rate); // Defined in b200_io_impl.cpp
+ UHD_MSG(status) << (boost::format("Asking for clock rate %.6f MHz... ") % (new_tick_rate/1e6)) << std::flush;
+ check_tick_rate_with_current_streamers(new_tick_rate); // Defined in b200_io_impl.cpp
+
+ // Make sure the clock rate is actually changed before doing
+ // the full Monty of setting regs and loopback tests etc.
+ if (std::abs(new_tick_rate - _tick_rate) < 1.0) {
+ UHD_MSG(status) << "OK" << std::endl;
+ return _tick_rate;
+ }
- _tick_rate = _codec_ctrl->set_clock_rate(rate);
- UHD_MSG(status) << (boost::format("Actually got clock rate %.6f MHz\n") % (_tick_rate/1e6));
+ _tick_rate = _codec_ctrl->set_clock_rate(new_tick_rate);
+ UHD_MSG(status) << std::endl << (boost::format("Actually got clock rate %.6f MHz.") % (_tick_rate/1e6)) << std::endl;
//reset after clock rate change
this->reset_codec_dcm();
@@ -839,12 +949,14 @@ void b200_impl::check_fpga_compat(void)
if (signature != 0xACE0BA5E) throw uhd::runtime_error(
"b200::check_fpga_compat signature register readback failed");
- if (compat_major != B200_FPGA_COMPAT_NUM){
+ const boost::uint16_t expected = (_product == B205 ? B205_FPGA_COMPAT_NUM : B200_FPGA_COMPAT_NUM);
+ if (compat_major != expected)
+ {
throw uhd::runtime_error(str(boost::format(
"Expected FPGA compatibility number %d, but got %d:\n"
"The FPGA build is not compatible with the host code build.\n"
"%s"
- ) % int(B200_FPGA_COMPAT_NUM) % compat_major % print_utility_error("uhd_images_downloader.py")));
+ ) % int(expected) % compat_major % print_utility_error("uhd_images_downloader.py")));
}
_tree->create<std::string>("/mboards/0/fpga_version").set(str(boost::format("%u.%u")
% compat_major % compat_minor));
@@ -856,41 +968,118 @@ void b200_impl::set_mb_eeprom(const uhd::usrp::mboard_eeprom_t &mb_eeprom)
}
+boost::uint32_t b200_impl::get_fp_gpio(gpio_core_200::sptr gpio)
+{
+ return boost::uint32_t(gpio->read_gpio(dboard_iface::UNIT_RX));
+}
+
+void b200_impl::set_fp_gpio(gpio_core_200::sptr gpio, const gpio_attr_t attr, const boost::uint32_t value)
+{
+ switch (attr)
+ {
+ case GPIO_CTRL: return gpio->set_pin_ctrl(dboard_iface::UNIT_RX, value);
+ case GPIO_DDR: return gpio->set_gpio_ddr(dboard_iface::UNIT_RX, value);
+ case GPIO_OUT: return gpio->set_gpio_out(dboard_iface::UNIT_RX, value);
+ case GPIO_ATR_0X: return gpio->set_atr_reg(dboard_iface::UNIT_RX, dboard_iface::ATR_REG_IDLE, value);
+ case GPIO_ATR_RX: return gpio->set_atr_reg(dboard_iface::UNIT_RX, dboard_iface::ATR_REG_RX_ONLY, value);
+ case GPIO_ATR_TX: return gpio->set_atr_reg(dboard_iface::UNIT_RX, dboard_iface::ATR_REG_TX_ONLY, value);
+ case GPIO_ATR_XX: return gpio->set_atr_reg(dboard_iface::UNIT_RX, dboard_iface::ATR_REG_FULL_DUPLEX, value);
+ default: UHD_THROW_INVALID_CODE_PATH();
+ }
+}
+
/***********************************************************************
* Reference time and clock
**********************************************************************/
void b200_impl::update_clock_source(const std::string &source)
{
- // present the PLL with a valid 10 MHz signal before switching its reference
- _gpio_state.ref_sel = (source == "gpsdo")? 1 : 0;
- this->update_gpio_state();
+ // For B205, ref_sel selects whether or not to lock to the external clock source
+ if (_product == B205)
+ {
+ if (source == "external" and _time_source == EXTERNAL)
+ {
+ throw uhd::value_error("external reference cannot be both a clock source and a time source");
+ }
- if (source == "internal"){
- _adf4001_iface->set_lock_to_ext_ref(false);
+ if (source == "internal")
+ {
+ if (_gpio_state.ref_sel != 0)
+ {
+ _gpio_state.ref_sel = 0;
+ this->update_gpio_state();
+ }
+ }
+ else if (source == "external")
+ {
+ if (_gpio_state.ref_sel != 1)
+ {
+ _gpio_state.ref_sel = 1;
+ this->update_gpio_state();
+ }
+ }
+ else
+ {
+ throw uhd::key_error("update_clock_source: unknown source: " + source);
+ }
+ return;
}
- else if ((source == "external")
- or (source == "gpsdo")){
+ // For all other devices, ref_sel selects the external or gpsdo clock source
+ // and the ADF4001 selects whether to lock to it or not
+ if (source == "internal")
+ {
+ _adf4001_iface->set_lock_to_ext_ref(false);
+ }
+ else if (source == "external")
+ {
+ if (_gpio_state.ref_sel != 0)
+ {
+ _gpio_state.ref_sel = 0;
+ this->update_gpio_state();
+ }
_adf4001_iface->set_lock_to_ext_ref(true);
- } else {
+ }
+ else if (_gps and source == "gpsdo")
+ {
+ if (_gpio_state.ref_sel != 1)
+ {
+ _gpio_state.ref_sel = 1;
+ this->update_gpio_state();
+ }
+ _adf4001_iface->set_lock_to_ext_ref(true);
+ }
+ else
+ {
throw uhd::key_error("update_clock_source: unknown source: " + source);
}
}
void b200_impl::update_time_source(const std::string &source)
{
- boost::uint32_t value = 0;
+ if (_product == B205 and source == "external" and _gpio_state.ref_sel == 1)
+ {
+ throw uhd::value_error("external reference cannot be both a time source and a clock source");
+ }
+
+ // We assume source is valid for this device (if it's gone through
+ // the prop three, then it definitely is thanks to our coercer)
+ time_source_t value;
if (source == "none")
- value = 3;
+ value = NONE;
else if (source == "internal")
- value = 2;
+ value = INTERNAL;
else if (source == "external")
- value = 1;
- else if (source == "gpsdo")
- value = 0;
- else throw uhd::key_error("update_time_source: unknown source: " + source);
- _local_ctrl->poke32(TOREG(SR_CORE_PPS_SEL), value);
+ value = EXTERNAL;
+ else if (_gps and source == "gpsdo")
+ value = GPSDO;
+ else
+ throw uhd::key_error("update_time_source: unknown source: " + source);
+ if (_time_source != value)
+ {
+ _local_ctrl->poke32(TOREG(SR_CORE_PPS_SEL), value);
+ _time_source = value;
+ }
}
/***********************************************************************
@@ -899,6 +1088,11 @@ void b200_impl::update_time_source(const std::string &source)
void b200_impl::update_bandsel(const std::string& which, double freq)
{
+ // B205 does not have bandsels
+ if (_product == B205) {
+ return;
+ }
+
if(which[0] == 'R') {
if(freq < 2.2e9) {
_gpio_state.rx_bandsel_a = 0;