// // Copyright 2012-2015 Ettus Research LLC // Copyright 2018 Ettus Research, a National Instruments Company // // SPDX-License-Identifier: GPL-3.0-or-later // #include "b200_impl.hpp" #include "../../transport/libusb1_base.hpp" #include "b200_regs.hpp" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include using namespace uhd; using namespace uhd::usrp; using namespace uhd::usrp::gpio_atr; using namespace uhd::transport; namespace { constexpr int64_t REENUMERATION_TIMEOUT_MS = 3000; } // B200 + B210: class b200_ad9361_client_t : public ad9361_params { public: ~b200_ad9361_client_t() override {} double get_band_edge(frequency_band_t band) override { switch (band) { 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; } } clocking_mode_t get_clocking_mode() override { return clocking_mode_t::AD9361_XTAL_N_CLK_PATH; } digital_interface_mode_t get_digital_interface_mode() override { return AD9361_DDR_FDD_LVCMOS; } digital_interface_delays_t get_digital_interface_timing() override { digital_interface_delays_t delays; delays.rx_clk_delay = 0; delays.rx_data_delay = 0xF; delays.tx_clk_delay = 0; delays.tx_data_delay = 0xF; return delays; } }; // B205 class b2xxmini_ad9361_client_t : public ad9361_params { public: ~b2xxmini_ad9361_client_t() override {} double get_band_edge(frequency_band_t band) override { 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() override { return clocking_mode_t::AD9361_XTAL_N_CLK_PATH; } digital_interface_mode_t get_digital_interface_mode() override { return AD9361_DDR_FDD_LVCMOS; } digital_interface_delays_t get_digital_interface_timing() override { digital_interface_delays_t delays; delays.rx_clk_delay = 0; delays.rx_data_delay = 0xF; 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& 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. // 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 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."); } product_id = boost::lexical_cast(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 B2XX_PRODUCT_ID[product_id]; } std::vector get_b200_device_handles(const device_addr_t& hint) { std::vector 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(hint.get("vid")), uhd::cast::hexstr_cast(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) { device_addrs_t b200_addrs; // return an empty list of addresses when type is set to non-b200 if (hint.has_key("type") and hint["type"] != "b200") return b200_addrs; // Return an empty list of addresses when an address or resource is specified, // since an address and resource is intended for a different, non-USB, device. for (device_addr_t hint_i : separate_device_addr(hint)) { if (hint_i.has_key("addr") || hint_i.has_key("resource")) return b200_addrs; } // 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. size_t found = 0; for (usb_device_handle::sptr handle : get_b200_device_handles(hint)) { // extract the firmware path for the b200 std::string b200_fw_image; try { b200_fw_image = hint.get("fw", B200_FW_FILE_NAME); b200_fw_image = uhd::find_image_path(b200_fw_image, STR(UHD_IMAGES_DIR)); // FIXME } catch (uhd::exception& e) { UHD_LOGGER_WARNING("B200") << e.what(); return b200_addrs; } UHD_LOGGER_DEBUG("B200") << "the firmware image: " << b200_fw_image; usb_control::sptr control; try { control = usb_control::make(handle, 0); } catch (const uhd::exception&) { continue; } // ignore claimed // check if fw was already loaded if (!(handle->firmware_loaded())) { b200_iface::make(control)->load_firmware(b200_fw_image); } found++; } const auto timeout_time = std::chrono::steady_clock::now() + std::chrono::milliseconds(REENUMERATION_TIMEOUT_MS); // search for the device until found or timeout while (std::chrono::steady_clock::now() < timeout_time and b200_addrs.empty() and found != 0) { for (usb_device_handle::sptr handle : get_b200_device_handles(hint)) { usb_control::sptr control; try { control = usb_control::make(handle, 0); } catch (const uhd::exception&) { continue; } // ignore claimed b200_iface::sptr iface = b200_iface::make(control); const mboard_eeprom_t mb_eeprom = b200_impl::get_mb_eeprom(iface); device_addr_t new_addr; new_addr["type"] = "b200"; new_addr["name"] = mb_eeprom["name"]; new_addr["serial"] = handle->get_serial(); try { // Turn the 16-Bit product ID into a string representation new_addr["product"] = B2XX_STR_NAMES[get_b200_product(handle, mb_eeprom)]; } catch (const uhd::runtime_error&) { // 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 if ((not hint.has_key("name") or hint["name"] == new_addr["name"]) and (not hint.has_key("serial") or hint["serial"] == new_addr["serial"])) { b200_addrs.push_back(new_addr); } } } return b200_addrs; } /*********************************************************************** * Make **********************************************************************/ static device::sptr b200_make(const device_addr_t& 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&) { UHD_LOGGER_INFO("B200") << "Detected bad USB state; resetting."; libusb::device_handle::sptr dev_handle(libusb::device_handle::get_cached_handle( std::static_pointer_cast(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) { device::register_device(&b200_find, &b200_make, device::USRP); } /*********************************************************************** * Structors **********************************************************************/ b200_impl::b200_impl( const uhd::device_addr_t& device_addr, usb_device_handle::sptr& handle) : _product(B200) , // Some safe value _revision(0) , _enable_user_regs(device_addr.has_key("enable_user_regs")) , _time_source(UNKNOWN) , _tick_rate(0.0) // Forces a clock initialization at startup { _tree = property_tree::make(); _type = device::USRP; const fs_path mb_path = "/mboards/0"; // try to match the given device address with something on the USB bus uint16_t vid = B200_VENDOR_ID; 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(device_addr.get("vid")); specified_vid = true; } if (device_addr.has_key("pid")) { pid = uhd::cast::hexstr_cast(device_addr.get("pid")); specified_pid = true; } std::vector 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, B200MINI_PRODUCT_ID)); vid_pid_pair_list.push_back( usb_device_handle::vid_pid_pair_t(vid, B205MINI_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, B200MINI_PRODUCT_ID)); vid_pid_pair_list.push_back( usb_device_handle::vid_pid_pair_t(B200_VENDOR_ID, B205MINI_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 device_list = usb_device_handle::get_device_list(vid_pid_pair_list); // locate the matching handle in the device list for (usb_device_handle::sptr dev_handle : device_list) { try { if (dev_handle->get_serial() == device_addr["serial"]) { handle = dev_handle; break; } } catch (const uhd::exception&) { continue; } } UHD_ASSERT_THROW(handle.get() != NULL); // better be found // create control objects usb_control::sptr control = usb_control::make(handle, 0); _iface = b200_iface::make(control); this->check_fw_compat(); // check after making //////////////////////////////////////////////////////////////////// // setup the mboard eeprom //////////////////////////////////////////////////////////////////// const mboard_eeprom_t mb_eeprom = get_mb_eeprom(_iface); _tree->create(mb_path / "eeprom") .set(mb_eeprom) .add_coerced_subscriber( std::bind(&b200_impl::set_mb_eeprom, this, std::placeholders::_1)); //////////////////////////////////////////////////////////////////// // Identify the device type //////////////////////////////////////////////////////////////////// std::string default_file_name; std::string product_name; try { // This will throw if the product ID is invalid: _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: if (not device_addr.has_key("fpga")) { throw e; } // In this case, we must provide a default product name: product_name = "B200?"; } if (not mb_eeprom["revision"].empty()) { _revision = boost::lexical_cast(mb_eeprom["revision"]); } UHD_LOGGER_INFO("B200") << "Detected Device: " << B2XX_STR_NAMES[_product]; _gpsdo_capable = (not(_product == B200MINI or _product == B205MINI)); //////////////////////////////////////////////////////////////////// // Set up frontend mapping //////////////////////////////////////////////////////////////////// // Explanation: The AD9361 has 2 frontends, FE1 and FE2. // On the B210 FE1 maps to the B-side (or radio 1), and FE2 maps // to the A-side (or radio 0). So, logically, the radios are swapped // between the host side and the AD9361-side. // B200 is more complicated: On Revs <= 4, the A-side is connected, // which means FE2 is used (like B210). On Revs >= 5, the left side // ("B-side") is connected, because these revs use an AD9364, which // does not have an FE2, so we don't swap FEs. // Swapped setup: _fe1 = 1; _fe2 = 0; _gpio_state.swap_atr = 1; // Unswapped setup: if (_product == B200MINI or _product == B205MINI 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 } //////////////////////////////////////////////////////////////////// // Load the FPGA image, then reset GPIF //////////////////////////////////////////////////////////////////// // extract the FPGA path for the B200 std::string b200_fpga_image = find_image_path( device_addr.has_key("fpga") ? device_addr["fpga"] : default_file_name); uint32_t status = _iface->load_fpga(b200_fpga_image); if (status != 0) { throw uhd::runtime_error(str(boost::format("fx3 is in state %1%") % status)); } _iface->reset_gpif(); //////////////////////////////////////////////////////////////////// // Create control transport //////////////////////////////////////////////////////////////////// uint8_t usb_speed = _iface->get_usb_speed(); UHD_LOGGER_INFO("B200") << "Operating over USB " << (int)usb_speed << "."; const std::string min_frame_size = (usb_speed == 3) ? "1024" : "512"; device_addr_t ctrl_xport_args; ctrl_xport_args["recv_frame_size"] = min_frame_size; ctrl_xport_args["num_recv_frames"] = "16"; 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, 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 _tree->create(mb_path / "link_max_rate") .set((usb_speed == 3) ? B200_MAX_RATE_USB3 : B200_MAX_RATE_USB2); _tree->create(mb_path / "usb_version").set(usb_speed); //////////////////////////////////////////////////////////////////// // Async task structure //////////////////////////////////////////////////////////////////// _async_task_data.reset(new AsyncTaskData()); _async_task_data->async_md.reset(new async_md_type(1000 /*messages deep*/)); if (_gpsdo_capable) { _async_task_data->gpsdo_uart = b200_uart::make(_ctrl_transport, B200_TX_GPS_UART_SID); } _async_task = uhd::msg_task::make(std::bind( &b200_impl::handle_async_task, this, _ctrl_transport, _async_task_data)); //////////////////////////////////////////////////////////////////// // Local control endpoint //////////////////////////////////////////////////////////////////// _local_ctrl = b200_radio_ctrl_core::make(false /*lilE*/, _ctrl_transport, zero_copy_if::sptr() /*null*/, B200_LOCAL_CTRL_SID); _local_ctrl->hold_task(_async_task); _async_task_data->local_ctrl = _local_ctrl; // weak this->check_fpga_compat(); /* Initialize the GPIOs, set the default bandsels to the lower range. Note * that calling update_bandsel calls update_gpio_state(). */ update_bandsel("RX", 800e6); update_bandsel("TX", 850e6); //////////////////////////////////////////////////////////////////// // Create the GPSDO control //////////////////////////////////////////////////////////////////// if (_gpsdo_capable) { if ((_local_ctrl->peek32(RB32_CORE_STATUS) & 0xff) != B200_GPSDO_ST_NONE) { UHD_LOGGER_INFO("B200") << "Detecting internal GPSDO.... " << std::flush; try { _gps = gps_ctrl::make(_async_task_data->gpsdo_uart); } catch (std::exception& e) { UHD_LOGGER_ERROR("B200") << "An error occurred making GPSDO control: " << e.what(); } if (_gps and _gps->gps_detected()) { for (const std::string& name : _gps->get_sensors()) { _tree->create(mb_path / "sensors" / name) .set_publisher(std::bind(&gps_ctrl::get_sensor, _gps, name)); } } else { _local_ctrl->poke32(TOREG(SR_CORE_GPSDO_ST), B200_GPSDO_ST_NONE); } } } //////////////////////////////////////////////////////////////////// // Initialize the properties tree //////////////////////////////////////////////////////////////////// _tree->create("/name").set("B-Series Device"); _tree->create(mb_path / "name").set(product_name); _tree->create(mb_path / "codename") .set((_product == B200MINI or _product == B205MINI) ? "Pixie" : "Sasquatch"); //////////////////////////////////////////////////////////////////// // Create data transport // This happens after FPGA ctrl instantiated so any junk that might // be in the FPGAs buffers doesn't get pulled into the transport // before being cleared. //////////////////////////////////////////////////////////////////// device_addr_t data_xport_args; const int max_transfer = usb_speed == 3 ? 1024 : 512; int recv_frame_size = device_addr.cast("recv_frame_size", B200_USB_DATA_DEFAULT_FRAME_SIZE); // Check that recv_frame_size limits. if (recv_frame_size < B200_USB_DATA_MIN_RECV_FRAME_SIZE) { UHD_LOGGER_WARNING("B200") << "Requested recv_frame_size of " << recv_frame_size << " is too small. It will be set to " << B200_USB_DATA_MIN_RECV_FRAME_SIZE << "."; recv_frame_size = B200_USB_DATA_MIN_RECV_FRAME_SIZE; } else if (recv_frame_size > B200_USB_DATA_MAX_RECV_FRAME_SIZE) { UHD_LOGGER_WARNING("B200") << "Requested recv_frame_size of " << recv_frame_size << " is too large. It will be set to " << B200_USB_DATA_MAX_RECV_FRAME_SIZE << "."; recv_frame_size = B200_USB_DATA_MAX_RECV_FRAME_SIZE; } else if (recv_frame_size % max_transfer == 0 or recv_frame_size % 8 != 0) { // The Cypress FX3 does not properly handle recv_frame_sizes that are // aligned to the maximum transfer size and the FPGA code requires the // data to be aligned to 8 byte words. The code below coerces the // recv_frame_size to a value that is a multiple of 8 bytes, not // a multiple of the maximum transfer size, and aligned to 24 bytes // to support full 8 byte word alignment for sc8, sc12, and sc16 data // types. // Align to 8 byte words recv_frame_size += 8 - (recv_frame_size % 8); if (recv_frame_size % max_transfer == 0) { recv_frame_size = (((recv_frame_size - 16) / 24) * 24) + 16; } UHD_LOGGER_WARNING("B200") << "The recv_frame_size must be a multiple of 8 bytes and not a multiple of " << max_transfer << " bytes. Requested recv_frame_size of " << device_addr["recv_frame_size"] << " coerced to " << recv_frame_size << "."; } data_xport_args["recv_frame_size"] = std::to_string(recv_frame_size); data_xport_args["num_recv_frames"] = device_addr.get("num_recv_frames", "16"); data_xport_args["send_frame_size"] = device_addr.get( "send_frame_size", std::to_string(B200_USB_DATA_DEFAULT_FRAME_SIZE)); 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 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 _demux = recv_packet_demuxer_3000::make(_data_transport); //////////////////////////////////////////////////////////////////// // create time and clock control objects //////////////////////////////////////////////////////////////////// _spi_iface = b200_local_spi_core::make(_local_ctrl); if (not(_product == B200MINI or _product == B205MINI)) { _adf4001_iface = std::make_shared(_spi_iface); } //////////////////////////////////////////////////////////////////// // Init codec - turns on clocks //////////////////////////////////////////////////////////////////// UHD_LOGGER_INFO("B200") << "Initialize CODEC control..."; reset_codec(); ad9361_params::sptr client_settings; if (_product == B200MINI or _product == B205MINI) { client_settings = std::make_shared(); } else { client_settings = std::make_shared(); } _codec_ctrl = ad9361_ctrl::make_spi(client_settings, _spi_iface, AD9361_SLAVENO); //////////////////////////////////////////////////////////////////// // create codec control objects //////////////////////////////////////////////////////////////////// { const fs_path codec_path = mb_path / ("rx_codecs") / "A"; _tree->create(codec_path / "name") .set(product_name + " RX dual ADC"); _tree->create(codec_path / "gains"); // empty cuz gains are in frontend } { const fs_path codec_path = mb_path / ("tx_codecs") / "A"; _tree->create(codec_path / "name") .set(product_name + " TX dual DAC"); _tree->create(codec_path / "gains"); // empty cuz gains are in frontend } //////////////////////////////////////////////////////////////////// // create clock control objects //////////////////////////////////////////////////////////////////// _tree->create(mb_path / "tick_rate") .set_coercer(std::bind(&b200_impl::set_tick_rate, this, std::placeholders::_1)) .set_publisher(std::bind(&b200_impl::get_tick_rate, this)) .add_coerced_subscriber( std::bind(&b200_impl::update_tick_rate, this, std::placeholders::_1)); _tree->create(mb_path / "tick_rate/range").set_publisher([this]() { return this->_codec_ctrl->get_clock_rate_range(); }); _tree->create(mb_path / "time" / "cmd"); _tree->create(mb_path / "auto_tick_rate").set(false); //////////////////////////////////////////////////////////////////// // and do the misc mboard sensors //////////////////////////////////////////////////////////////////// _tree->create(mb_path / "sensors" / "ref_locked") .set_publisher(std::bind(&b200_impl::get_ref_locked, this)); //////////////////////////////////////////////////////////////////// // create frontend mapping //////////////////////////////////////////////////////////////////// std::vector default_map(2, 0); default_map[1] = 1; // Set this to A->0 B->1 even if there's only A _tree->create>(mb_path / "rx_chan_dsp_mapping").set(default_map); _tree->create>(mb_path / "tx_chan_dsp_mapping").set(default_map); _tree->create(mb_path / "rx_subdev_spec") .set_coercer( std::bind(&b200_impl::coerce_subdev_spec, this, std::placeholders::_1)) .set(subdev_spec_t()) .add_coerced_subscriber( std::bind(&b200_impl::update_subdev_spec, this, "rx", std::placeholders::_1)); _tree->create(mb_path / "tx_subdev_spec") .set_coercer( std::bind(&b200_impl::coerce_subdev_spec, this, std::placeholders::_1)) .set(subdev_spec_t()) .add_coerced_subscriber( std::bind(&b200_impl::update_subdev_spec, this, "tx", std::placeholders::_1)); //////////////////////////////////////////////////////////////////// // setup radio control //////////////////////////////////////////////////////////////////// UHD_LOGGER_INFO("B200") << "Initialize Radio control..."; const size_t num_radio_chains = ((_local_ctrl->peek32(RB32_CORE_STATUS) >> 8) & 0xff); UHD_ASSERT_THROW(num_radio_chains > 0); UHD_ASSERT_THROW(num_radio_chains <= 2); _radio_perifs.resize(num_radio_chains); _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 for (radio_perifs_t& perif : _radio_perifs) { _codec_mgr->loopback_self_test( [&perif](const uint32_t value) { perif.ctrl->poke32(TOREG(SR_CODEC_IDLE), value); }, [&perif]() { return perif.ctrl->peek64(RB64_CODEC_READBACK); }); } // register time now and pps onto available radio cores _tree->create(mb_path / "time" / "now") .set_publisher(std::bind(&time_core_3000::get_time_now, _radio_perifs[0].time64)) .add_coerced_subscriber( std::bind(&b200_impl::set_time, this, std::placeholders::_1)) .set(0.0); // re-sync the times when the tick rate changes _tree->access(mb_path / "tick_rate") .add_coerced_subscriber(std::bind(&b200_impl::sync_times, this)); _tree->create(mb_path / "time" / "pps") .set_publisher( std::bind(&time_core_3000::get_time_last_pps, _radio_perifs[0].time64)); for (radio_perifs_t& perif : _radio_perifs) { _tree->access(mb_path / "time" / "pps") .add_coerced_subscriber(std::bind( &time_core_3000::set_time_next_pps, perif.time64, std::placeholders::_1)); } // setup time source props const std::vector time_sources = (_gpsdo_capable) ? std::vector{"none", "internal", "external", "gpsdo"} : std::vector{"none", "internal", "external"}; _tree->create>(mb_path / "time_source" / "options") .set(time_sources); _tree->create(mb_path / "time_source" / "value") .set_coercer(std::bind( &check_option_valid, "time source", time_sources, std::placeholders::_1)) .add_coerced_subscriber( std::bind(&b200_impl::update_time_source, this, std::placeholders::_1)); // setup reference source props const std::vector clock_sources = (_gpsdo_capable) ? std::vector{"internal", "external", "gpsdo"} : std::vector{"internal", "external"}; _tree->create>(mb_path / "clock_source" / "options") .set(clock_sources); _tree->create(mb_path / "clock_source" / "value") .set_coercer(std::bind( &check_option_valid, "clock source", clock_sources, std::placeholders::_1)) .add_coerced_subscriber( std::bind(&b200_impl::update_clock_source, this, std::placeholders::_1)); //////////////////////////////////////////////////////////////////// // front panel gpio //////////////////////////////////////////////////////////////////// _radio_perifs[0].fp_gpio = gpio_atr_3000::make(_radio_perifs[0].ctrl, TOREG(SR_FP_GPIO), RB32_FP_GPIO); for (const auto& attr : gpio_attr_map) { switch (attr.first) { case usrp::gpio_atr::GPIO_SRC: _tree ->create>( mb_path / "gpio" / "FP0" / attr.second) .set(std::vector( 32, usrp::gpio_atr::default_attr_value_map.at(attr.first))) .add_coerced_subscriber([](const std::vector&) { throw uhd::runtime_error("This device does not support setting " "the GPIO_SRC attribute."); }); break; case usrp::gpio_atr::GPIO_CTRL: case usrp::gpio_atr::GPIO_DDR: _tree ->create>( mb_path / "gpio" / "FP0" / attr.second) .set(std::vector( 32, usrp::gpio_atr::default_attr_value_map.at(attr.first))) .add_coerced_subscriber([this, attr]( const std::vector str_val) { uint32_t val = 0; for (size_t i = 0; i < str_val.size(); i++) { val += usrp::gpio_atr::gpio_attr_value_pair.at(attr.second) .at(str_val[i]) << i; } _radio_perifs[0].fp_gpio->set_gpio_attr(attr.first, val); }); break; case usrp::gpio_atr::GPIO_READBACK: _tree->create(mb_path / "gpio" / "FP0" / "READBACK") .set_publisher( std::bind(&gpio_atr_3000::read_gpio, _radio_perifs[0].fp_gpio)); break; default: _tree->create(mb_path / "gpio" / "FP0" / attr.second) .set(0) .add_coerced_subscriber(std::bind(&gpio_atr_3000::set_gpio_attr, _radio_perifs[0].fp_gpio, attr.first, std::placeholders::_1)); } } //////////////////////////////////////////////////////////////////// // dboard eeproms but not really //////////////////////////////////////////////////////////////////// dboard_eeprom_t db_eeprom; _tree->create(mb_path / "dboards" / "A" / "rx_eeprom") .set(db_eeprom); _tree->create(mb_path / "dboards" / "A" / "tx_eeprom") .set(db_eeprom); _tree->create(mb_path / "dboards" / "A" / "gdb_eeprom") .set(db_eeprom); //////////////////////////////////////////////////////////////////// // do some post-init tasks //////////////////////////////////////////////////////////////////// // Init the clock rate and the auto mcr appropriately if (not device_addr.has_key("master_clock_rate")) { UHD_LOGGER_INFO("B200") << "Setting master clock rate selection to 'automatic'."; } // We can automatically choose a master clock rate, but not if the user specifies one const double default_tick_rate = device_addr.cast("master_clock_rate", ad936x_manager::DEFAULT_TICK_RATE); _tree->access(mb_path / "tick_rate").set(default_tick_rate); _tree->access(mb_path / "auto_tick_rate") .set(not device_addr.has_key("master_clock_rate")); // subdev spec contains full width of selections subdev_spec_t rx_spec, tx_spec; for (const std::string& fe : _tree->list(mb_path / "dboards" / "A" / "rx_frontends")) { rx_spec.push_back(subdev_spec_pair_t("A", fe)); } for (const std::string& fe : _tree->list(mb_path / "dboards" / "A" / "tx_frontends")) { tx_spec.push_back(subdev_spec_pair_t("A", fe)); } _tree->access(mb_path / "rx_subdev_spec").set(rx_spec); _tree->access(mb_path / "tx_subdev_spec").set(tx_spec); // init to internal clock and time source _tree->access(mb_path / "clock_source/value").set("internal"); _tree->access(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 / ad936x_manager::DEFAULT_DECIM); _radio_perifs[i].duc->set_host_rate( default_tick_rate / ad936x_manager::DEFAULT_INTERP); } } b200_impl::~b200_impl(void) { UHD_SAFE_CALL(_async_task.reset();) } /*********************************************************************** * setup radio control objects **********************************************************************/ void b200_impl::setup_radio(const size_t dspno) { radio_perifs_t& perif = _radio_perifs[dspno]; const fs_path mb_path = "/mboards/0"; //////////////////////////////////////////////////////////////////// // Set up transport //////////////////////////////////////////////////////////////////// const uint32_t sid = (dspno == 0) ? B200_CTRL0_MSG_SID : B200_CTRL1_MSG_SID; //////////////////////////////////////////////////////////////////// // radio control //////////////////////////////////////////////////////////////////// perif.ctrl = b200_radio_ctrl_core::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(mb_path / "time" / "cmd") .add_coerced_subscriber(std::bind( &b200_radio_ctrl_core::set_time, perif.ctrl, std::placeholders::_1)); _tree->access(mb_path / "tick_rate") .add_coerced_subscriber(std::bind( &b200_radio_ctrl_core::set_tick_rate, perif.ctrl, std::placeholders::_1)); this->register_loopback_self_test(perif.ctrl); //////////////////////////////////////////////////////////////////// // Set up peripherals //////////////////////////////////////////////////////////////////// perif.atr = gpio_atr_3000::make_write_only(perif.ctrl, TOREG(SR_ATR)); perif.atr->set_atr_mode(MODE_ATR, 0xFFFFFFFF); // 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(usrp::fe_connection_t(dspno == 1 ? "IbQb" : "IQ")); perif.ddc->set_freq(rx_dsp_core_3000::DEFAULT_CORDIC_FREQ); perif.deframer = tx_vita_core_3000::make_no_radio_buff(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); if (_enable_user_regs) { UHD_LOG_DEBUG("B200", "Enabling user settings registers"); perif.user_settings = user_settings_core_3000::make( perif.ctrl, TOREG(SR_USER_SR_BASE), TOREG(SR_USER_RB_ADDR)); if (!perif.user_settings) { const std::string error_msg = "Failed to create user settings bus!"; UHD_LOG_ERROR("B200", error_msg); throw uhd::runtime_error(error_msg); } } //////////////////////////////////////////////////////////////////// // 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 //////////////////////////////////////////////////////////////////// const fs_path rx_dsp_path = mb_path / "rx_dsps" / dspno; perif.ddc->populate_subtree(_tree->subtree(rx_dsp_path)); _tree->create(rx_dsp_path / "rate" / "set").set(false); _tree->access(rx_dsp_path / "rate" / "value") .set_coercer(std::bind(&b200_impl::coerce_rx_samp_rate, this, perif.ddc, dspno, std::placeholders::_1)) .add_coerced_subscriber([this, rx_dsp_path](const double) { if (this->_tree) { this->_tree->access(rx_dsp_path / "rate" / "set").set(true); } }) .add_coerced_subscriber(std::bind( &b200_impl::update_rx_samp_rate, this, dspno, std::placeholders::_1)); _tree->create(rx_dsp_path / "stream_cmd") .add_coerced_subscriber(std::bind(&rx_vita_core_3000::issue_stream_command, perif.framer, std::placeholders::_1)); _tree->access(mb_path / "tick_rate") .add_coerced_subscriber(std::bind( &rx_vita_core_3000::set_tick_rate, perif.framer, std::placeholders::_1)) .add_coerced_subscriber(std::bind(&b200_impl::update_rx_dsp_tick_rate, this, std::placeholders::_1, perif.ddc, rx_dsp_path)); //////////////////////////////////////////////////////////////////// // create tx dsp control objects //////////////////////////////////////////////////////////////////// const fs_path tx_dsp_path = mb_path / "tx_dsps" / dspno; perif.duc->populate_subtree(_tree->subtree(tx_dsp_path)); _tree->create(tx_dsp_path / "rate" / "set").set(false); _tree->access(tx_dsp_path / "rate" / "value") .set_coercer(std::bind(&b200_impl::coerce_tx_samp_rate, this, perif.duc, dspno, std::placeholders::_1)) .add_coerced_subscriber([this, tx_dsp_path](const double) { if (this->_tree) { this->_tree->access(tx_dsp_path / "rate" / "set").set(true); } }) .add_coerced_subscriber(std::bind( &b200_impl::update_tx_samp_rate, this, dspno, std::placeholders::_1)); _tree->access(mb_path / "tick_rate") .add_coerced_subscriber(std::bind(&b200_impl::update_tx_dsp_tick_rate, this, std::placeholders::_1, perif.duc, tx_dsp_path)); //////////////////////////////////////////////////////////////////// // create RF frontend interfacing //////////////////////////////////////////////////////////////////// // The "calibration serial" is the motherboard serial plus the frontend // (A or B) separated by colon, e.g. "1234ABC:A". const std::string cal_serial = _tree->access(mb_path / "eeprom").get()["serial"] + "#" + (dspno ? "B" : "A"); // The "calibration key" is either b2xxmini_power_cal_$dir_$ant, or // b2xx_power_cal_$dir_$ant, depending on the form factor. // $dir is either "tx" or "rx", and "ant" is either "tx_rx" or "rx2" (i.e., // sanitized version of the antenna names that work in filenames. const std::string cal_key_base = (_product == B200MINI or _product == B205MINI) ? "b2xxmini_pwr_" : "b2xx_pwr_"; for (direction_t dir : std::vector{RX_DIRECTION, TX_DIRECTION}) { const std::string dir_key = (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" / (dir_key + "_frontends") / (dspno ? "B" : "A"); const std::vector ants = (dir == RX_DIRECTION) ? std::vector{"TX/RX", "RX2"} : std::vector{"TX/RX"}; // This will connect all the AD936x-specific items _codec_mgr->populate_frontend_subtree(_tree->subtree(rf_fe_path), key, dir); // Antenna controls are board-specific, not AD936x specific if (dir == RX_DIRECTION) { _tree->create(rf_fe_path / "antenna" / "value") .add_coerced_subscriber([this, dspno](const std::string& antenna) { this->update_antenna_sel(dspno, antenna); }) .set("RX2"); } else if (dir == TX_DIRECTION) { _tree->create(rf_fe_path / "antenna" / "value").set("TX/RX"); } // We don't add any baseband correction auto ggroup = uhd::gain_group::make(); constexpr char HW_GAIN_STAGE[] = "hw"; ggroup->register_fcns(HW_GAIN_STAGE, {// Get gain range: [key]() { return ad9361_ctrl::get_gain_range(key); }, // Get gain: [this, rf_fe_path, key]() { return _tree ->access(rf_fe_path / "gains" / ad9361_ctrl::get_gain_names(key).at(0) / "value") .get(); }, // Set gain: [this, rf_fe_path, key](const double gain) { _tree ->access(rf_fe_path / "gains" / ad9361_ctrl::get_gain_names(key).at(0) / "value") .set(gain); }}); // Add power controls perif.pwr_mgr.insert({dir_key, pwr_cal_mgr::make( cal_serial, "B200-CAL-" + key, // Frequency getter: [this, rf_fe_path]() { return _tree->access(rf_fe_path / "freq" / "value").get(); }, // Current key getter (see notes on calibration key above): [this, rf_fe_path, cal_key_base, dir_key]() { return cal_key_base + dir_key + "_" + pwr_cal_mgr::sanitize_antenna_name( _tree->access( rf_fe_path / "antenna" / "value") .get()); }, ggroup)}); perif.pwr_mgr.at(dir_key)->populate_subtree(_tree->subtree(rf_fe_path)); perif.pwr_mgr.at(dir_key)->set_temperature( _tree->access(rf_fe_path / "sensors" / "temp") .get() .to_int()); // Now connect all the b200_impl-specific items _tree->create(rf_fe_path / "sensors" / "lo_locked") .set_publisher( [this, dir]() { return this->get_fe_pll_locked(dir == TX_DIRECTION); }); _tree->access(rf_fe_path / "freq" / "value") .add_coerced_subscriber([this, key](const double freq) { return this->update_bandsel(key, freq); }) // Every time we retune, we re-set the power level. .add_coerced_subscriber([pwr_mgr = perif.pwr_mgr.at(dir_key)]( const double) { pwr_mgr->update_power(); }) ; _tree->create>(rf_fe_path / "antenna" / "options") .set(ants); // When we set the gain, we need to disable power tracking. Note that // the power manager also calls into the gains property, and thus // clobbers its own tracking mode, but that's OK because set_power() will // always reset the tracking mode. _tree ->access( rf_fe_path / "gains" / ad9361_ctrl::get_gain_names(key).at(0) / "value") .add_coerced_subscriber([pwr_mgr = perif.pwr_mgr.at(dir_key)](const double) { pwr_mgr->set_tracking_mode(pwr_cal_mgr::tracking_mode::TRACK_GAIN); }); if (_enable_user_regs) { _tree->create(rf_fe_path / "user_settings/iface") .set(perif.user_settings); } } } /*********************************************************************** * loopback tests **********************************************************************/ void b200_impl::register_loopback_self_test(wb_iface::sptr iface) { bool test_fail = false; UHD_LOGGER_INFO("B200") << "Performing register loopback test... "; size_t hash = size_t(time(NULL)); for (size_t i = 0; i < 100; i++) { boost::hash_combine(hash, i); iface->poke32(TOREG(SR_TEST), uint32_t(hash)); test_fail = iface->peek32(RB32_TEST) != uint32_t(hash); if (test_fail) break; // exit loop on any failure } UHD_LOGGER_INFO("B200") << "Register loopback test " << ((test_fail) ? "failed" : "passed"); } /*********************************************************************** * Sample and tick rate comprehension below **********************************************************************/ 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) { throw uhd::value_error(boost::str( boost::format("cannot not setup %d %s channels (maximum is %d)") % chan_count % (direction.empty() ? "data" : direction) % max_chans)); } else { const double max_tick_rate = ad9361_device_t::AD9361_MAX_CLOCK_RATE / ((chan_count <= 1) ? 1 : 2); if (tick_rate - max_tick_rate >= 1.0) { throw uhd::value_error(boost::str( boost::format( "current master clock rate (%.6f MHz) exceeds maximum possible " "master clock rate (%.6f MHz) when using %d %s channels") % (tick_rate / 1e6) % (max_tick_rate / 1e6) % chan_count % (direction.empty() ? "data" : direction))); } const double min_tick_rate = ad9361_device_t::AD9361_MIN_CLOCK_RATE / ((chan_count <= 1) ? 1 : 2); if (min_tick_rate - tick_rate >= 1.0) { throw uhd::value_error(boost::str( boost::format( "current master clock rate (%.6f MHz) is less than minimum possible " "master clock rate (%.6f MHz) when using %d %s channels") % (tick_rate / 1e6) % (min_tick_rate / 1e6) % chan_count % (direction.empty() ? "data" : direction))); } } } double b200_impl::set_tick_rate(const double new_tick_rate) { UHD_LOGGER_INFO("B200") << (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_LOGGER_INFO("B200") << "OK"; return _tick_rate; } _tick_rate = _codec_ctrl->set_clock_rate(new_tick_rate); UHD_LOGGER_INFO("B200") << (boost::format("Actually got clock rate %.6f MHz.") % (_tick_rate / 1e6)); for (radio_perifs_t& perif : _radio_perifs) { perif.time64->set_tick_rate(_tick_rate); perif.time64->self_test(); } return _tick_rate; } /*********************************************************************** * compat checks **********************************************************************/ void b200_impl::check_fw_compat(void) { uint16_t compat_num = _iface->get_compat_num(); uint32_t compat_major = (uint32_t)(compat_num >> 8); uint32_t compat_minor = (uint32_t)(compat_num & 0xFF); if (compat_major != B200_FW_COMPAT_NUM_MAJOR) { throw uhd::runtime_error(str( boost::format( "Expected firmware compatibility number %d.%d, but got %d.%d:\n" "The firmware build is not compatible with the host code build.\n" "%s") % int(B200_FW_COMPAT_NUM_MAJOR) % int(B200_FW_COMPAT_NUM_MINOR) % compat_major % compat_minor % print_utility_error("uhd_images_downloader.py"))); } _tree->create("/mboards/0/fw_version") .set(str(boost::format("%u.%u") % compat_major % compat_minor)); } void b200_impl::check_fpga_compat(void) { const uint64_t compat = _local_ctrl->peek64(0); const uint32_t signature = uint32_t(compat >> 32); const uint16_t compat_major = uint16_t(compat >> 16); const uint16_t compat_minor = uint16_t(compat & 0xffff); if (signature != 0xACE0BA5E) throw uhd::runtime_error( "b200::check_fpga_compat signature register readback failed"); const uint16_t expected = ((_product == B200MINI or _product == B205MINI) ? 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(expected) % compat_major % print_utility_error("uhd_images_downloader.py"))); } _tree->create("/mboards/0/fpga_version") .set(str(boost::format("%u.%u") % compat_major % compat_minor)); } /*********************************************************************** * Reference time and clock **********************************************************************/ void b200_impl::update_clock_source(const std::string& source) { // For B205, ref_sel selects whether or not to lock to the external clock source if (_product == B200MINI or _product == B205MINI) { 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") { 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; } // 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 if (source == "gpsdo") { if (not _gps or not _gps->gps_detected()) { throw uhd::key_error( "update_clock_source: gpsdo selected, but no gpsdo detected!"); } 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) { if ((_product == B200MINI or _product == B205MINI) 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 = NONE; else if (source == "internal") value = INTERNAL; else if (source == "external") 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_SYNC), value); _time_source = value; } } void b200_impl::set_time(const uhd::time_spec_t& t) { for (radio_perifs_t& perif : _radio_perifs) perif.time64->set_time_sync(t); _local_ctrl->poke32(TOREG(SR_CORE_SYNC), 1 << 2 | uint32_t(_time_source)); _local_ctrl->poke32(TOREG(SR_CORE_SYNC), _time_source); } void b200_impl::sync_times() { set_time(_radio_perifs[0].time64->get_time_now()); } /*********************************************************************** * GPIO setup **********************************************************************/ void b200_impl::update_bandsel(const std::string& which, double freq) { // B205 does not have bandsels if (_product == B200MINI or _product == B205MINI) { return; } if (which[0] == 'R') { if (freq < 2.2e9) { _gpio_state.rx_bandsel_a = 0; _gpio_state.rx_bandsel_b = 0; _gpio_state.rx_bandsel_c = 1; } else if ((freq >= 2.2e9) && (freq < 4e9)) { _gpio_state.rx_bandsel_a = 0; _gpio_state.rx_bandsel_b = 1; _gpio_state.rx_bandsel_c = 0; } else if ((freq >= 4e9) && (freq <= 6e9)) { _gpio_state.rx_bandsel_a = 1; _gpio_state.rx_bandsel_b = 0; _gpio_state.rx_bandsel_c = 0; } else { UHD_THROW_INVALID_CODE_PATH(); } } else if (which[0] == 'T') { if (freq < 2.5e9) { _gpio_state.tx_bandsel_a = 0; _gpio_state.tx_bandsel_b = 1; } else if ((freq >= 2.5e9) && (freq <= 6e9)) { _gpio_state.tx_bandsel_a = 1; _gpio_state.tx_bandsel_b = 0; } else { UHD_THROW_INVALID_CODE_PATH(); } } else { UHD_THROW_INVALID_CODE_PATH(); } update_gpio_state(); } void b200_impl::reset_codec() { _gpio_state.codec_arst = 1; update_gpio_state(); _gpio_state.codec_arst = 0; update_gpio_state(); } void b200_impl::update_gpio_state(void) { const uint32_t misc_word = 0 | (_gpio_state.swap_atr << 8) | (_gpio_state.tx_bandsel_a << 7) | (_gpio_state.tx_bandsel_b << 6) | (_gpio_state.rx_bandsel_a << 5) | (_gpio_state.rx_bandsel_b << 4) | (_gpio_state.rx_bandsel_c << 3) | (_gpio_state.codec_arst << 2) | (_gpio_state.mimo << 1) | (_gpio_state.ref_sel << 0); _local_ctrl->poke32(TOREG(SR_CORE_MISC), misc_word); } void b200_impl::update_atrs(void) { if (_radio_perifs.size() > _fe1 and _radio_perifs[_fe1].atr) { radio_perifs_t& perif = _radio_perifs[_fe1]; const bool enb_rx = bool(perif.rx_streamer.lock()); const bool enb_tx = bool(perif.tx_streamer.lock()); const bool is_rx2 = perif.ant_rx2; const uint32_t rxonly = (enb_rx) ? ((is_rx2) ? STATE_RX1_RX2 : STATE_RX1_TXRX) : STATE_OFF; const uint32_t txonly = (enb_tx) ? (STATE_TX1_TXRX) : STATE_OFF; uint32_t fd = STATE_OFF; if (enb_rx and enb_tx) fd = STATE_FDX1_TXRX; if (enb_rx and not enb_tx) fd = rxonly; if (not enb_rx and enb_tx) fd = txonly; gpio_atr_3000::sptr atr = perif.atr; atr->set_atr_reg(ATR_REG_IDLE, STATE_OFF); atr->set_atr_reg(ATR_REG_RX_ONLY, rxonly); atr->set_atr_reg(ATR_REG_TX_ONLY, txonly); atr->set_atr_reg(ATR_REG_FULL_DUPLEX, fd); } if (_radio_perifs.size() > _fe2 and _radio_perifs[_fe2].atr) { radio_perifs_t& perif = _radio_perifs[_fe2]; const bool enb_rx = bool(perif.rx_streamer.lock()); const bool enb_tx = bool(perif.tx_streamer.lock()); const bool is_rx2 = perif.ant_rx2; const uint32_t rxonly = (enb_rx) ? ((is_rx2) ? STATE_RX2_RX2 : STATE_RX2_TXRX) : STATE_OFF; const uint32_t txonly = (enb_tx) ? (STATE_TX2_TXRX) : STATE_OFF; uint32_t fd = STATE_OFF; if (enb_rx and enb_tx) fd = STATE_FDX2_TXRX; if (enb_rx and not enb_tx) fd = rxonly; if (not enb_rx and enb_tx) fd = txonly; gpio_atr_3000::sptr atr = perif.atr; atr->set_atr_reg(ATR_REG_IDLE, STATE_OFF); atr->set_atr_reg(ATR_REG_RX_ONLY, rxonly); atr->set_atr_reg(ATR_REG_TX_ONLY, txonly); atr->set_atr_reg(ATR_REG_FULL_DUPLEX, fd); } } void b200_impl::update_antenna_sel(const size_t which, const std::string& ant) { if (ant != "TX/RX" and ant != "RX2") throw uhd::value_error("b200: unknown RX antenna option: " + ant); _radio_perifs[which].ant_rx2 = (ant == "RX2"); this->update_atrs(); } void b200_impl::update_enables(void) { // extract settings from state variables const bool enb_tx1 = (_radio_perifs.size() > _fe1) and bool(_radio_perifs[_fe1].tx_streamer.lock()); const bool enb_rx1 = (_radio_perifs.size() > _fe1) and bool(_radio_perifs[_fe1].rx_streamer.lock()); const bool enb_tx2 = (_radio_perifs.size() > _fe2) and bool(_radio_perifs[_fe2].tx_streamer.lock()); const bool enb_rx2 = (_radio_perifs.size() > _fe2) and bool(_radio_perifs[_fe2].rx_streamer.lock()); const size_t num_rx = (enb_rx1 ? 1 : 0) + (enb_rx2 ? 1 : 0); const size_t num_tx = (enb_tx1 ? 1 : 0) + (enb_tx2 ? 1 : 0); const bool mimo = num_rx == 2 or num_tx == 2; if ((num_rx + num_tx) == 3) { throw uhd::runtime_error( "b200: 2 RX 1 TX and 1 RX 2 TX configurations not possible"); } // setup the active chains in the codec _codec_ctrl->set_active_chains(enb_tx1, enb_tx2, enb_rx1, enb_rx2); if ((num_rx + num_tx) == 0) _codec_ctrl->set_active_chains(true, false, true, false); // enable something // figure out if mimo is enabled based on new state _gpio_state.mimo = (mimo) ? 1 : 0; update_gpio_state(); // atrs change based on enables this->update_atrs(); } sensor_value_t b200_impl::get_ref_locked(void) { const bool lock = (_local_ctrl->peek32(RB32_CORE_MISC) & 0x1) == 0x1; return sensor_value_t("Ref", lock, "locked", "unlocked"); } sensor_value_t b200_impl::get_fe_pll_locked(const bool is_tx) { const uint32_t st = _local_ctrl->peek32(RB32_CORE_PLL); const bool locked = is_tx ? ((st & 0x1) > 0) : ((st & 0x2) > 0); return sensor_value_t("LO", locked, "locked", "unlocked"); }