diff options
Diffstat (limited to 'host/lib/usrp')
150 files changed, 16492 insertions, 3354 deletions
diff --git a/host/lib/usrp/CMakeLists.txt b/host/lib/usrp/CMakeLists.txt index 5c9592970..f769417d9 100644 --- a/host/lib/usrp/CMakeLists.txt +++ b/host/lib/usrp/CMakeLists.txt @@ -18,8 +18,6 @@ ######################################################################## # This file included, use CMake directory variables ######################################################################## -find_package(GPSD) - INCLUDE_DIRECTORIES(${CMAKE_CURRENT_SOURCE_DIR}) LIBUHD_APPEND_SOURCES( @@ -32,6 +30,7 @@ LIBUHD_APPEND_SOURCES( ${CMAKE_CURRENT_SOURCE_DIR}/mboard_eeprom.cpp ${CMAKE_CURRENT_SOURCE_DIR}/multi_usrp.cpp ${CMAKE_CURRENT_SOURCE_DIR}/subdev_spec.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/fe_connection.cpp ) IF(ENABLE_C_API) @@ -43,8 +42,6 @@ IF(ENABLE_C_API) ) ENDIF(ENABLE_C_API) -LIBUHD_REGISTER_COMPONENT("GPSD" ENABLE_GPSD OFF "ENABLE_LIBUHD;ENABLE_GPSD;LIBGPS_FOUND" OFF OFF) - IF(ENABLE_GPSD) LIBUHD_APPEND_SOURCES( ${CMAKE_CURRENT_SOURCE_DIR}/gpsd_iface.cpp @@ -55,6 +52,7 @@ ENDIF(ENABLE_GPSD) INCLUDE_SUBDIRECTORY(cores) INCLUDE_SUBDIRECTORY(dboard) INCLUDE_SUBDIRECTORY(common) +INCLUDE_SUBDIRECTORY(device3) INCLUDE_SUBDIRECTORY(usrp1) INCLUDE_SUBDIRECTORY(usrp2) INCLUDE_SUBDIRECTORY(b100) @@ -62,3 +60,4 @@ INCLUDE_SUBDIRECTORY(e100) INCLUDE_SUBDIRECTORY(e300) INCLUDE_SUBDIRECTORY(x300) INCLUDE_SUBDIRECTORY(b200) +INCLUDE_SUBDIRECTORY(n230) diff --git a/host/lib/usrp/b100/CMakeLists.txt b/host/lib/usrp/b100/CMakeLists.txt index 1558cd974..66129458c 100644 --- a/host/lib/usrp/b100/CMakeLists.txt +++ b/host/lib/usrp/b100/CMakeLists.txt @@ -22,8 +22,6 @@ ######################################################################## # Conditionally configure the B100 support ######################################################################## -LIBUHD_REGISTER_COMPONENT("B100" ENABLE_B100 ON "ENABLE_LIBUHD;ENABLE_USB" OFF OFF) - IF(ENABLE_B100) LIBUHD_APPEND_SOURCES( ${CMAKE_CURRENT_SOURCE_DIR}/b100_impl.cpp diff --git a/host/lib/usrp/b100/b100_impl.cpp b/host/lib/usrp/b100/b100_impl.cpp index c4279913c..eec9f0e9a 100644 --- a/host/lib/usrp/b100/b100_impl.cpp +++ b/host/lib/usrp/b100/b100_impl.cpp @@ -281,7 +281,7 @@ b100_impl::b100_impl(const device_addr_t &device_addr){ _tree->create<std::string>(mb_path / "name").set("B100"); _tree->create<std::string>(mb_path / "codename").set("B-Hundo"); _tree->create<std::string>(mb_path / "load_eeprom") - .subscribe(boost::bind(&fx2_ctrl::usrp_load_eeprom, _fx2_ctrl, _1)); + .add_coerced_subscriber(boost::bind(&fx2_ctrl::usrp_load_eeprom, _fx2_ctrl, _1)); //////////////////////////////////////////////////////////////////// // setup the mboard eeprom @@ -289,20 +289,20 @@ b100_impl::b100_impl(const device_addr_t &device_addr){ const mboard_eeprom_t mb_eeprom(*_fx2_ctrl, B100_EEPROM_MAP_KEY); _tree->create<mboard_eeprom_t>(mb_path / "eeprom") .set(mb_eeprom) - .subscribe(boost::bind(&b100_impl::set_mb_eeprom, this, _1)); + .add_coerced_subscriber(boost::bind(&b100_impl::set_mb_eeprom, this, _1)); //////////////////////////////////////////////////////////////////// // create clock control objects //////////////////////////////////////////////////////////////////// //^^^ clock created up top, just reg props here... ^^^ _tree->create<double>(mb_path / "tick_rate") - .publish(boost::bind(&b100_clock_ctrl::get_fpga_clock_rate, _clock_ctrl)) - .subscribe(boost::bind(&fifo_ctrl_excelsior::set_tick_rate, _fifo_ctrl, _1)) - .subscribe(boost::bind(&b100_impl::update_tick_rate, this, _1)); + .set_publisher(boost::bind(&b100_clock_ctrl::get_fpga_clock_rate, _clock_ctrl)) + .add_coerced_subscriber(boost::bind(&fifo_ctrl_excelsior::set_tick_rate, _fifo_ctrl, _1)) + .add_coerced_subscriber(boost::bind(&b100_impl::update_tick_rate, this, _1)); - //subscribe the command time while we are at it + //add_coerced_subscriber the command time while we are at it _tree->create<time_spec_t>(mb_path / "time/cmd") - .subscribe(boost::bind(&fifo_ctrl_excelsior::set_time, _fifo_ctrl, _1)); + .add_coerced_subscriber(boost::bind(&fifo_ctrl_excelsior::set_time, _fifo_ctrl, _1)); //////////////////////////////////////////////////////////////////// // create codec control objects @@ -313,20 +313,20 @@ b100_impl::b100_impl(const device_addr_t &device_addr){ _tree->create<std::string>(rx_codec_path / "name").set("ad9522"); _tree->create<meta_range_t>(rx_codec_path / "gains/pga/range").set(b100_codec_ctrl::rx_pga_gain_range); _tree->create<double>(rx_codec_path / "gains/pga/value") - .coerce(boost::bind(&b100_impl::update_rx_codec_gain, this, _1)) + .set_coercer(boost::bind(&b100_impl::update_rx_codec_gain, this, _1)) .set(0.0); _tree->create<std::string>(tx_codec_path / "name").set("ad9522"); _tree->create<meta_range_t>(tx_codec_path / "gains/pga/range").set(b100_codec_ctrl::tx_pga_gain_range); _tree->create<double>(tx_codec_path / "gains/pga/value") - .subscribe(boost::bind(&b100_codec_ctrl::set_tx_pga_gain, _codec_ctrl, _1)) - .publish(boost::bind(&b100_codec_ctrl::get_tx_pga_gain, _codec_ctrl)) + .add_coerced_subscriber(boost::bind(&b100_codec_ctrl::set_tx_pga_gain, _codec_ctrl, _1)) + .set_publisher(boost::bind(&b100_codec_ctrl::get_tx_pga_gain, _codec_ctrl)) .set(0.0); //////////////////////////////////////////////////////////////////// // and do the misc mboard sensors //////////////////////////////////////////////////////////////////// _tree->create<sensor_value_t>(mb_path / "sensors/ref_locked") - .publish(boost::bind(&b100_impl::get_ref_locked, this)); + .set_publisher(boost::bind(&b100_impl::get_ref_locked, this)); //////////////////////////////////////////////////////////////////// // create frontend control objects @@ -335,27 +335,27 @@ b100_impl::b100_impl(const device_addr_t &device_addr){ _tx_fe = tx_frontend_core_200::make(_fifo_ctrl, TOREG(SR_TX_FE)); _tree->create<subdev_spec_t>(mb_path / "rx_subdev_spec") - .subscribe(boost::bind(&b100_impl::update_rx_subdev_spec, this, _1)); + .add_coerced_subscriber(boost::bind(&b100_impl::update_rx_subdev_spec, this, _1)); _tree->create<subdev_spec_t>(mb_path / "tx_subdev_spec") - .subscribe(boost::bind(&b100_impl::update_tx_subdev_spec, this, _1)); + .add_coerced_subscriber(boost::bind(&b100_impl::update_tx_subdev_spec, this, _1)); const fs_path rx_fe_path = mb_path / "rx_frontends" / "A"; const fs_path tx_fe_path = mb_path / "tx_frontends" / "A"; _tree->create<std::complex<double> >(rx_fe_path / "dc_offset" / "value") - .coerce(boost::bind(&rx_frontend_core_200::set_dc_offset, _rx_fe, _1)) + .set_coercer(boost::bind(&rx_frontend_core_200::set_dc_offset, _rx_fe, _1)) .set(std::complex<double>(0.0, 0.0)); _tree->create<bool>(rx_fe_path / "dc_offset" / "enable") - .subscribe(boost::bind(&rx_frontend_core_200::set_dc_offset_auto, _rx_fe, _1)) + .add_coerced_subscriber(boost::bind(&rx_frontend_core_200::set_dc_offset_auto, _rx_fe, _1)) .set(true); _tree->create<std::complex<double> >(rx_fe_path / "iq_balance" / "value") - .subscribe(boost::bind(&rx_frontend_core_200::set_iq_balance, _rx_fe, _1)) + .add_coerced_subscriber(boost::bind(&rx_frontend_core_200::set_iq_balance, _rx_fe, _1)) .set(std::complex<double>(0.0, 0.0)); _tree->create<std::complex<double> >(tx_fe_path / "dc_offset" / "value") - .coerce(boost::bind(&tx_frontend_core_200::set_dc_offset, _tx_fe, _1)) + .set_coercer(boost::bind(&tx_frontend_core_200::set_dc_offset, _tx_fe, _1)) .set(std::complex<double>(0.0, 0.0)); _tree->create<std::complex<double> >(tx_fe_path / "iq_balance" / "value") - .subscribe(boost::bind(&tx_frontend_core_200::set_iq_balance, _tx_fe, _1)) + .add_coerced_subscriber(boost::bind(&tx_frontend_core_200::set_iq_balance, _tx_fe, _1)) .set(std::complex<double>(0.0, 0.0)); //////////////////////////////////////////////////////////////////// @@ -374,20 +374,20 @@ b100_impl::b100_impl(const device_addr_t &device_addr){ _rx_dsps[dspno]->set_link_rate(B100_LINK_RATE_BPS); _tree->access<double>(mb_path / "tick_rate") - .subscribe(boost::bind(&rx_dsp_core_200::set_tick_rate, _rx_dsps[dspno], _1)); + .add_coerced_subscriber(boost::bind(&rx_dsp_core_200::set_tick_rate, _rx_dsps[dspno], _1)); fs_path rx_dsp_path = mb_path / str(boost::format("rx_dsps/%u") % dspno); _tree->create<meta_range_t>(rx_dsp_path / "rate/range") - .publish(boost::bind(&rx_dsp_core_200::get_host_rates, _rx_dsps[dspno])); + .set_publisher(boost::bind(&rx_dsp_core_200::get_host_rates, _rx_dsps[dspno])); _tree->create<double>(rx_dsp_path / "rate/value") .set(1e6) //some default - .coerce(boost::bind(&rx_dsp_core_200::set_host_rate, _rx_dsps[dspno], _1)) - .subscribe(boost::bind(&b100_impl::update_rx_samp_rate, this, dspno, _1)); + .set_coercer(boost::bind(&rx_dsp_core_200::set_host_rate, _rx_dsps[dspno], _1)) + .add_coerced_subscriber(boost::bind(&b100_impl::update_rx_samp_rate, this, dspno, _1)); _tree->create<double>(rx_dsp_path / "freq/value") - .coerce(boost::bind(&rx_dsp_core_200::set_freq, _rx_dsps[dspno], _1)); + .set_coercer(boost::bind(&rx_dsp_core_200::set_freq, _rx_dsps[dspno], _1)); _tree->create<meta_range_t>(rx_dsp_path / "freq/range") - .publish(boost::bind(&rx_dsp_core_200::get_freq_range, _rx_dsps[dspno])); + .set_publisher(boost::bind(&rx_dsp_core_200::get_freq_range, _rx_dsps[dspno])); _tree->create<stream_cmd_t>(rx_dsp_path / "stream_cmd") - .subscribe(boost::bind(&rx_dsp_core_200::issue_stream_command, _rx_dsps[dspno], _1)); + .add_coerced_subscriber(boost::bind(&rx_dsp_core_200::issue_stream_command, _rx_dsps[dspno], _1)); } //////////////////////////////////////////////////////////////////// @@ -398,17 +398,17 @@ b100_impl::b100_impl(const device_addr_t &device_addr){ ); _tx_dsp->set_link_rate(B100_LINK_RATE_BPS); _tree->access<double>(mb_path / "tick_rate") - .subscribe(boost::bind(&tx_dsp_core_200::set_tick_rate, _tx_dsp, _1)); + .add_coerced_subscriber(boost::bind(&tx_dsp_core_200::set_tick_rate, _tx_dsp, _1)); _tree->create<meta_range_t>(mb_path / "tx_dsps/0/rate/range") - .publish(boost::bind(&tx_dsp_core_200::get_host_rates, _tx_dsp)); + .set_publisher(boost::bind(&tx_dsp_core_200::get_host_rates, _tx_dsp)); _tree->create<double>(mb_path / "tx_dsps/0/rate/value") .set(1e6) //some default - .coerce(boost::bind(&tx_dsp_core_200::set_host_rate, _tx_dsp, _1)) - .subscribe(boost::bind(&b100_impl::update_tx_samp_rate, this, 0, _1)); + .set_coercer(boost::bind(&tx_dsp_core_200::set_host_rate, _tx_dsp, _1)) + .add_coerced_subscriber(boost::bind(&b100_impl::update_tx_samp_rate, this, 0, _1)); _tree->create<double>(mb_path / "tx_dsps/0/freq/value") - .coerce(boost::bind(&tx_dsp_core_200::set_freq, _tx_dsp, _1)); + .set_coercer(boost::bind(&tx_dsp_core_200::set_freq, _tx_dsp, _1)); _tree->create<meta_range_t>(mb_path / "tx_dsps/0/freq/range") - .publish(boost::bind(&tx_dsp_core_200::get_freq_range, _tx_dsp)); + .set_publisher(boost::bind(&tx_dsp_core_200::get_freq_range, _tx_dsp)); //////////////////////////////////////////////////////////////////// // create time control objects @@ -422,21 +422,21 @@ b100_impl::b100_impl(const device_addr_t &device_addr){ _fifo_ctrl, TOREG(SR_TIME64), time64_rb_bases ); _tree->access<double>(mb_path / "tick_rate") - .subscribe(boost::bind(&time64_core_200::set_tick_rate, _time64, _1)); + .add_coerced_subscriber(boost::bind(&time64_core_200::set_tick_rate, _time64, _1)); _tree->create<time_spec_t>(mb_path / "time/now") - .publish(boost::bind(&time64_core_200::get_time_now, _time64)) - .subscribe(boost::bind(&time64_core_200::set_time_now, _time64, _1)); + .set_publisher(boost::bind(&time64_core_200::get_time_now, _time64)) + .add_coerced_subscriber(boost::bind(&time64_core_200::set_time_now, _time64, _1)); _tree->create<time_spec_t>(mb_path / "time/pps") - .publish(boost::bind(&time64_core_200::get_time_last_pps, _time64)) - .subscribe(boost::bind(&time64_core_200::set_time_next_pps, _time64, _1)); + .set_publisher(boost::bind(&time64_core_200::get_time_last_pps, _time64)) + .add_coerced_subscriber(boost::bind(&time64_core_200::set_time_next_pps, _time64, _1)); //setup time source props _tree->create<std::string>(mb_path / "time_source/value") - .subscribe(boost::bind(&time64_core_200::set_time_source, _time64, _1)); + .add_coerced_subscriber(boost::bind(&time64_core_200::set_time_source, _time64, _1)); _tree->create<std::vector<std::string> >(mb_path / "time_source/options") - .publish(boost::bind(&time64_core_200::get_time_sources, _time64)); + .set_publisher(boost::bind(&time64_core_200::get_time_sources, _time64)); //setup reference source props _tree->create<std::string>(mb_path / "clock_source/value") - .subscribe(boost::bind(&b100_impl::update_clock_source, this, _1)); + .add_coerced_subscriber(boost::bind(&b100_impl::update_clock_source, this, _1)); static const std::vector<std::string> clock_sources = boost::assign::list_of("internal")("external")("auto"); _tree->create<std::vector<std::string> >(mb_path / "clock_source/options").set(clock_sources); @@ -445,7 +445,7 @@ b100_impl::b100_impl(const device_addr_t &device_addr){ //////////////////////////////////////////////////////////////////// _user = user_settings_core_200::make(_fifo_ctrl, TOREG(SR_USER_REGS)); _tree->create<user_settings_core_200::user_reg_t>(mb_path / "user/regs") - .subscribe(boost::bind(&user_settings_core_200::set_reg, _user, _1)); + .add_coerced_subscriber(boost::bind(&user_settings_core_200::set_reg, _user, _1)); //////////////////////////////////////////////////////////////////// // create dboard control objects @@ -463,32 +463,31 @@ b100_impl::b100_impl(const device_addr_t &device_addr){ //create the properties and register subscribers _tree->create<dboard_eeprom_t>(mb_path / "dboards/A/rx_eeprom") .set(rx_db_eeprom) - .subscribe(boost::bind(&b100_impl::set_db_eeprom, this, "rx", _1)); + .add_coerced_subscriber(boost::bind(&b100_impl::set_db_eeprom, this, "rx", _1)); _tree->create<dboard_eeprom_t>(mb_path / "dboards/A/tx_eeprom") .set(tx_db_eeprom) - .subscribe(boost::bind(&b100_impl::set_db_eeprom, this, "tx", _1)); + .add_coerced_subscriber(boost::bind(&b100_impl::set_db_eeprom, this, "tx", _1)); _tree->create<dboard_eeprom_t>(mb_path / "dboards/A/gdb_eeprom") .set(gdb_eeprom) - .subscribe(boost::bind(&b100_impl::set_db_eeprom, this, "gdb", _1)); + .add_coerced_subscriber(boost::bind(&b100_impl::set_db_eeprom, this, "gdb", _1)); //create a new dboard interface and manager - _dboard_iface = make_b100_dboard_iface(_fifo_ctrl, _fpga_i2c_ctrl, _fifo_ctrl/*spi*/, _clock_ctrl, _codec_ctrl); - _tree->create<dboard_iface::sptr>(mb_path / "dboards/A/iface").set(_dboard_iface); _dboard_manager = dboard_manager::make( rx_db_eeprom.id, tx_db_eeprom.id, gdb_eeprom.id, - _dboard_iface, _tree->subtree(mb_path / "dboards/A") + make_b100_dboard_iface(_fifo_ctrl, _fpga_i2c_ctrl, _fifo_ctrl/*spi*/, _clock_ctrl, _codec_ctrl), + _tree->subtree(mb_path / "dboards/A") ); //bind frontend corrections to the dboard freq props const fs_path db_tx_fe_path = mb_path / "dboards" / "A" / "tx_frontends"; BOOST_FOREACH(const std::string &name, _tree->list(db_tx_fe_path)){ _tree->access<double>(db_tx_fe_path / name / "freq" / "value") - .subscribe(boost::bind(&b100_impl::set_tx_fe_corrections, this, _1)); + .add_coerced_subscriber(boost::bind(&b100_impl::set_tx_fe_corrections, this, _1)); } const fs_path db_rx_fe_path = mb_path / "dboards" / "A" / "rx_frontends"; BOOST_FOREACH(const std::string &name, _tree->list(db_rx_fe_path)){ _tree->access<double>(db_rx_fe_path / name / "freq" / "value") - .subscribe(boost::bind(&b100_impl::set_rx_fe_corrections, this, _1)); + .add_coerced_subscriber(boost::bind(&b100_impl::set_rx_fe_corrections, this, _1)); } //initialize io handling @@ -503,8 +502,8 @@ b100_impl::b100_impl(const device_addr_t &device_addr){ //////////////////////////////////////////////////////////////////// this->update_rates(); - _tree->access<double>(mb_path / "tick_rate") //now subscribe the clock rate setter - .subscribe(boost::bind(&b100_clock_ctrl::set_fpga_clock_rate, _clock_ctrl, _1)); + _tree->access<double>(mb_path / "tick_rate") //now add_coerced_subscriber the clock rate setter + .add_coerced_subscriber(boost::bind(&b100_clock_ctrl::set_fpga_clock_rate, _clock_ctrl, _1)); //reset cordic rates and their properties to zero BOOST_FOREACH(const std::string &name, _tree->list(mb_path / "rx_dsps")){ diff --git a/host/lib/usrp/b100/b100_impl.hpp b/host/lib/usrp/b100/b100_impl.hpp index 5a8f70d73..7f37030d2 100644 --- a/host/lib/usrp/b100/b100_impl.hpp +++ b/host/lib/usrp/b100/b100_impl.hpp @@ -126,7 +126,6 @@ private: //dboard stuff uhd::usrp::dboard_manager::sptr _dboard_manager; - uhd::usrp::dboard_iface::sptr _dboard_iface; bool _ignore_cal_file; std::vector<boost::weak_ptr<uhd::rx_streamer> > _rx_streamers; diff --git a/host/lib/usrp/b100/dboard_iface.cpp b/host/lib/usrp/b100/dboard_iface.cpp index 325efeec1..9829f3f09 100644 --- a/host/lib/usrp/b100/dboard_iface.cpp +++ b/host/lib/usrp/b100/dboard_iface.cpp @@ -1,5 +1,5 @@ // -// Copyright 2011 Ettus Research LLC +// Copyright 2011,2015,2016 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 @@ -66,12 +66,16 @@ public: void write_aux_dac(unit_t, aux_dac_t, double); double read_aux_adc(unit_t, aux_adc_t); - void _set_pin_ctrl(unit_t, boost::uint16_t); - void _set_atr_reg(unit_t, atr_reg_t, boost::uint16_t); - void _set_gpio_ddr(unit_t, boost::uint16_t); - void _set_gpio_out(unit_t, boost::uint16_t); - void set_gpio_debug(unit_t, int); - boost::uint16_t read_gpio(unit_t); + void set_pin_ctrl(unit_t unit, boost::uint32_t value, boost::uint32_t mask = 0xffffffff); + boost::uint32_t get_pin_ctrl(unit_t unit); + void set_atr_reg(unit_t unit, atr_reg_t reg, boost::uint32_t value, boost::uint32_t mask = 0xffffffff); + boost::uint32_t get_atr_reg(unit_t unit, atr_reg_t reg); + void set_gpio_ddr(unit_t unit, boost::uint32_t value, boost::uint32_t mask = 0xffffffff); + boost::uint32_t get_gpio_ddr(unit_t unit); + void set_gpio_out(unit_t unit, boost::uint32_t value, boost::uint32_t mask = 0xffffffff); + boost::uint32_t get_gpio_out(unit_t unit); + boost::uint32_t read_gpio(unit_t unit); + void set_command_time(const uhd::time_spec_t& t); uhd::time_spec_t get_command_time(void); @@ -97,6 +101,7 @@ public: double get_clock_rate(unit_t); void set_clock_enabled(unit_t, bool); double get_codec_rate(unit_t); + void set_fe_connection(unit_t unit, const std::string&, const fe_connection_t& fe_conn); private: timed_wb_iface::sptr _wb_iface; @@ -127,6 +132,7 @@ void b100_dboard_iface::set_clock_rate(unit_t unit, double rate){ switch(unit){ case UNIT_RX: return _clock->set_rx_dboard_clock_rate(rate); case UNIT_TX: return _clock->set_tx_dboard_clock_rate(rate); + case UNIT_BOTH: set_clock_rate(UNIT_RX, rate); set_clock_rate(UNIT_TX, rate); return; } } @@ -142,14 +148,15 @@ double b100_dboard_iface::get_clock_rate(unit_t unit){ switch(unit){ case UNIT_RX: return _clock->get_rx_clock_rate(); case UNIT_TX: return _clock->get_tx_clock_rate(); + default: UHD_THROW_INVALID_CODE_PATH(); } - UHD_THROW_INVALID_CODE_PATH(); } void b100_dboard_iface::set_clock_enabled(unit_t unit, bool enb){ switch(unit){ case UNIT_RX: return _clock->enable_rx_dboard_clock(enb); case UNIT_TX: return _clock->enable_tx_dboard_clock(enb); + case UNIT_BOTH: set_clock_enabled(UNIT_RX, enb); set_clock_enabled(UNIT_TX, enb); return; } } @@ -160,28 +167,40 @@ double b100_dboard_iface::get_codec_rate(unit_t){ /*********************************************************************** * GPIO **********************************************************************/ -void b100_dboard_iface::_set_pin_ctrl(unit_t unit, boost::uint16_t value){ - return _gpio->set_pin_ctrl(unit, value); +void b100_dboard_iface::set_pin_ctrl(unit_t unit, boost::uint32_t value, boost::uint32_t mask){ + _gpio->set_pin_ctrl(unit, static_cast<boost::uint16_t>(value), static_cast<boost::uint16_t>(mask)); } -void b100_dboard_iface::_set_gpio_ddr(unit_t unit, boost::uint16_t value){ - return _gpio->set_gpio_ddr(unit, value); +boost::uint32_t b100_dboard_iface::get_pin_ctrl(unit_t unit){ + return static_cast<boost::uint32_t>(_gpio->get_pin_ctrl(unit)); } -void b100_dboard_iface::_set_gpio_out(unit_t unit, boost::uint16_t value){ - return _gpio->set_gpio_out(unit, value); +void b100_dboard_iface::set_atr_reg(unit_t unit, atr_reg_t reg, boost::uint32_t value, boost::uint32_t mask){ + _gpio->set_atr_reg(unit, reg, static_cast<boost::uint16_t>(value), static_cast<boost::uint16_t>(mask)); } -boost::uint16_t b100_dboard_iface::read_gpio(unit_t unit){ - return _gpio->read_gpio(unit); +boost::uint32_t b100_dboard_iface::get_atr_reg(unit_t unit, atr_reg_t reg){ + return static_cast<boost::uint32_t>(_gpio->get_atr_reg(unit, reg)); +} + +void b100_dboard_iface::set_gpio_ddr(unit_t unit, boost::uint32_t value, boost::uint32_t mask){ + _gpio->set_gpio_ddr(unit, static_cast<boost::uint16_t>(value), static_cast<boost::uint16_t>(mask)); +} + +boost::uint32_t b100_dboard_iface::get_gpio_ddr(unit_t unit){ + return static_cast<boost::uint32_t>(_gpio->get_gpio_ddr(unit)); } -void b100_dboard_iface::_set_atr_reg(unit_t unit, atr_reg_t atr, boost::uint16_t value){ - return _gpio->set_atr_reg(unit, atr, value); +void b100_dboard_iface::set_gpio_out(unit_t unit, boost::uint32_t value, boost::uint32_t mask){ + _gpio->set_gpio_out(unit, static_cast<boost::uint16_t>(value), static_cast<boost::uint16_t>(mask)); } -void b100_dboard_iface::set_gpio_debug(unit_t, int){ - throw uhd::not_implemented_error("no set_gpio_debug implemented"); +boost::uint32_t b100_dboard_iface::get_gpio_out(unit_t unit){ + return static_cast<boost::uint32_t>(_gpio->get_gpio_out(unit)); +} + +boost::uint32_t b100_dboard_iface::read_gpio(unit_t unit){ + return _gpio->read_gpio(unit); } /*********************************************************************** @@ -196,8 +215,8 @@ static boost::uint32_t unit_to_otw_spi_dev(dboard_iface::unit_t unit){ switch(unit){ case dboard_iface::UNIT_TX: return B100_SPI_SS_TX_DB; case dboard_iface::UNIT_RX: return B100_SPI_SS_RX_DB; + default: UHD_THROW_INVALID_CODE_PATH(); } - UHD_THROW_INVALID_CODE_PATH(); } void b100_dboard_iface::write_spi( @@ -268,3 +287,8 @@ uhd::time_spec_t b100_dboard_iface::get_command_time(void) { return _wb_iface->get_time(); } + +void b100_dboard_iface::set_fe_connection(unit_t, const std::string&, const fe_connection_t&) +{ + throw uhd::not_implemented_error("fe connection configuration support not implemented"); +} diff --git a/host/lib/usrp/b200/CMakeLists.txt b/host/lib/usrp/b200/CMakeLists.txt index 76710dc65..4b9e2de55 100644 --- a/host/lib/usrp/b200/CMakeLists.txt +++ b/host/lib/usrp/b200/CMakeLists.txt @@ -22,8 +22,6 @@ ######################################################################## # Conditionally configure the B200 support ######################################################################## -LIBUHD_REGISTER_COMPONENT("B200" ENABLE_B200 ON "ENABLE_LIBUHD;ENABLE_USB" OFF OFF) - IF(ENABLE_B200) LIBUHD_APPEND_SOURCES( ${CMAKE_CURRENT_SOURCE_DIR}/b200_image_loader.cpp diff --git a/host/lib/usrp/b200/b200_iface.cpp b/host/lib/usrp/b200/b200_iface.cpp index 207c418fc..218f8fd0e 100644 --- a/host/lib/usrp/b200/b200_iface.cpp +++ b/host/lib/usrp/b200/b200_iface.cpp @@ -142,7 +142,7 @@ public: boost::uint16_t index, unsigned char *buff, boost::uint16_t length, - boost::int32_t timeout = 0) { + boost::uint32_t timeout = 0) { return _usb_ctrl->submit(VRT_VENDOR_OUT, // bmReqeustType request, // bRequest value, // wValue @@ -157,7 +157,7 @@ public: boost::uint16_t index, unsigned char *buff, boost::uint16_t length, - boost::int32_t timeout = 0) { + boost::uint32_t timeout = 0) { return _usb_ctrl->submit(VRT_VENDOR_IN, // bmReqeustType request, // bRequest value, // wValue diff --git a/host/lib/usrp/b200/b200_impl.cpp b/host/lib/usrp/b200/b200_impl.cpp index d7663c68e..aed1204d3 100644 --- a/host/lib/usrp/b200/b200_impl.cpp +++ b/host/lib/usrp/b200/b200_impl.cpp @@ -42,6 +42,7 @@ using namespace uhd; using namespace uhd::usrp; +using namespace uhd::usrp::gpio_atr; using namespace uhd::transport; static const boost::posix_time::milliseconds REENUMERATION_TIMEOUT_MS(3000); @@ -364,7 +365,7 @@ b200_impl::b200_impl(const uhd::device_addr_t& device_addr, usb_device_handle::s const mboard_eeprom_t mb_eeprom(*_iface, "B200"); _tree->create<mboard_eeprom_t>(mb_path / "eeprom") .set(mb_eeprom) - .subscribe(boost::bind(&b200_impl::set_mb_eeprom, this, _1)); + .add_coerced_subscriber(boost::bind(&b200_impl::set_mb_eeprom, this, _1)); //////////////////////////////////////////////////////////////////// // Identify the device type @@ -502,7 +503,7 @@ b200_impl::b200_impl(const uhd::device_addr_t& device_addr, usb_device_handle::s 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)); + .set_publisher(boost::bind(&gps_ctrl::get_sensor, _gps, name)); } } else @@ -579,9 +580,9 @@ b200_impl::b200_impl(const uhd::device_addr_t& device_addr, usb_device_handle::s // create clock control objects //////////////////////////////////////////////////////////////////// _tree->create<double>(mb_path / "tick_rate") - .coerce(boost::bind(&b200_impl::set_tick_rate, this, _1)) - .publish(boost::bind(&b200_impl::get_tick_rate, this)) - .subscribe(boost::bind(&b200_impl::update_tick_rate, this, _1)); + .set_coercer(boost::bind(&b200_impl::set_tick_rate, this, _1)) + .set_publisher(boost::bind(&b200_impl::get_tick_rate, this)) + .add_coerced_subscriber(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); @@ -589,7 +590,7 @@ b200_impl::b200_impl(const uhd::device_addr_t& device_addr, usb_device_handle::s // and do the misc mboard sensors //////////////////////////////////////////////////////////////////// _tree->create<sensor_value_t>(mb_path / "sensors" / "ref_locked") - .publish(boost::bind(&b200_impl::get_ref_locked, this)); + .set_publisher(boost::bind(&b200_impl::get_ref_locked, this)); //////////////////////////////////////////////////////////////////// // create frontend mapping @@ -598,13 +599,13 @@ b200_impl::b200_impl(const uhd::device_addr_t& device_addr, usb_device_handle::s _tree->create<std::vector<size_t> >(mb_path / "rx_chan_dsp_mapping").set(default_map); _tree->create<std::vector<size_t> >(mb_path / "tx_chan_dsp_mapping").set(default_map); _tree->create<subdev_spec_t>(mb_path / "rx_subdev_spec") - .coerce(boost::bind(&b200_impl::coerce_subdev_spec, this, _1)) + .set_coercer(boost::bind(&b200_impl::coerce_subdev_spec, this, _1)) .set(subdev_spec_t()) - .subscribe(boost::bind(&b200_impl::update_subdev_spec, this, "rx", _1)); + .add_coerced_subscriber(boost::bind(&b200_impl::update_subdev_spec, this, "rx", _1)); _tree->create<subdev_spec_t>(mb_path / "tx_subdev_spec") - .coerce(boost::bind(&b200_impl::coerce_subdev_spec, this, _1)) + .set_coercer(boost::bind(&b200_impl::coerce_subdev_spec, this, _1)) .set(subdev_spec_t()) - .subscribe(boost::bind(&b200_impl::update_subdev_spec, this, "tx", _1)); + .add_coerced_subscriber(boost::bind(&b200_impl::update_subdev_spec, this, "tx", _1)); //////////////////////////////////////////////////////////////////// // setup radio control @@ -619,27 +620,31 @@ b200_impl::b200_impl(const uhd::device_addr_t& device_addr, usb_device_handle::s 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 BOOST_FOREACH(radio_perifs_t &perif, _radio_perifs) { - _codec_mgr->loopback_self_test(perif.ctrl, TOREG(SR_CODEC_IDLE), RB64_CODEC_READBACK); + _codec_mgr->loopback_self_test( + boost::bind( + &radio_ctrl_core_3000::poke32, perif.ctrl, TOREG(SR_CODEC_IDLE), _1 + ), + boost::bind(&radio_ctrl_core_3000::peek64, perif.ctrl, RB64_CODEC_READBACK) + ); } //register time now and pps onto available radio cores _tree->create<time_spec_t>(mb_path / "time" / "now") - .publish(boost::bind(&time_core_3000::get_time_now, _radio_perifs[0].time64)) - .subscribe(boost::bind(&b200_impl::set_time, this, _1)) + .set_publisher(boost::bind(&time_core_3000::get_time_now, _radio_perifs[0].time64)) + .add_coerced_subscriber(boost::bind(&b200_impl::set_time, this, _1)) .set(0.0); //re-sync the times when the tick rate changes _tree->access<double>(mb_path / "tick_rate") - .subscribe(boost::bind(&b200_impl::sync_times, this)); + .add_coerced_subscriber(boost::bind(&b200_impl::sync_times, this)); _tree->create<time_spec_t>(mb_path / "time" / "pps") - .publish(boost::bind(&time_core_3000::get_time_last_pps, _radio_perifs[0].time64)); + .set_publisher(boost::bind(&time_core_3000::get_time_last_pps, _radio_perifs[0].time64)); BOOST_FOREACH(radio_perifs_t &perif, _radio_perifs) { _tree->access<time_spec_t>(mb_path / "time" / "pps") - .subscribe(boost::bind(&time_core_3000::set_time_next_pps, perif.time64, _1)); + .add_coerced_subscriber(boost::bind(&time_core_3000::set_time_next_pps, perif.time64, _1)); } //setup time source props @@ -649,8 +654,8 @@ b200_impl::b200_impl(const uhd::device_addr_t& device_addr, usb_device_handle::s _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)); + .set_coercer(boost::bind(&check_option_valid, "time source", time_sources, _1)) + .add_coerced_subscriber(boost::bind(&b200_impl::update_time_source, this, _1)); //setup reference source props static const std::vector<std::string> clock_sources = (_gpsdo_capable) ? boost::assign::list_of("internal")("external")("gpsdo") : @@ -658,21 +663,21 @@ b200_impl::b200_impl(const uhd::device_addr_t& device_addr, usb_device_handle::s _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)); + .set_coercer(boost::bind(&check_option_valid, "clock source", clock_sources, _1)) + .add_coerced_subscriber(boost::bind(&b200_impl::update_clock_source, this, _1)); //////////////////////////////////////////////////////////////////// // front panel gpio //////////////////////////////////////////////////////////////////// - _radio_perifs[0].fp_gpio = gpio_core_200::make(_radio_perifs[0].ctrl, TOREG(SR_FP_GPIO), RB32_FP_GPIO); + _radio_perifs[0].fp_gpio = gpio_atr_3000::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)); + .add_coerced_subscriber(boost::bind(&gpio_atr_3000::set_gpio_attr, _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)); + .set_publisher(boost::bind(&gpio_atr_3000::read_gpio, _radio_perifs[0].fp_gpio)); //////////////////////////////////////////////////////////////////// // dboard eeproms but not really @@ -685,10 +690,14 @@ b200_impl::b200_impl(const uhd::device_addr_t& device_addr, usb_device_handle::s //////////////////////////////////////////////////////////////////// // do some post-init tasks //////////////////////////////////////////////////////////////////// - - //init the clock rate to something reasonable - double default_tick_rate = device_addr.cast<double>("master_clock_rate", ad936x_manager::DEFAULT_TICK_RATE); + // Init the clock rate and the auto mcr appropriately + if (not device_addr.has_key("master_clock_rate")) { + UHD_MSG(status) << "Setting master clock rate selection to 'automatic'." << std::endl; + } + // We can automatically choose a master clock rate, but not if the user specifies one + const 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); + _tree->access<bool>(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; @@ -712,12 +721,6 @@ b200_impl::b200_impl(const uhd::device_addr_t& device_addr, usb_device_handle::s _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); } - // 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; - } - } b200_impl::~b200_impl(void) @@ -761,22 +764,23 @@ void b200_impl::setup_radio(const size_t dspno) perif.ctrl->hold_task(_async_task); _async_task_data->radio_ctrl[dspno] = perif.ctrl; //weak _tree->access<time_spec_t>(mb_path / "time" / "cmd") - .subscribe(boost::bind(&radio_ctrl_core_3000::set_time, perif.ctrl, _1)); + .add_coerced_subscriber(boost::bind(&radio_ctrl_core_3000::set_time, perif.ctrl, _1)); _tree->access<double>(mb_path / "tick_rate") - .subscribe(boost::bind(&radio_ctrl_core_3000::set_tick_rate, perif.ctrl, _1)); + .add_coerced_subscriber(boost::bind(&radio_ctrl_core_3000::set_tick_rate, perif.ctrl, _1)); this->register_loopback_self_test(perif.ctrl); //////////////////////////////////////////////////////////////////// // Set up peripherals //////////////////////////////////////////////////////////////////// - perif.atr = gpio_core_200_32wo::make(perif.ctrl, TOREG(SR_ATR)); + 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("IQ", false, dspno == 1 ? true : false, dspno == 1 ? true : false); + 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(perif.ctrl, TOREG(SR_TX_CTRL)); + 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); @@ -796,15 +800,15 @@ void b200_impl::setup_radio(const size_t dspno) perif.ddc->populate_subtree(_tree->subtree(rx_dsp_path)); _tree->create<bool>(rx_dsp_path / "rate" / "set").set(false); _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(&lambda_set_bool_prop, boost::weak_ptr<property_tree>(_tree), rx_dsp_path / "rate" / "set", true, _1)) - .subscribe(boost::bind(&b200_impl::update_rx_samp_rate, this, dspno, _1)) + .set_coercer(boost::bind(&b200_impl::coerce_rx_samp_rate, this, perif.ddc, dspno, _1)) + .add_coerced_subscriber(boost::bind(&lambda_set_bool_prop, boost::weak_ptr<property_tree>(_tree), rx_dsp_path / "rate" / "set", true, _1)) + .add_coerced_subscriber(boost::bind(&b200_impl::update_rx_samp_rate, this, dspno, _1)) ; _tree->create<stream_cmd_t>(rx_dsp_path / "stream_cmd") - .subscribe(boost::bind(&rx_vita_core_3000::issue_stream_command, perif.framer, _1)); + .add_coerced_subscriber(boost::bind(&rx_vita_core_3000::issue_stream_command, perif.framer, _1)); _tree->access<double>(mb_path / "tick_rate") - .subscribe(boost::bind(&rx_vita_core_3000::set_tick_rate, perif.framer, _1)) - .subscribe(boost::bind(&b200_impl::update_rx_dsp_tick_rate, this, _1, perif.ddc, rx_dsp_path)) + .add_coerced_subscriber(boost::bind(&rx_vita_core_3000::set_tick_rate, perif.framer, _1)) + .add_coerced_subscriber(boost::bind(&b200_impl::update_rx_dsp_tick_rate, this, _1, perif.ddc, rx_dsp_path)) ; //////////////////////////////////////////////////////////////////// @@ -814,13 +818,12 @@ void b200_impl::setup_radio(const size_t dspno) perif.duc->populate_subtree(_tree->subtree(tx_dsp_path)); _tree->create<bool>(tx_dsp_path / "rate" / "set").set(false); _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(&lambda_set_bool_prop, boost::weak_ptr<property_tree>(_tree), tx_dsp_path / "rate" / "set", true, _1)) - .subscribe(boost::bind(&b200_impl::update_tx_samp_rate, this, dspno, _1)) + .set_coercer(boost::bind(&b200_impl::coerce_tx_samp_rate, this, perif.duc, dspno, _1)) + .add_coerced_subscriber(boost::bind(&lambda_set_bool_prop, boost::weak_ptr<property_tree>(_tree), tx_dsp_path / "rate" / "set", true, _1)) + .add_coerced_subscriber(boost::bind(&b200_impl::update_tx_samp_rate, this, dspno, _1)) ; _tree->access<double>(mb_path / "tick_rate") - .subscribe(boost::bind(&tx_vita_core_3000::set_tick_rate, perif.deframer, _1)) - .subscribe(boost::bind(&b200_impl::update_tx_dsp_tick_rate, this, _1, perif.duc, tx_dsp_path)) + .add_coerced_subscriber(boost::bind(&b200_impl::update_tx_dsp_tick_rate, this, _1, perif.duc, tx_dsp_path)) ; //////////////////////////////////////////////////////////////////// @@ -840,17 +843,17 @@ void b200_impl::setup_radio(const size_t dspno) // 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, dir == TX_DIRECTION)) + .set_publisher(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)) + .add_coerced_subscriber(boost::bind(&b200_impl::update_bandsel, this, key, _1)) ; 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)) + .add_coerced_subscriber(boost::bind(&b200_impl::update_antenna_sel, this, dspno, _1)) .set("RX2") ; @@ -986,27 +989,6 @@ void b200_impl::set_mb_eeprom(const uhd::usrp::mboard_eeprom_t &mb_eeprom) mb_eeprom.commit(*_iface, "B200"); } - -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 **********************************************************************/ @@ -1189,11 +1171,11 @@ void b200_impl::update_atrs(void) 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_core_200_32wo::sptr atr = perif.atr; - atr->set_atr_reg(dboard_iface::ATR_REG_IDLE, STATE_OFF); - atr->set_atr_reg(dboard_iface::ATR_REG_RX_ONLY, rxonly); - atr->set_atr_reg(dboard_iface::ATR_REG_TX_ONLY, txonly); - atr->set_atr_reg(dboard_iface::ATR_REG_FULL_DUPLEX, fd); + 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) { @@ -1207,11 +1189,11 @@ void b200_impl::update_atrs(void) 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_core_200_32wo::sptr atr = perif.atr; - atr->set_atr_reg(dboard_iface::ATR_REG_IDLE, STATE_OFF); - atr->set_atr_reg(dboard_iface::ATR_REG_RX_ONLY, rxonly); - atr->set_atr_reg(dboard_iface::ATR_REG_TX_ONLY, txonly); - atr->set_atr_reg(dboard_iface::ATR_REG_FULL_DUPLEX, fd); + 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); } } diff --git a/host/lib/usrp/b200/b200_impl.hpp b/host/lib/usrp/b200/b200_impl.hpp index 6c36e883d..130e93891 100644 --- a/host/lib/usrp/b200/b200_impl.hpp +++ b/host/lib/usrp/b200/b200_impl.hpp @@ -27,7 +27,7 @@ #include "rx_vita_core_3000.hpp" #include "tx_vita_core_3000.hpp" #include "time_core_3000.hpp" -#include "gpio_core_200.hpp" +#include "gpio_atr_3000.hpp" #include "radio_ctrl_core_3000.hpp" #include "rx_dsp_core_3000.hpp" #include "tx_dsp_core_3000.hpp" @@ -49,8 +49,8 @@ #include "recv_packet_demuxer_3000.hpp" static const boost::uint8_t B200_FW_COMPAT_NUM_MAJOR = 8; static const boost::uint8_t B200_FW_COMPAT_NUM_MINOR = 0; -static const boost::uint16_t B200_FPGA_COMPAT_NUM = 13; -static const boost::uint16_t B205_FPGA_COMPAT_NUM = 4; +static const boost::uint16_t B200_FPGA_COMPAT_NUM = 14; +static const boost::uint16_t B205_FPGA_COMPAT_NUM = 5; static const double B200_BUS_CLOCK_RATE = 100e6; static const boost::uint32_t B200_GPSDO_ST_NONE = 0x83; static const size_t B200_MAX_RATE_USB2 = 53248000; // bytes/s @@ -180,8 +180,8 @@ private: struct radio_perifs_t { radio_ctrl_core_3000::sptr ctrl; - gpio_core_200_32wo::sptr atr; - gpio_core_200::sptr fp_gpio; + uhd::usrp::gpio_atr::gpio_atr_3000::sptr atr; + uhd::usrp::gpio_atr::gpio_atr_3000::sptr fp_gpio; time_core_3000::sptr time64; rx_vita_core_3000::sptr framer; rx_dsp_core_3000::sptr ddc; @@ -224,14 +224,10 @@ private: enum time_source_t {GPSDO=0,EXTERNAL=1,INTERNAL=2,NONE=3,UNKNOWN=4} _time_source; void update_gpio_state(void); - void reset_codec_dcm(void); void update_enables(void); void update_atrs(void); - boost::uint32_t get_fp_gpio(gpio_core_200::sptr); - void set_fp_gpio(gpio_core_200::sptr, const gpio_attr_t, const boost::uint32_t); - double _tick_rate; double get_tick_rate(void){return _tick_rate;} double set_tick_rate(const double rate); diff --git a/host/lib/usrp/b200/b200_io_impl.cpp b/host/lib/usrp/b200/b200_io_impl.cpp index d0d769504..283091140 100644 --- a/host/lib/usrp/b200/b200_io_impl.cpp +++ b/host/lib/usrp/b200/b200_io_impl.cpp @@ -158,7 +158,6 @@ void b200_impl::update_tick_rate(const double new_tick_rate) boost::shared_ptr<sph::send_packet_streamer> my_streamer = boost::dynamic_pointer_cast<sph::send_packet_streamer>(perif.tx_streamer.lock()); if (my_streamer) my_streamer->set_tick_rate(new_tick_rate); - perif.deframer->set_tick_rate(new_tick_rate); } } diff --git a/host/lib/usrp/common/CMakeLists.txt b/host/lib/usrp/common/CMakeLists.txt index 9dabc4e0b..9f4cf09b5 100644 --- a/host/lib/usrp/common/CMakeLists.txt +++ b/host/lib/usrp/common/CMakeLists.txt @@ -29,7 +29,8 @@ INCLUDE_DIRECTORIES("${CMAKE_CURRENT_SOURCE_DIR}/ad9361_driver") LIBUHD_APPEND_SOURCES( ${CMAKE_CURRENT_SOURCE_DIR}/adf4001_ctrl.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/adf435x_common.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/adf435x.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/adf5355.cpp ${CMAKE_CURRENT_SOURCE_DIR}/ad9361_ctrl.cpp ${CMAKE_CURRENT_SOURCE_DIR}/ad936x_manager.cpp ${CMAKE_CURRENT_SOURCE_DIR}/ad9361_driver/ad9361_device.cpp @@ -37,4 +38,5 @@ LIBUHD_APPEND_SOURCES( ${CMAKE_CURRENT_SOURCE_DIR}/validate_subdev_spec.cpp ${CMAKE_CURRENT_SOURCE_DIR}/recv_packet_demuxer.cpp ${CMAKE_CURRENT_SOURCE_DIR}/fifo_ctrl_excelsior.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/usrp3_fw_ctrl_iface.cpp ) diff --git a/host/lib/usrp/common/ad9361_ctrl.cpp b/host/lib/usrp/common/ad9361_ctrl.cpp index 54f0fcdbf..654311424 100644 --- a/host/lib/usrp/common/ad9361_ctrl.cpp +++ b/host/lib/usrp/common/ad9361_ctrl.cpp @@ -93,18 +93,30 @@ class ad9361_ctrl_impl : public ad9361_ctrl { public: ad9361_ctrl_impl(ad9361_params::sptr client_settings, ad9361_io::sptr io_iface): - _device(client_settings, io_iface) + _device(client_settings, io_iface), _safe_spi(io_iface), _timed_spi(io_iface) { _device.initialize(); } + void set_timed_spi(uhd::spi_iface::sptr spi_iface, boost::uint32_t slave_num) + { + _timed_spi = boost::make_shared<ad9361_io_spi>(spi_iface, slave_num); + _use_timed_spi(); + } + + void set_safe_spi(uhd::spi_iface::sptr spi_iface, boost::uint32_t slave_num) + { + _safe_spi = boost::make_shared<ad9361_io_spi>(spi_iface, slave_num); + } + double set_gain(const std::string &which, const double value) { boost::lock_guard<boost::mutex> lock(_mutex); ad9361_device_t::direction_t direction = _get_direction_from_antenna(which); ad9361_device_t::chain_t chain =_get_chain_from_antenna(which); - return _device.set_gain(direction, chain, value); + double return_val = _device.set_gain(direction, chain, value); + return return_val; } void set_agc(const std::string &which, bool enable) @@ -112,7 +124,7 @@ public: boost::lock_guard<boost::mutex> lock(_mutex); ad9361_device_t::chain_t chain =_get_chain_from_antenna(which); - _device.set_agc(chain, enable); + _device.set_agc(chain, enable); } void set_agc_mode(const std::string &which, const std::string &mode) @@ -133,6 +145,9 @@ public: { boost::lock_guard<boost::mutex> lock(_mutex); + // Changing clock rate will disrupt AD9361's sample clock + _use_safe_spi(); + //clip to known bounds const meta_range_t clock_rate_range = ad9361_ctrl::get_clock_rate_range(); const double clipped_rate = clock_rate_range.clip(rate); @@ -144,7 +159,11 @@ public: ) % (rate/1e6) % (clipped_rate/1e6) << std::endl; } - return _device.set_clock_rate(clipped_rate); + double return_rate = _device.set_clock_rate(clipped_rate); + + _use_timed_spi(); + + return return_rate; } //! set which RX and TX chains/antennas are active @@ -152,7 +171,11 @@ public: { boost::lock_guard<boost::mutex> lock(_mutex); + // If both RX chains are disabled then the AD9361's sample clock is disabled + _use_safe_spi(); _device.set_active_chains(tx1, tx2, rx1, rx2); + _use_timed_spi(); + } //! tune the given frontend, return the exact value @@ -166,7 +189,8 @@ public: const double value = ad9361_ctrl::get_rf_freq_range().clip(clipped_freq); ad9361_device_t::direction_t direction = _get_direction_from_antenna(which); - return _device.tune(direction, value); + double return_val = _device.tune(direction, value); + return return_val; } //! get the current frequency for the given frontend @@ -283,16 +307,28 @@ private: return ad9361_device_t::CHAIN_1; } - ad9361_device_t _device; - boost::mutex _mutex; + void _use_safe_spi() { + _device.set_io_iface(_safe_spi); + } + + void _use_timed_spi() { + _device.set_io_iface(_timed_spi); + } + + ad9361_device_t _device; + ad9361_io::sptr _safe_spi; // SPI core that uses an always available clock + ad9361_io::sptr _timed_spi; // SPI core that has a dependency on the AD9361's sample clock (i.e. radio clk) + boost::mutex _mutex; }; //---------------------------------------------------------------------- // Make an instance of the AD9361 Control interface //---------------------------------------------------------------------- ad9361_ctrl::sptr ad9361_ctrl::make_spi( - ad9361_params::sptr client_settings, uhd::spi_iface::sptr spi_iface, boost::uint32_t slave_num) -{ + ad9361_params::sptr client_settings, + uhd::spi_iface::sptr spi_iface, + boost::uint32_t slave_num +) { boost::shared_ptr<ad9361_io_spi> spi_io_iface = boost::make_shared<ad9361_io_spi>(spi_iface, slave_num); return sptr(new ad9361_ctrl_impl(client_settings, spi_io_iface)); } diff --git a/host/lib/usrp/common/ad9361_ctrl.hpp b/host/lib/usrp/common/ad9361_ctrl.hpp index 5c438ee9c..5770c3ec4 100644 --- a/host/lib/usrp/common/ad9361_ctrl.hpp +++ b/host/lib/usrp/common/ad9361_ctrl.hpp @@ -56,7 +56,13 @@ public: //! make a new codec control object static sptr make_spi( - ad9361_params::sptr client_settings, uhd::spi_iface::sptr spi_iface, boost::uint32_t slave_num); + ad9361_params::sptr client_settings, + uhd::spi_iface::sptr spi_iface, + boost::uint32_t slave_num + ); + + virtual void set_timed_spi(uhd::spi_iface::sptr spi_iface, boost::uint32_t slave_num) = 0; + virtual void set_safe_spi(uhd::spi_iface::sptr spi_iface, boost::uint32_t slave_num) = 0; //! Get a list of gain names for RX or TX static std::vector<std::string> get_gain_names(const std::string &/*which*/) @@ -89,8 +95,10 @@ public: //! get the clock rate range for the frontend static uhd::meta_range_t get_clock_rate_range(void) { - //return uhd::meta_range_t(220e3, 61.44e6); - return uhd::meta_range_t(5e6, ad9361_device_t::AD9361_MAX_CLOCK_RATE); //5 MHz DCM low end + return uhd::meta_range_t( + ad9361_device_t::AD9361_MIN_CLOCK_RATE, + ad9361_device_t::AD9361_MAX_CLOCK_RATE + ); } //! set the filter bandwidth for the frontend's analog low pass diff --git a/host/lib/usrp/common/ad9361_driver/ad9361_device.cpp b/host/lib/usrp/common/ad9361_driver/ad9361_device.cpp index 0a8a61575..095017bb6 100644 --- a/host/lib/usrp/common/ad9361_driver/ad9361_device.cpp +++ b/host/lib/usrp/common/ad9361_driver/ad9361_device.cpp @@ -91,6 +91,7 @@ int get_num_taps(int max_num_taps) { } const double ad9361_device_t::AD9361_MAX_GAIN = 89.75; +const double ad9361_device_t::AD9361_MIN_CLOCK_RATE = 220e3; const double ad9361_device_t::AD9361_MAX_CLOCK_RATE = 61.44e6; const double ad9361_device_t::AD9361_CAL_VALID_WINDOW = 100e6; // Max bandwdith is due to filter rolloff in analog filter stage @@ -770,7 +771,7 @@ void ad9361_device_t::_calibrate_rf_dc_offset() size_t count = 0; _io_iface->poke8(0x016, 0x02); while (_io_iface->peek8(0x016) & 0x02) { - if (count > 100) { + if (count > 200) { throw uhd::runtime_error("[ad9361_device_t] RF DC Offset Calibration Failure"); break; } @@ -821,7 +822,7 @@ void ad9361_device_t::_calibrate_rx_quadrature() size_t count = 0; _io_iface->poke8(0x016, 0x20); while (_io_iface->peek8(0x016) & 0x20) { - if (count > 100) { + if (count > 1000) { throw uhd::runtime_error("[ad9361_device_t] Rx Quadrature Calibration Failure"); break; } @@ -1564,6 +1565,12 @@ void ad9361_device_t::initialize() _io_iface->poke8(0x000, 0x00); boost::this_thread::sleep(boost::posix_time::milliseconds(20)); + /* Check device ID to make sure iface works */ + boost::uint32_t device_id = (_io_iface->peek8(0x037) & 0x8); + if (device_id != 0x8) { + throw uhd::runtime_error(str(boost::format("[ad9361_device_t::initialize] Device ID readback failure. Expected: 0x8, Received: 0x%x") % device_id)); + } + /* There is not a WAT big enough for this. */ _io_iface->poke8(0x3df, 0x01); @@ -1773,6 +1780,10 @@ void ad9361_device_t::initialize() _io_iface->poke8(0x014, 0x21); } +void ad9361_device_t::set_io_iface(ad9361_io::sptr io_iface) +{ + _io_iface = io_iface; +} /* This function sets the RX / TX rate between AD9361 and the FPGA, and * thus determines the interpolation / decimation required in the FPGA to diff --git a/host/lib/usrp/common/ad9361_driver/ad9361_device.h b/host/lib/usrp/common/ad9361_driver/ad9361_device.h index 66bc2e8b9..d0e8a7e39 100644 --- a/host/lib/usrp/common/ad9361_driver/ad9361_device.h +++ b/host/lib/usrp/common/ad9361_driver/ad9361_device.h @@ -75,6 +75,9 @@ public: /* Initialize the AD9361 codec. */ void initialize(); + /* Set SPI interface */ + void set_io_iface(ad9361_io::sptr io_iface); + /* This function sets the RX / TX rate between AD9361 and the FPGA, and * thus determines the interpolation / decimation required in the FPGA to * achieve the user's requested rate. @@ -157,6 +160,7 @@ public: //Constants static const double AD9361_MAX_GAIN; static const double AD9361_MAX_CLOCK_RATE; + static const double AD9361_MIN_CLOCK_RATE; static const double AD9361_CAL_VALID_WINDOW; static const double AD9361_RECOMMENDED_MAX_BANDWIDTH; static const double DEFAULT_RX_FREQ; diff --git a/host/lib/usrp/common/ad936x_manager.cpp b/host/lib/usrp/common/ad936x_manager.cpp index de8c4c7ab..e7af411fa 100644 --- a/host/lib/usrp/common/ad936x_manager.cpp +++ b/host/lib/usrp/common/ad936x_manager.cpp @@ -93,14 +93,12 @@ class ad936x_manager_impl : public ad936x_manager // worst case conditions to stress the interface. // void loopback_self_test( - wb_iface::sptr iface, - wb_iface::wb_addr_type codec_idle_addr, - wb_iface::wb_addr_type codec_readback_addr + boost::function<void(uint32_t)> poker_functor, + boost::function<uint64_t()> peeker_functor ) { // Put AD936x in loopback mode _codec_ctrl->data_port_loopback(true); UHD_MSG(status) << "Performing CODEC loopback test... " << std::flush; - UHD_ASSERT_THROW(bool(iface)); size_t hash = size_t(time(NULL)); // Allow some time for AD936x to enter loopback mode. @@ -118,10 +116,10 @@ class ad936x_manager_impl : public ad936x_manager const boost::uint32_t word32 = boost::uint32_t(hash) & 0xfff0fff0; // Write test word to codec_idle idle register (on TX side) - iface->poke32(codec_idle_addr, word32); + poker_functor(word32); // Read back values - TX is lower 32-bits and RX is upper 32-bits - const boost::uint64_t rb_word64 = iface->peek64(codec_readback_addr); + const boost::uint64_t rb_word64 = peeker_functor(); const boost::uint32_t rb_tx = boost::uint32_t(rb_word64 >> 32); const boost::uint32_t rb_rx = boost::uint32_t(rb_word64 & 0xffffffff); @@ -136,7 +134,7 @@ class ad936x_manager_impl : public ad936x_manager UHD_MSG(status) << "pass" << std::endl; // Zero out the idle data. - iface->poke32(codec_idle_addr, 0); + poker_functor(0); // Take AD936x out of loopback mode _codec_ctrl->data_port_loopback(false); @@ -211,11 +209,11 @@ class ad936x_manager_impl : public ad936x_manager // Sensors subtree->create<sensor_value_t>("sensors/temp") - .publish(boost::bind(&ad9361_ctrl::get_temperature, _codec_ctrl)) + .set_publisher(boost::bind(&ad9361_ctrl::get_temperature, _codec_ctrl)) ; if (dir == RX_DIRECTION) { subtree->create<sensor_value_t>("sensors/rssi") - .publish(boost::bind(&ad9361_ctrl::get_rssi, _codec_ctrl, key)) + .set_publisher(boost::bind(&ad9361_ctrl::get_rssi, _codec_ctrl, key)) ; } @@ -226,7 +224,7 @@ class ad936x_manager_impl : public ad936x_manager .set(ad9361_ctrl::get_gain_range(key)); subtree->create<double>(uhd::fs_path("gains") / name / "value") .set(ad936x_manager::DEFAULT_GAIN) - .coerce(boost::bind(&ad9361_ctrl::set_gain, _codec_ctrl, key, _1)) + .set_coercer(boost::bind(&ad9361_ctrl::set_gain, _codec_ctrl, key, _1)) ; } @@ -238,19 +236,19 @@ class ad936x_manager_impl : public ad936x_manager // Analog Bandwidths subtree->create<double>("bandwidth/value") .set(ad936x_manager::DEFAULT_BANDWIDTH) - .coerce(boost::bind(&ad9361_ctrl::set_bw_filter, _codec_ctrl, key, _1)) + .set_coercer(boost::bind(&ad9361_ctrl::set_bw_filter, _codec_ctrl, key, _1)) ; subtree->create<meta_range_t>("bandwidth/range") - .publish(boost::bind(&ad9361_ctrl::get_bw_filter_range, key)) + .set_publisher(boost::bind(&ad9361_ctrl::get_bw_filter_range, key)) ; // LO Tuning subtree->create<meta_range_t>("freq/range") - .publish(boost::bind(&ad9361_ctrl::get_rf_freq_range)) + .set_publisher(boost::bind(&ad9361_ctrl::get_rf_freq_range)) ; subtree->create<double>("freq/value") - .publish(boost::bind(&ad9361_ctrl::get_freq, _codec_ctrl, key)) - .coerce(boost::bind(&ad9361_ctrl::tune, _codec_ctrl, key, _1)) + .set_publisher(boost::bind(&ad9361_ctrl::get_freq, _codec_ctrl, key)) + .set_coercer(boost::bind(&ad9361_ctrl::tune, _codec_ctrl, key, _1)) ; // Frontend corrections @@ -258,21 +256,21 @@ class ad936x_manager_impl : public ad936x_manager { subtree->create<bool>("dc_offset/enable" ) .set(ad936x_manager::DEFAULT_AUTO_DC_OFFSET) - .subscribe(boost::bind(&ad9361_ctrl::set_dc_offset_auto, _codec_ctrl, key, _1)) + .add_coerced_subscriber(boost::bind(&ad9361_ctrl::set_dc_offset_auto, _codec_ctrl, key, _1)) ; subtree->create<bool>("iq_balance/enable" ) .set(ad936x_manager::DEFAULT_AUTO_IQ_BALANCE) - .subscribe(boost::bind(&ad9361_ctrl::set_iq_balance_auto, _codec_ctrl, key, _1)) + .add_coerced_subscriber(boost::bind(&ad9361_ctrl::set_iq_balance_auto, _codec_ctrl, key, _1)) ; // AGC setup const std::list<std::string> mode_strings = boost::assign::list_of("slow")("fast"); subtree->create<bool>("gain/agc/enable") .set(DEFAULT_AGC_ENABLE) - .subscribe(boost::bind((&ad9361_ctrl::set_agc), _codec_ctrl, key, _1)) + .add_coerced_subscriber(boost::bind((&ad9361_ctrl::set_agc), _codec_ctrl, key, _1)) ; subtree->create<std::string>("gain/agc/mode/value") - .subscribe(boost::bind((&ad9361_ctrl::set_agc_mode), _codec_ctrl, key, _1)).set(mode_strings.front()) + .add_coerced_subscriber(boost::bind((&ad9361_ctrl::set_agc_mode), _codec_ctrl, key, _1)).set(mode_strings.front()) ; subtree->create< std::list<std::string> >("gain/agc/mode/options") .set(mode_strings) @@ -282,8 +280,8 @@ class ad936x_manager_impl : public ad936x_manager // Frontend filters BOOST_FOREACH(const std::string &filter_name, _codec_ctrl->get_filter_names(key)) { subtree->create<filter_info_base::sptr>(uhd::fs_path("filters") / filter_name / "value" ) - .publish(boost::bind(&ad9361_ctrl::get_filter, _codec_ctrl, key, filter_name)) - .subscribe(boost::bind(&ad9361_ctrl::set_filter, _codec_ctrl, key, filter_name, _1)); + .set_publisher(boost::bind(&ad9361_ctrl::get_filter, _codec_ctrl, key, filter_name)) + .add_coerced_subscriber(boost::bind(&ad9361_ctrl::set_filter, _codec_ctrl, key, filter_name, _1)); } } diff --git a/host/lib/usrp/common/ad936x_manager.hpp b/host/lib/usrp/common/ad936x_manager.hpp index 9b4a351c6..c456715e3 100644 --- a/host/lib/usrp/common/ad936x_manager.hpp +++ b/host/lib/usrp/common/ad936x_manager.hpp @@ -80,9 +80,8 @@ public: * \throws a uhd::runtime_error if the loopback value didn't match. */ virtual void loopback_self_test( - wb_iface::sptr iface, - wb_iface::wb_addr_type codec_idle_addr, - wb_iface::wb_addr_type codec_readback_addr + boost::function<void(uint32_t)> poker_functor, + boost::function<uint64_t()> peeker_functor ) = 0; /*! Determine a tick rate that will work with a given sampling rate diff --git a/host/lib/usrp/common/adf435x.cpp b/host/lib/usrp/common/adf435x.cpp new file mode 100644 index 000000000..f1ba6ad05 --- /dev/null +++ b/host/lib/usrp/common/adf435x.cpp @@ -0,0 +1,34 @@ +// +// Copyright 2013-2014 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 +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +#include "adf435x.hpp" + +using namespace uhd; + +adf435x_iface::~adf435x_iface() +{ +} + +adf435x_iface::sptr adf435x_iface::make_adf4350(write_fn_t write) +{ + return sptr(new adf435x_impl<adf4350_regs_t>(write)); +} + +adf435x_iface::sptr adf435x_iface::make_adf4351(write_fn_t write) +{ + return sptr(new adf435x_impl<adf4351_regs_t>(write)); +} diff --git a/host/lib/usrp/common/adf435x.hpp b/host/lib/usrp/common/adf435x.hpp new file mode 100644 index 000000000..d08c6b9dd --- /dev/null +++ b/host/lib/usrp/common/adf435x.hpp @@ -0,0 +1,364 @@ +// +// Copyright 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 +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +#ifndef INCLUDED_ADF435X_HPP +#define INCLUDED_ADF435X_HPP + +#include <uhd/exception.hpp> +#include <uhd/types/dict.hpp> +#include <uhd/types/ranges.hpp> +#include <uhd/utils/log.hpp> +#include <boost/function.hpp> +#include <boost/thread.hpp> +#include <boost/math/special_functions/round.hpp> +#include <vector> +#include "adf4350_regs.hpp" +#include "adf4351_regs.hpp" + +class adf435x_iface +{ +public: + typedef boost::shared_ptr<adf435x_iface> sptr; + typedef boost::function<void(std::vector<boost::uint32_t>)> write_fn_t; + + static sptr make_adf4350(write_fn_t write); + static sptr make_adf4351(write_fn_t write); + + virtual ~adf435x_iface() = 0; + + enum output_t { RF_OUTPUT_A, RF_OUTPUT_B }; + + enum prescaler_t { PRESCALER_4_5, PRESCALER_8_9 }; + + enum feedback_sel_t { FB_SEL_FUNDAMENTAL, FB_SEL_DIVIDED }; + + enum output_power_t { OUTPUT_POWER_M4DBM, OUTPUT_POWER_M1DBM, OUTPUT_POWER_2DBM, OUTPUT_POWER_5DBM }; + + enum muxout_t { MUXOUT_3STATE, MUXOUT_DVDD, MUXOUT_DGND, MUXOUT_RDIV, MUXOUT_NDIV, MUXOUT_ALD, MUXOUT_DLD }; + + virtual void set_reference_freq(double fref) = 0; + + virtual void set_prescaler(prescaler_t prescaler) = 0; + + virtual void set_feedback_select(feedback_sel_t fb_sel) = 0; + + virtual void set_output_power(output_power_t power) = 0; + + virtual void set_output_enable(output_t output, bool enable) = 0; + + virtual void set_muxout_mode(muxout_t mode) = 0; + + virtual uhd::range_t get_int_range() = 0; + + virtual double set_frequency(double target_freq, bool int_n_mode, bool flush = false) = 0; + + virtual void commit(void) = 0; +}; + +template <typename adf435x_regs_t> +class adf435x_impl : public adf435x_iface +{ +public: + adf435x_impl(write_fn_t write_fn) : + _write_fn(write_fn), + _regs(), + _fb_after_divider(false), + _reference_freq(0.0), + _N_min(-1) + {} + + virtual ~adf435x_impl() {}; + + void set_reference_freq(double fref) + { + _reference_freq = fref; + } + + void set_feedback_select(feedback_sel_t fb_sel) + { + _fb_after_divider = (fb_sel == FB_SEL_DIVIDED); + } + + void set_prescaler(prescaler_t prescaler) + { + if (prescaler == PRESCALER_8_9) { + _regs.prescaler = adf435x_regs_t::PRESCALER_8_9; + _N_min = 75; + } else { + _regs.prescaler = adf435x_regs_t::PRESCALER_4_5; + _N_min = 23; + } + } + + void set_output_power(output_power_t power) + { + switch (power) { + case OUTPUT_POWER_M4DBM: _regs.output_power = adf435x_regs_t::OUTPUT_POWER_M4DBM; break; + case OUTPUT_POWER_M1DBM: _regs.output_power = adf435x_regs_t::OUTPUT_POWER_M1DBM; break; + case OUTPUT_POWER_2DBM: _regs.output_power = adf435x_regs_t::OUTPUT_POWER_2DBM; break; + case OUTPUT_POWER_5DBM: _regs.output_power = adf435x_regs_t::OUTPUT_POWER_5DBM; break; + default: UHD_THROW_INVALID_CODE_PATH(); + } + } + + void set_output_enable(output_t output, bool enable) + { + switch (output) { + case RF_OUTPUT_A: _regs.rf_output_enable = enable ? adf435x_regs_t::RF_OUTPUT_ENABLE_ENABLED: + adf435x_regs_t::RF_OUTPUT_ENABLE_DISABLED; + break; + case RF_OUTPUT_B: _regs.aux_output_enable = enable ? adf435x_regs_t::AUX_OUTPUT_ENABLE_ENABLED: + adf435x_regs_t::AUX_OUTPUT_ENABLE_DISABLED; + break; + } + } + + void set_muxout_mode(muxout_t mode) + { + switch (mode) { + case MUXOUT_3STATE: _regs.muxout = adf435x_regs_t::MUXOUT_3STATE; break; + case MUXOUT_DVDD: _regs.muxout = adf435x_regs_t::MUXOUT_DVDD; break; + case MUXOUT_DGND: _regs.muxout = adf435x_regs_t::MUXOUT_DGND; break; + case MUXOUT_RDIV: _regs.muxout = adf435x_regs_t::MUXOUT_RDIV; break; + case MUXOUT_NDIV: _regs.muxout = adf435x_regs_t::MUXOUT_NDIV; break; + case MUXOUT_ALD: _regs.muxout = adf435x_regs_t::MUXOUT_ANALOG_LD; break; + case MUXOUT_DLD: _regs.muxout = adf435x_regs_t::MUXOUT_DLD; break; + default: UHD_THROW_INVALID_CODE_PATH(); + } + } + + uhd::range_t get_int_range() + { + if (_N_min < 0) throw uhd::runtime_error("set_prescaler must be called before get_int_range"); + return uhd::range_t(_N_min, 4095); + } + + double set_frequency(double target_freq, bool int_n_mode, bool flush = false) + { + static const double REF_DOUBLER_THRESH_FREQ = 12.5e6; + static const double PFD_FREQ_MAX = 25.0e6; + static const double BAND_SEL_FREQ_MAX = 100e3; + static const double VCO_FREQ_MIN = 2.2e9; + static const double VCO_FREQ_MAX = 4.4e9; + + //Default invalid value for actual_freq + double actual_freq = 0; + + uhd::range_t rf_divider_range = _get_rfdiv_range(); + uhd::range_t int_range = get_int_range(); + + double pfd_freq = 0; + boost::uint16_t R = 0, BS = 0, N = 0, FRAC = 0, MOD = 0; + boost::uint16_t RFdiv = static_cast<boost::uint16_t>(rf_divider_range.start()); + bool D = false, T = false; + + //Reference doubler for 50% duty cycle + D = (_reference_freq <= REF_DOUBLER_THRESH_FREQ); + + //increase RF divider until acceptable VCO frequency + double vco_freq = target_freq; + while (vco_freq < VCO_FREQ_MIN && RFdiv < static_cast<boost::uint16_t>(rf_divider_range.stop())) { + vco_freq *= 2; + RFdiv *= 2; + } + + /* + * The goal here is to loop though possible R dividers, + * band select clock dividers, N (int) dividers, and FRAC + * (frac) dividers. + * + * Calculate the N and F dividers for each set of values. + * The loop exits when it meets all of the constraints. + * The resulting loop values are loaded into the registers. + * + * from pg.21 + * + * f_pfd = f_ref*(1+D)/(R*(1+T)) + * f_vco = (N + (FRAC/MOD))*f_pfd + * N = f_vco/f_pfd - FRAC/MOD = f_vco*((R*(T+1))/(f_ref*(1+D))) - FRAC/MOD + * f_actual = f_vco/RFdiv) + */ + double feedback_freq = _fb_after_divider ? target_freq : vco_freq; + + for(R = 1; R <= 1023; R+=1){ + //PFD input frequency = f_ref/R ... ignoring Reference doubler/divide-by-2 (D & T) + pfd_freq = _reference_freq*(D?2:1)/(R*(T?2:1)); + + //keep the PFD frequency at or below 25MHz (Loop Filter Bandwidth) + if (pfd_freq > PFD_FREQ_MAX) continue; + + //First, ignore fractional part of tuning + N = boost::uint16_t(std::floor(feedback_freq/pfd_freq)); + + //keep N > minimum int divider requirement + if (N < static_cast<boost::uint16_t>(int_range.start())) continue; + + for(BS=1; BS <= 255; BS+=1){ + //keep the band select frequency at or below band_sel_freq_max + //constraint on band select clock + if (pfd_freq/BS > BAND_SEL_FREQ_MAX) continue; + goto done_loop; + } + } done_loop: + + //Fractional-N calculation + MOD = 4095; //max fractional accuracy + FRAC = static_cast<boost::uint16_t>(boost::math::round((feedback_freq/pfd_freq - N)*MOD)); + if (int_n_mode) { + if (FRAC > (MOD / 2)) { //Round integer such that actual freq is closest to target + N++; + } + FRAC = 0; + } + + //Reference divide-by-2 for 50% duty cycle + // if R even, move one divide by 2 to to regs.reference_divide_by_2 + if(R % 2 == 0) { + T = true; + R /= 2; + } + + //Typical phase resync time documented in data sheet pg.24 + static const double PHASE_RESYNC_TIME = 400e-6; + + //If feedback after divider, then compensation for the divider is pulled into the INT value + int rf_div_compensation = _fb_after_divider ? 1 : RFdiv; + + //Compute the actual frequency in terms of _reference_freq, N, FRAC, MOD, D, R and T. + actual_freq = ( + double((N + (double(FRAC)/double(MOD))) * + (_reference_freq*(D?2:1)/(R*(T?2:1)))) + ) / rf_div_compensation; + + _regs.frac_12_bit = FRAC; + _regs.int_16_bit = N; + _regs.mod_12_bit = MOD; + _regs.clock_divider_12_bit = std::max<boost::uint16_t>(1, boost::uint16_t(std::ceil(PHASE_RESYNC_TIME*pfd_freq/MOD))); + _regs.feedback_select = _fb_after_divider ? + adf435x_regs_t::FEEDBACK_SELECT_DIVIDED : + adf435x_regs_t::FEEDBACK_SELECT_FUNDAMENTAL; + _regs.clock_div_mode = _fb_after_divider ? + adf435x_regs_t::CLOCK_DIV_MODE_RESYNC_ENABLE : + adf435x_regs_t::CLOCK_DIV_MODE_FAST_LOCK; + _regs.r_counter_10_bit = R; + _regs.reference_divide_by_2 = T ? + adf435x_regs_t::REFERENCE_DIVIDE_BY_2_ENABLED : + adf435x_regs_t::REFERENCE_DIVIDE_BY_2_DISABLED; + _regs.reference_doubler = D ? + adf435x_regs_t::REFERENCE_DOUBLER_ENABLED : + adf435x_regs_t::REFERENCE_DOUBLER_DISABLED; + _regs.band_select_clock_div = boost::uint8_t(BS); + _regs.rf_divider_select = static_cast<typename adf435x_regs_t::rf_divider_select_t>(_get_rfdiv_setting(RFdiv)); + _regs.ldf = int_n_mode ? + adf435x_regs_t::LDF_INT_N : + adf435x_regs_t::LDF_FRAC_N; + + std::string tuning_str = (int_n_mode) ? "Integer-N" : "Fractional"; + UHD_LOGV(often) + << boost::format("ADF 435X Frequencies (MHz): REQUESTED=%0.9f, ACTUAL=%0.9f" + ) % (target_freq/1e6) % (actual_freq/1e6) << std::endl + << boost::format("ADF 435X Intermediates (MHz): Feedback=%0.2f, VCO=%0.2f, PFD=%0.2f, BAND=%0.2f, REF=%0.2f" + ) % (feedback_freq/1e6) % (vco_freq/1e6) % (pfd_freq/1e6) % (pfd_freq/BS/1e6) % (_reference_freq/1e6) << std::endl + << boost::format("ADF 435X Tuning: %s") % tuning_str.c_str() << std::endl + << boost::format("ADF 435X Settings: R=%d, BS=%d, N=%d, FRAC=%d, MOD=%d, T=%d, D=%d, RFdiv=%d" + ) % R % BS % N % FRAC % MOD % T % D % RFdiv << std::endl; + + UHD_ASSERT_THROW((_regs.frac_12_bit & ((boost::uint16_t)~0xFFF)) == 0); + UHD_ASSERT_THROW((_regs.mod_12_bit & ((boost::uint16_t)~0xFFF)) == 0); + UHD_ASSERT_THROW((_regs.clock_divider_12_bit & ((boost::uint16_t)~0xFFF)) == 0); + UHD_ASSERT_THROW((_regs.r_counter_10_bit & ((boost::uint16_t)~0x3FF)) == 0); + + UHD_ASSERT_THROW(vco_freq >= VCO_FREQ_MIN and vco_freq <= VCO_FREQ_MAX); + UHD_ASSERT_THROW(RFdiv >= static_cast<boost::uint16_t>(rf_divider_range.start())); + UHD_ASSERT_THROW(RFdiv <= static_cast<boost::uint16_t>(rf_divider_range.stop())); + UHD_ASSERT_THROW(_regs.int_16_bit >= static_cast<boost::uint16_t>(int_range.start())); + UHD_ASSERT_THROW(_regs.int_16_bit <= static_cast<boost::uint16_t>(int_range.stop())); + + if (flush) commit(); + return actual_freq; + } + + void commit() + { + //reset counters + _regs.counter_reset = adf435x_regs_t::COUNTER_RESET_ENABLED; + std::vector<boost::uint32_t> regs; + regs.push_back(_regs.get_reg(boost::uint32_t(2))); + _write_fn(regs); + _regs.counter_reset = adf435x_regs_t::COUNTER_RESET_DISABLED; + + //write the registers + //correct power-up sequence to write registers (5, 4, 3, 2, 1, 0) + regs.clear(); + for (int addr = 5; addr >= 0; addr--) { + regs.push_back(_regs.get_reg(boost::uint32_t(addr))); + } + _write_fn(regs); + } + +protected: + uhd::range_t _get_rfdiv_range(); + int _get_rfdiv_setting(boost::uint16_t div); + + write_fn_t _write_fn; + adf435x_regs_t _regs; + double _fb_after_divider; + double _reference_freq; + int _N_min; +}; + +template <> +inline uhd::range_t adf435x_impl<adf4350_regs_t>::_get_rfdiv_range() +{ + return uhd::range_t(1, 16); +} + +template <> +inline uhd::range_t adf435x_impl<adf4351_regs_t>::_get_rfdiv_range() +{ + return uhd::range_t(1, 64); +} + +template <> +inline int adf435x_impl<adf4350_regs_t>::_get_rfdiv_setting(boost::uint16_t div) +{ + switch (div) { + case 1: return int(adf4350_regs_t::RF_DIVIDER_SELECT_DIV1); + case 2: return int(adf4350_regs_t::RF_DIVIDER_SELECT_DIV2); + case 4: return int(adf4350_regs_t::RF_DIVIDER_SELECT_DIV4); + case 8: return int(adf4350_regs_t::RF_DIVIDER_SELECT_DIV8); + case 16: return int(adf4350_regs_t::RF_DIVIDER_SELECT_DIV16); + default: UHD_THROW_INVALID_CODE_PATH(); + } +} + +template <> +inline int adf435x_impl<adf4351_regs_t>::_get_rfdiv_setting(boost::uint16_t div) +{ + switch (div) { + case 1: return int(adf4351_regs_t::RF_DIVIDER_SELECT_DIV1); + case 2: return int(adf4351_regs_t::RF_DIVIDER_SELECT_DIV2); + case 4: return int(adf4351_regs_t::RF_DIVIDER_SELECT_DIV4); + case 8: return int(adf4351_regs_t::RF_DIVIDER_SELECT_DIV8); + case 16: return int(adf4351_regs_t::RF_DIVIDER_SELECT_DIV16); + case 32: return int(adf4351_regs_t::RF_DIVIDER_SELECT_DIV32); + case 64: return int(adf4351_regs_t::RF_DIVIDER_SELECT_DIV64); + default: UHD_THROW_INVALID_CODE_PATH(); + } +} + +#endif // INCLUDED_ADF435X_HPP diff --git a/host/lib/usrp/common/adf435x_common.cpp b/host/lib/usrp/common/adf435x_common.cpp deleted file mode 100644 index 474a1c932..000000000 --- a/host/lib/usrp/common/adf435x_common.cpp +++ /dev/null @@ -1,161 +0,0 @@ -// -// Copyright 2013-2014 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 -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with this program. If not, see <http://www.gnu.org/licenses/>. -// - -#include "adf435x_common.hpp" - -#include <boost/math/special_functions/round.hpp> -#include <uhd/types/tune_request.hpp> -#include <uhd/utils/log.hpp> -#include <cmath> - - -using namespace uhd; - -/*********************************************************************** - * ADF 4350/4351 Tuning Utility - **********************************************************************/ -adf435x_tuning_settings tune_adf435x_synth( - const double target_freq, - const double ref_freq, - const adf435x_tuning_constraints& constraints, - double& actual_freq) -{ - //Default invalid value for actual_freq - actual_freq = 0; - - double pfd_freq = 0; - boost::uint16_t R = 0, BS = 0, N = 0, FRAC = 0, MOD = 0; - boost::uint16_t RFdiv = static_cast<boost::uint16_t>(constraints.rf_divider_range.start()); - bool D = false, T = false; - - //Reference doubler for 50% duty cycle - //If ref_freq < 12.5MHz enable the reference doubler - D = (ref_freq <= constraints.ref_doubler_threshold); - - static const double MIN_VCO_FREQ = 2.2e9; - static const double MAX_VCO_FREQ = 4.4e9; - - //increase RF divider until acceptable VCO frequency - double vco_freq = target_freq; - while (vco_freq < MIN_VCO_FREQ && RFdiv < static_cast<boost::uint16_t>(constraints.rf_divider_range.stop())) { - vco_freq *= 2; - RFdiv *= 2; - } - - /* - * The goal here is to loop though possible R dividers, - * band select clock dividers, N (int) dividers, and FRAC - * (frac) dividers. - * - * Calculate the N and F dividers for each set of values. - * The loop exits when it meets all of the constraints. - * The resulting loop values are loaded into the registers. - * - * from pg.21 - * - * f_pfd = f_ref*(1+D)/(R*(1+T)) - * f_vco = (N + (FRAC/MOD))*f_pfd - * N = f_vco/f_pfd - FRAC/MOD = f_vco*((R*(T+1))/(f_ref*(1+D))) - FRAC/MOD - * f_actual = f_vco/RFdiv) - */ - double feedback_freq = constraints.feedback_after_divider ? target_freq : vco_freq; - - for(R = 1; R <= 1023; R+=1){ - //PFD input frequency = f_ref/R ... ignoring Reference doubler/divide-by-2 (D & T) - pfd_freq = ref_freq*(D?2:1)/(R*(T?2:1)); - - //keep the PFD frequency at or below 25MHz (Loop Filter Bandwidth) - if (pfd_freq > constraints.pfd_freq_max) continue; - - //First, ignore fractional part of tuning - N = boost::uint16_t(std::floor(feedback_freq/pfd_freq)); - - //keep N > minimum int divider requirement - if (N < static_cast<boost::uint16_t>(constraints.int_range.start())) continue; - - for(BS=1; BS <= 255; BS+=1){ - //keep the band select frequency at or below band_sel_freq_max - //constraint on band select clock - if (pfd_freq/BS > constraints.band_sel_freq_max) continue; - goto done_loop; - } - } done_loop: - - //Fractional-N calculation - MOD = 4095; //max fractional accuracy - FRAC = static_cast<boost::uint16_t>(boost::math::round((feedback_freq/pfd_freq - N)*MOD)); - if (constraints.force_frac0) { - if (FRAC > (MOD / 2)) { //Round integer such that actual freq is closest to target - N++; - } - FRAC = 0; - } - - //Reference divide-by-2 for 50% duty cycle - // if R even, move one divide by 2 to to regs.reference_divide_by_2 - if(R % 2 == 0) { - T = true; - R /= 2; - } - - //Typical phase resync time documented in data sheet pg.24 - static const double PHASE_RESYNC_TIME = 400e-6; - - //If feedback after divider, then compensation for the divider is pulled into the INT value - int rf_div_compensation = constraints.feedback_after_divider ? 1 : RFdiv; - - //Compute the actual frequency in terms of ref_freq, N, FRAC, MOD, D, R and T. - actual_freq = ( - double((N + (double(FRAC)/double(MOD))) * - (ref_freq*(D?2:1)/(R*(T?2:1)))) - ) / rf_div_compensation; - - //load the settings - adf435x_tuning_settings settings; - settings.frac_12_bit = FRAC; - settings.int_16_bit = N; - settings.mod_12_bit = MOD; - settings.clock_divider_12_bit = std::max<boost::uint16_t>(1, boost::uint16_t(std::ceil(PHASE_RESYNC_TIME*pfd_freq/MOD))); - settings.r_counter_10_bit = R; - settings.r_divide_by_2_en = T; - settings.r_doubler_en = D; - settings.band_select_clock_div = boost::uint8_t(BS); - settings.rf_divider = RFdiv; - - std::string tuning_str = (constraints.force_frac0) ? "Integer-N" : "Fractional"; - UHD_LOGV(often) - << boost::format("ADF 435X Frequencies (MHz): REQUESTED=%0.9f, ACTUAL=%0.9f" - ) % (target_freq/1e6) % (actual_freq/1e6) << std::endl - << boost::format("ADF 435X Intermediates (MHz): Feedback=%0.2f, VCO=%0.2f, PFD=%0.2f, BAND=%0.2f, REF=%0.2f" - ) % (feedback_freq/1e6) % (vco_freq/1e6) % (pfd_freq/1e6) % (pfd_freq/BS/1e6) % (ref_freq/1e6) << std::endl - << boost::format("ADF 435X Tuning: %s") % tuning_str.c_str() << std::endl - << boost::format("ADF 435X Settings: R=%d, BS=%d, N=%d, FRAC=%d, MOD=%d, T=%d, D=%d, RFdiv=%d" - ) % R % BS % N % FRAC % MOD % T % D % RFdiv << std::endl; - - UHD_ASSERT_THROW((settings.frac_12_bit & ((boost::uint16_t)~0xFFF)) == 0); - UHD_ASSERT_THROW((settings.mod_12_bit & ((boost::uint16_t)~0xFFF)) == 0); - UHD_ASSERT_THROW((settings.clock_divider_12_bit & ((boost::uint16_t)~0xFFF)) == 0); - UHD_ASSERT_THROW((settings.r_counter_10_bit & ((boost::uint16_t)~0x3FF)) == 0); - - UHD_ASSERT_THROW(vco_freq >= MIN_VCO_FREQ and vco_freq <= MAX_VCO_FREQ); - UHD_ASSERT_THROW(settings.rf_divider >= static_cast<boost::uint16_t>(constraints.rf_divider_range.start())); - UHD_ASSERT_THROW(settings.rf_divider <= static_cast<boost::uint16_t>(constraints.rf_divider_range.stop())); - UHD_ASSERT_THROW(settings.int_16_bit >= static_cast<boost::uint16_t>(constraints.int_range.start())); - UHD_ASSERT_THROW(settings.int_16_bit <= static_cast<boost::uint16_t>(constraints.int_range.stop())); - - return settings; -} diff --git a/host/lib/usrp/common/adf435x_common.hpp b/host/lib/usrp/common/adf435x_common.hpp deleted file mode 100644 index 617b9d97f..000000000 --- a/host/lib/usrp/common/adf435x_common.hpp +++ /dev/null @@ -1,63 +0,0 @@ -// -// Copyright 2014 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 -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with this program. If not, see <http://www.gnu.org/licenses/>. -// - -#ifndef INCLUDED_ADF435X_COMMON_HPP -#define INCLUDED_ADF435X_COMMON_HPP - -#include <boost/cstdint.hpp> -#include <uhd/property_tree.hpp> -#include <uhd/types/ranges.hpp> - -//Common IO Pins -#define ADF435X_CE (1 << 3) -#define ADF435X_PDBRF (1 << 2) -#define ADF435X_MUXOUT (1 << 1) // INPUT!!! -#define LOCKDET_MASK (1 << 0) // INPUT!!! - -#define RX_ATTN_SHIFT 8 //lsb of RX Attenuator Control -#define RX_ATTN_MASK (63 << RX_ATTN_SHIFT) //valid bits of RX Attenuator Control - -struct adf435x_tuning_constraints { - bool force_frac0; - bool feedback_after_divider; - double ref_doubler_threshold; - double pfd_freq_max; - double band_sel_freq_max; - uhd::range_t rf_divider_range; - uhd::range_t int_range; -}; - -struct adf435x_tuning_settings { - boost::uint16_t frac_12_bit; - boost::uint16_t int_16_bit; - boost::uint16_t mod_12_bit; - boost::uint16_t r_counter_10_bit; - bool r_doubler_en; - bool r_divide_by_2_en; - boost::uint16_t clock_divider_12_bit; - boost::uint8_t band_select_clock_div; - boost::uint16_t rf_divider; -}; - -adf435x_tuning_settings tune_adf435x_synth( - const double target_freq, - const double ref_freq, - const adf435x_tuning_constraints& constraints, - double& actual_freq -); - -#endif /* INCLUDED_ADF435X_COMMON_HPP */ diff --git a/host/lib/usrp/common/adf5355.cpp b/host/lib/usrp/common/adf5355.cpp new file mode 100644 index 000000000..bb0906724 --- /dev/null +++ b/host/lib/usrp/common/adf5355.cpp @@ -0,0 +1,376 @@ +// +// Copyright 2013-2014 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 +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +#include "adf5355.hpp" +#include "adf5355_regs.hpp" +#include <uhd/utils/math.hpp> +#include <boost/math/common_factor_rt.hpp> //gcd +#include <boost/thread.hpp> + +using namespace uhd; + +template<typename data_t> +data_t clamp(data_t val, data_t min, data_t max) { + return (val < min) ? min : ((val > max) ? max : val); +} + +template<typename data_t> +double todbl(data_t val) { + return static_cast<double>(val); +} + +static const double ADF5355_DOUBLER_MAX_REF_FREQ = 60e6; +static const double ADF5355_MAX_FREQ_PFD = 125e6; +static const double ADF5355_PRESCALER_THRESH = 7e9; + +static const double ADF5355_MIN_VCO_FREQ = 3.4e9; +static const double ADF5355_MAX_VCO_FREQ = 6.8e9; +static const double ADF5355_MAX_OUT_FREQ = 6.8e9; +static const double ADF5355_MIN_OUT_FREQ = (3.4e9 / 64); +static const double ADF5355_MAX_OUTB_FREQ = (6.8e9 * 2); +static const double ADF5355_MIN_OUTB_FREQ = (3.4e9 * 2); + +static const double ADF5355_PHASE_RESYNC_TIME = 400e-6; + +static const boost::uint32_t ADF5355_MOD1 = 16777216; +static const boost::uint32_t ADF5355_MAX_MOD2 = 16384; +static const boost::uint16_t ADF5355_MIN_INT_PRESCALER_89 = 75; + +class adf5355_impl : public adf5355_iface +{ +public: + adf5355_impl(write_fn_t write_fn) : + _write_fn(write_fn), + _regs(), + _rewrite_regs(true), + _wait_time_us(0), + _ref_freq(0.0), + _pfd_freq(0.0), + _fb_after_divider(false) + { + _regs.counter_reset = adf5355_regs_t::COUNTER_RESET_DISABLED; + _regs.cp_three_state = adf5355_regs_t::CP_THREE_STATE_DISABLED; + _regs.power_down = adf5355_regs_t::POWER_DOWN_DISABLED; + _regs.pd_polarity = adf5355_regs_t::PD_POLARITY_POSITIVE; + _regs.mux_logic = adf5355_regs_t::MUX_LOGIC_3_3V; + _regs.ref_mode = adf5355_regs_t::REF_MODE_SINGLE; + _regs.muxout = adf5355_regs_t::MUXOUT_DLD; + _regs.double_buff_div = adf5355_regs_t::DOUBLE_BUFF_DIV_DISABLED; + + _regs.rf_out_a_enabled = adf5355_regs_t::RF_OUT_A_ENABLED_ENABLED; + _regs.rf_out_b_enabled = adf5355_regs_t::RF_OUT_B_ENABLED_DISABLED; + _regs.mute_till_lock_detect = adf5355_regs_t::MUTE_TILL_LOCK_DETECT_MUTE_DISABLED; + _regs.ld_mode = adf5355_regs_t::LD_MODE_FRAC_N; + _regs.frac_n_ld_precision = adf5355_regs_t::FRAC_N_LD_PRECISION_5NS; + _regs.ld_cyc_count = adf5355_regs_t::LD_CYC_COUNT_1024; + _regs.le_sync = adf5355_regs_t::LE_SYNC_LE_SYNCED_TO_REFIN; + _regs.phase_resync = adf5355_regs_t::PHASE_RESYNC_DISABLED; + _regs.reference_divide_by_2 = adf5355_regs_t::REFERENCE_DIVIDE_BY_2_DISABLED; + _regs.reference_doubler = adf5355_regs_t::REFERENCE_DOUBLER_DISABLED; + + _regs.autocal_en = adf5355_regs_t::AUTOCAL_EN_ENABLED; + _regs.prescaler = adf5355_regs_t::PRESCALER_4_5; + _regs.charge_pump_current = adf5355_regs_t::CHARGE_PUMP_CURRENT_0_94MA; + + _regs.gated_bleed = adf5355_regs_t::GATED_BLEED_DISABLED; + _regs.negative_bleed = adf5355_regs_t::NEGATIVE_BLEED_ENABLED; + _regs.feedback_select = adf5355_regs_t::FEEDBACK_SELECT_FUNDAMENTAL; + _regs.output_power = adf5355_regs_t::OUTPUT_POWER_5DBM; + _regs.cp_bleed_current = 2; + _regs.r_counter_10_bit = 8; + + + _regs.ld_cyc_count = adf5355_regs_t::LD_CYC_COUNT_1024; + _regs.loss_of_lock_mode = adf5355_regs_t::LOSS_OF_LOCK_MODE_DISABLED; + _regs.frac_n_ld_precision = adf5355_regs_t::FRAC_N_LD_PRECISION_5NS; + _regs.ld_mode = adf5355_regs_t::LD_MODE_FRAC_N; + + _regs.vco_band_div = 3; + _regs.timeout = 11; + _regs.auto_level_timeout = 30; + _regs.synth_lock_timeout = 12; + + _regs.adc_clock_divider = 16; + _regs.adc_conversion = adf5355_regs_t::ADC_CONVERSION_ENABLED; + _regs.adc_enable = adf5355_regs_t::ADC_ENABLE_ENABLED; + + _regs.phase_resync_clk_div = 0; + } + + ~adf5355_impl() + { + _regs.power_down = adf5355_regs_t::POWER_DOWN_ENABLED; + commit(); + } + + void set_feedback_select(feedback_sel_t fb_sel) + { + _fb_after_divider = (fb_sel == FB_SEL_DIVIDED); + + if (_fb_after_divider) { + _regs.feedback_select = adf5355_regs_t::FEEDBACK_SELECT_DIVIDED; + } else { + _regs.feedback_select = adf5355_regs_t::FEEDBACK_SELECT_FUNDAMENTAL; + } + } + + void set_reference_freq(double fref, bool force = false) + { + //Skip the body if the reference frequency does not change + if (uhd::math::frequencies_are_equal(fref, _ref_freq) and (not force)) + return; + + _ref_freq = fref; + + //----------------------------------------------------------- + //Set reference settings + + //Reference doubler for 50% duty cycle + bool doubler_en = (_ref_freq <= ADF5355_DOUBLER_MAX_REF_FREQ); + + /* Calculate and maximize PFD frequency */ + // TODO Target PFD should be configurable + /* TwinRX requires PFD of 6.25 MHz or less */ + const double TWINRX_PFD_FREQ = 6.25e6; + _pfd_freq = TWINRX_PFD_FREQ; + + int ref_div_factor = 16; + + //Reference divide-by-2 for 50% duty cycle + // if R even, move one divide by 2 to to regs.reference_divide_by_2 + bool div2_en = (ref_div_factor % 2 == 0); + if (div2_en) { + ref_div_factor /= 2; + } + + _regs.reference_divide_by_2 = div2_en ? + adf5355_regs_t::REFERENCE_DIVIDE_BY_2_ENABLED : + adf5355_regs_t::REFERENCE_DIVIDE_BY_2_DISABLED; + _regs.reference_doubler = doubler_en ? + adf5355_regs_t::REFERENCE_DOUBLER_ENABLED : + adf5355_regs_t::REFERENCE_DOUBLER_DISABLED; + _regs.r_counter_10_bit = ref_div_factor; + UHD_ASSERT_THROW((_regs.r_counter_10_bit & ((boost::uint16_t)~0x3FF)) == 0); + + //----------------------------------------------------------- + //Set timeouts (code from ADI driver) + _regs.timeout = clamp<boost::uint16_t>( + static_cast<boost::uint16_t>(ceil(_pfd_freq / (20e3 * 30))), 1, 1023); + UHD_ASSERT_THROW((_regs.timeout & ((boost::uint16_t)~0x3FF)) == 0); + _regs.synth_lock_timeout = + static_cast<boost::uint8_t>(ceil((_pfd_freq * 2) / (100e3 * _regs.timeout))); + UHD_ASSERT_THROW((_regs.synth_lock_timeout & ((boost::uint16_t)~0x1F)) == 0); + _regs.auto_level_timeout = + static_cast<boost::uint8_t>(ceil((_pfd_freq * 5) / (100e3 * _regs.timeout))); + + //----------------------------------------------------------- + //Set VCO band divider + _regs.vco_band_div = + static_cast<boost::uint8_t>(ceil(_pfd_freq / 2.4e6)); + + //----------------------------------------------------------- + //Set ADC delay (code from ADI driver) + _regs.adc_enable = adf5355_regs_t::ADC_ENABLE_ENABLED; + _regs.adc_conversion = adf5355_regs_t::ADC_CONVERSION_ENABLED; + _regs.adc_clock_divider = clamp<boost::uint8_t>( + static_cast<boost::uint8_t>(ceil(((_pfd_freq / 100e3) - 2) / 4)), 1, 255); + _wait_time_us = static_cast<boost::uint32_t>( + ceil(16e6 / (_pfd_freq / ((4 * _regs.adc_clock_divider) + 2)))); + + //----------------------------------------------------------- + //Phase resync + _regs.phase_resync = adf5355_regs_t::PHASE_RESYNC_DISABLED; // Disabled during development + _regs.phase_adjust = adf5355_regs_t::PHASE_ADJUST_DISABLED; + _regs.sd_load_reset = adf5355_regs_t::SD_LOAD_RESET_ON_REG0_UPDATE; + _regs.phase_resync_clk_div = static_cast<boost::uint16_t>( + floor(ADF5355_PHASE_RESYNC_TIME * _pfd_freq)); + + _rewrite_regs = true; + } + + void set_output_power(output_power_t power) + { + adf5355_regs_t::output_power_t setting; + switch (power) { + case OUTPUT_POWER_M4DBM: setting = adf5355_regs_t::OUTPUT_POWER_M4DBM; break; + case OUTPUT_POWER_M1DBM: setting = adf5355_regs_t::OUTPUT_POWER_M1DBM; break; + case OUTPUT_POWER_2DBM: setting = adf5355_regs_t::OUTPUT_POWER_2DBM; break; + case OUTPUT_POWER_5DBM: setting = adf5355_regs_t::OUTPUT_POWER_5DBM; break; + default: UHD_THROW_INVALID_CODE_PATH(); + } + if (_regs.output_power != setting) _rewrite_regs = true; + _regs.output_power = setting; + } + + void set_output_enable(output_t output, bool enable) { + + switch (output) { + case RF_OUTPUT_A: _regs.rf_out_a_enabled = enable ? adf5355_regs_t::RF_OUT_A_ENABLED_ENABLED : + adf5355_regs_t::RF_OUT_A_ENABLED_DISABLED; + break; + case RF_OUTPUT_B: _regs.rf_out_b_enabled = enable ? adf5355_regs_t::RF_OUT_B_ENABLED_ENABLED : + adf5355_regs_t::RF_OUT_B_ENABLED_DISABLED; + break; + } + } + + void set_muxout_mode(muxout_t mode) + { + switch (mode) { + case MUXOUT_3STATE: _regs.muxout = adf5355_regs_t::MUXOUT_3STATE; break; + case MUXOUT_DVDD: _regs.muxout = adf5355_regs_t::MUXOUT_DVDD; break; + case MUXOUT_DGND: _regs.muxout = adf5355_regs_t::MUXOUT_DGND; break; + case MUXOUT_RDIV: _regs.muxout = adf5355_regs_t::MUXOUT_RDIV; break; + case MUXOUT_NDIV: _regs.muxout = adf5355_regs_t::MUXOUT_NDIV; break; + case MUXOUT_ALD: _regs.muxout = adf5355_regs_t::MUXOUT_ANALOG_LD; break; + case MUXOUT_DLD: _regs.muxout = adf5355_regs_t::MUXOUT_DLD; break; + default: UHD_THROW_INVALID_CODE_PATH(); + } + } + + double set_frequency(double target_freq, double freq_resolution, bool flush = false) + { + if (target_freq > ADF5355_MAX_OUT_FREQ or target_freq < ADF5355_MIN_OUT_FREQ) { + throw uhd::runtime_error("requested frequency out of range."); + } + if ((boost::uint32_t) freq_resolution == 0) { + throw uhd::runtime_error("requested resolution cannot be less than 1."); + } + + /* Calculate target VCOout frequency */ + //Increase RF divider until acceptable VCO frequency + double target_vco_freq = target_freq; + boost::uint32_t rf_divider = 1; + while (target_vco_freq < ADF5355_MIN_VCO_FREQ && rf_divider < 64) { + target_vco_freq *= 2; + rf_divider *= 2; + } + + switch (rf_divider) { + case 1: _regs.rf_divider_select = adf5355_regs_t::RF_DIVIDER_SELECT_DIV1; break; + case 2: _regs.rf_divider_select = adf5355_regs_t::RF_DIVIDER_SELECT_DIV2; break; + case 4: _regs.rf_divider_select = adf5355_regs_t::RF_DIVIDER_SELECT_DIV4; break; + case 8: _regs.rf_divider_select = adf5355_regs_t::RF_DIVIDER_SELECT_DIV8; break; + case 16: _regs.rf_divider_select = adf5355_regs_t::RF_DIVIDER_SELECT_DIV16; break; + case 32: _regs.rf_divider_select = adf5355_regs_t::RF_DIVIDER_SELECT_DIV32; break; + case 64: _regs.rf_divider_select = adf5355_regs_t::RF_DIVIDER_SELECT_DIV64; break; + default: UHD_THROW_INVALID_CODE_PATH(); + } + + //Compute fractional PLL params + double prescaler_input_freq = target_vco_freq; + if (_fb_after_divider) { + prescaler_input_freq /= rf_divider; + } + + double N = prescaler_input_freq / _pfd_freq; + boost::uint16_t INT = static_cast<boost::uint16_t>(floor(N)); + boost::uint32_t FRAC1 = static_cast<boost::uint32_t>(floor((N - INT) * ADF5355_MOD1)); + double residue = ADF5355_MOD1 * (N - (INT + FRAC1 / ADF5355_MOD1)); + + double gcd = boost::math::gcd(static_cast<int>(_pfd_freq), static_cast<int>(freq_resolution)); + boost::uint16_t MOD2 = static_cast<boost::uint16_t>(floor(_pfd_freq / gcd)); + + if (MOD2 > ADF5355_MAX_MOD2) { + MOD2 = ADF5355_MAX_MOD2; + } + boost::uint16_t FRAC2 = ceil(residue * MOD2); + + double coerced_vco_freq = _pfd_freq * ( + todbl(INT) + ( + (todbl(FRAC1) + + (todbl(FRAC2) / todbl(MOD2))) + / todbl(ADF5355_MOD1) + ) + ); + + double coerced_out_freq = coerced_vco_freq / rf_divider; + + /* Update registers */ + _regs.int_16_bit = INT; + _regs.frac1_24_bit = FRAC1; + _regs.frac2_14_bit = FRAC2; + _regs.mod2_14_bit = MOD2; + _regs.phase_24_bit = 0; + +/* + if (_regs.int_16_bit >= ADF5355_MIN_INT_PRESCALER_89) { + _regs.prescaler = adf5355_regs_t::PRESCALER_8_9; + } else { + _regs.prescaler = adf5355_regs_t::PRESCALER_4_5; + } + + // ADI: Tests have shown that the optimal bleed set is the following: + // 4/N < IBLEED/ICP < 10/N */ +/* + boost::uint32_t cp_curr_ua = + (static_cast<boost::uint32_t>(_regs.charge_pump_current) + 1) * 315; + _regs.cp_bleed_current = clamp<boost::uint8_t>( + ceil((todbl(400)*cp_curr_ua) / (_regs.int_16_bit*375)), 1, 255); + _regs.negative_bleed = adf5355_regs_t::NEGATIVE_BLEED_ENABLED; + _regs.gated_bleed = adf5355_regs_t::GATED_BLEED_DISABLED; +*/ + + if (flush) commit(); + return coerced_out_freq; + } + + void commit() + { + if (_rewrite_regs) { + //For a full state sync write registers in reverse order 12 - 0 + addr_vtr_t regs; + for (int addr = 12; addr >= 0; addr--) { + regs.push_back(_regs.get_reg(boost::uint32_t(addr))); + } + _write_fn(regs); + _rewrite_regs = false; + + } else { + //Frequency update sequence from data sheet + static const size_t ONE_REG = 1; + _write_fn(addr_vtr_t(ONE_REG, _regs.get_reg(6))); + _regs.counter_reset = adf5355_regs_t::COUNTER_RESET_ENABLED; + _write_fn(addr_vtr_t(ONE_REG, _regs.get_reg(4))); + _write_fn(addr_vtr_t(ONE_REG, _regs.get_reg(2))); + _write_fn(addr_vtr_t(ONE_REG, _regs.get_reg(1))); + _regs.autocal_en = adf5355_regs_t::AUTOCAL_EN_DISABLED; + _write_fn(addr_vtr_t(ONE_REG, _regs.get_reg(0))); + _regs.counter_reset = adf5355_regs_t::COUNTER_RESET_DISABLED; + _write_fn(addr_vtr_t(ONE_REG, _regs.get_reg(4))); + boost::this_thread::sleep(boost::posix_time::microsec(_wait_time_us)); + _regs.autocal_en = adf5355_regs_t::AUTOCAL_EN_ENABLED; + _write_fn(addr_vtr_t(ONE_REG, _regs.get_reg(0))); + } + } + +private: //Members + typedef std::vector<boost::uint32_t> addr_vtr_t; + + write_fn_t _write_fn; + adf5355_regs_t _regs; + bool _rewrite_regs; + boost::uint32_t _wait_time_us; + double _ref_freq; + double _pfd_freq; + double _fb_after_divider; +}; + +adf5355_iface::sptr adf5355_iface::make(write_fn_t write) +{ + return sptr(new adf5355_impl(write)); +} diff --git a/host/lib/usrp/common/adf5355.hpp b/host/lib/usrp/common/adf5355.hpp new file mode 100644 index 000000000..fb262cc9f --- /dev/null +++ b/host/lib/usrp/common/adf5355.hpp @@ -0,0 +1,57 @@ +// +// Copyright 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 +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +#ifndef INCLUDED_ADF5355_HPP +#define INCLUDED_ADF5355_HPP + +#include <boost/function.hpp> +#include <vector> + +class adf5355_iface +{ +public: + typedef boost::shared_ptr<adf5355_iface> sptr; + typedef boost::function<void(std::vector<boost::uint32_t>)> write_fn_t; + + static sptr make(write_fn_t write); + + virtual ~adf5355_iface() {} + + enum output_t { RF_OUTPUT_A, RF_OUTPUT_B }; + + enum feedback_sel_t { FB_SEL_FUNDAMENTAL, FB_SEL_DIVIDED }; + + enum output_power_t { OUTPUT_POWER_M4DBM, OUTPUT_POWER_M1DBM, OUTPUT_POWER_2DBM, OUTPUT_POWER_5DBM }; + + enum muxout_t { MUXOUT_3STATE, MUXOUT_DVDD, MUXOUT_DGND, MUXOUT_RDIV, MUXOUT_NDIV, MUXOUT_ALD, MUXOUT_DLD }; + + virtual void set_reference_freq(double fref, bool force = false) = 0; + + virtual void set_feedback_select(feedback_sel_t fb_sel) = 0; + + virtual void set_output_power(output_power_t power) = 0; + + virtual void set_output_enable(output_t output, bool enable) = 0; + + virtual void set_muxout_mode(muxout_t mode) = 0; + + virtual double set_frequency(double target_freq, double freq_resolution, bool flush = false) = 0; + + virtual void commit(void) = 0; +}; + +#endif // INCLUDED_ADF5355_HPP diff --git a/host/lib/usrp/common/apply_corrections.cpp b/host/lib/usrp/common/apply_corrections.cpp index 3d33e7d11..272e0e093 100644 --- a/host/lib/usrp/common/apply_corrections.cpp +++ b/host/lib/usrp/common/apply_corrections.cpp @@ -1,5 +1,5 @@ // -// Copyright 2011 Ettus Research LLC +// Copyright 2011-2016 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 @@ -144,6 +144,34 @@ static void apply_fe_corrections( /*********************************************************************** * Wrapper routines with nice try/catch + print **********************************************************************/ +void uhd::usrp::apply_tx_fe_corrections( //overloading to work according to rfnoc tree struct + property_tree::sptr sub_tree, //starts at mboards/x + const uhd::fs_path db_path, + const uhd::fs_path tx_fe_corr_path, + const double lo_freq //actual lo freq +){ + boost::mutex::scoped_lock l(corrections_mutex); + try{ + apply_fe_corrections( + sub_tree, + db_path + "/tx_eeprom", + tx_fe_corr_path + "/iq_balance/value", + "tx_iq_cal_v0.2_", + lo_freq + ); + apply_fe_corrections( + sub_tree, + db_path + "/tx_eeprom", + tx_fe_corr_path + "/dc_offset/value", + "tx_dc_cal_v0.2_", + lo_freq + ); + } + catch(const std::exception &e){ + UHD_MSG(error) << "Failure in apply_tx_fe_corrections: " << e.what() << std::endl; + } +} + void uhd::usrp::apply_tx_fe_corrections( property_tree::sptr sub_tree, //starts at mboards/x const std::string &slot, //name of dboard slot @@ -171,6 +199,27 @@ void uhd::usrp::apply_tx_fe_corrections( } } +void uhd::usrp::apply_rx_fe_corrections( //overloading to work according to rfnoc tree struct + property_tree::sptr sub_tree, //starts at mboards/x + const uhd::fs_path db_path, + const uhd::fs_path rx_fe_corr_path, + const double lo_freq //actual lo freq +){ + boost::mutex::scoped_lock l(corrections_mutex); + try{ + apply_fe_corrections( + sub_tree, + db_path + "/rx_eeprom", + rx_fe_corr_path + "/iq_balance/value", + "rx_iq_cal_v0.2_", + lo_freq + ); + } + catch(const std::exception &e){ + UHD_MSG(error) << "Failure in apply_tx_fe_corrections: " << e.what() << std::endl; + } +} + void uhd::usrp::apply_rx_fe_corrections( property_tree::sptr sub_tree, //starts at mboards/x const std::string &slot, //name of dboard slot diff --git a/host/lib/usrp/common/apply_corrections.hpp b/host/lib/usrp/common/apply_corrections.hpp index c516862d1..0ab5377f3 100644 --- a/host/lib/usrp/common/apply_corrections.hpp +++ b/host/lib/usrp/common/apply_corrections.hpp @@ -1,5 +1,5 @@ // -// Copyright 2011 Ettus Research LLC +// Copyright 2011-2016 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 @@ -26,16 +26,28 @@ namespace uhd{ namespace usrp{ void apply_tx_fe_corrections( property_tree::sptr sub_tree, //starts at mboards/x - const std::string &slot, //name of dboard slot + const fs_path db_path, + const fs_path tx_fe_corr_path, const double tx_lo_freq //actual lo freq ); + void apply_tx_fe_corrections( + property_tree::sptr sub_tree, //starts at mboards/x + const std::string &slot, //name of dboard slot + const double tx_lo_freq //actual lo freq + ); void apply_rx_fe_corrections( property_tree::sptr sub_tree, //starts at mboards/x const std::string &slot, //name of dboard slot const double rx_lo_freq //actual lo freq ); + void apply_rx_fe_corrections( + property_tree::sptr sub_tree, //starts at mboards/x + const fs_path db_path, + const fs_path rx_fe_corr_path, + const double rx_lo_freq //actual lo freq + ); }} //namespace uhd::usrp #endif /* INCLUDED_LIBUHD_USRP_COMMON_APPLY_CORRECTIONS_HPP */ diff --git a/host/lib/usrp/common/constrained_device_args.hpp b/host/lib/usrp/common/constrained_device_args.hpp new file mode 100644 index 000000000..1bfd1df00 --- /dev/null +++ b/host/lib/usrp/common/constrained_device_args.hpp @@ -0,0 +1,283 @@ +// +// Copyright 2014 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 +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +#ifndef INCLUDED_LIBUHD_USRP_COMMON_CONSTRAINED_DEV_ARGS_HPP +#define INCLUDED_LIBUHD_USRP_COMMON_CONSTRAINED_DEV_ARGS_HPP + +#include <uhd/types/device_addr.hpp> +#include <uhd/exception.hpp> +#include <boost/lexical_cast.hpp> +#include <boost/format.hpp> +#include <boost/algorithm/string.hpp> +#include <boost/assign/list_of.hpp> +#include <vector> +#include <string> + +namespace uhd { +namespace usrp { + + /*! + * constrained_device_args_t provides a base and utilities to + * map key=value pairs passed in through the device creation + * args interface (device_addr_t). + * + * Inherit from this class to create typed device specific + * arguments and use the base class methods to handle parsing + * the device_addr or any key=value string to populate the args + * + * This file contains a library of different types of args the + * the user can pass in. The library can be extended to support + * non-intrinsic types by the client. + * + */ + class constrained_device_args_t { + public: //Types + + /*! + * Base argument type. All other arguments inherit from this. + */ + class generic_arg { + public: + generic_arg(const std::string& key): _key(key) {} + inline const std::string& key() const { return _key; } + inline virtual std::string to_string() const = 0; + private: + std::string _key; + }; + + /*! + * String argument type. Can be case sensitive or insensitive + */ + template<bool case_sensitive> + class str_arg : public generic_arg { + public: + str_arg(const std::string& name, const std::string& default_value) : + generic_arg(name) { set(default_value); } + + inline void set(const std::string& value) { + _value = case_sensitive ? value : boost::algorithm::to_lower_copy(value); + } + inline const std::string& get() const { + return _value; + } + inline void parse(const std::string& str_rep) { + set(str_rep); + } + inline virtual std::string to_string() const { + return key() + "=" + get(); + } + inline bool operator==(const std::string& rhs) const { + return get() == boost::algorithm::to_lower_copy(rhs); + } + private: + std::string _value; + }; + typedef str_arg<false> str_ci_arg; + typedef str_arg<true> str_cs_arg; + + /*! + * Numeric argument type. The template type data_t allows the + * client to constrain the type of the number. + */ + template<typename data_t> + class num_arg : public generic_arg { + public: + num_arg(const std::string& name, const data_t default_value) : + generic_arg(name) { set(default_value); } + + inline void set(const data_t value) { + _value = value; + } + inline const data_t get() const { + return _value; + } + inline void parse(const std::string& str_rep) { + try { + _value = boost::lexical_cast<data_t>(str_rep); + } catch (std::exception& ex) { + throw uhd::value_error(str(boost::format( + "Error parsing numeric parameter %s: %s.") % + key() % ex.what() + )); + } + } + inline virtual std::string to_string() const { + return key() + "=" + boost::lexical_cast<std::string>(get()); + } + private: + data_t _value; + }; + + /*! + * Enumeration argument type. The template type enum_t allows the + * client to use their own enum and specify a string mapping for + * the values of the enum + * + * NOTE: The constraint on enum_t is that the values must start with + * 0 and be sequential + */ + template<typename enum_t> + class enum_arg : public generic_arg { + public: + enum_arg( + const std::string& name, + const enum_t default_value, + const std::vector<std::string>& values) : + generic_arg(name), _str_values(values) + { set(default_value); } + + inline void set(const enum_t value) { + _value = value; + } + inline const enum_t get() const { + return _value; + } + inline void parse(const std::string& str_rep, bool assert_invalid = true) { + std::string valid_values_str; + for (size_t i = 0; i < _str_values.size(); i++) { + if (boost::algorithm::to_lower_copy(str_rep) == + boost::algorithm::to_lower_copy(_str_values[i])) + { + valid_values_str += ((i==0)?"":", ") + _str_values[i]; + set(static_cast<enum_t>(static_cast<int>(i))); + return; + } + } + //If we reach here then, the string enum value was invalid + if (assert_invalid) { + throw uhd::value_error(str(boost::format( + "Invalid device arg value: %s=%s (Valid: {%s})") % + key() % str_rep % valid_values_str + )); + } + } + inline virtual std::string to_string() const { + size_t index = static_cast<size_t>(static_cast<int>(_value)); + UHD_ASSERT_THROW(index < _str_values.size()); + return key() + "=" + _str_values[index]; + } + + private: + enum_t _value; + std::vector<std::string> _str_values; + }; + + /*! + * Boolean argument type. + */ + class bool_arg : public generic_arg { + public: + bool_arg(const std::string& name, const bool default_value) : + generic_arg(name) { set(default_value); } + + inline void set(const bool value) { + _value = value; + } + inline bool get() const { + return _value; + } + inline void parse(const std::string& str_rep) { + try { + _value = (boost::lexical_cast<int>(str_rep) != 0); + } catch (std::exception& ex) { + if (str_rep.empty()) { + //If str_rep is empty then the device_addr was set + //without a value which means that the user "set" the flag + _value = true; + } else if (boost::algorithm::to_lower_copy(str_rep) == "true" || + boost::algorithm::to_lower_copy(str_rep) == "yes" || + boost::algorithm::to_lower_copy(str_rep) == "y") { + _value = true; + } else if (boost::algorithm::to_lower_copy(str_rep) == "false" || + boost::algorithm::to_lower_copy(str_rep) == "no" || + boost::algorithm::to_lower_copy(str_rep) == "n") { + _value = false; + } else { + throw uhd::value_error(str(boost::format( + "Error parsing boolean parameter %s: %s.") % + key() % ex.what() + )); + } + } + } + inline virtual std::string to_string() const { + return key() + "=" + (get() ? "true" : "false"); + } + private: + bool _value; + }; + + public: //Methods + constrained_device_args_t() {} + virtual ~constrained_device_args_t() {} + + void parse(const std::string& str_args) { + device_addr_t dev_args(str_args); + _parse(dev_args); + } + + void parse(const device_addr_t& dev_args) { + _parse(dev_args); + } + + inline virtual std::string to_string() const = 0; + + protected: //Methods + //Override _parse to provide an implementation to parse all + //client specific device args + virtual void _parse(const device_addr_t& dev_args) = 0; + + /*! + * Utility: Ensure that the value of the device arg is between min and max + */ + template<typename num_data_t> + static inline void _enforce_range(const num_arg<num_data_t>& arg, const num_data_t& min, const num_data_t& max) { + if (arg.get() > max || arg.get() < min) { + throw uhd::value_error(str(boost::format( + "Invalid device arg value: %s (Minimum: %s, Maximum: %s)") % + arg.to_string() % + boost::lexical_cast<std::string>(min) % boost::lexical_cast<std::string>(max))); + } + } + + /*! + * Utility: Ensure that the value of the device arg is is contained in valid_values + */ + template<typename arg_t, typename data_t> + static inline void _enforce_discrete(const arg_t& arg, const std::vector<data_t>& valid_values) { + bool match = false; + BOOST_FOREACH(const data_t& val, valid_values) { + if (val == arg.get()) { + match = true; + break; + } + } + if (!match) { + std::string valid_values_str; + for (size_t i = 0; i < valid_values.size(); i++) { + valid_values_str += ((i==0)?"":", ") + boost::lexical_cast<std::string>(valid_values[i]); + throw uhd::value_error(str(boost::format( + "Invalid device arg value: %s (Valid: {%s})") % + arg.to_string() % valid_values_str + )); + } + } + } + }; +}} //namespaces + +#endif /* INCLUDED_LIBUHD_USRP_COMMON_CONSTRAINED_DEV_ARGS_HPP */ diff --git a/host/lib/usrp/common/fw_comm_protocol.h b/host/lib/usrp/common/fw_comm_protocol.h new file mode 100644 index 000000000..14adb33a9 --- /dev/null +++ b/host/lib/usrp/common/fw_comm_protocol.h @@ -0,0 +1,102 @@ +// +// Copyright 2014 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 +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +#ifndef INCLUDED_FW_COMM_PROTOCOL +#define INCLUDED_FW_COMM_PROTOCOL + +#include <stdint.h> +#ifndef __cplusplus +#include <stdbool.h> +#endif + +/*! + * Structs and constants for communication between firmware and host. + * This header is shared by the firmware and host code. + * Therefore, this header may only contain valid C code. + */ +#ifdef __cplusplus +extern "C" { +#endif + +#define FW_COMM_PROTOCOL_SIGNATURE 0xACE3 +#define FW_COMM_PROTOCOL_VERSION 0 +#define FW_COMM_MAX_DATA_WORDS 16 +#define FW_COMM_PROTOCOL_MTU 256 + +#define FW_COMM_FLAGS_ACK 0x00000001 +#define FW_COMM_FLAGS_CMD_MASK 0x00000FF0 +#define FW_COMM_FLAGS_ERROR_MASK 0xFF000000 + +#define FW_COMM_CMD_ECHO 0x00000000 +#define FW_COMM_CMD_POKE32 0x00000010 +#define FW_COMM_CMD_PEEK32 0x00000020 +#define FW_COMM_CMD_BLOCK_POKE32 0x00000030 +#define FW_COMM_CMD_BLOCK_PEEK32 0x00000040 + +#define FW_COMM_ERR_PKT_ERROR 0x80000000 +#define FW_COMM_ERR_CMD_ERROR 0x40000000 +#define FW_COMM_ERR_SIZE_ERROR 0x20000000 + +#define FW_COMM_GENERATE_ID(prod) ((((uint32_t) FW_COMM_PROTOCOL_SIGNATURE) << 0) | \ + (((uint32_t) prod) << 16) | \ + (((uint32_t) FW_COMM_PROTOCOL_VERSION) << 24)) + +#define FW_COMM_GET_PROTOCOL_SIG(id) ((uint16_t)(id & 0xFFFF)) +#define FW_COMM_GET_PRODUCT_ID(id) ((uint8_t)(id >> 16)) +#define FW_COMM_GET_PROTOCOL_VER(id) ((uint8_t)(id >> 24)) + +typedef struct +{ + uint32_t id; //Protocol and device identifier + uint32_t flags; //Holds commands and ack messages + uint32_t sequence; //Sequence number (specific to FW communication transactions) + uint32_t data_words; //Number of data words in payload + uint32_t addr; //Address field for the command in flags + uint32_t data[FW_COMM_MAX_DATA_WORDS]; //Data field for the command in flags +} fw_comm_pkt_t; + +#ifdef __cplusplus +} //extern "C" +#endif + +// The following definitions are only useful in firmware. Exclude in host code. +#ifndef __cplusplus + +typedef void (*poke32_func)(const uint32_t addr, const uint32_t data); +typedef uint32_t (*peek32_func)(const uint32_t addr); + +/*! + * Process a firmware communication packet and compute a response. + * Args: + * - (in) request: Pointer to the request struct + * - (out) response: Pointer to the response struct + * - (in) product_id: The 8-bit usrp3 specific product ID (for request filtering) + * - (func) poke_callback, peek_callback: Callback functions for a single peek/poke + * - return value: Send a response packet + */ +bool process_fw_comm_protocol_pkt( + const fw_comm_pkt_t* request, + fw_comm_pkt_t* response, + uint8_t product_id, + uint32_t iface_id, + poke32_func poke_callback, + peek32_func peek_callback +); + +#endif //ifdef __cplusplus + +#endif /* INCLUDED_FW_COMM_PROTOCOL */ diff --git a/host/lib/usrp/common/usrp3_fw_ctrl_iface.cpp b/host/lib/usrp/common/usrp3_fw_ctrl_iface.cpp new file mode 100644 index 000000000..ef541e37f --- /dev/null +++ b/host/lib/usrp/common/usrp3_fw_ctrl_iface.cpp @@ -0,0 +1,246 @@ +// +// Copyright 2013 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 +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +#include "usrp3_fw_ctrl_iface.hpp" + +#include <uhd/utils/byteswap.hpp> +#include <uhd/utils/msg.hpp> +#include <uhd/exception.hpp> +#include <boost/format.hpp> +#include <boost/asio.hpp> //used for htonl and ntohl +#include <boost/foreach.hpp> +#include "fw_comm_protocol.h" + +namespace uhd { namespace usrp { namespace usrp3 { + +//---------------------------------------------------------- +// Factory method +//---------------------------------------------------------- +uhd::wb_iface::sptr usrp3_fw_ctrl_iface::make( + uhd::transport::udp_simple::sptr udp_xport, + const boost::uint16_t product_id, + const bool verbose) +{ + return wb_iface::sptr(new usrp3_fw_ctrl_iface(udp_xport, product_id, verbose)); +} + +//---------------------------------------------------------- +// udp_fw_ctrl_iface +//---------------------------------------------------------- + +usrp3_fw_ctrl_iface::usrp3_fw_ctrl_iface( + uhd::transport::udp_simple::sptr udp_xport, + const boost::uint16_t product_id, + const bool verbose) : + _product_id(product_id), _verbose(verbose), _udp_xport(udp_xport), + _seq_num(0) +{ + flush(); + peek32(0); +} + +usrp3_fw_ctrl_iface::~usrp3_fw_ctrl_iface() +{ + flush(); +} + +void usrp3_fw_ctrl_iface::flush() +{ + boost::mutex::scoped_lock lock(_mutex); + _flush(); +} + +void usrp3_fw_ctrl_iface::poke32(const wb_addr_type addr, const boost::uint32_t data) +{ + boost::mutex::scoped_lock lock(_mutex); + + for (size_t i = 1; i <= NUM_RETRIES; i++) { + try { + _poke32(addr, data); + return; + } catch(const std::exception &ex) { + const std::string error_msg = str(boost::format( + "udp fw poke32 failure #%u\n%s") % i % ex.what()); + if (_verbose) UHD_MSG(warning) << error_msg << std::endl; + if (i == NUM_RETRIES) throw uhd::io_error(error_msg); + } + } +} + +boost::uint32_t usrp3_fw_ctrl_iface::peek32(const wb_addr_type addr) +{ + boost::mutex::scoped_lock lock(_mutex); + + for (size_t i = 1; i <= NUM_RETRIES; i++) { + try { + return _peek32(addr); + } catch(const std::exception &ex) { + const std::string error_msg = str(boost::format( + "udp fw peek32 failure #%u\n%s") % i % ex.what()); + if (_verbose) UHD_MSG(warning) << error_msg << std::endl; + if (i == NUM_RETRIES) throw uhd::io_error(error_msg); + } + } + return 0; +} + +void usrp3_fw_ctrl_iface::_poke32(const wb_addr_type addr, const boost::uint32_t data) +{ + //Load request struct + fw_comm_pkt_t request; + request.id = uhd::htonx<boost::uint32_t>(FW_COMM_GENERATE_ID(_product_id)); + request.flags = uhd::htonx<boost::uint32_t>(FW_COMM_FLAGS_ACK | FW_COMM_CMD_POKE32); + request.sequence = uhd::htonx<boost::uint32_t>(_seq_num++); + request.addr = uhd::htonx(addr); + request.data_words = 1; + request.data[0] = uhd::htonx(data); + + //Send request + _flush(); + _udp_xport->send(boost::asio::buffer(&request, sizeof(request))); + + //Recv reply + fw_comm_pkt_t reply; + const size_t nbytes = _udp_xport->recv(boost::asio::buffer(&reply, sizeof(reply)), 1.0); + if (nbytes == 0) throw uhd::io_error("udp fw poke32 - reply timed out"); + + //Sanity checks + const size_t flags = uhd::ntohx<boost::uint32_t>(reply.flags); + UHD_ASSERT_THROW(nbytes == sizeof(reply)); + UHD_ASSERT_THROW(not (flags & FW_COMM_FLAGS_ERROR_MASK)); + UHD_ASSERT_THROW(flags & FW_COMM_CMD_POKE32); + UHD_ASSERT_THROW(flags & FW_COMM_FLAGS_ACK); + UHD_ASSERT_THROW(reply.sequence == request.sequence); + UHD_ASSERT_THROW(reply.addr == request.addr); + UHD_ASSERT_THROW(reply.data[0] == request.data[0]); +} + +boost::uint32_t usrp3_fw_ctrl_iface::_peek32(const wb_addr_type addr) +{ + //Load request struct + fw_comm_pkt_t request; + request.id = uhd::htonx<boost::uint32_t>(FW_COMM_GENERATE_ID(_product_id)); + request.flags = uhd::htonx<boost::uint32_t>(FW_COMM_FLAGS_ACK | FW_COMM_CMD_PEEK32); + request.sequence = uhd::htonx<boost::uint32_t>(_seq_num++); + request.addr = uhd::htonx(addr); + request.data_words = 1; + request.data[0] = 0; + + //Send request + _flush(); + _udp_xport->send(boost::asio::buffer(&request, sizeof(request))); + + //Recv reply + fw_comm_pkt_t reply; + const size_t nbytes = _udp_xport->recv(boost::asio::buffer(&reply, sizeof(reply)), 1.0); + if (nbytes == 0) throw uhd::io_error("udp fw peek32 - reply timed out"); + + //Sanity checks + const size_t flags = uhd::ntohx<boost::uint32_t>(reply.flags); + UHD_ASSERT_THROW(nbytes == sizeof(reply)); + UHD_ASSERT_THROW(not (flags & FW_COMM_FLAGS_ERROR_MASK)); + UHD_ASSERT_THROW(flags & FW_COMM_CMD_PEEK32); + UHD_ASSERT_THROW(flags & FW_COMM_FLAGS_ACK); + UHD_ASSERT_THROW(reply.sequence == request.sequence); + UHD_ASSERT_THROW(reply.addr == request.addr); + + //return result! + return uhd::ntohx<boost::uint32_t>(reply.data[0]); +} + +void usrp3_fw_ctrl_iface::_flush(void) +{ + char buff[FW_COMM_PROTOCOL_MTU] = {}; + while (_udp_xport->recv(boost::asio::buffer(buff), 0.0)) { + /*NOP*/ + } +} + +std::vector<std::string> usrp3_fw_ctrl_iface::discover_devices( + const std::string& addr_hint, const std::string& port, + boost::uint16_t product_id) +{ + std::vector<std::string> addrs; + + //Create a UDP transport to communicate: + //Some devices will cause a throw when opened for a broadcast address. + //We print and recover so the caller can loop through all bcast addrs. + uhd::transport::udp_simple::sptr udp_bcast_xport; + try { + udp_bcast_xport = uhd::transport::udp_simple::make_broadcast(addr_hint, port); + } catch(const std::exception &e) { + UHD_MSG(error) << boost::format("Cannot open UDP transport on %s for discovery\n%s") + % addr_hint % e.what() << std::endl; + return addrs; + } + + //Send dummy request + fw_comm_pkt_t request; + request.id = uhd::htonx<boost::uint32_t>(FW_COMM_GENERATE_ID(product_id)); + request.flags = uhd::htonx<boost::uint32_t>(FW_COMM_FLAGS_ACK|FW_COMM_CMD_ECHO); + request.sequence = uhd::htonx<boost::uint32_t>(std::rand()); + udp_bcast_xport->send(boost::asio::buffer(&request, sizeof(request))); + + //loop for replies until timeout + while (true) { + char buff[FW_COMM_PROTOCOL_MTU] = {}; + const size_t nbytes = udp_bcast_xport->recv(boost::asio::buffer(buff), 0.050); + if (nbytes != sizeof(fw_comm_pkt_t)) break; //No more responses or responses are invalid + + const fw_comm_pkt_t *reply = (const fw_comm_pkt_t *)buff; + if (request.id == reply->id && + request.flags == reply->flags && + request.sequence == reply->sequence) + { + addrs.push_back(udp_bcast_xport->get_recv_addr()); + } + } + + return addrs; +} + +boost::uint32_t usrp3_fw_ctrl_iface::get_iface_id( + const std::string& addr, const std::string& port, + boost::uint16_t product_id) +{ + uhd::transport::udp_simple::sptr udp_xport = + uhd::transport::udp_simple::make_connected(addr, port); + + //Send dummy request + fw_comm_pkt_t request; + request.id = uhd::htonx<boost::uint32_t>(FW_COMM_GENERATE_ID(product_id)); + request.flags = uhd::htonx<boost::uint32_t>(FW_COMM_FLAGS_ACK|FW_COMM_CMD_ECHO); + request.sequence = uhd::htonx<boost::uint32_t>(std::rand()); + udp_xport->send(boost::asio::buffer(&request, sizeof(request))); + + //loop for replies until timeout + char buff[FW_COMM_PROTOCOL_MTU] = {}; + const size_t nbytes = udp_xport->recv(boost::asio::buffer(buff), 1.0); + + const fw_comm_pkt_t *reply = (const fw_comm_pkt_t *)buff; + if (nbytes > 0 && + request.id == reply->id && + request.flags == reply->flags && + request.sequence == reply->sequence) + { + return uhd::ntohx<boost::uint32_t>(reply->data[0]); + } else { + throw uhd::io_error("udp get_iface_id - bad response"); + } +} + +}}} //namespace diff --git a/host/lib/usrp/common/usrp3_fw_ctrl_iface.hpp b/host/lib/usrp/common/usrp3_fw_ctrl_iface.hpp new file mode 100644 index 000000000..33286861b --- /dev/null +++ b/host/lib/usrp/common/usrp3_fw_ctrl_iface.hpp @@ -0,0 +1,72 @@ +// +// Copyright 2014 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 +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +#ifndef INCLUDED_LIBUHD_USRP_USRP3_UDP_FW_CTRL_IFACE_HPP +#define INCLUDED_LIBUHD_USRP_USRP3_UDP_FW_CTRL_IFACE_HPP + +#include <uhd/types/wb_iface.hpp> +#include <uhd/transport/udp_simple.hpp> +#include <boost/thread/mutex.hpp> +#include <vector> + +namespace uhd { namespace usrp { namespace usrp3 { + +class usrp3_fw_ctrl_iface : public uhd::wb_iface +{ +public: + usrp3_fw_ctrl_iface( + uhd::transport::udp_simple::sptr udp_xport, + const boost::uint16_t product_id, + const bool verbose); + virtual ~usrp3_fw_ctrl_iface(); + + // -- uhd::wb_iface -- + void poke32(const wb_addr_type addr, const boost::uint32_t data); + boost::uint32_t peek32(const wb_addr_type addr); + void flush(); + + static uhd::wb_iface::sptr make( + uhd::transport::udp_simple::sptr udp_xport, + const boost::uint16_t product_id, + const bool verbose = true); + // -- uhd::wb_iface -- + + static std::vector<std::string> discover_devices( + const std::string& addr_hint, const std::string& port, + boost::uint16_t product_id); + + static boost::uint32_t get_iface_id( + const std::string& addr, const std::string& port, + boost::uint16_t product_id); + +private: + void _poke32(const wb_addr_type addr, const boost::uint32_t data); + boost::uint32_t _peek32(const wb_addr_type addr); + void _flush(void); + + const boost::uint16_t _product_id; + const bool _verbose; + uhd::transport::udp_simple::sptr _udp_xport; + boost::uint32_t _seq_num; + boost::mutex _mutex; + + static const size_t NUM_RETRIES = 3; +}; + +}}} //namespace + +#endif //INCLUDED_LIBUHD_USRP_USRP3_USRP3_UDP_FW_CTRL_HPP diff --git a/host/lib/usrp/cores/CMakeLists.txt b/host/lib/usrp/cores/CMakeLists.txt index f28ae040f..1e16dd39e 100644 --- a/host/lib/usrp/cores/CMakeLists.txt +++ b/host/lib/usrp/cores/CMakeLists.txt @@ -30,6 +30,7 @@ LIBUHD_APPEND_SOURCES( ${CMAKE_CURRENT_SOURCE_DIR}/rx_dsp_core_200.cpp ${CMAKE_CURRENT_SOURCE_DIR}/tx_dsp_core_200.cpp ${CMAKE_CURRENT_SOURCE_DIR}/rx_frontend_core_200.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/rx_frontend_core_3000.cpp ${CMAKE_CURRENT_SOURCE_DIR}/tx_frontend_core_200.cpp ${CMAKE_CURRENT_SOURCE_DIR}/user_settings_core_200.cpp ${CMAKE_CURRENT_SOURCE_DIR}/rx_vita_core_3000.cpp @@ -37,7 +38,11 @@ LIBUHD_APPEND_SOURCES( ${CMAKE_CURRENT_SOURCE_DIR}/time_core_3000.cpp ${CMAKE_CURRENT_SOURCE_DIR}/spi_core_3000.cpp ${CMAKE_CURRENT_SOURCE_DIR}/i2c_core_100_wb32.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/dsp_core_utils.cpp ${CMAKE_CURRENT_SOURCE_DIR}/rx_dsp_core_3000.cpp ${CMAKE_CURRENT_SOURCE_DIR}/tx_dsp_core_3000.cpp ${CMAKE_CURRENT_SOURCE_DIR}/radio_ctrl_core_3000.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/gpio_atr_3000.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/dma_fifo_core_3000.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/user_settings_core_3000.cpp ) diff --git a/host/lib/usrp/cores/dma_fifo_core_3000.cpp b/host/lib/usrp/cores/dma_fifo_core_3000.cpp new file mode 100644 index 000000000..5df28f7c2 --- /dev/null +++ b/host/lib/usrp/cores/dma_fifo_core_3000.cpp @@ -0,0 +1,401 @@ +// +// Copyright 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 +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +#include "dma_fifo_core_3000.hpp" +#include <uhd/exception.hpp> +#include <boost/thread/thread.hpp> //sleep +#include <uhd/utils/soft_register.hpp> +#include <uhd/utils/msg.hpp> + +using namespace uhd; + +#define SR_DRAM_BIST_BASE 16 + +dma_fifo_core_3000::~dma_fifo_core_3000(void) { + /* NOP */ +} + +class dma_fifo_core_3000_impl : public dma_fifo_core_3000 +{ +protected: + class rb_addr_reg_t : public soft_reg32_wo_t { + public: + UHD_DEFINE_SOFT_REG_FIELD(ADDR, /*width*/ 3, /*shift*/ 0); //[2:0] + + static const boost::uint32_t RB_FIFO_STATUS = 0; + static const boost::uint32_t RB_BIST_STATUS = 1; + static const boost::uint32_t RB_BIST_XFER_CNT = 2; + static const boost::uint32_t RB_BIST_CYC_CNT = 3; + + rb_addr_reg_t(boost::uint32_t base): + soft_reg32_wo_t(base + 0) + { + //Initial values + set(ADDR, RB_FIFO_STATUS); + } + }; + + class fifo_ctrl_reg_t : public soft_reg32_wo_t { + public: + UHD_DEFINE_SOFT_REG_FIELD(CLEAR_FIFO, /*width*/ 1, /*shift*/ 0); //[0] + UHD_DEFINE_SOFT_REG_FIELD(RD_SUPPRESS_EN, /*width*/ 1, /*shift*/ 1); //[1] + UHD_DEFINE_SOFT_REG_FIELD(BURST_TIMEOUT, /*width*/ 12, /*shift*/ 4); //[15:4] + UHD_DEFINE_SOFT_REG_FIELD(RD_SUPPRESS_THRESH, /*width*/ 16, /*shift*/ 16); //[31:16] + + fifo_ctrl_reg_t(boost::uint32_t base): + soft_reg32_wo_t(base + 4) + { + //Initial values + set(CLEAR_FIFO, 1); + set(RD_SUPPRESS_EN, 0); + set(BURST_TIMEOUT, 256); + set(RD_SUPPRESS_THRESH, 0); + } + }; + + class base_addr_reg_t : public soft_reg32_wo_t { + public: + UHD_DEFINE_SOFT_REG_FIELD(BASE_ADDR, /*width*/ 30, /*shift*/ 0); //[29:0] + + base_addr_reg_t(boost::uint32_t base): + soft_reg32_wo_t(base + 8) + { + //Initial values + set(BASE_ADDR, 0x00000000); + } + }; + + class addr_mask_reg_t : public soft_reg32_wo_t { + public: + UHD_DEFINE_SOFT_REG_FIELD(ADDR_MASK, /*width*/ 30, /*shift*/ 0); //[29:0] + + addr_mask_reg_t(boost::uint32_t base): + soft_reg32_wo_t(base + 12) + { + //Initial values + set(ADDR_MASK, 0xFF000000); + } + }; + + class bist_ctrl_reg_t : public soft_reg32_wo_t { + public: + UHD_DEFINE_SOFT_REG_FIELD(GO, /*width*/ 1, /*shift*/ 0); //[0] + UHD_DEFINE_SOFT_REG_FIELD(CONTINUOUS_MODE, /*width*/ 1, /*shift*/ 1); //[1] + UHD_DEFINE_SOFT_REG_FIELD(TEST_PATT, /*width*/ 2, /*shift*/ 4); //[5:4] + + static const boost::uint32_t TEST_PATT_ZERO_ONE = 0; + static const boost::uint32_t TEST_PATT_CHECKERBOARD = 1; + static const boost::uint32_t TEST_PATT_COUNT = 2; + static const boost::uint32_t TEST_PATT_COUNT_INV = 3; + + bist_ctrl_reg_t(boost::uint32_t base): + soft_reg32_wo_t(base + 16) + { + //Initial values + set(GO, 0); + set(CONTINUOUS_MODE, 0); + set(TEST_PATT, TEST_PATT_ZERO_ONE); + } + }; + + class bist_cfg_reg_t : public soft_reg32_wo_t { + public: + UHD_DEFINE_SOFT_REG_FIELD(MAX_PKTS, /*width*/ 18, /*shift*/ 0); //[17:0] + UHD_DEFINE_SOFT_REG_FIELD(MAX_PKT_SIZE, /*width*/ 13, /*shift*/ 18); //[30:18] + UHD_DEFINE_SOFT_REG_FIELD(PKT_SIZE_RAMP, /*width*/ 1, /*shift*/ 31); //[31] + + bist_cfg_reg_t(boost::uint32_t base): + soft_reg32_wo_t(base + 20) + { + //Initial values + set(MAX_PKTS, 0); + set(MAX_PKT_SIZE, 0); + set(PKT_SIZE_RAMP, 0); + } + }; + + class bist_delay_reg_t : public soft_reg32_wo_t { + public: + UHD_DEFINE_SOFT_REG_FIELD(TX_PKT_DELAY, /*width*/ 16, /*shift*/ 0); //[15:0] + UHD_DEFINE_SOFT_REG_FIELD(RX_SAMP_DELAY, /*width*/ 8, /*shift*/ 16); //[23:16] + + bist_delay_reg_t(boost::uint32_t base): + soft_reg32_wo_t(base + 24) + { + //Initial values + set(TX_PKT_DELAY, 0); + set(RX_SAMP_DELAY, 0); + } + }; + + class bist_sid_reg_t : public soft_reg32_wo_t { + public: + UHD_DEFINE_SOFT_REG_FIELD(SID, /*width*/ 32, /*shift*/ 0); //[31:0] + + bist_sid_reg_t(boost::uint32_t base): + soft_reg32_wo_t(base + 28) + { + //Initial values + set(SID, 0); + } + }; + +public: + class fifo_readback { + public: + fifo_readback(wb_iface::sptr iface, const size_t base, const size_t rb_addr) : + _iface(iface), _addr_reg(base), _rb_addr(rb_addr) + { + _addr_reg.initialize(*iface, true); + } + + bool is_fifo_instantiated() { + boost::lock_guard<boost::mutex> lock(_mutex); + _addr_reg.write(rb_addr_reg_t::ADDR, rb_addr_reg_t::RB_FIFO_STATUS); + return _iface->peek32(_rb_addr) & 0x80000000; + } + + boost::uint32_t get_occupied_cnt() { + boost::lock_guard<boost::mutex> lock(_mutex); + _addr_reg.write(rb_addr_reg_t::ADDR, rb_addr_reg_t::RB_FIFO_STATUS); + return _iface->peek32(_rb_addr) & 0x7FFFFFF; + } + + boost::uint32_t is_fifo_busy() { + boost::lock_guard<boost::mutex> lock(_mutex); + _addr_reg.write(rb_addr_reg_t::ADDR, rb_addr_reg_t::RB_FIFO_STATUS); + return _iface->peek32(_rb_addr) & 0x40000000; + } + + struct bist_status_t { + bool running; + bool finished; + boost::uint8_t error; + }; + + bist_status_t get_bist_status() { + boost::lock_guard<boost::mutex> lock(_mutex); + _addr_reg.write(rb_addr_reg_t::ADDR, rb_addr_reg_t::RB_BIST_STATUS); + boost::uint32_t st32 = _iface->peek32(_rb_addr) & 0xF; + bist_status_t status; + status.running = st32 & 0x1; + status.finished = st32 & 0x2; + status.error = static_cast<boost::uint8_t>((st32>>2) & 0x3); + return status; + } + + bool is_ext_bist_supported() { + boost::lock_guard<boost::mutex> lock(_mutex); + _addr_reg.write(rb_addr_reg_t::ADDR, rb_addr_reg_t::RB_BIST_STATUS); + return _iface->peek32(_rb_addr) & 0x80000000; + } + + double get_xfer_ratio() { + boost::lock_guard<boost::mutex> lock(_mutex); + boost::uint32_t xfer_cnt = 0, cyc_cnt = 0; + _addr_reg.write(rb_addr_reg_t::ADDR, rb_addr_reg_t::RB_BIST_XFER_CNT); + xfer_cnt = _iface->peek32(_rb_addr); + _addr_reg.write(rb_addr_reg_t::ADDR, rb_addr_reg_t::RB_BIST_CYC_CNT); + cyc_cnt = _iface->peek32(_rb_addr); + return (static_cast<double>(xfer_cnt)/cyc_cnt); + } + + private: + wb_iface::sptr _iface; + rb_addr_reg_t _addr_reg; + const size_t _rb_addr; + boost::mutex _mutex; + }; + +public: + dma_fifo_core_3000_impl(wb_iface::sptr iface, const size_t base, const size_t readback): + _iface(iface), _base(base), _fifo_readback(iface, base, readback), + _fifo_ctrl_reg(base), _base_addr_reg(base), _addr_mask_reg(base), + _bist_ctrl_reg(base), _bist_cfg_reg(base), _bist_delay_reg(base), _bist_sid_reg(base) + { + _fifo_ctrl_reg.initialize(*iface, true); + _base_addr_reg.initialize(*iface, true); + _addr_mask_reg.initialize(*iface, true); + _bist_ctrl_reg.initialize(*iface, true); + _bist_cfg_reg.initialize(*iface, true); + _has_ext_bist = _fifo_readback.is_ext_bist_supported(); + if (_has_ext_bist) { + _bist_delay_reg.initialize(*iface, true); + _bist_sid_reg.initialize(*iface, true); + } + flush(); + } + + virtual ~dma_fifo_core_3000_impl() + { + } + + virtual void flush() { + //Clear the FIFO and hold it in that state + _fifo_ctrl_reg.write(fifo_ctrl_reg_t::CLEAR_FIFO, 1); + //Re-arm the FIFO + _wait_for_fifo_empty(); + _fifo_ctrl_reg.write(fifo_ctrl_reg_t::CLEAR_FIFO, 0); + } + + virtual void resize(const boost::uint32_t base_addr, const boost::uint32_t size) { + //Validate parameters + if (size < 8192) throw uhd::runtime_error("DMA FIFO must be larger than 8KiB"); + boost::uint32_t size_mask = size - 1; + if (size & size_mask) throw uhd::runtime_error("DMA FIFO size must be a power of 2"); + + //Clear the FIFO and hold it in that state + _fifo_ctrl_reg.write(fifo_ctrl_reg_t::CLEAR_FIFO, 1); + //Write base address and mask + _base_addr_reg.write(base_addr_reg_t::BASE_ADDR, base_addr); + _addr_mask_reg.write(addr_mask_reg_t::ADDR_MASK, ~size_mask); + + //Re-arm the FIFO + flush(); + } + + virtual boost::uint32_t get_bytes_occupied() { + return _fifo_readback.get_occupied_cnt() * 8; + } + + virtual bool ext_bist_supported() { + return _fifo_readback.is_ext_bist_supported(); + } + + virtual boost::uint8_t run_bist(bool finite = true, boost::uint32_t timeout_ms = 500) { + return run_ext_bist(finite, 0, 0, 0, timeout_ms); + } + + virtual boost::uint8_t run_ext_bist( + bool finite, + boost::uint32_t rx_samp_delay, + boost::uint32_t tx_pkt_delay, + boost::uint32_t sid, + boost::uint32_t timeout_ms = 500 + ) { + boost::lock_guard<boost::mutex> lock(_mutex); + + _wait_for_bist_done(timeout_ms, true); //Stop previous BIST and wait (if running) + _bist_ctrl_reg.write(bist_ctrl_reg_t::GO, 0); //Reset + + _bist_cfg_reg.set(bist_cfg_reg_t::MAX_PKTS, (2^18)-1); + _bist_cfg_reg.set(bist_cfg_reg_t::MAX_PKT_SIZE, 8000); + _bist_cfg_reg.set(bist_cfg_reg_t::PKT_SIZE_RAMP, 0); + _bist_cfg_reg.flush(); + + if (_has_ext_bist) { + _bist_delay_reg.set(bist_delay_reg_t::RX_SAMP_DELAY, rx_samp_delay); + _bist_delay_reg.set(bist_delay_reg_t::TX_PKT_DELAY, tx_pkt_delay); + _bist_delay_reg.flush(); + + _bist_sid_reg.write(bist_sid_reg_t::SID, sid); + } else { + if (rx_samp_delay != 0 || tx_pkt_delay != 0 || sid != 0) { + throw uhd::not_implemented_error( + "dma_fifo_core_3000: Runtime delay and SID support only available on FPGA images with extended BIST enabled"); + } + } + + _bist_ctrl_reg.set(bist_ctrl_reg_t::TEST_PATT, bist_ctrl_reg_t::TEST_PATT_COUNT); + _bist_ctrl_reg.set(bist_ctrl_reg_t::CONTINUOUS_MODE, finite ? 0 : 1); + _bist_ctrl_reg.write(bist_ctrl_reg_t::GO, 1); + + if (!finite) { + boost::this_thread::sleep(boost::posix_time::milliseconds(timeout_ms)); + } + + _wait_for_bist_done(timeout_ms, !finite); + if (!_fifo_readback.get_bist_status().finished) { + throw uhd::runtime_error("dma_fifo_core_3000: DRAM BIST state machine is in a bad state."); + } + + return _fifo_readback.get_bist_status().error; + } + + virtual double get_bist_throughput(double fifo_clock_rate) { + if (_has_ext_bist) { + _wait_for_bist_done(1000); + static const double BYTES_PER_CYC = 8; + return _fifo_readback.get_xfer_ratio() * fifo_clock_rate * BYTES_PER_CYC; + } else { + throw uhd::not_implemented_error( + "dma_fifo_core_3000: Throughput counter only available on FPGA images with extended BIST enabled"); + } + } + +private: + void _wait_for_fifo_empty() + { + boost::posix_time::ptime start_time = boost::posix_time::microsec_clock::local_time(); + boost::posix_time::time_duration elapsed; + + while (_fifo_readback.is_fifo_busy()) { + boost::this_thread::sleep(boost::posix_time::microsec(1000)); + elapsed = boost::posix_time::microsec_clock::local_time() - start_time; + if (elapsed.total_milliseconds() > 100) break; + } + } + + void _wait_for_bist_done(boost::uint32_t timeout_ms, bool force_stop = false) + { + boost::posix_time::ptime start_time = boost::posix_time::microsec_clock::local_time(); + boost::posix_time::time_duration elapsed; + + while (_fifo_readback.get_bist_status().running) { + if (force_stop) { + _bist_ctrl_reg.write(bist_ctrl_reg_t::GO, 0); + force_stop = false; + } + boost::this_thread::sleep(boost::posix_time::microsec(1000)); + elapsed = boost::posix_time::microsec_clock::local_time() - start_time; + if (elapsed.total_milliseconds() > timeout_ms) break; + } + } + +private: + wb_iface::sptr _iface; + const size_t _base; + boost::mutex _mutex; + bool _has_ext_bist; + + fifo_readback _fifo_readback; + fifo_ctrl_reg_t _fifo_ctrl_reg; + base_addr_reg_t _base_addr_reg; + addr_mask_reg_t _addr_mask_reg; + bist_ctrl_reg_t _bist_ctrl_reg; + bist_cfg_reg_t _bist_cfg_reg; + bist_delay_reg_t _bist_delay_reg; + bist_sid_reg_t _bist_sid_reg; +}; + +// +// Static make function +// +dma_fifo_core_3000::sptr dma_fifo_core_3000::make(wb_iface::sptr iface, const size_t set_base, const size_t rb_addr) +{ + if (check(iface, set_base, rb_addr)) { + return sptr(new dma_fifo_core_3000_impl(iface, set_base, rb_addr)); + } else { + throw uhd::runtime_error(""); + } +} + +bool dma_fifo_core_3000::check(wb_iface::sptr iface, const size_t set_base, const size_t rb_addr) +{ + dma_fifo_core_3000_impl::fifo_readback fifo_rb(iface, set_base, rb_addr); + return fifo_rb.is_fifo_instantiated(); +} diff --git a/host/lib/usrp/cores/dma_fifo_core_3000.hpp b/host/lib/usrp/cores/dma_fifo_core_3000.hpp new file mode 100644 index 000000000..41430e5c3 --- /dev/null +++ b/host/lib/usrp/cores/dma_fifo_core_3000.hpp @@ -0,0 +1,86 @@ +// +// Copyright 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 +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +#ifndef INCLUDED_LIBUHD_USRP_DMA_FIFO_CORE_3000_HPP +#define INCLUDED_LIBUHD_USRP_DMA_FIFO_CORE_3000_HPP + +#include <uhd/config.hpp> +#include <boost/shared_ptr.hpp> +#include <boost/noncopyable.hpp> +#include <uhd/types/wb_iface.hpp> + + +class dma_fifo_core_3000 : boost::noncopyable +{ +public: + typedef boost::shared_ptr<dma_fifo_core_3000> sptr; + virtual ~dma_fifo_core_3000(void) = 0; + + /*! + * Create a DMA FIFO controller using the given bus, settings and readback base + * Throws uhd::runtime_error if a DMA FIFO is not instantiated in the FPGA + */ + static sptr make(uhd::wb_iface::sptr iface, const size_t set_base, const size_t rb_addr); + + /*! + * Check if a DMA FIFO is instantiated in the FPGA + */ + static bool check(uhd::wb_iface::sptr iface, const size_t set_base, const size_t rb_addr); + + /*! + * Flush the DMA FIFO. Will clear all contents. + */ + virtual void flush() = 0; + + /*! + * Resize and rebase the DMA FIFO. Will clear all contents. + */ + virtual void resize(const boost::uint32_t base_addr, const boost::uint32_t size) = 0; + + /*! + * Get the (approx) number of bytes currently in the DMA FIFO + */ + virtual boost::uint32_t get_bytes_occupied() = 0; + + /*! + * Run the built-in-self-test routine for the DMA FIFO + */ + virtual boost::uint8_t run_bist(bool finite = true, boost::uint32_t timeout_ms = 500) = 0; + + /*! + * Is extended BIST supported + */ + virtual bool ext_bist_supported() = 0; + + /*! + * Run the built-in-self-test routine for the DMA FIFO (extended BIST only) + */ + virtual boost::uint8_t run_ext_bist( + bool finite, + boost::uint32_t rx_samp_delay, + boost::uint32_t tx_pkt_delay, + boost::uint32_t sid, + boost::uint32_t timeout_ms = 500) = 0; + + /*! + * Get the throughput measured from the last invocation of the BIST (extended BIST only) + */ + virtual double get_bist_throughput(double fifo_clock_rate) = 0; + +}; + +#endif /* INCLUDED_LIBUHD_USRP_DMA_FIFO_CORE_3000_HPP */ diff --git a/host/lib/usrp/cores/dsp_core_utils.cpp b/host/lib/usrp/cores/dsp_core_utils.cpp new file mode 100644 index 000000000..aea809ae8 --- /dev/null +++ b/host/lib/usrp/cores/dsp_core_utils.cpp @@ -0,0 +1,66 @@ +// +// Copyright 2016 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 +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +#include "dsp_core_utils.hpp" +#include <uhd/utils/math.hpp> +#include <uhd/exception.hpp> +#include <boost/math/special_functions/round.hpp> +#include <boost/math/special_functions/sign.hpp> + +static const int32_t MAX_FREQ_WORD = boost::numeric::bounds<boost::int32_t>::highest(); +static const int32_t MIN_FREQ_WORD = boost::numeric::bounds<boost::int32_t>::lowest(); + +void get_freq_and_freq_word( + const double requested_freq, + const double tick_rate, + double &actual_freq, + int32_t &freq_word +) { + //correct for outside of rate (wrap around) + double freq = std::fmod(requested_freq, tick_rate); + if (std::abs(freq) > tick_rate/2.0) + freq -= boost::math::sign(freq) * tick_rate; + + //confirm that the target frequency is within range of the CORDIC + UHD_ASSERT_THROW(std::abs(freq) <= tick_rate/2.0); + + /* Now calculate the frequency word. It is possible for this calculation + * to cause an overflow. As the requested DSP frequency approaches the + * master clock rate, that ratio multiplied by the scaling factor (2^32) + * will generally overflow within the last few kHz of tunable range. + * Thus, we check to see if the operation will overflow before doing it, + * and if it will, we set it to the integer min or max of this system. + */ + freq_word = 0; + + static const double scale_factor = std::pow(2.0, 32); + if ((freq / tick_rate) >= (MAX_FREQ_WORD / scale_factor)) { + /* Operation would have caused a positive overflow of int32. */ + freq_word = MAX_FREQ_WORD; + + } else if ((freq / tick_rate) <= (MIN_FREQ_WORD / scale_factor)) { + /* Operation would have caused a negative overflow of int32. */ + freq_word = MIN_FREQ_WORD; + + } else { + /* The operation is safe. Perform normally. */ + freq_word = int32_t(boost::math::round((freq / tick_rate) * scale_factor)); + } + + actual_freq = (double(freq_word) / scale_factor) * tick_rate; +} + diff --git a/host/lib/usrp/cores/dsp_core_utils.hpp b/host/lib/usrp/cores/dsp_core_utils.hpp new file mode 100644 index 000000000..d5d43f236 --- /dev/null +++ b/host/lib/usrp/cores/dsp_core_utils.hpp @@ -0,0 +1,33 @@ +// +// Copyright 2016 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 +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +#ifndef INCLUDED_LIBUHD_DSP_CORE_UTILS_HPP +#define INCLUDED_LIBUHD_DSP_CORE_UTILS_HPP + +#include <stdint.h> + +/*! For a requested frequency and sampling rate, return the + * correct frequency word (to set the CORDIC) and the actual frequency. + */ +void get_freq_and_freq_word( + const double requested_freq, + const double tick_rate, + double &actual_freq, + int32_t &freq_word +); + +#endif /* INCLUDED_LIBUHD_DSP_CORE_UTILS_HPP */ diff --git a/host/lib/usrp/cores/gpio_atr_3000.cpp b/host/lib/usrp/cores/gpio_atr_3000.cpp new file mode 100644 index 000000000..5844af601 --- /dev/null +++ b/host/lib/usrp/cores/gpio_atr_3000.cpp @@ -0,0 +1,341 @@ +// +// Copyright 2011,2014 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 +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +#include "gpio_atr_3000.hpp" +#include <uhd/types/dict.hpp> +#include <uhd/utils/soft_register.hpp> + +using namespace uhd; +using namespace usrp; + +//------------------------------------------------------------- +// gpio_atr_3000 +//------------------------------------------------------------- + +#define REG_ATR_IDLE_OFFSET (base + 0) +#define REG_ATR_RX_OFFSET (base + 4) +#define REG_ATR_TX_OFFSET (base + 8) +#define REG_ATR_FDX_OFFSET (base + 12) +#define REG_DDR_OFFSET (base + 16) +#define REG_ATR_DISABLE_OFFSET (base + 20) + +namespace uhd { namespace usrp { namespace gpio_atr { + +class gpio_atr_3000_impl : public gpio_atr_3000{ +public: + gpio_atr_3000_impl( + wb_iface::sptr iface, + const wb_iface::wb_addr_type base, + const wb_iface::wb_addr_type rb_addr = READBACK_DISABLED + ): + _iface(iface), _rb_addr(rb_addr), + _atr_idle_reg(REG_ATR_IDLE_OFFSET, _atr_disable_reg), + _atr_rx_reg(REG_ATR_RX_OFFSET), + _atr_tx_reg(REG_ATR_TX_OFFSET), + _atr_fdx_reg(REG_ATR_FDX_OFFSET), + _ddr_reg(REG_DDR_OFFSET), + _atr_disable_reg(REG_ATR_DISABLE_OFFSET) + { + _atr_idle_reg.initialize(*_iface, true); + _atr_rx_reg.initialize(*_iface, true); + _atr_tx_reg.initialize(*_iface, true); + _atr_fdx_reg.initialize(*_iface, true); + _ddr_reg.initialize(*_iface, true); + _atr_disable_reg.initialize(*_iface, true); + } + + virtual void set_atr_mode(const gpio_atr_mode_t mode, const boost::uint32_t mask) + { + //Each bit in the "ATR Disable" register determines whether the respective bit in the GPIO + //output bus is driven by the ATR engine or a static register. + //For each bit position, a 1 means that the bit is static and 0 means that the bit + //is driven by the ATR state machine. + //This setting will only get applied to all bits in the "mask" that are 1. All other + //bits will retain their old value. + _atr_disable_reg.set_with_mask((mode==MODE_ATR) ? ~MASK_SET_ALL : MASK_SET_ALL, mask); + _atr_disable_reg.flush(); + } + + virtual void set_gpio_ddr(const gpio_ddr_t dir, const boost::uint32_t mask) + { + //Each bit in the "DDR" register determines whether the respective bit in the GPIO + //bus is an input or an output. + //For each bit position, a 1 means that the bit is an output and 0 means that the bit + //is an input. + //This setting will only get applied to all bits in the "mask" that are 1. All other + //bits will retain their old value. + _ddr_reg.set_with_mask((dir==DDR_INPUT) ? ~MASK_SET_ALL : MASK_SET_ALL, mask); + _ddr_reg.flush(); + } + + virtual void set_atr_reg(const gpio_atr_reg_t atr, const boost::uint32_t value, const boost::uint32_t mask = MASK_SET_ALL) + { + //Set the value of the specified ATR register. For bits with ATR Disable set to 1, + //the IDLE register will hold the output state + //This setting will only get applied to all bits in the "mask" that are 1. All other + //bits will retain their old value. + masked_reg_t* reg = NULL; + switch (atr) { + case ATR_REG_IDLE: reg = &_atr_idle_reg; break; + case ATR_REG_RX_ONLY: reg = &_atr_rx_reg; break; + case ATR_REG_TX_ONLY: reg = &_atr_tx_reg; break; + case ATR_REG_FULL_DUPLEX: reg = &_atr_fdx_reg; break; + default: reg = &_atr_idle_reg; break; + } + //For protection we only write to bits that have the mode ATR by masking the user + //specified "mask" with ~atr_disable. + reg->set_with_mask(value, mask); + reg->flush(); + } + + virtual void set_gpio_out(const boost::uint32_t value, const boost::uint32_t mask = MASK_SET_ALL) { + //Set the value of the specified GPIO output register. + //This setting will only get applied to all bits in the "mask" that are 1. All other + //bits will retain their old value. + + //For protection we only write to bits that have the mode GPIO by masking the user + //specified "mask" with atr_disable. + _atr_idle_reg.set_gpio_out_with_mask(value, mask); + _atr_idle_reg.flush(); + } + + virtual boost::uint32_t read_gpio() + { + //Read the state of the GPIO pins + //If a pin is configured as an input, reads the actual value of the pin + //If a pin is configured as an output, reads the last value written to the pin + if (_rb_addr != READBACK_DISABLED) { + return _iface->peek32(_rb_addr); + } else { + throw uhd::runtime_error("read_gpio not supported for write-only interface."); + } + } + + inline virtual void set_gpio_attr(const gpio_attr_t attr, const boost::uint32_t value) + { + //An attribute based API to configure all settings for the GPIO bus in one function + //call. This API does not have a mask so it configures all bits at the same time. + switch (attr) + { + case GPIO_CTRL: + set_atr_mode(MODE_ATR, value); //Configure mode=ATR for all bits that are set + set_atr_mode(MODE_GPIO, ~value); //Configure mode=GPIO for all bits that are unset + break; + case GPIO_DDR: + set_gpio_ddr(DDR_OUTPUT, value); //Configure as output for all bits that are set + set_gpio_ddr(DDR_INPUT, ~value); //Configure as input for all bits that are unset + break; + case GPIO_OUT: + //Only set bits that are driven statically + set_gpio_out(value); + break; + case GPIO_ATR_0X: + //Only set bits that are driven by the ATR engine + set_atr_reg(ATR_REG_IDLE, value); + break; + case GPIO_ATR_RX: + //Only set bits that are driven by the ATR engine + set_atr_reg(ATR_REG_RX_ONLY, value); + break; + case GPIO_ATR_TX: + //Only set bits that are driven by the ATR engine + set_atr_reg(ATR_REG_TX_ONLY, value); + break; + case GPIO_ATR_XX: + //Only set bits that are driven by the ATR engine + set_atr_reg(ATR_REG_FULL_DUPLEX, value); + break; + default: + UHD_THROW_INVALID_CODE_PATH(); + } + } + +protected: + //Special RB addr value to indicate no readback + //This value is invalid as a real address because it is not a multiple of 4 + static const wb_iface::wb_addr_type READBACK_DISABLED = 0xFFFFFFFF; + + class masked_reg_t : public uhd::soft_reg32_wo_t { + public: + masked_reg_t(const wb_iface::wb_addr_type offset): uhd::soft_reg32_wo_t(offset) { + uhd::soft_reg32_wo_t::set(REGISTER, 0); + } + + virtual void set_with_mask(const boost::uint32_t value, const boost::uint32_t mask) { + uhd::soft_reg32_wo_t::set(REGISTER, + (value&mask)|(uhd::soft_reg32_wo_t::get(REGISTER)&(~mask))); + } + + virtual boost::uint32_t get() { + return uhd::soft_reg32_wo_t::get(uhd::soft_reg32_wo_t::REGISTER); + } + + virtual void flush() { + uhd::soft_reg32_wo_t::flush(); + } + }; + + class atr_idle_reg_t : public masked_reg_t { + public: + atr_idle_reg_t(const wb_iface::wb_addr_type offset, masked_reg_t& atr_disable_reg): + masked_reg_t(offset), + _atr_idle_cache(0), _gpio_out_cache(0), + _atr_disable_reg(atr_disable_reg) + { } + + virtual void set_with_mask(const boost::uint32_t value, const boost::uint32_t mask) { + _atr_idle_cache = (value&mask)|(_atr_idle_cache&(~mask)); + } + + virtual boost::uint32_t get() { + return _atr_idle_cache; + } + + void set_gpio_out_with_mask(const boost::uint32_t value, const boost::uint32_t mask) { + _gpio_out_cache = (value&mask)|(_gpio_out_cache&(~mask)); + } + + virtual boost::uint32_t get_gpio_out() { + return _gpio_out_cache; + } + + virtual void flush() { + set(REGISTER, + (_atr_idle_cache & (~_atr_disable_reg.get())) | + (_gpio_out_cache & _atr_disable_reg.get()) + ); + masked_reg_t::flush(); + } + + private: + boost::uint32_t _atr_idle_cache; + boost::uint32_t _gpio_out_cache; + masked_reg_t& _atr_disable_reg; + }; + + wb_iface::sptr _iface; + wb_iface::wb_addr_type _rb_addr; + atr_idle_reg_t _atr_idle_reg; + masked_reg_t _atr_rx_reg; + masked_reg_t _atr_tx_reg; + masked_reg_t _atr_fdx_reg; + masked_reg_t _ddr_reg; + masked_reg_t _atr_disable_reg; +}; + +gpio_atr_3000::sptr gpio_atr_3000::make( + wb_iface::sptr iface, const wb_iface::wb_addr_type base, const wb_iface::wb_addr_type rb_addr +) { + return sptr(new gpio_atr_3000_impl(iface, base, rb_addr)); +} + +gpio_atr_3000::sptr gpio_atr_3000::make_write_only( + wb_iface::sptr iface, const wb_iface::wb_addr_type base +) { + gpio_atr_3000::sptr gpio_iface(new gpio_atr_3000_impl(iface, base)); + gpio_iface->set_gpio_ddr(DDR_OUTPUT, MASK_SET_ALL); + return gpio_iface; +} + +//------------------------------------------------------------- +// db_gpio_atr_3000 +//------------------------------------------------------------- + +class db_gpio_atr_3000_impl : public gpio_atr_3000_impl, public db_gpio_atr_3000 { +public: + db_gpio_atr_3000_impl(wb_iface::sptr iface, const wb_iface::wb_addr_type base, const wb_iface::wb_addr_type rb_addr): + gpio_atr_3000_impl(iface, base, rb_addr) { /* NOP */ } + + inline void set_pin_ctrl(const db_unit_t unit, const boost::uint32_t value, const boost::uint32_t mask) + { + gpio_atr_3000_impl::set_atr_mode(MODE_ATR, compute_mask(unit, value&mask)); + gpio_atr_3000_impl::set_atr_mode(MODE_GPIO, compute_mask(unit, (~value)&mask)); + } + + inline boost::uint32_t get_pin_ctrl(const db_unit_t unit) + { + return (~_atr_disable_reg.get()) >> compute_shift(unit); + } + + inline void set_gpio_ddr(const db_unit_t unit, const boost::uint32_t value, const boost::uint32_t mask) + { + gpio_atr_3000_impl::set_gpio_ddr(DDR_OUTPUT, compute_mask(unit, value&mask)); + gpio_atr_3000_impl::set_gpio_ddr(DDR_INPUT, compute_mask(unit, (~value)&mask)); + } + + inline boost::uint32_t get_gpio_ddr(const db_unit_t unit) + { + return _ddr_reg.get() >> compute_shift(unit); + } + + inline void set_atr_reg(const db_unit_t unit, const gpio_atr_reg_t atr, const boost::uint32_t value, const boost::uint32_t mask) + { + gpio_atr_3000_impl::set_atr_reg(atr, value << compute_shift(unit), compute_mask(unit, mask)); + } + + inline boost::uint32_t get_atr_reg(const db_unit_t unit, const gpio_atr_reg_t atr) + { + masked_reg_t* reg = NULL; + switch (atr) { + case ATR_REG_IDLE: reg = &_atr_idle_reg; break; + case ATR_REG_RX_ONLY: reg = &_atr_rx_reg; break; + case ATR_REG_TX_ONLY: reg = &_atr_tx_reg; break; + case ATR_REG_FULL_DUPLEX: reg = &_atr_fdx_reg; break; + default: reg = &_atr_idle_reg; break; + } + return (reg->get() & compute_mask(unit, MASK_SET_ALL)) >> compute_shift(unit); + } + + inline void set_gpio_out(const db_unit_t unit, const boost::uint32_t value, const boost::uint32_t mask) + { + gpio_atr_3000_impl::set_gpio_out( + static_cast<boost::uint32_t>(value) << compute_shift(unit), + compute_mask(unit, mask)); + } + + inline boost::uint32_t get_gpio_out(const db_unit_t unit) + { + return (_atr_idle_reg.get_gpio_out() & compute_mask(unit, MASK_SET_ALL)) >> compute_shift(unit); + } + + inline boost::uint32_t read_gpio(const db_unit_t unit) + { + return (gpio_atr_3000_impl::read_gpio() & compute_mask(unit, MASK_SET_ALL)) >> compute_shift(unit); + } + +private: + inline boost::uint32_t compute_shift(const db_unit_t unit) { + switch (unit) { + case dboard_iface::UNIT_RX: return 0; + case dboard_iface::UNIT_TX: return 16; + default: return 0; + } + } + + inline boost::uint32_t compute_mask(const db_unit_t unit, const boost::uint32_t mask) { + boost::uint32_t tmp_mask = (unit == dboard_iface::UNIT_BOTH) ? mask : (mask & 0xFFFF); + return tmp_mask << (compute_shift(unit)); + } +}; + +db_gpio_atr_3000::sptr db_gpio_atr_3000::make( + wb_iface::sptr iface, const wb_iface::wb_addr_type base, const wb_iface::wb_addr_type rb_addr +) { + return sptr(new db_gpio_atr_3000_impl(iface, base, rb_addr)); +} + +}}} diff --git a/host/lib/usrp/cores/gpio_atr_3000.hpp b/host/lib/usrp/cores/gpio_atr_3000.hpp new file mode 100644 index 000000000..7b90429fe --- /dev/null +++ b/host/lib/usrp/cores/gpio_atr_3000.hpp @@ -0,0 +1,183 @@ +// +// Copyright 2011,2014,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 +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +#ifndef INCLUDED_LIBUHD_USRP_GPIO_CORE_3000_HPP +#define INCLUDED_LIBUHD_USRP_GPIO_CORE_3000_HPP + +#include <uhd/config.hpp> +#include <uhd/usrp/dboard_iface.hpp> +#include <uhd/usrp/gpio_defs.hpp> +#include <boost/shared_ptr.hpp> +#include <uhd/types/wb_iface.hpp> + +namespace uhd { namespace usrp { namespace gpio_atr { + +class gpio_atr_3000 : boost::noncopyable { +public: + typedef boost::shared_ptr<gpio_atr_3000> sptr; + + static const boost::uint32_t MASK_SET_ALL = 0xFFFFFFFF; + + virtual ~gpio_atr_3000(void) {}; + + /*! + * Create a read-write GPIO ATR interface object + * + * \param iface register iface to GPIO ATR registers + * \param base base settings offset for GPIO ATR registers + * \param base readback offset for GPIO ATR registers + */ + static sptr make( + uhd::wb_iface::sptr iface, + const uhd::wb_iface::wb_addr_type base, + const uhd::wb_iface::wb_addr_type rb_addr); + + /*! + * Create a write-only GPIO ATR interface object + * + * \param iface register iface to GPIO ATR registers + * \param base base settings offset for GPIO ATR registers + */ + static sptr make_write_only( + uhd::wb_iface::sptr iface, const uhd::wb_iface::wb_addr_type base); + + /*! + * Select the ATR mode for all bits in the mask + * + * \param mode the mode to apply {ATR = outputs driven by ATR state machine, GPIO = outputs static} + * \param mask apply the mode to all non-zero bits in the mask + */ + virtual void set_atr_mode(const gpio_atr_mode_t mode, const boost::uint32_t mask) = 0; + + /*! + * Select the data direction for all bits in the mask + * + * \param dir the direction {OUTPUT, INPUT} + * \param mask apply the mode to all non-zero bits in the mask + */ + virtual void set_gpio_ddr(const gpio_ddr_t dir, const boost::uint32_t mask) = 0; + + /*! + * Write the specified (masked) value to the ATR register + * + * \param atr the type of ATR register to write to {IDLE, RX, TX, FDX} + * \param value the value to write + * \param mask only writes to the bits where mask is non-zero + */ + virtual void set_atr_reg(const gpio_atr_reg_t atr, const boost::uint32_t value, const boost::uint32_t mask = MASK_SET_ALL) = 0; + + /*! + * Write to a static GPIO output + * + * \param value the value to write + * \param mask only writes to the bits where mask is non-zero + */ + virtual void set_gpio_out(const boost::uint32_t value, const boost::uint32_t mask = MASK_SET_ALL) = 0; + + /*! + * Read the state of the GPIO pins + * If a pin is configured as an input, reads the actual value of the pin + * If a pin is configured as an output, reads the last value written to the pin + * + * \return the value read back + */ + virtual boost::uint32_t read_gpio() = 0; + + /*! + * Set a GPIO attribute + * + * \param attr the attribute to set + * \param value the value to write to the attribute + */ + virtual void set_gpio_attr(const gpio_attr_t attr, const boost::uint32_t value) = 0; +}; + +class db_gpio_atr_3000 { +public: + typedef boost::shared_ptr<db_gpio_atr_3000> sptr; + + typedef uhd::usrp::dboard_iface::unit_t db_unit_t; + + virtual ~db_gpio_atr_3000(void) {}; + + /*! + * Create a read-write GPIO ATR interface object for a daughterboard connector + * + * \param iface register iface to GPIO ATR registers + * \param base base settings offset for GPIO ATR registers + * \param base readback offset for GPIO ATR registers + */ + static sptr make( + uhd::wb_iface::sptr iface, + const uhd::wb_iface::wb_addr_type base, + const uhd::wb_iface::wb_addr_type rb_addr); + + /*! + * Configure the GPIO mode for all pins in the daughterboard connector + * + * \param unit the side of the daughterboard interface to configure (TX or RX) + * \param value if value[i] is 1, the i'th bit is in ATR mode otherwise it is in GPIO mode + */ + virtual void set_pin_ctrl(const db_unit_t unit, const boost::uint32_t value, const boost::uint32_t mask) = 0; + + virtual boost::uint32_t get_pin_ctrl(const db_unit_t unit) = 0; + + /*! + * Configure the direction for all pins in the daughterboard connector + * + * \param unit the side of the daughterboard interface to configure (TX or RX) + * \param value if value[i] is 1, the i'th bit is an output otherwise it is an input + */ + virtual void set_gpio_ddr(const db_unit_t unit, const boost::uint32_t value, const boost::uint32_t mask) = 0; + + virtual boost::uint32_t get_gpio_ddr(const db_unit_t unit) = 0; + + /*! + * Write the specified value to the ATR register (all bits) + * + * \param atr the type of ATR register to write to {IDLE, RX, TX, FDX} + * \param unit the side of the daughterboard interface to configure (TX or RX) + * \param value the value to write + */ + virtual void set_atr_reg(const db_unit_t unit, const gpio_atr_reg_t atr, const boost::uint32_t value, const boost::uint32_t mask) = 0; + + virtual boost::uint32_t get_atr_reg(const db_unit_t unit, const gpio_atr_reg_t atr) = 0; + + /*! + * Write the specified value to the GPIO register (all bits) + * + * \param atr the type of ATR register to write to {IDLE, RX, TX, FDX} + * \param value the value to write + */ + virtual void set_gpio_out(const db_unit_t unit, const boost::uint32_t value, const boost::uint32_t mask) = 0; + + virtual boost::uint32_t get_gpio_out(const db_unit_t unit) = 0; + + /*! + * Read the state of the GPIO pins + * If a pin is configured as an input, reads the actual value of the pin + * If a pin is configured as an output, reads the last value written to the pin + * + * \param unit the side of the daughterboard interface to configure (TX or RX) + * \return the value read back + */ + virtual boost::uint32_t read_gpio(const db_unit_t unit) = 0; +}; + +}}} //namespaces + +#endif /* INCLUDED_LIBUHD_USRP_GPIO_CORE_3000_HPP */ diff --git a/host/lib/usrp/cores/gpio_core_200.cpp b/host/lib/usrp/cores/gpio_core_200.cpp index 704a71d5f..8ada95b1f 100644 --- a/host/lib/usrp/cores/gpio_core_200.cpp +++ b/host/lib/usrp/cores/gpio_core_200.cpp @@ -27,6 +27,11 @@ using namespace uhd; using namespace usrp; +template <typename T> +static void shadow_it(T &shadow, const T &value, const T &mask){ + shadow = (shadow & ~mask) | (value & mask); +} + gpio_core_200::~gpio_core_200(void){ /* NOP */ } @@ -36,13 +41,20 @@ public: gpio_core_200_impl(wb_iface::sptr iface, const size_t base, const size_t rb_addr): _iface(iface), _base(base), _rb_addr(rb_addr), _first_atr(true) { /* NOP */ } - void set_pin_ctrl(const unit_t unit, const boost::uint16_t value){ - _pin_ctrl[unit] = value; //shadow + void set_pin_ctrl(const unit_t unit, const boost::uint16_t value, const boost::uint16_t mask){ + if (unit == dboard_iface::UNIT_BOTH) throw uhd::runtime_error("UNIT_BOTH not supported in gpio_core_200"); + shadow_it(_pin_ctrl[unit], value, mask); update(); //full update } - void set_atr_reg(const unit_t unit, const atr_reg_t atr, const boost::uint16_t value){ - _atr_regs[unit][atr] = value; //shadow + boost::uint16_t get_pin_ctrl(unit_t unit){ + if (unit == dboard_iface::UNIT_BOTH) throw uhd::runtime_error("UNIT_BOTH not supported in gpio_core_200"); + return _pin_ctrl[unit]; + } + + void set_atr_reg(const unit_t unit, const atr_reg_t atr, const boost::uint16_t value, const boost::uint16_t mask){ + if (unit == dboard_iface::UNIT_BOTH) throw uhd::runtime_error("UNIT_BOTH not supported in gpio_core_200"); + shadow_it(_atr_regs[unit][atr], value, mask); if (_first_atr) { // To preserve legacy behavior, update all registers the first time @@ -53,20 +65,38 @@ public: update(atr); } - void set_gpio_ddr(const unit_t unit, const boost::uint16_t value){ - _gpio_ddr[unit] = value; //shadow + boost::uint16_t get_atr_reg(unit_t unit, atr_reg_t reg){ + if (unit == dboard_iface::UNIT_BOTH) throw uhd::runtime_error("UNIT_BOTH not supported in gpio_core_200"); + return _atr_regs[unit][reg]; + } + + void set_gpio_ddr(const unit_t unit, const boost::uint16_t value, const boost::uint16_t mask){ + if (unit == dboard_iface::UNIT_BOTH) throw uhd::runtime_error("UNIT_BOTH not supported in gpio_core_200"); + shadow_it(_gpio_ddr[unit], value, mask); _iface->poke32(REG_GPIO_DDR, //update the 32 bit register (boost::uint32_t(_gpio_ddr[dboard_iface::UNIT_RX]) << shift_by_unit(dboard_iface::UNIT_RX)) | (boost::uint32_t(_gpio_ddr[dboard_iface::UNIT_TX]) << shift_by_unit(dboard_iface::UNIT_TX)) ); } - void set_gpio_out(const unit_t unit, const boost::uint16_t value){ - _gpio_out[unit] = value; //shadow + boost::uint16_t get_gpio_ddr(unit_t unit){ + if (unit == dboard_iface::UNIT_BOTH) throw uhd::runtime_error("UNIT_BOTH not supported in gpio_core_200"); + return _gpio_ddr[unit]; + } + + void set_gpio_out(const unit_t unit, const boost::uint16_t value, const boost::uint16_t mask){ + if (unit == dboard_iface::UNIT_BOTH) throw uhd::runtime_error("UNIT_BOTH not supported in gpio_core_200"); + shadow_it(_gpio_out[unit], value, mask); this->update(); //full update } + boost::uint16_t get_gpio_out(unit_t unit){ + if (unit == dboard_iface::UNIT_BOTH) throw uhd::runtime_error("UNIT_BOTH not supported in gpio_core_200"); + return _gpio_out[unit]; + } + boost::uint16_t read_gpio(const unit_t unit){ + if (unit == dboard_iface::UNIT_BOTH) throw uhd::runtime_error("UNIT_BOTH not supported in gpio_core_200"); return boost::uint16_t(_iface->peek32(_rb_addr) >> shift_by_unit(unit)); } @@ -85,26 +115,26 @@ private: } void update(void){ - update(dboard_iface::ATR_REG_IDLE); - update(dboard_iface::ATR_REG_TX_ONLY); - update(dboard_iface::ATR_REG_RX_ONLY); - update(dboard_iface::ATR_REG_FULL_DUPLEX); + update(gpio_atr::ATR_REG_IDLE); + update(gpio_atr::ATR_REG_TX_ONLY); + update(gpio_atr::ATR_REG_RX_ONLY); + update(gpio_atr::ATR_REG_FULL_DUPLEX); } void update(const atr_reg_t atr){ size_t addr; switch (atr) { - case dboard_iface::ATR_REG_IDLE: + case gpio_atr::ATR_REG_IDLE: addr = REG_GPIO_IDLE; break; - case dboard_iface::ATR_REG_TX_ONLY: + case gpio_atr::ATR_REG_TX_ONLY: addr = REG_GPIO_TX_ONLY; break; - case dboard_iface::ATR_REG_RX_ONLY: + case gpio_atr::ATR_REG_RX_ONLY: addr = REG_GPIO_RX_ONLY; break; - case dboard_iface::ATR_REG_FULL_DUPLEX: + case gpio_atr::ATR_REG_FULL_DUPLEX: addr = REG_GPIO_BOTH; break; default: @@ -144,27 +174,32 @@ public: gpio_core_200_32wo_impl(wb_iface::sptr iface, const size_t base): _iface(iface), _base(base) { + set_ddr_reg(); + } + + void set_ddr_reg(){ _iface->poke32(REG_GPIO_DDR, 0xffffffff); } + void set_atr_reg(const atr_reg_t atr, const boost::uint32_t value){ - if (atr == dboard_iface::ATR_REG_IDLE) + if (atr == gpio_atr::ATR_REG_IDLE) _iface->poke32(REG_GPIO_IDLE, value); - else if (atr == dboard_iface::ATR_REG_TX_ONLY) + else if (atr == gpio_atr::ATR_REG_TX_ONLY) _iface->poke32(REG_GPIO_TX_ONLY, value); - else if (atr == dboard_iface::ATR_REG_RX_ONLY) + else if (atr == gpio_atr::ATR_REG_RX_ONLY) _iface->poke32(REG_GPIO_RX_ONLY, value); - else if (atr == dboard_iface::ATR_REG_FULL_DUPLEX) + else if (atr == gpio_atr::ATR_REG_FULL_DUPLEX) _iface->poke32(REG_GPIO_BOTH, value); else UHD_THROW_INVALID_CODE_PATH(); } void set_all_regs(const boost::uint32_t value){ - set_atr_reg(dboard_iface::ATR_REG_IDLE, value); - set_atr_reg(dboard_iface::ATR_REG_TX_ONLY, value); - set_atr_reg(dboard_iface::ATR_REG_RX_ONLY, value); - set_atr_reg(dboard_iface::ATR_REG_FULL_DUPLEX, value); + set_atr_reg(gpio_atr::ATR_REG_IDLE, value); + set_atr_reg(gpio_atr::ATR_REG_TX_ONLY, value); + set_atr_reg(gpio_atr::ATR_REG_RX_ONLY, value); + set_atr_reg(gpio_atr::ATR_REG_FULL_DUPLEX, value); } private: diff --git a/host/lib/usrp/cores/gpio_core_200.hpp b/host/lib/usrp/cores/gpio_core_200.hpp index e22834fd9..c697f0e77 100644 --- a/host/lib/usrp/cores/gpio_core_200.hpp +++ b/host/lib/usrp/cores/gpio_core_200.hpp @@ -20,6 +20,7 @@ #include <uhd/config.hpp> #include <uhd/usrp/dboard_iface.hpp> +#include <uhd/usrp/gpio_defs.hpp> #include <boost/assign.hpp> #include <boost/cstdint.hpp> #include <boost/utility.hpp> @@ -27,28 +28,6 @@ #include <uhd/types/wb_iface.hpp> #include <map> -typedef enum { - GPIO_CTRL, - GPIO_DDR, - GPIO_OUT, - GPIO_ATR_0X, - GPIO_ATR_RX, - GPIO_ATR_TX, - GPIO_ATR_XX -} gpio_attr_t; - -typedef std::map<gpio_attr_t,std::string> gpio_attr_map_t; -static const gpio_attr_map_t gpio_attr_map = - boost::assign::map_list_of - (GPIO_CTRL, "CTRL") - (GPIO_DDR, "DDR") - (GPIO_OUT, "OUT") - (GPIO_ATR_0X, "ATR_0X") - (GPIO_ATR_RX, "ATR_RX") - (GPIO_ATR_TX, "ATR_TX") - (GPIO_ATR_XX, "ATR_XX") -; - class gpio_core_200 : boost::noncopyable{ public: typedef boost::shared_ptr<gpio_core_200> sptr; @@ -59,20 +38,32 @@ public: virtual ~gpio_core_200(void) = 0; //! makes a new GPIO core from iface and slave base - static sptr make(uhd::wb_iface::sptr iface, const size_t base, const size_t rb_addr); + static sptr make( + uhd::wb_iface::sptr iface, const size_t base, const size_t rb_addr); //! 1 = ATR - virtual void set_pin_ctrl(const unit_t unit, const boost::uint16_t value) = 0; + virtual void set_pin_ctrl( + const unit_t unit, const boost::uint16_t value, const boost::uint16_t mask) = 0; + + virtual boost::uint16_t get_pin_ctrl(unit_t unit) = 0; - virtual void set_atr_reg(const unit_t unit, const atr_reg_t atr, const boost::uint16_t value) = 0; + virtual void set_atr_reg( + const unit_t unit, const atr_reg_t atr, const boost::uint16_t value, const boost::uint16_t mask) = 0; + + virtual boost::uint16_t get_atr_reg(unit_t unit, atr_reg_t reg) = 0; //! 1 = OUTPUT - virtual void set_gpio_ddr(const unit_t unit, const boost::uint16_t value) = 0; + virtual void set_gpio_ddr( + const unit_t unit, const boost::uint16_t value, const boost::uint16_t mask) = 0; - virtual void set_gpio_out(const unit_t unit, const boost::uint16_t value) = 0; + virtual boost::uint16_t get_gpio_ddr(unit_t unit) = 0; - virtual boost::uint16_t read_gpio(const unit_t unit) = 0; + virtual void set_gpio_out( + const unit_t unit, const boost::uint16_t value, const boost::uint16_t mask) = 0; + virtual boost::uint16_t get_gpio_out(unit_t unit) = 0; + + virtual boost::uint16_t read_gpio(const unit_t unit) = 0; }; //! Simple wrapper for 32 bit write only @@ -86,6 +77,8 @@ public: static sptr make(uhd::wb_iface::sptr iface, const size_t); + virtual void set_ddr_reg() = 0; + virtual void set_atr_reg(const atr_reg_t atr, const boost::uint32_t value) = 0; virtual void set_all_regs(const boost::uint32_t value) = 0; diff --git a/host/lib/usrp/cores/rx_dsp_core_200.cpp b/host/lib/usrp/cores/rx_dsp_core_200.cpp index b899085c0..e51862d3b 100644 --- a/host/lib/usrp/cores/rx_dsp_core_200.cpp +++ b/host/lib/usrp/cores/rx_dsp_core_200.cpp @@ -16,6 +16,7 @@ // #include "rx_dsp_core_200.hpp" +#include "dsp_core_utils.hpp" #include <uhd/types/dict.hpp> #include <uhd/exception.hpp> #include <uhd/utils/math.hpp> @@ -24,7 +25,6 @@ #include <boost/assign/list_of.hpp> #include <boost/thread/thread.hpp> //thread sleep #include <boost/math/special_functions/round.hpp> -#include <boost/math/special_functions/sign.hpp> #include <boost/numeric/conversion/bounds.hpp> #include <algorithm> #include <cmath> @@ -223,42 +223,11 @@ public: return _fxpt_scalar_correction*_host_extra_scaling/32767.; } - double set_freq(const double freq_){ - //correct for outside of rate (wrap around) - double freq = std::fmod(freq_, _tick_rate); - if (std::abs(freq) > _tick_rate/2.0) - freq -= boost::math::sign(freq)*_tick_rate; - - //confirm that the target frequency is within range of the CORDIC - UHD_ASSERT_THROW(std::abs(freq) <= _tick_rate/2.0); - - /* Now calculate the frequency word. It is possible for this calculation - * to cause an overflow. As the requested DSP frequency approaches the - * master clock rate, that ratio multiplied by the scaling factor (2^32) - * will generally overflow within the last few kHz of tunable range. - * Thus, we check to see if the operation will overflow before doing it, - * and if it will, we set it to the integer min or max of this system. - */ - boost::int32_t freq_word = 0; - - static const double scale_factor = std::pow(2.0, 32); - if((freq / _tick_rate) >= (uhd::math::BOOST_INT32_MAX / scale_factor)) { - /* Operation would have caused a positive overflow of int32. */ - freq_word = uhd::math::BOOST_INT32_MAX; - - } else if((freq / _tick_rate) <= (uhd::math::BOOST_INT32_MIN / scale_factor)) { - /* Operation would have caused a negative overflow of int32. */ - freq_word = uhd::math::BOOST_INT32_MIN; - - } else { - /* The operation is safe. Perform normally. */ - freq_word = boost::int32_t(boost::math::round((freq / _tick_rate) * scale_factor)); - } - - //program the frequency word into the device DSP - const double actual_freq = (double(freq_word) / scale_factor) * _tick_rate; + double set_freq(const double requested_freq){ + double actual_freq; + int32_t freq_word; + get_freq_and_freq_word(requested_freq, _tick_rate, actual_freq, freq_word); _iface->poke32(REG_DSP_RX_FREQ, boost::uint32_t(freq_word)); - return actual_freq; } diff --git a/host/lib/usrp/cores/rx_dsp_core_3000.cpp b/host/lib/usrp/cores/rx_dsp_core_3000.cpp index 035bc6a3f..eedbbef95 100644 --- a/host/lib/usrp/cores/rx_dsp_core_3000.cpp +++ b/host/lib/usrp/cores/rx_dsp_core_3000.cpp @@ -16,6 +16,7 @@ // #include "rx_dsp_core_3000.hpp" +#include "dsp_core_utils.hpp" #include <uhd/types/dict.hpp> #include <uhd/exception.hpp> #include <uhd/utils/math.hpp> @@ -24,7 +25,6 @@ #include <boost/assign/list_of.hpp> #include <boost/thread/thread.hpp> //thread sleep #include <boost/math/special_functions/round.hpp> -#include <boost/math/special_functions/sign.hpp> #include <algorithm> #include <cmath> @@ -69,6 +69,7 @@ public: _scaling_adjustment = 1.0; _dsp_extra_scaling = 1.0; _tick_rate = 1.0; + _dsp_freq_offset = 0.0; } ~rx_dsp_core_3000_impl(void) @@ -79,17 +80,41 @@ public: ) } - void set_mux(const std::string &mode, const bool fe_swapped, const bool invert_i, const bool invert_q){ - static const uhd::dict<std::string, boost::uint32_t> mode_to_mux = boost::assign::map_list_of - ("IQ", 0) - ("QI", FLAG_DSP_RX_MUX_SWAP_IQ) - ("I", FLAG_DSP_RX_MUX_REAL_MODE) - ("Q", FLAG_DSP_RX_MUX_SWAP_IQ | FLAG_DSP_RX_MUX_REAL_MODE) - ; - _iface->poke32(REG_DSP_RX_MUX, mode_to_mux[mode] - | (fe_swapped ? FLAG_DSP_RX_MUX_SWAP_IQ : 0) - | (invert_i ? FLAG_DSP_RX_MUX_INVERT_I : 0) - | (invert_q ? FLAG_DSP_RX_MUX_INVERT_Q : 0)); + void set_mux(const uhd::usrp::fe_connection_t& fe_conn){ + boost::uint32_t reg_val = 0; + switch (fe_conn.get_sampling_mode()) { + case uhd::usrp::fe_connection_t::REAL: + case uhd::usrp::fe_connection_t::HETERODYNE: + reg_val = FLAG_DSP_RX_MUX_REAL_MODE; + break; + default: + reg_val = 0; + break; + } + + if (fe_conn.is_iq_swapped()) reg_val |= FLAG_DSP_RX_MUX_SWAP_IQ; + if (fe_conn.is_i_inverted()) reg_val |= FLAG_DSP_RX_MUX_INVERT_I; + if (fe_conn.is_q_inverted()) reg_val |= FLAG_DSP_RX_MUX_INVERT_Q; + + _iface->poke32(REG_DSP_RX_MUX, reg_val); + + if (fe_conn.get_sampling_mode() == uhd::usrp::fe_connection_t::HETERODYNE) { + //1. Remember the sign of the IF frequency. + // It will be discarded in the next step + int if_freq_sign = boost::math::sign(fe_conn.get_if_freq()); + //2. Map IF frequency to the range [0, _tick_rate) + double if_freq = std::abs(std::fmod(fe_conn.get_if_freq(), _tick_rate)); + //3. Map IF frequency to the range [-_tick_rate/2, _tick_rate/2) + // This is the aliased frequency + if (if_freq > (_tick_rate / 2.0)) { + if_freq -= _tick_rate; + } + //4. Set DSP offset to spin the signal in the opposite + // direction as the aliased frequency + _dsp_freq_offset = if_freq * (-if_freq_sign); + } else { + _dsp_freq_offset = 0.0; + } } void set_tick_rate(const double rate){ @@ -209,47 +234,18 @@ public: return _fxpt_scalar_correction*_host_extra_scaling/32767.; } - double set_freq(const double freq_){ - //correct for outside of rate (wrap around) - double freq = std::fmod(freq_, _tick_rate); - if (std::abs(freq) > _tick_rate/2.0) - freq -= boost::math::sign(freq)*_tick_rate; - - //confirm that the target frequency is within range of the CORDIC - UHD_ASSERT_THROW(std::abs(freq) <= _tick_rate/2.0); - - /* Now calculate the frequency word. It is possible for this calculation - * to cause an overflow. As the requested DSP frequency approaches the - * master clock rate, that ratio multiplied by the scaling factor (2^32) - * will generally overflow within the last few kHz of tunable range. - * Thus, we check to see if the operation will overflow before doing it, - * and if it will, we set it to the integer min or max of this system. - */ - boost::int32_t freq_word = 0; - - static const double scale_factor = std::pow(2.0, 32); - if((freq / _tick_rate) >= (uhd::math::BOOST_INT32_MAX / scale_factor)) { - /* Operation would have caused a positive overflow of int32. */ - freq_word = uhd::math::BOOST_INT32_MAX; - - } else if((freq / _tick_rate) <= (uhd::math::BOOST_INT32_MIN / scale_factor)) { - /* Operation would have caused a negative overflow of int32. */ - freq_word = uhd::math::BOOST_INT32_MIN; - - } else { - /* The operation is safe. Perform normally. */ - freq_word = boost::int32_t(boost::math::round((freq / _tick_rate) * scale_factor)); - } - - //program the frequency word into the device DSP - const double actual_freq = (double(freq_word) / scale_factor) * _tick_rate; + double set_freq(const double requested_freq){ + double actual_freq; + int32_t freq_word; + get_freq_and_freq_word(requested_freq + _dsp_freq_offset, _tick_rate, actual_freq, freq_word); _iface->poke32(REG_DSP_RX_FREQ, boost::uint32_t(freq_word)); - return actual_freq; } uhd::meta_range_t get_freq_range(void){ - return uhd::meta_range_t(-_tick_rate/2, +_tick_rate/2, _tick_rate/std::pow(2.0, 32)); + //Too keep the DSP range symmetric about 0, we use abs(_dsp_freq_offset) + const double offset = std::abs<double>(_dsp_freq_offset); + return uhd::meta_range_t(-(_tick_rate-offset)/2, +(_tick_rate-offset)/2, _tick_rate/std::pow(2.0, 32)); } void setup(const uhd::stream_args_t &stream_args){ @@ -284,18 +280,18 @@ public: void populate_subtree(property_tree::sptr subtree) { subtree->create<meta_range_t>("rate/range") - .publish(boost::bind(&rx_dsp_core_3000::get_host_rates, this)) + .set_publisher(boost::bind(&rx_dsp_core_3000::get_host_rates, this)) ; subtree->create<double>("rate/value") .set(DEFAULT_RATE) - .coerce(boost::bind(&rx_dsp_core_3000::set_host_rate, this, _1)) + .set_coercer(boost::bind(&rx_dsp_core_3000::set_host_rate, this, _1)) ; subtree->create<double>("freq/value") .set(DEFAULT_CORDIC_FREQ) - .coerce(boost::bind(&rx_dsp_core_3000::set_freq, this, _1)) + .set_coercer(boost::bind(&rx_dsp_core_3000::set_freq, this, _1)) ; subtree->create<meta_range_t>("freq/range") - .publish(boost::bind(&rx_dsp_core_3000::get_freq_range, this)) + .set_publisher(boost::bind(&rx_dsp_core_3000::get_freq_range, this)) ; } @@ -305,6 +301,7 @@ private: const bool _is_b200; //TODO: Obsolete this when we switch to the new DDC on the B200 double _tick_rate, _link_rate; double _scaling_adjustment, _dsp_extra_scaling, _host_extra_scaling, _fxpt_scalar_correction; + double _dsp_freq_offset; }; rx_dsp_core_3000::sptr rx_dsp_core_3000::make(wb_iface::sptr iface, const size_t dsp_base, const bool is_b200 /* = false */) diff --git a/host/lib/usrp/cores/rx_dsp_core_3000.hpp b/host/lib/usrp/cores/rx_dsp_core_3000.hpp index 65801de1d..41b328357 100644 --- a/host/lib/usrp/cores/rx_dsp_core_3000.hpp +++ b/host/lib/usrp/cores/rx_dsp_core_3000.hpp @@ -24,6 +24,7 @@ #include <uhd/types/stream_cmd.hpp> #include <uhd/types/wb_iface.hpp> #include <uhd/property_tree.hpp> +#include <uhd/usrp/fe_connection.hpp> #include <boost/utility.hpp> #include <boost/shared_ptr.hpp> #include <string> @@ -43,7 +44,7 @@ public: const bool is_b200 = false //TODO: Obsolete this when we switch to the new DDC on the B200 ); - virtual void set_mux(const std::string &mode, const bool fe_swapped = false, const bool invert_i = false, const bool invert_q = false) = 0; + virtual void set_mux(const uhd::usrp::fe_connection_t& fe_conn) = 0; virtual void set_tick_rate(const double rate) = 0; diff --git a/host/lib/usrp/cores/rx_frontend_core_200.cpp b/host/lib/usrp/cores/rx_frontend_core_200.cpp index 7ac920553..0a60bf87c 100644 --- a/host/lib/usrp/cores/rx_frontend_core_200.cpp +++ b/host/lib/usrp/cores/rx_frontend_core_200.cpp @@ -83,15 +83,15 @@ public: { subtree->create<std::complex<double> >("dc_offset/value") .set(DEFAULT_DC_OFFSET_VALUE) - .coerce(boost::bind(&rx_frontend_core_200::set_dc_offset, this, _1)) + .set_coercer(boost::bind(&rx_frontend_core_200::set_dc_offset, this, _1)) ; subtree->create<bool>("dc_offset/enable") .set(DEFAULT_DC_OFFSET_ENABLE) - .subscribe(boost::bind(&rx_frontend_core_200::set_dc_offset_auto, this, _1)) + .add_coerced_subscriber(boost::bind(&rx_frontend_core_200::set_dc_offset_auto, this, _1)) ; subtree->create<std::complex<double> >("iq_balance/value") .set(DEFAULT_IQ_BALANCE_VALUE) - .subscribe(boost::bind(&rx_frontend_core_200::set_iq_balance, this, _1)) + .add_coerced_subscriber(boost::bind(&rx_frontend_core_200::set_iq_balance, this, _1)) ; } diff --git a/host/lib/usrp/cores/rx_frontend_core_3000.cpp b/host/lib/usrp/cores/rx_frontend_core_3000.cpp new file mode 100644 index 000000000..23197cf5a --- /dev/null +++ b/host/lib/usrp/cores/rx_frontend_core_3000.cpp @@ -0,0 +1,186 @@ +// +// Copyright 2011-2012,2014-2016 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 +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +#include "rx_frontend_core_3000.hpp" +#include "dsp_core_utils.hpp" +#include <boost/math/special_functions/round.hpp> +#include <boost/assign/list_of.hpp> +#include <boost/bind.hpp> +#include <uhd/types/dict.hpp> + +using namespace uhd; + +#define REG_RX_FE_MAG_CORRECTION (_base + 0 ) //18 bits +#define REG_RX_FE_PHASE_CORRECTION (_base + 4 ) //18 bits +#define REG_RX_FE_OFFSET_I (_base + 8 ) //18 bits +#define REG_RX_FE_OFFSET_Q (_base + 12) //18 bits +#define REG_RX_FE_MAPPING (_base + 16) +#define REG_RX_FE_HET_CORDIC_PHASE (_base + 20) + +#define FLAG_DSP_RX_MAPPING_SWAP_IQ (1 << 0) +#define FLAG_DSP_RX_MAPPING_REAL_MODE (1 << 1) +#define FLAG_DSP_RX_MAPPING_INVERT_Q (1 << 2) +#define FLAG_DSP_RX_MAPPING_INVERT_I (1 << 3) +#define FLAG_DSP_RX_MAPPING_REAL_DECIM (1 << 4) +//#define FLAG_DSP_RX_MAPPING_RESERVED (1 << 5) +//#define FLAG_DSP_RX_MAPPING_RESERVED (1 << 6) +#define FLAG_DSP_RX_MAPPING_BYPASS_ALL (1 << 7) + +#define OFFSET_FIXED (1ul << 31) +#define OFFSET_SET (1ul << 30) +#define FLAG_MASK (OFFSET_FIXED | OFFSET_SET) + +using namespace uhd::usrp; + +static boost::uint32_t fs_to_bits(const double num, const size_t bits){ + return boost::int32_t(boost::math::round(num * (1 << (bits-1)))); +} + +rx_frontend_core_3000::~rx_frontend_core_3000(void){ + /* NOP */ +} + +const std::complex<double> rx_frontend_core_3000::DEFAULT_DC_OFFSET_VALUE = std::complex<double>(0.0, 0.0); +const bool rx_frontend_core_3000::DEFAULT_DC_OFFSET_ENABLE = true; +const std::complex<double> rx_frontend_core_3000::DEFAULT_IQ_BALANCE_VALUE = std::complex<double>(0.0, 0.0); + +class rx_frontend_core_3000_impl : public rx_frontend_core_3000{ +public: + rx_frontend_core_3000_impl(wb_iface::sptr iface, const size_t base): + _i_dc_off(0), _q_dc_off(0), + _adc_rate(0.0), + _fe_conn(fe_connection_t("IQ")), + _iface(iface), _base(base) + { + //NOP + } + + void set_adc_rate(const double rate) { + _adc_rate = rate; + } + + void bypass_all(bool bypass_en) { + if (bypass_en) { + _iface->poke32(REG_RX_FE_MAPPING, FLAG_DSP_RX_MAPPING_BYPASS_ALL); + } else { + set_fe_connection(_fe_conn); + } + } + + void set_fe_connection(const fe_connection_t& fe_conn) { + boost::uint32_t mapping_reg_val = 0; + switch (fe_conn.get_sampling_mode()) { + case fe_connection_t::REAL: + case fe_connection_t::HETERODYNE: + mapping_reg_val = FLAG_DSP_RX_MAPPING_REAL_MODE|FLAG_DSP_RX_MAPPING_REAL_DECIM; + break; + default: + mapping_reg_val = 0; + break; + } + + if (fe_conn.is_iq_swapped()) mapping_reg_val |= FLAG_DSP_RX_MAPPING_SWAP_IQ; + if (fe_conn.is_i_inverted()) mapping_reg_val |= FLAG_DSP_RX_MAPPING_INVERT_I; + if (fe_conn.is_q_inverted()) mapping_reg_val |= FLAG_DSP_RX_MAPPING_INVERT_Q; + + _iface->poke32(REG_RX_FE_MAPPING, mapping_reg_val); + + UHD_ASSERT_THROW(_adc_rate!=0.0) + double cordic_freq = 0.0, actual_cordic_freq = 0.0; + if (fe_conn.get_sampling_mode() == fe_connection_t::HETERODYNE) { + //1. Remember the sign of the IF frequency. + // It will be discarded in the next step + int if_freq_sign = boost::math::sign(fe_conn.get_if_freq()); + //2. Map IF frequency to the range [0, _adc_rate) + double if_freq = std::abs(std::fmod(fe_conn.get_if_freq(), _adc_rate)); + //3. Map IF frequency to the range [-_adc_rate/2, _adc_rate/2) + // This is the aliased frequency + if (if_freq > (_adc_rate / 2.0)) { + if_freq -= _adc_rate; + } + //4. Set DSP offset to spin the signal in the opposite + // direction as the aliased frequency + cordic_freq = if_freq * (-if_freq_sign); + } + int32_t freq_word; + get_freq_and_freq_word(cordic_freq, _adc_rate, actual_cordic_freq, freq_word); + _iface->poke32(REG_RX_FE_HET_CORDIC_PHASE, boost::uint32_t(freq_word)); + + _fe_conn = fe_conn; + } + + void set_dc_offset_auto(const bool enb) { + _set_dc_offset(enb ? 0 : OFFSET_FIXED); + } + + std::complex<double> set_dc_offset(const std::complex<double> &off) { + static const double scaler = double(1ul << 29); + _i_dc_off = boost::math::iround(off.real()*scaler); + _q_dc_off = boost::math::iround(off.imag()*scaler); + + _set_dc_offset(OFFSET_SET | OFFSET_FIXED); + + return std::complex<double>(_i_dc_off/scaler, _q_dc_off/scaler); + } + + void _set_dc_offset(const boost::uint32_t flags) { + _iface->poke32(REG_RX_FE_OFFSET_I, flags | (_i_dc_off & ~FLAG_MASK)); + _iface->poke32(REG_RX_FE_OFFSET_Q, flags | (_q_dc_off & ~FLAG_MASK)); + } + + void set_iq_balance(const std::complex<double> &cor) { + _iface->poke32(REG_RX_FE_MAG_CORRECTION, fs_to_bits(cor.real(), 18)); + _iface->poke32(REG_RX_FE_PHASE_CORRECTION, fs_to_bits(cor.imag(), 18)); + } + + void populate_subtree(uhd::property_tree::sptr subtree) { + subtree->create<std::complex<double> >("dc_offset/value") + .set(DEFAULT_DC_OFFSET_VALUE) + .set_coercer(boost::bind(&rx_frontend_core_3000::set_dc_offset, this, _1)) + ; + subtree->create<bool>("dc_offset/enable") + .set(DEFAULT_DC_OFFSET_ENABLE) + .add_coerced_subscriber(boost::bind(&rx_frontend_core_3000::set_dc_offset_auto, this, _1)) + ; + subtree->create<std::complex<double> >("iq_balance/value") + .set(DEFAULT_IQ_BALANCE_VALUE) + .add_coerced_subscriber(boost::bind(&rx_frontend_core_3000::set_iq_balance, this, _1)) + ; + } + + double get_output_rate() { + switch (_fe_conn.get_sampling_mode()) { + case fe_connection_t::REAL: + case fe_connection_t::HETERODYNE: + return _adc_rate / 2; + default: + return _adc_rate; + } + return _adc_rate; + } + +private: + boost::int32_t _i_dc_off, _q_dc_off; + double _adc_rate; + fe_connection_t _fe_conn; + wb_iface::sptr _iface; + const size_t _base; +}; + +rx_frontend_core_3000::sptr rx_frontend_core_3000::make(wb_iface::sptr iface, const size_t base){ + return sptr(new rx_frontend_core_3000_impl(iface, base)); +} diff --git a/host/lib/usrp/cores/rx_frontend_core_3000.hpp b/host/lib/usrp/cores/rx_frontend_core_3000.hpp new file mode 100644 index 000000000..baa58331e --- /dev/null +++ b/host/lib/usrp/cores/rx_frontend_core_3000.hpp @@ -0,0 +1,69 @@ +// +// Copyright 2011,2014-2016 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 +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +#ifndef INCLUDED_LIBUHD_USRP_TX_FRONTEND_CORE_3000_HPP +#define INCLUDED_LIBUHD_USRP_TX_FRONTEND_CORE_3000_HPP + +#include <uhd/config.hpp> +#include <uhd/types/wb_iface.hpp> +#include <uhd/property_tree.hpp> +#include <uhd/usrp/fe_connection.hpp> +#include <boost/utility.hpp> +#include <boost/shared_ptr.hpp> +#include <complex> +#include <string> + +class rx_frontend_core_3000 : boost::noncopyable{ +public: + static const std::complex<double> DEFAULT_DC_OFFSET_VALUE; + static const bool DEFAULT_DC_OFFSET_ENABLE; + static const std::complex<double> DEFAULT_IQ_BALANCE_VALUE; + + typedef boost::shared_ptr<rx_frontend_core_3000> sptr; + + virtual ~rx_frontend_core_3000(void) = 0; + + static sptr make(uhd::wb_iface::sptr iface, const size_t base); + + /*! Set the input sampling rate (i.e. ADC rate) + */ + virtual void set_adc_rate(const double rate) = 0; + + virtual void bypass_all(bool bypass_en) = 0; + + virtual void set_fe_connection(const uhd::usrp::fe_connection_t& fe_conn) = 0; + + virtual void set_dc_offset_auto(const bool enb) = 0; + + virtual std::complex<double> set_dc_offset(const std::complex<double> &off) = 0; + + virtual void set_iq_balance(const std::complex<double> &cor) = 0; + + virtual void populate_subtree(uhd::property_tree::sptr subtree) = 0; + + /*! Return the sampling rate at the output + * + * In real mode, the frontend core will decimate the sampling rate by a + * factor of 2. + * + * \returns RX sampling rate + */ + virtual double get_output_rate(void) = 0; + +}; + +#endif /* INCLUDED_LIBUHD_USRP_TX_FRONTEND_CORE_3000_HPP */ diff --git a/host/lib/usrp/cores/rx_vita_core_3000.cpp b/host/lib/usrp/cores/rx_vita_core_3000.cpp index f61da7cc3..54c57c2d5 100644 --- a/host/lib/usrp/cores/rx_vita_core_3000.cpp +++ b/host/lib/usrp/cores/rx_vita_core_3000.cpp @@ -20,6 +20,8 @@ #include <uhd/utils/safe_call.hpp> #include <boost/assign/list_of.hpp> #include <boost/tuple/tuple.hpp> +#include <boost/date_time.hpp> +#include <boost/thread.hpp> #define REG_FRAMER_MAXLEN _base + 4*4 + 0 #define REG_FRAMER_SID _base + 4*4 + 4 @@ -63,13 +65,26 @@ struct rx_vita_core_3000_impl : rx_vita_core_3000 void configure_flow_control(const size_t window_size) { + // The window needs to be disabled in the case where this object is + // uncleanly destroyed and the FC window is left enabled + _iface->poke32(REG_FC_ENABLE, 0); + + // Sleep for a large amount of time to allow the source flow control + // module in the FPGA to flush all the packets buffered upstream. + // At 1 ms * 200 MHz = 200k cycles, 8 bytes * 200k cycles = 1.6 MB + // of flushed data, when the typical amount of data buffered + // is on the order of kilobytes + boost::this_thread::sleep(boost::posix_time::milliseconds(1.0)); + _iface->poke32(REG_FC_WINDOW, window_size-1); _iface->poke32(REG_FC_ENABLE, window_size?1:0); } void clear(void) { - this->configure_flow_control(0); //disable fc + // FC should never be disabled, this will actually become + // impossible in the future + //this->configure_flow_control(0); //disable fc } void set_nsamps_per_packet(const size_t nsamps) diff --git a/host/lib/usrp/cores/spi_core_3000.cpp b/host/lib/usrp/cores/spi_core_3000.cpp index 0656d910a..01df71cec 100644 --- a/host/lib/usrp/cores/spi_core_3000.cpp +++ b/host/lib/usrp/cores/spi_core_3000.cpp @@ -20,9 +20,10 @@ #include <uhd/utils/msg.hpp> #include <boost/thread/thread.hpp> //sleep -#define SPI_DIV _base + 0 -#define SPI_CTRL _base + 4 -#define SPI_DATA _base + 8 +#define SPI_DIV _base + 0 +#define SPI_CTRL _base + 4 +#define SPI_DATA _base + 8 +#define SPI_SHUTDOWN _base + 12 using namespace uhd; @@ -34,7 +35,7 @@ class spi_core_3000_impl : public spi_core_3000 { public: spi_core_3000_impl(wb_iface::sptr iface, const size_t base, const size_t readback): - _iface(iface), _base(base), _readback(readback), _ctrl_word_cache(0) + _iface(iface), _base(base), _readback(readback), _ctrl_word_cache(0), _divider_cache(0) { this->set_divider(30); } @@ -46,7 +47,21 @@ public: size_t num_bits, bool readback ){ - boost::mutex::scoped_lock lock(_mutex); + boost::lock_guard<boost::mutex> lock(_mutex); + + //load SPI divider + size_t spi_divider = _div; + if (config.use_custom_divider) { + //The resulting SPI frequency will be f_system/(2*(divider+1)) + //This math ensures the frequency will be equal to or less than the target + spi_divider = (config.divider-1)/2; + } + + //conditionally send SPI divider + if (spi_divider != _divider_cache) { + _iface->poke32(SPI_DIV, spi_divider); + _divider_cache = spi_divider; + } //load control word boost::uint32_t ctrl_word = 0; @@ -55,17 +70,16 @@ public: if (config.mosi_edge == spi_config_t::EDGE_FALL) ctrl_word |= (1 << 31); if (config.miso_edge == spi_config_t::EDGE_RISE) ctrl_word |= (1 << 30); - //load data word (must be in upper bits) - const boost::uint32_t data_out = data << (32 - num_bits); - //conditionally send control word if (_ctrl_word_cache != ctrl_word) { - _iface->poke32(SPI_DIV, _div); _iface->poke32(SPI_CTRL, ctrl_word); _ctrl_word_cache = ctrl_word; } + //load data word (must be in upper bits) + const boost::uint32_t data_out = data << (32 - num_bits); + //send data word _iface->poke32(SPI_DATA, data_out); @@ -78,6 +92,17 @@ public: return 0; } + void set_shutdown(const bool shutdown) + { + _shutdown_cache = shutdown; + _iface->poke32(SPI_SHUTDOWN, _shutdown_cache); + } + + bool get_shutdown() + { + return(_shutdown_cache); + } + void set_divider(const double div) { _div = size_t((div/2) - 0.5); @@ -89,8 +114,10 @@ private: const size_t _base; const size_t _readback; boost::uint32_t _ctrl_word_cache; + bool _shutdown_cache; boost::mutex _mutex; size_t _div; + size_t _divider_cache; }; spi_core_3000::sptr spi_core_3000::make(wb_iface::sptr iface, const size_t base, const size_t readback) diff --git a/host/lib/usrp/cores/spi_core_3000.hpp b/host/lib/usrp/cores/spi_core_3000.hpp index 6a439772e..8d5177196 100644 --- a/host/lib/usrp/cores/spi_core_3000.hpp +++ b/host/lib/usrp/cores/spi_core_3000.hpp @@ -36,6 +36,13 @@ public: //! Set the spi clock divider to something usable virtual void set_divider(const double div) = 0; + + //! Place SPI core in shutdown mode. All attempted SPI transactions are dropped by + // the core. + virtual void set_shutdown(const bool shutdown) = 0; + + //! Get state of shutdown register + virtual bool get_shutdown() = 0; }; #endif /* INCLUDED_LIBUHD_USRP_SPI_CORE_3000_HPP */ diff --git a/host/lib/usrp/cores/tx_dsp_core_200.cpp b/host/lib/usrp/cores/tx_dsp_core_200.cpp index 2ef9f4406..4c456a10d 100644 --- a/host/lib/usrp/cores/tx_dsp_core_200.cpp +++ b/host/lib/usrp/cores/tx_dsp_core_200.cpp @@ -16,13 +16,13 @@ // #include "tx_dsp_core_200.hpp" +#include "dsp_core_utils.hpp" #include <uhd/types/dict.hpp> #include <uhd/exception.hpp> #include <uhd/utils/math.hpp> #include <uhd/utils/msg.hpp> #include <boost/assign/list_of.hpp> #include <boost/math/special_functions/round.hpp> -#include <boost/math/special_functions/sign.hpp> #include <boost/thread/thread.hpp> //sleep #include <algorithm> #include <cmath> @@ -163,42 +163,11 @@ public: return _fxpt_scalar_correction*_host_extra_scaling*32767.; } - double set_freq(const double freq_){ - //correct for outside of rate (wrap around) - double freq = std::fmod(freq_, _tick_rate); - if (std::abs(freq) > _tick_rate/2.0) - freq -= boost::math::sign(freq)*_tick_rate; - - //confirm that the target frequency is within range of the CORDIC - UHD_ASSERT_THROW(std::abs(freq) <= _tick_rate/2.0); - - /* Now calculate the frequency word. It is possible for this calculation - * to cause an overflow. As the requested DSP frequency approaches the - * master clock rate, that ratio multiplied by the scaling factor (2^32) - * will generally overflow within the last few kHz of tunable range. - * Thus, we check to see if the operation will overflow before doing it, - * and if it will, we set it to the integer min or max of this system. - */ - boost::int32_t freq_word = 0; - - static const double scale_factor = std::pow(2.0, 32); - if((freq / _tick_rate) >= (uhd::math::BOOST_INT32_MAX / scale_factor)) { - /* Operation would have caused a positive overflow of int32. */ - freq_word = uhd::math::BOOST_INT32_MAX; - - } else if((freq / _tick_rate) <= (uhd::math::BOOST_INT32_MIN / scale_factor)) { - /* Operation would have caused a negative overflow of int32. */ - freq_word = uhd::math::BOOST_INT32_MIN; - - } else { - /* The operation is safe. Perform normally. */ - freq_word = boost::int32_t(boost::math::round((freq / _tick_rate) * scale_factor)); - } - - //program the frequency word into the device DSP - const double actual_freq = (double(freq_word) / scale_factor) * _tick_rate; + double set_freq(const double requested_freq){ + double actual_freq; + int32_t freq_word; + get_freq_and_freq_word(requested_freq, _tick_rate, actual_freq, freq_word); _iface->poke32(REG_DSP_TX_FREQ, boost::uint32_t(freq_word)); - return actual_freq; } diff --git a/host/lib/usrp/cores/tx_dsp_core_3000.cpp b/host/lib/usrp/cores/tx_dsp_core_3000.cpp index 7e447ae7d..3889bbdc4 100644 --- a/host/lib/usrp/cores/tx_dsp_core_3000.cpp +++ b/host/lib/usrp/cores/tx_dsp_core_3000.cpp @@ -16,13 +16,13 @@ // #include "tx_dsp_core_3000.hpp" +#include "dsp_core_utils.hpp" #include <uhd/types/dict.hpp> #include <uhd/exception.hpp> #include <uhd/utils/math.hpp> #include <uhd/utils/msg.hpp> #include <boost/assign/list_of.hpp> #include <boost/math/special_functions/round.hpp> -#include <boost/math/special_functions/sign.hpp> #include <boost/thread/thread.hpp> //sleep #include <algorithm> #include <cmath> @@ -136,42 +136,11 @@ public: return _fxpt_scalar_correction*_host_extra_scaling*32767.; } - double set_freq(const double freq_){ - //correct for outside of rate (wrap around) - double freq = std::fmod(freq_, _tick_rate); - if (std::abs(freq) > _tick_rate/2.0) - freq -= boost::math::sign(freq)*_tick_rate; - - //confirm that the target frequency is within range of the CORDIC - UHD_ASSERT_THROW(std::abs(freq) <= _tick_rate/2.0); - - /* Now calculate the frequency word. It is possible for this calculation - * to cause an overflow. As the requested DSP frequency approaches the - * master clock rate, that ratio multiplied by the scaling factor (2^32) - * will generally overflow within the last few kHz of tunable range. - * Thus, we check to see if the operation will overflow before doing it, - * and if it will, we set it to the integer min or max of this system. - */ - boost::int32_t freq_word = 0; - - static const double scale_factor = std::pow(2.0, 32); - if((freq / _tick_rate) >= (uhd::math::BOOST_INT32_MAX / scale_factor)) { - /* Operation would have caused a positive overflow of int32. */ - freq_word = uhd::math::BOOST_INT32_MAX; - - } else if((freq / _tick_rate) <= (uhd::math::BOOST_INT32_MIN / scale_factor)) { - /* Operation would have caused a negative overflow of int32. */ - freq_word = uhd::math::BOOST_INT32_MIN; - - } else { - /* The operation is safe. Perform normally. */ - freq_word = boost::int32_t(boost::math::round((freq / _tick_rate) * scale_factor)); - } - - //program the frequency word into the device DSP - const double actual_freq = (double(freq_word) / scale_factor) * _tick_rate; + double set_freq(const double requested_freq) { + double actual_freq; + int32_t freq_word; + get_freq_and_freq_word(requested_freq, _tick_rate, actual_freq, freq_word); _iface->poke32(REG_DSP_TX_FREQ, boost::uint32_t(freq_word)); - return actual_freq; } @@ -211,18 +180,18 @@ public: void populate_subtree(property_tree::sptr subtree) { subtree->create<meta_range_t>("rate/range") - .publish(boost::bind(&tx_dsp_core_3000::get_host_rates, this)) + .set_publisher(boost::bind(&tx_dsp_core_3000::get_host_rates, this)) ; subtree->create<double>("rate/value") .set(DEFAULT_RATE) - .coerce(boost::bind(&tx_dsp_core_3000::set_host_rate, this, _1)) + .set_coercer(boost::bind(&tx_dsp_core_3000::set_host_rate, this, _1)) ; subtree->create<double>("freq/value") .set(DEFAULT_CORDIC_FREQ) - .coerce(boost::bind(&tx_dsp_core_3000::set_freq, this, _1)) + .set_coercer(boost::bind(&tx_dsp_core_3000::set_freq, this, _1)) ; subtree->create<meta_range_t>("freq/range") - .publish(boost::bind(&tx_dsp_core_3000::get_freq_range, this)) + .set_publisher(boost::bind(&tx_dsp_core_3000::get_freq_range, this)) ; } diff --git a/host/lib/usrp/cores/tx_frontend_core_200.cpp b/host/lib/usrp/cores/tx_frontend_core_200.cpp index 0fa028571..be4f77f39 100644 --- a/host/lib/usrp/cores/tx_frontend_core_200.cpp +++ b/host/lib/usrp/cores/tx_frontend_core_200.cpp @@ -79,11 +79,11 @@ public: { subtree->create< std::complex<double> >("dc_offset/value") .set(DEFAULT_DC_OFFSET_VALUE) - .coerce(boost::bind(&tx_frontend_core_200::set_dc_offset, this, _1)) + .set_coercer(boost::bind(&tx_frontend_core_200::set_dc_offset, this, _1)) ; subtree->create< std::complex<double> >("iq_balance/value") .set(DEFAULT_IQ_BALANCE_VALUE) - .subscribe(boost::bind(&tx_frontend_core_200::set_iq_balance, this, _1)) + .add_coerced_subscriber(boost::bind(&tx_frontend_core_200::set_iq_balance, this, _1)) ; } diff --git a/host/lib/usrp/cores/tx_vita_core_3000.cpp b/host/lib/usrp/cores/tx_vita_core_3000.cpp index 71a2b7e21..c76b384d9 100644 --- a/host/lib/usrp/cores/tx_vita_core_3000.cpp +++ b/host/lib/usrp/cores/tx_vita_core_3000.cpp @@ -18,9 +18,11 @@ #include "tx_vita_core_3000.hpp" #include <uhd/utils/safe_call.hpp> -#define REG_CTRL_ERROR_POLICY _base + 0 -#define REG_DEFRAMER_CYCLE_FC_UPS _base + 2*4 + 0 -#define REG_DEFRAMER_PACKET_FC_UPS _base + 2*4 + 4 +#define REG_CTRL_ERROR_POLICY (_base + 0) +#define REG_FC_PRE_RADIO_RESP_BASE (_base + 2*4) +#define REG_FC_PRE_FIFO_RESP_BASE (_base + 4*4) +#define REG_CTRL_FC_CYCLE_OFFSET (0*4) +#define REG_CTRL_FC_PACKET_OFFSET (1*4) using namespace uhd; @@ -32,12 +34,22 @@ struct tx_vita_core_3000_impl : tx_vita_core_3000 { tx_vita_core_3000_impl( wb_iface::sptr iface, - const size_t base + const size_t base, + fc_monitor_loc fc_location ): _iface(iface), - _base(base) + _base(base), + _fc_base((fc_location==FC_PRE_RADIO or fc_location==FC_DEFAULT) ? + REG_FC_PRE_RADIO_RESP_BASE : REG_FC_PRE_FIFO_RESP_BASE), + _fc_location(fc_location) { - this->set_tick_rate(1); //init to non zero + if (fc_location != FC_DEFAULT) { + //Turn off the other FC monitoring module + const size_t other_fc_base = (fc_location==FC_PRE_RADIO) ? + REG_FC_PRE_FIFO_RESP_BASE : REG_FC_PRE_RADIO_RESP_BASE; + _iface->poke32(other_fc_base + REG_CTRL_FC_CYCLE_OFFSET, 0); + _iface->poke32(other_fc_base + REG_CTRL_FC_PACKET_OFFSET, 0); + } this->set_underflow_policy("next_packet"); this->clear(); } @@ -56,11 +68,6 @@ struct tx_vita_core_3000_impl : tx_vita_core_3000 this->set_underflow_policy(_policy); //clears the seq } - void set_tick_rate(const double rate) - { - _tick_rate = rate; - } - void set_underflow_policy(const std::string &policy) { if (policy == "next_packet") @@ -89,23 +96,35 @@ struct tx_vita_core_3000_impl : tx_vita_core_3000 void configure_flow_control(const size_t cycs_per_up, const size_t pkts_per_up) { - if (cycs_per_up == 0) _iface->poke32(REG_DEFRAMER_CYCLE_FC_UPS, 0); - else _iface->poke32(REG_DEFRAMER_CYCLE_FC_UPS, (1 << 31) | ((cycs_per_up) & 0xffffff)); + if (cycs_per_up == 0) _iface->poke32(_fc_base + REG_CTRL_FC_CYCLE_OFFSET, 0); + else _iface->poke32(_fc_base + REG_CTRL_FC_CYCLE_OFFSET, (1 << 31) | ((cycs_per_up) & 0xffffff)); - if (pkts_per_up == 0) _iface->poke32(REG_DEFRAMER_PACKET_FC_UPS, 0); - else _iface->poke32(REG_DEFRAMER_PACKET_FC_UPS, (1 << 31) | ((pkts_per_up) & 0xffff)); + if (pkts_per_up == 0) _iface->poke32(_fc_base + REG_CTRL_FC_PACKET_OFFSET, 0); + else _iface->poke32(_fc_base + REG_CTRL_FC_PACKET_OFFSET, (1 << 31) | ((pkts_per_up) & 0xffff)); } - wb_iface::sptr _iface; - const size_t _base; - double _tick_rate; - std::string _policy; + wb_iface::sptr _iface; + const size_t _base; + const size_t _fc_base; + std::string _policy; + fc_monitor_loc _fc_location; + }; tx_vita_core_3000::sptr tx_vita_core_3000::make( wb_iface::sptr iface, + const size_t base, + fc_monitor_loc fc_location +) +{ + return tx_vita_core_3000::sptr(new tx_vita_core_3000_impl(iface, base, fc_location)); +} + +tx_vita_core_3000::sptr tx_vita_core_3000::make_no_radio_buff( + wb_iface::sptr iface, const size_t base ) { - return tx_vita_core_3000::sptr(new tx_vita_core_3000_impl(iface, base)); + //No internal radio buffer so only pre-radio monitoring is supported. + return tx_vita_core_3000::sptr(new tx_vita_core_3000_impl(iface, base, FC_DEFAULT)); } diff --git a/host/lib/usrp/cores/tx_vita_core_3000.hpp b/host/lib/usrp/cores/tx_vita_core_3000.hpp index 4c0052d4f..bd0f20ba4 100644 --- a/host/lib/usrp/cores/tx_vita_core_3000.hpp +++ b/host/lib/usrp/cores/tx_vita_core_3000.hpp @@ -32,17 +32,27 @@ class tx_vita_core_3000 : boost::noncopyable public: typedef boost::shared_ptr<tx_vita_core_3000> sptr; + enum fc_monitor_loc { + FC_DEFAULT, + FC_PRE_RADIO, + FC_PRE_FIFO + }; + virtual ~tx_vita_core_3000(void) = 0; static sptr make( uhd::wb_iface::sptr iface, + const size_t base, + fc_monitor_loc fc_location = FC_PRE_RADIO + ); + + static sptr make_no_radio_buff( + uhd::wb_iface::sptr iface, const size_t base ); virtual void clear(void) = 0; - virtual void set_tick_rate(const double rate) = 0; - virtual void setup(const uhd::stream_args_t &stream_args) = 0; virtual void configure_flow_control(const size_t cycs_per_up, const size_t pkts_per_up) = 0; diff --git a/host/lib/usrp/cores/user_settings_core_3000.cpp b/host/lib/usrp/cores/user_settings_core_3000.cpp new file mode 100644 index 000000000..549264f57 --- /dev/null +++ b/host/lib/usrp/cores/user_settings_core_3000.cpp @@ -0,0 +1,85 @@ +// +// Copyright 2012 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 +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +#include "user_settings_core_3000.hpp" +#include <uhd/exception.hpp> +#include <boost/thread/thread.hpp> + +using namespace uhd; + +#define REG_USER_SR_ADDR _sr_base_addr + 0 +#define REG_USER_SR_DATA _sr_base_addr + 4 +#define REG_USER_RB_ADDR _sr_base_addr + 8 + +class user_settings_core_3000_impl : public user_settings_core_3000 { +public: + user_settings_core_3000_impl( + wb_iface::sptr iface, + const wb_addr_type sr_base_addr, const wb_addr_type rb_reg_addr): + _iface(iface), _sr_base_addr(sr_base_addr), _rb_reg_addr(rb_reg_addr) + { + } + + void poke64(const wb_addr_type offset, const boost::uint64_t value) + { + if (offset % sizeof(boost::uint64_t) != 0) throw uhd::value_error("poke64: Incorrect address alignment"); + poke32(offset, static_cast<boost::uint32_t>(value)); + poke32(offset + 4, static_cast<boost::uint32_t>(value >> 32)); + } + + boost::uint64_t peek64(const wb_addr_type offset) + { + if (offset % sizeof(boost::uint64_t) != 0) throw uhd::value_error("peek64: Incorrect address alignment"); + + boost::unique_lock<boost::mutex> lock(_mutex); + _iface->poke32(REG_USER_RB_ADDR, offset >> 3); //Translate byte offset to 64-bit offset + return _iface->peek64(_rb_reg_addr); + } + + void poke32(const wb_addr_type offset, const boost::uint32_t value) + { + if (offset % sizeof(boost::uint32_t) != 0) throw uhd::value_error("poke32: Incorrect address alignment"); + + boost::unique_lock<boost::mutex> lock(_mutex); + _iface->poke32(REG_USER_SR_ADDR, offset >> 2); //Translate byte offset to 64-bit offset + _iface->poke32(REG_USER_SR_DATA, value); + } + + boost::uint32_t peek32(const wb_addr_type offset) + { + if (offset % sizeof(boost::uint32_t) != 0) throw uhd::value_error("peek32: Incorrect address alignment"); + + boost::uint64_t value = peek64((offset >> 3) << 3); + if ((offset & 0x7) == 0) { + return static_cast<boost::uint32_t>(value); + } else { + return static_cast<boost::uint32_t>(value >> 32); + } + } + +private: + wb_iface::sptr _iface; + const wb_addr_type _sr_base_addr; + const wb_addr_type _rb_reg_addr; + boost::mutex _mutex; +}; + +wb_iface::sptr user_settings_core_3000::make(wb_iface::sptr iface, + const wb_addr_type sr_base_addr, const wb_addr_type rb_reg_addr) +{ + return sptr(new user_settings_core_3000_impl(iface, sr_base_addr, rb_reg_addr)); +} diff --git a/host/lib/usrp/cores/user_settings_core_3000.hpp b/host/lib/usrp/cores/user_settings_core_3000.hpp new file mode 100644 index 000000000..6891b9e81 --- /dev/null +++ b/host/lib/usrp/cores/user_settings_core_3000.hpp @@ -0,0 +1,35 @@ +// +// Copyright 2012 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 +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +#ifndef INCLUDED_LIBUHD_USRP_USER_SETTINGS_CORE_3000_HPP +#define INCLUDED_LIBUHD_USRP_USER_SETTINGS_CORE_3000_HPP + +#include <uhd/config.hpp> +#include <boost/utility.hpp> +#include <boost/shared_ptr.hpp> +#include <uhd/types/wb_iface.hpp> + +class user_settings_core_3000 : public uhd::wb_iface { +public: + virtual ~user_settings_core_3000() {} + + static sptr make( + wb_iface::sptr iface, + const wb_addr_type sr_base_addr, const wb_addr_type rb_reg_addr); +}; + +#endif /* INCLUDED_LIBUHD_USRP_USER_SETTINGS_CORE_3000_HPP */ diff --git a/host/lib/usrp/dboard/CMakeLists.txt b/host/lib/usrp/dboard/CMakeLists.txt index 6cebecdbf..183496734 100644 --- a/host/lib/usrp/dboard/CMakeLists.txt +++ b/host/lib/usrp/dboard/CMakeLists.txt @@ -39,5 +39,9 @@ LIBUHD_APPEND_SOURCES( ${CMAKE_CURRENT_SOURCE_DIR}/db_dbsrx2.cpp ${CMAKE_CURRENT_SOURCE_DIR}/db_tvrx2.cpp ${CMAKE_CURRENT_SOURCE_DIR}/db_e3x0.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/twinrx/twinrx_ctrl.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/twinrx/twinrx_experts.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/twinrx/twinrx_gain_tables.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/db_twinrx.cpp ) diff --git a/host/lib/usrp/dboard/db_basic_and_lf.cpp b/host/lib/usrp/dboard/db_basic_and_lf.cpp index 2b30dab52..941a80ea4 100644 --- a/host/lib/usrp/dboard/db_basic_and_lf.cpp +++ b/host/lib/usrp/dboard/db_basic_and_lf.cpp @@ -50,7 +50,7 @@ static const uhd::dict<std::string, double> subdev_bandwidth_scalar = map_list_o class basic_rx : public rx_dboard_base{ public: basic_rx(ctor_args_t args, double max_freq); - ~basic_rx(void); + virtual ~basic_rx(void); private: double _max_freq; @@ -59,7 +59,7 @@ private: class basic_tx : public tx_dboard_base{ public: basic_tx(ctor_args_t args, double max_freq); - ~basic_tx(void); + virtual ~basic_tx(void); private: double _max_freq; @@ -121,7 +121,7 @@ basic_rx::basic_rx(ctor_args_t args, double max_freq) : rx_dboard_base(args){ this->get_rx_subtree()->create<int>("gains"); //phony property so this dir exists this->get_rx_subtree()->create<double>("freq/value") - .publish(&always_zero_freq); + .set_publisher(&always_zero_freq); this->get_rx_subtree()->create<meta_range_t>("freq/range") .set(freq_range_t(-_max_freq, +_max_freq)); this->get_rx_subtree()->create<std::string>("antenna/value") @@ -176,7 +176,7 @@ basic_tx::basic_tx(ctor_args_t args, double max_freq) : tx_dboard_base(args){ this->get_tx_subtree()->create<int>("gains"); //phony property so this dir exists this->get_tx_subtree()->create<double>("freq/value") - .publish(&always_zero_freq); + .set_publisher(&always_zero_freq); this->get_tx_subtree()->create<meta_range_t>("freq/range") .set(freq_range_t(-_max_freq, +_max_freq)); this->get_tx_subtree()->create<std::string>("antenna/value") diff --git a/host/lib/usrp/dboard/db_dbsrx.cpp b/host/lib/usrp/dboard/db_dbsrx.cpp index 9d04d8e16..6e1846fb8 100644 --- a/host/lib/usrp/dboard/db_dbsrx.cpp +++ b/host/lib/usrp/dboard/db_dbsrx.cpp @@ -66,7 +66,7 @@ static const double usrp1_gpio_clock_rate_limit = 4e6; class dbsrx : public rx_dboard_base{ public: dbsrx(ctor_args_t args); - ~dbsrx(void); + virtual ~dbsrx(void); private: double _lo_freq; @@ -204,16 +204,16 @@ dbsrx::dbsrx(ctor_args_t args) : rx_dboard_base(args){ this->get_rx_subtree()->create<std::string>("name") .set("DBSRX"); this->get_rx_subtree()->create<sensor_value_t>("sensors/lo_locked") - .publish(boost::bind(&dbsrx::get_locked, this)); + .set_publisher(boost::bind(&dbsrx::get_locked, this)); BOOST_FOREACH(const std::string &name, dbsrx_gain_ranges.keys()){ this->get_rx_subtree()->create<double>("gains/"+name+"/value") - .coerce(boost::bind(&dbsrx::set_gain, this, _1, name)) + .set_coercer(boost::bind(&dbsrx::set_gain, this, _1, name)) .set(dbsrx_gain_ranges[name].start()); this->get_rx_subtree()->create<meta_range_t>("gains/"+name+"/range") .set(dbsrx_gain_ranges[name]); } this->get_rx_subtree()->create<double>("freq/value") - .coerce(boost::bind(&dbsrx::set_lo_freq, this, _1)); + .set_coercer(boost::bind(&dbsrx::set_lo_freq, this, _1)); this->get_rx_subtree()->create<meta_range_t>("freq/range") .set(dbsrx_freq_range); this->get_rx_subtree()->create<std::string>("antenna/value") @@ -227,7 +227,7 @@ dbsrx::dbsrx(ctor_args_t args) : rx_dboard_base(args){ this->get_rx_subtree()->create<bool>("use_lo_offset") .set(false); this->get_rx_subtree()->create<double>("bandwidth/value") - .coerce(boost::bind(&dbsrx::set_bandwidth, this, _1)); + .set_coercer(boost::bind(&dbsrx::set_bandwidth, this, _1)); this->get_rx_subtree()->create<meta_range_t>("bandwidth/range") .set(dbsrx_bandwidth_range); diff --git a/host/lib/usrp/dboard/db_dbsrx2.cpp b/host/lib/usrp/dboard/db_dbsrx2.cpp index 1debe3c8f..11d706ed6 100644 --- a/host/lib/usrp/dboard/db_dbsrx2.cpp +++ b/host/lib/usrp/dboard/db_dbsrx2.cpp @@ -60,7 +60,7 @@ static const uhd::dict<std::string, gain_range_t> dbsrx2_gain_ranges = map_list_ class dbsrx2 : public rx_dboard_base{ public: dbsrx2(ctor_args_t args); - ~dbsrx2(void); + virtual ~dbsrx2(void); private: double _lo_freq; @@ -191,16 +191,16 @@ dbsrx2::dbsrx2(ctor_args_t args) : rx_dboard_base(args){ this->get_rx_subtree()->create<std::string>("name") .set("DBSRX2"); this->get_rx_subtree()->create<sensor_value_t>("sensors/lo_locked") - .publish(boost::bind(&dbsrx2::get_locked, this)); + .set_publisher(boost::bind(&dbsrx2::get_locked, this)); BOOST_FOREACH(const std::string &name, dbsrx2_gain_ranges.keys()){ this->get_rx_subtree()->create<double>("gains/"+name+"/value") - .coerce(boost::bind(&dbsrx2::set_gain, this, _1, name)) + .set_coercer(boost::bind(&dbsrx2::set_gain, this, _1, name)) .set(dbsrx2_gain_ranges[name].start()); this->get_rx_subtree()->create<meta_range_t>("gains/"+name+"/range") .set(dbsrx2_gain_ranges[name]); } this->get_rx_subtree()->create<double>("freq/value") - .coerce(boost::bind(&dbsrx2::set_lo_freq, this, _1)) + .set_coercer(boost::bind(&dbsrx2::set_lo_freq, this, _1)) .set(dbsrx2_freq_range.start()); this->get_rx_subtree()->create<meta_range_t>("freq/range") .set(dbsrx2_freq_range); @@ -218,7 +218,7 @@ dbsrx2::dbsrx2(ctor_args_t args) : rx_dboard_base(args){ double codec_rate = this->get_iface()->get_codec_rate(dboard_iface::UNIT_RX); this->get_rx_subtree()->create<double>("bandwidth/value") - .coerce(boost::bind(&dbsrx2::set_bandwidth, this, _1)) + .set_coercer(boost::bind(&dbsrx2::set_bandwidth, this, _1)) .set(2.0*(0.8*codec_rate/2.0)); //bandwidth in lowpass, convert to complex bandpass //default to anti-alias at different codec_rate this->get_rx_subtree()->create<meta_range_t>("bandwidth/range") diff --git a/host/lib/usrp/dboard/db_e3x0.cpp b/host/lib/usrp/dboard/db_e3x0.cpp index 523927d49..c7cc52d73 100644 --- a/host/lib/usrp/dboard/db_e3x0.cpp +++ b/host/lib/usrp/dboard/db_e3x0.cpp @@ -29,7 +29,7 @@ class e310_dboard : public xcvr_dboard_base{ public: e310_dboard(ctor_args_t args) : xcvr_dboard_base(args) {} - ~e310_dboard(void) {} + virtual ~e310_dboard(void) {} }; /*********************************************************************** @@ -40,7 +40,7 @@ class e300_dboard : public xcvr_dboard_base{ public: e300_dboard(ctor_args_t args) : xcvr_dboard_base(args) {} - ~e300_dboard(void) {} + virtual ~e300_dboard(void) {} }; /*********************************************************************** diff --git a/host/lib/usrp/dboard/db_rfx.cpp b/host/lib/usrp/dboard/db_rfx.cpp index 1342c913d..dbb1600ec 100644 --- a/host/lib/usrp/dboard/db_rfx.cpp +++ b/host/lib/usrp/dboard/db_rfx.cpp @@ -78,7 +78,7 @@ public: const freq_range_t &freq_range, bool rx_div2, bool tx_div2 ); - ~rfx_xcvr(void); + virtual ~rfx_xcvr(void); private: const freq_range_t _freq_range; @@ -183,20 +183,20 @@ rfx_xcvr::rfx_xcvr( else this->get_rx_subtree()->create<std::string>("name").set("RFX RX"); this->get_rx_subtree()->create<sensor_value_t>("sensors/lo_locked") - .publish(boost::bind(&rfx_xcvr::get_locked, this, dboard_iface::UNIT_RX)); + .set_publisher(boost::bind(&rfx_xcvr::get_locked, this, dboard_iface::UNIT_RX)); BOOST_FOREACH(const std::string &name, _rx_gain_ranges.keys()){ this->get_rx_subtree()->create<double>("gains/"+name+"/value") - .coerce(boost::bind(&rfx_xcvr::set_rx_gain, this, _1, name)) + .set_coercer(boost::bind(&rfx_xcvr::set_rx_gain, this, _1, name)) .set(_rx_gain_ranges[name].start()); this->get_rx_subtree()->create<meta_range_t>("gains/"+name+"/range") .set(_rx_gain_ranges[name]); } this->get_rx_subtree()->create<double>("freq/value") - .coerce(boost::bind(&rfx_xcvr::set_lo_freq, this, dboard_iface::UNIT_RX, _1)) + .set_coercer(boost::bind(&rfx_xcvr::set_lo_freq, this, dboard_iface::UNIT_RX, _1)) .set((_freq_range.start() + _freq_range.stop())/2.0); this->get_rx_subtree()->create<meta_range_t>("freq/range").set(_freq_range); this->get_rx_subtree()->create<std::string>("antenna/value") - .subscribe(boost::bind(&rfx_xcvr::set_rx_ant, this, _1)) + .add_coerced_subscriber(boost::bind(&rfx_xcvr::set_rx_ant, this, _1)) .set("RX2"); this->get_rx_subtree()->create<std::vector<std::string> >("antenna/options") .set(rfx_rx_antennas); @@ -219,14 +219,14 @@ rfx_xcvr::rfx_xcvr( else this->get_tx_subtree()->create<std::string>("name").set("RFX TX"); this->get_tx_subtree()->create<sensor_value_t>("sensors/lo_locked") - .publish(boost::bind(&rfx_xcvr::get_locked, this, dboard_iface::UNIT_TX)); + .set_publisher(boost::bind(&rfx_xcvr::get_locked, this, dboard_iface::UNIT_TX)); this->get_tx_subtree()->create<int>("gains"); //phony property so this dir exists this->get_tx_subtree()->create<double>("freq/value") - .coerce(boost::bind(&rfx_xcvr::set_lo_freq, this, dboard_iface::UNIT_TX, _1)) + .set_coercer(boost::bind(&rfx_xcvr::set_lo_freq, this, dboard_iface::UNIT_TX, _1)) .set((_freq_range.start() + _freq_range.stop())/2.0); this->get_tx_subtree()->create<meta_range_t>("freq/range").set(_freq_range); this->get_tx_subtree()->create<std::string>("antenna/value") - .subscribe(boost::bind(&rfx_xcvr::set_tx_ant, this, _1)).set(rfx_tx_antennas.at(0)); + .add_coerced_subscriber(boost::bind(&rfx_xcvr::set_tx_ant, this, _1)).set(rfx_tx_antennas.at(0)); this->get_tx_subtree()->create<std::vector<std::string> >("antenna/options") .set(rfx_tx_antennas); this->get_tx_subtree()->create<std::string>("connection").set("IQ"); @@ -248,15 +248,15 @@ rfx_xcvr::rfx_xcvr( this->get_iface()->set_gpio_ddr(dboard_iface::UNIT_RX, output_enables); //setup the tx atr (this does not change with antenna) - this->get_iface()->set_atr_reg(dboard_iface::UNIT_TX, dboard_iface::ATR_REG_IDLE, _power_up | ANT_XX | MIXER_DIS); - this->get_iface()->set_atr_reg(dboard_iface::UNIT_TX, dboard_iface::ATR_REG_RX_ONLY, _power_up | ANT_RX | MIXER_DIS); - this->get_iface()->set_atr_reg(dboard_iface::UNIT_TX, dboard_iface::ATR_REG_TX_ONLY, _power_up | ANT_TX | MIXER_ENB); - this->get_iface()->set_atr_reg(dboard_iface::UNIT_TX, dboard_iface::ATR_REG_FULL_DUPLEX, _power_up | ANT_TX | MIXER_ENB); + this->get_iface()->set_atr_reg(dboard_iface::UNIT_TX, gpio_atr::ATR_REG_IDLE, _power_up | ANT_XX | MIXER_DIS); + this->get_iface()->set_atr_reg(dboard_iface::UNIT_TX, gpio_atr::ATR_REG_RX_ONLY, _power_up | ANT_RX | MIXER_DIS); + this->get_iface()->set_atr_reg(dboard_iface::UNIT_TX, gpio_atr::ATR_REG_TX_ONLY, _power_up | ANT_TX | MIXER_ENB); + this->get_iface()->set_atr_reg(dboard_iface::UNIT_TX, gpio_atr::ATR_REG_FULL_DUPLEX, _power_up | ANT_TX | MIXER_ENB); //setup the rx atr (this does not change with antenna) - this->get_iface()->set_atr_reg(dboard_iface::UNIT_RX, dboard_iface::ATR_REG_IDLE, _power_up | ANT_XX | MIXER_DIS); - this->get_iface()->set_atr_reg(dboard_iface::UNIT_RX, dboard_iface::ATR_REG_TX_ONLY, _power_up | ANT_XX | MIXER_DIS); - this->get_iface()->set_atr_reg(dboard_iface::UNIT_RX, dboard_iface::ATR_REG_FULL_DUPLEX, _power_up | ANT_RX2| MIXER_ENB); + this->get_iface()->set_atr_reg(dboard_iface::UNIT_RX, gpio_atr::ATR_REG_IDLE, _power_up | ANT_XX | MIXER_DIS); + this->get_iface()->set_atr_reg(dboard_iface::UNIT_RX, gpio_atr::ATR_REG_TX_ONLY, _power_up | ANT_XX | MIXER_DIS); + this->get_iface()->set_atr_reg(dboard_iface::UNIT_RX, gpio_atr::ATR_REG_FULL_DUPLEX, _power_up | ANT_RX2| MIXER_ENB); } rfx_xcvr::~rfx_xcvr(void){ @@ -272,14 +272,14 @@ void rfx_xcvr::set_rx_ant(const std::string &ant){ //set the rx atr regs that change with antenna setting if (ant == "CAL") { - this->get_iface()->set_atr_reg(dboard_iface::UNIT_RX, dboard_iface::ATR_REG_TX_ONLY, _power_up | ANT_TXRX | MIXER_ENB); - this->get_iface()->set_atr_reg(dboard_iface::UNIT_RX, dboard_iface::ATR_REG_FULL_DUPLEX, _power_up | ANT_TXRX | MIXER_ENB); - this->get_iface()->set_atr_reg(dboard_iface::UNIT_RX, dboard_iface::ATR_REG_RX_ONLY, _power_up | MIXER_ENB | ANT_TXRX ); + this->get_iface()->set_atr_reg(dboard_iface::UNIT_RX, gpio_atr::ATR_REG_TX_ONLY, _power_up | ANT_TXRX | MIXER_ENB); + this->get_iface()->set_atr_reg(dboard_iface::UNIT_RX, gpio_atr::ATR_REG_FULL_DUPLEX, _power_up | ANT_TXRX | MIXER_ENB); + this->get_iface()->set_atr_reg(dboard_iface::UNIT_RX, gpio_atr::ATR_REG_RX_ONLY, _power_up | MIXER_ENB | ANT_TXRX ); } else { - this->get_iface()->set_atr_reg(dboard_iface::UNIT_RX, dboard_iface::ATR_REG_TX_ONLY, _power_up | ANT_XX | MIXER_DIS); - this->get_iface()->set_atr_reg(dboard_iface::UNIT_RX, dboard_iface::ATR_REG_FULL_DUPLEX, _power_up | ANT_RX2| MIXER_ENB); - this->get_iface()->set_atr_reg(dboard_iface::UNIT_RX, dboard_iface::ATR_REG_RX_ONLY, _power_up | MIXER_ENB | + this->get_iface()->set_atr_reg(dboard_iface::UNIT_RX, gpio_atr::ATR_REG_TX_ONLY, _power_up | ANT_XX | MIXER_DIS); + this->get_iface()->set_atr_reg(dboard_iface::UNIT_RX, gpio_atr::ATR_REG_FULL_DUPLEX, _power_up | ANT_RX2| MIXER_ENB); + this->get_iface()->set_atr_reg(dboard_iface::UNIT_RX, gpio_atr::ATR_REG_RX_ONLY, _power_up | MIXER_ENB | ((ant == "TX/RX")? ANT_TXRX : ANT_RX2)); } @@ -292,12 +292,12 @@ void rfx_xcvr::set_tx_ant(const std::string &ant){ //set the tx atr regs that change with antenna setting if (ant == "CAL") { - this->get_iface()->set_atr_reg(dboard_iface::UNIT_TX, dboard_iface::ATR_REG_TX_ONLY, _power_up | ANT_RX | MIXER_ENB); - this->get_iface()->set_atr_reg(dboard_iface::UNIT_TX, dboard_iface::ATR_REG_FULL_DUPLEX, _power_up | ANT_RX | MIXER_ENB); + this->get_iface()->set_atr_reg(dboard_iface::UNIT_TX, gpio_atr::ATR_REG_TX_ONLY, _power_up | ANT_RX | MIXER_ENB); + this->get_iface()->set_atr_reg(dboard_iface::UNIT_TX, gpio_atr::ATR_REG_FULL_DUPLEX, _power_up | ANT_RX | MIXER_ENB); } else { - this->get_iface()->set_atr_reg(dboard_iface::UNIT_TX, dboard_iface::ATR_REG_TX_ONLY, _power_up | ANT_TX | MIXER_ENB); - this->get_iface()->set_atr_reg(dboard_iface::UNIT_TX, dboard_iface::ATR_REG_FULL_DUPLEX, _power_up | ANT_TX | MIXER_ENB); + this->get_iface()->set_atr_reg(dboard_iface::UNIT_TX, gpio_atr::ATR_REG_TX_ONLY, _power_up | ANT_TX | MIXER_ENB); + this->get_iface()->set_atr_reg(dboard_iface::UNIT_TX, gpio_atr::ATR_REG_FULL_DUPLEX, _power_up | ANT_TX | MIXER_ENB); } } diff --git a/host/lib/usrp/dboard/db_sbx_common.cpp b/host/lib/usrp/dboard/db_sbx_common.cpp index ce5166c4c..be02cf77a 100644 --- a/host/lib/usrp/dboard/db_sbx_common.cpp +++ b/host/lib/usrp/dboard/db_sbx_common.cpp @@ -159,20 +159,20 @@ sbx_xcvr::sbx_xcvr(ctor_args_t args) : xcvr_dboard_base(args){ else this->get_rx_subtree()->create<std::string>("name").set("SBX/CBX RX"); this->get_rx_subtree()->create<sensor_value_t>("sensors/lo_locked") - .publish(boost::bind(&sbx_xcvr::get_locked, this, dboard_iface::UNIT_RX)); + .set_publisher(boost::bind(&sbx_xcvr::get_locked, this, dboard_iface::UNIT_RX)); BOOST_FOREACH(const std::string &name, sbx_rx_gain_ranges.keys()){ this->get_rx_subtree()->create<double>("gains/"+name+"/value") - .coerce(boost::bind(&sbx_xcvr::set_rx_gain, this, _1, name)) + .set_coercer(boost::bind(&sbx_xcvr::set_rx_gain, this, _1, name)) .set(sbx_rx_gain_ranges[name].start()); this->get_rx_subtree()->create<meta_range_t>("gains/"+name+"/range") .set(sbx_rx_gain_ranges[name]); } this->get_rx_subtree()->create<double>("freq/value") - .coerce(boost::bind(&sbx_xcvr::set_lo_freq, this, dboard_iface::UNIT_RX, _1)) + .set_coercer(boost::bind(&sbx_xcvr::set_lo_freq, this, dboard_iface::UNIT_RX, _1)) .set((freq_range.start() + freq_range.stop())/2.0); this->get_rx_subtree()->create<meta_range_t>("freq/range").set(freq_range); this->get_rx_subtree()->create<std::string>("antenna/value") - .subscribe(boost::bind(&sbx_xcvr::set_rx_ant, this, _1)) + .add_coerced_subscriber(boost::bind(&sbx_xcvr::set_rx_ant, this, _1)) .set("RX2"); this->get_rx_subtree()->create<std::vector<std::string> >("antenna/options") .set(sbx_rx_antennas); @@ -200,20 +200,20 @@ sbx_xcvr::sbx_xcvr(ctor_args_t args) : xcvr_dboard_base(args){ else this->get_tx_subtree()->create<std::string>("name").set("SBX/CBX TX"); this->get_tx_subtree()->create<sensor_value_t>("sensors/lo_locked") - .publish(boost::bind(&sbx_xcvr::get_locked, this, dboard_iface::UNIT_TX)); + .set_publisher(boost::bind(&sbx_xcvr::get_locked, this, dboard_iface::UNIT_TX)); BOOST_FOREACH(const std::string &name, sbx_tx_gain_ranges.keys()){ this->get_tx_subtree()->create<double>("gains/"+name+"/value") - .coerce(boost::bind(&sbx_xcvr::set_tx_gain, this, _1, name)) + .set_coercer(boost::bind(&sbx_xcvr::set_tx_gain, this, _1, name)) .set(sbx_tx_gain_ranges[name].start()); this->get_tx_subtree()->create<meta_range_t>("gains/"+name+"/range") .set(sbx_tx_gain_ranges[name]); } this->get_tx_subtree()->create<double>("freq/value") - .coerce(boost::bind(&sbx_xcvr::set_lo_freq, this, dboard_iface::UNIT_TX, _1)) + .set_coercer(boost::bind(&sbx_xcvr::set_lo_freq, this, dboard_iface::UNIT_TX, _1)) .set((freq_range.start() + freq_range.stop())/2.0); this->get_tx_subtree()->create<meta_range_t>("freq/range").set(freq_range); this->get_tx_subtree()->create<std::string>("antenna/value") - .subscribe(boost::bind(&sbx_xcvr::set_tx_ant, this, _1)) + .add_coerced_subscriber(boost::bind(&sbx_xcvr::set_tx_ant, this, _1)) .set(sbx_tx_antennas.at(0)); this->get_tx_subtree()->create<std::vector<std::string> >("antenna/options") .set(sbx_tx_antennas); @@ -237,8 +237,8 @@ sbx_xcvr::sbx_xcvr(ctor_args_t args) : xcvr_dboard_base(args){ this->get_iface()->set_gpio_ddr(dboard_iface::UNIT_TX, (TXIO_MASK|TX_LED_IO)); this->get_iface()->set_gpio_ddr(dboard_iface::UNIT_RX, (RXIO_MASK|RX_LED_IO)); - //flash LEDs - flash_leds(); + //Initialize ATR registers after direction and pin ctrl configuration + update_atr(); UHD_LOGV(often) << boost::format( "SBX GPIO Direction: RX: 0x%08x, TX: 0x%08x" @@ -265,39 +265,39 @@ void sbx_xcvr::update_atr(void){ //setup the tx atr (this does not change with antenna) this->get_iface()->set_atr_reg(dboard_iface::UNIT_TX, \ - dboard_iface::ATR_REG_IDLE, 0 | tx_lo_lpf_en \ + gpio_atr::ATR_REG_IDLE, 0 | tx_lo_lpf_en \ | tx_ld_led | tx_ant_led | TX_POWER_UP | ANT_XX | TX_MIXER_DIS); //setup the rx atr (this does not change with antenna) this->get_iface()->set_atr_reg(dboard_iface::UNIT_RX, \ - dboard_iface::ATR_REG_IDLE, rx_pga0_iobits | rx_lo_lpf_en \ + gpio_atr::ATR_REG_IDLE, rx_pga0_iobits | rx_lo_lpf_en \ | rx_ld_led | rx_ant_led | RX_POWER_UP | ANT_XX | RX_MIXER_DIS); //set the RX atr regs that change with antenna setting this->get_iface()->set_atr_reg(dboard_iface::UNIT_RX, \ - dboard_iface::ATR_REG_RX_ONLY, rx_pga0_iobits | rx_lo_lpf_en \ + gpio_atr::ATR_REG_RX_ONLY, rx_pga0_iobits | rx_lo_lpf_en \ | rx_ld_led | rx_ant_led | RX_POWER_UP | RX_MIXER_ENB \ | ((_rx_ant != "RX2")? ANT_TXRX : ANT_RX2)); this->get_iface()->set_atr_reg(dboard_iface::UNIT_RX, \ - dboard_iface::ATR_REG_TX_ONLY, rx_pga0_iobits | rx_lo_lpf_en \ + gpio_atr::ATR_REG_TX_ONLY, rx_pga0_iobits | rx_lo_lpf_en \ | rx_ld_led | rx_ant_led | RX_POWER_UP | RX_MIXER_DIS \ | ((_rx_ant == "CAL")? ANT_TXRX : ANT_RX2)); this->get_iface()->set_atr_reg(dboard_iface::UNIT_RX, \ - dboard_iface::ATR_REG_FULL_DUPLEX, rx_pga0_iobits | rx_lo_lpf_en \ + gpio_atr::ATR_REG_FULL_DUPLEX, rx_pga0_iobits | rx_lo_lpf_en \ | rx_ld_led | rx_ant_led | RX_POWER_UP | RX_MIXER_ENB \ | ((_rx_ant == "CAL")? ANT_TXRX : ANT_RX2)); //set the TX atr regs that change with antenna setting this->get_iface()->set_atr_reg(dboard_iface::UNIT_TX, \ - dboard_iface::ATR_REG_RX_ONLY, 0 | tx_lo_lpf_en \ + gpio_atr::ATR_REG_RX_ONLY, 0 | tx_lo_lpf_en \ | tx_ld_led | tx_ant_led | TX_POWER_UP | TX_MIXER_DIS \ | ((_rx_ant != "RX2")? ANT_RX : ANT_TX)); this->get_iface()->set_atr_reg(dboard_iface::UNIT_TX, \ - dboard_iface::ATR_REG_TX_ONLY, tx_pga0_iobits | tx_lo_lpf_en \ + gpio_atr::ATR_REG_TX_ONLY, tx_pga0_iobits | tx_lo_lpf_en \ | tx_ld_led | tx_ant_led | TX_POWER_UP | TX_MIXER_ENB \ | ((_tx_ant == "CAL")? ANT_RX : ANT_TX)); this->get_iface()->set_atr_reg(dboard_iface::UNIT_TX, \ - dboard_iface::ATR_REG_FULL_DUPLEX, tx_pga0_iobits | tx_lo_lpf_en \ + gpio_atr::ATR_REG_FULL_DUPLEX, tx_pga0_iobits | tx_lo_lpf_en \ | tx_ld_led | tx_ant_led | TX_POWER_UP | TX_MIXER_ENB \ | ((_tx_ant == "CAL")? ANT_RX : ANT_TX)); } @@ -352,45 +352,3 @@ sensor_value_t sbx_xcvr::get_locked(dboard_iface::unit_t unit) { return sensor_value_t("LO", locked, "locked", "unlocked"); } - - -void sbx_xcvr::flash_leds(void) { - //Remove LED gpios from ATR control temporarily and set to outputs - this->get_iface()->set_pin_ctrl(dboard_iface::UNIT_TX, TXIO_MASK); - this->get_iface()->set_pin_ctrl(dboard_iface::UNIT_RX, RXIO_MASK); - this->get_iface()->set_gpio_ddr(dboard_iface::UNIT_TX, (TXIO_MASK|RX_LED_IO)); - this->get_iface()->set_gpio_ddr(dboard_iface::UNIT_RX, (RXIO_MASK|RX_LED_IO)); - - this->get_iface()->set_gpio_out(dboard_iface::UNIT_TX, TX_LED_LD, TX_LED_IO); - boost::this_thread::sleep(boost::posix_time::milliseconds(100)); - - this->get_iface()->set_gpio_out(dboard_iface::UNIT_TX, \ - TX_LED_TXRX|TX_LED_LD, TX_LED_IO); - boost::this_thread::sleep(boost::posix_time::milliseconds(100)); - - this->get_iface()->set_gpio_out(dboard_iface::UNIT_RX, RX_LED_LD, RX_LED_IO); - boost::this_thread::sleep(boost::posix_time::milliseconds(100)); - - this->get_iface()->set_gpio_out(dboard_iface::UNIT_RX, \ - RX_LED_RX1RX2|RX_LED_LD, RX_LED_IO); - boost::this_thread::sleep(boost::posix_time::milliseconds(100)); - - this->get_iface()->set_gpio_out(dboard_iface::UNIT_RX, RX_LED_LD, RX_LED_IO); - boost::this_thread::sleep(boost::posix_time::milliseconds(100)); - - this->get_iface()->set_gpio_out(dboard_iface::UNIT_RX, 0, RX_LED_IO); - boost::this_thread::sleep(boost::posix_time::milliseconds(100)); - - this->get_iface()->set_gpio_out(dboard_iface::UNIT_TX, TX_LED_LD, TX_LED_IO); - boost::this_thread::sleep(boost::posix_time::milliseconds(100)); - - this->get_iface()->set_gpio_out(dboard_iface::UNIT_TX, 0, TX_LED_IO); - boost::this_thread::sleep(boost::posix_time::milliseconds(100)); - - //Put LED gpios back in ATR control and update atr - this->get_iface()->set_pin_ctrl(dboard_iface::UNIT_TX, (TXIO_MASK|TX_LED_IO)); - this->get_iface()->set_pin_ctrl(dboard_iface::UNIT_RX, (RXIO_MASK|RX_LED_IO)); - this->get_iface()->set_gpio_ddr(dboard_iface::UNIT_TX, (TXIO_MASK|TX_LED_IO)); - this->get_iface()->set_gpio_ddr(dboard_iface::UNIT_RX, (RXIO_MASK|RX_LED_IO)); -} - diff --git a/host/lib/usrp/dboard/db_sbx_common.hpp b/host/lib/usrp/dboard/db_sbx_common.hpp index 4800bbd83..c0e29f263 100644 --- a/host/lib/usrp/dboard/db_sbx_common.hpp +++ b/host/lib/usrp/dboard/db_sbx_common.hpp @@ -16,10 +16,15 @@ // #include <uhd/types/device_addr.hpp> - -#include "adf435x_common.hpp" +#include "adf435x.hpp" #include "max287x.hpp" +// LO Related +#define ADF435X_CE (1 << 3) +#define ADF435X_PDBRF (1 << 2) +#define ADF435X_MUXOUT (1 << 1) // INPUT!!! +#define LOCKDET_MASK (1 << 0) // INPUT!!! + // Common IO Pins #define LO_LPF_EN (1 << 15) @@ -36,6 +41,8 @@ #define RX_LED_LD (1 << 6) // LED for RX Lock Detect #define DIS_POWER_RX (1 << 5) // on UNIT_RX, 0 powers up RX #define RX_DISABLE (1 << 4) // on UNIT_RX, 1 disables RX Mixer and Baseband +#define RX_ATTN_SHIFT 8 //lsb of RX Attenuator Control +#define RX_ATTN_MASK (63 << RX_ATTN_SHIFT) //valid bits of RX Attenuator Control // TX Attenuator Pins #define TX_ATTN_SHIFT 8 // lsb of TX Attenuator Control @@ -184,12 +191,16 @@ protected: class sbx_version3 : public sbx_versionx { public: sbx_version3(sbx_xcvr *_self_sbx_xcvr); - ~sbx_version3(void); + virtual ~sbx_version3(void); double set_lo_freq(dboard_iface::unit_t unit, double target_freq); /*! This is the registered instance of the wrapper class, sbx_base. */ sbx_xcvr *self_base; + private: + adf435x_iface::sptr _txlo; + adf435x_iface::sptr _rxlo; + void write_lo_regs(dboard_iface::unit_t unit, const std::vector<boost::uint32_t> ®s); }; /*! @@ -200,12 +211,16 @@ protected: class sbx_version4 : public sbx_versionx { public: sbx_version4(sbx_xcvr *_self_sbx_xcvr); - ~sbx_version4(void); + virtual ~sbx_version4(void); double set_lo_freq(dboard_iface::unit_t unit, double target_freq); /*! This is the registered instance of the wrapper class, sbx_base. */ sbx_xcvr *self_base; + private: + adf435x_iface::sptr _txlo; + adf435x_iface::sptr _rxlo; + void write_lo_regs(dboard_iface::unit_t unit, const std::vector<boost::uint32_t> ®s); }; /*! @@ -218,7 +233,7 @@ protected: class cbx : public sbx_versionx { public: cbx(sbx_xcvr *_self_sbx_xcvr); - ~cbx(void); + virtual ~cbx(void); double set_lo_freq(dboard_iface::unit_t unit, double target_freq); diff --git a/host/lib/usrp/dboard/db_sbx_version3.cpp b/host/lib/usrp/dboard/db_sbx_version3.cpp index b848097d1..76ad7b04f 100644 --- a/host/lib/usrp/dboard/db_sbx_version3.cpp +++ b/host/lib/usrp/dboard/db_sbx_version3.cpp @@ -16,9 +16,7 @@ // -#include "adf4350_regs.hpp" #include "db_sbx_common.hpp" -#include "../common/adf435x_common.hpp" #include <uhd/types/tune_request.hpp> #include <boost/algorithm/string.hpp> @@ -32,12 +30,21 @@ using namespace boost::assign; sbx_xcvr::sbx_version3::sbx_version3(sbx_xcvr *_self_sbx_xcvr) { //register the handle to our base SBX class self_base = _self_sbx_xcvr; + _txlo = adf435x_iface::make_adf4350(boost::bind(&sbx_xcvr::sbx_version3::write_lo_regs, this, dboard_iface::UNIT_TX, _1)); + _rxlo = adf435x_iface::make_adf4350(boost::bind(&sbx_xcvr::sbx_version3::write_lo_regs, this, dboard_iface::UNIT_RX, _1)); } sbx_xcvr::sbx_version3::~sbx_version3(void){ /* NOP */ } +void sbx_xcvr::sbx_version3::write_lo_regs(dboard_iface::unit_t unit, const std::vector<boost::uint32_t> ®s) +{ + BOOST_FOREACH(boost::uint32_t reg, regs) + { + self_base->get_iface()->write_spi(unit, spi_config_t::EDGE_RISE, reg, 32); + } +} /*********************************************************************** * Tuning @@ -57,95 +64,27 @@ double sbx_xcvr::sbx_version3::set_lo_freq(dboard_iface::unit_t unit, double tar device_addr_t tune_args = subtree->access<device_addr_t>("tune_args").get(); bool is_int_n = boost::iequals(tune_args.get("mode_n",""), "integer"); - //clip the input - target_freq = sbx_freq_range.clip(target_freq); - - //map prescaler setting to mininmum integer divider (N) values (pg.18 prescaler) - static const uhd::dict<int, int> prescaler_to_min_int_div = map_list_of - (0,23) //adf4350_regs_t::PRESCALER_4_5 - (1,75) //adf4350_regs_t::PRESCALER_8_9 - ; - - //map rf divider select output dividers to enums - static const uhd::dict<int, adf4350_regs_t::rf_divider_select_t> rfdivsel_to_enum = map_list_of - (1, adf4350_regs_t::RF_DIVIDER_SELECT_DIV1) - (2, adf4350_regs_t::RF_DIVIDER_SELECT_DIV2) - (4, adf4350_regs_t::RF_DIVIDER_SELECT_DIV4) - (8, adf4350_regs_t::RF_DIVIDER_SELECT_DIV8) - (16, adf4350_regs_t::RF_DIVIDER_SELECT_DIV16) - ; - - //use 8/9 prescaler for vco_freq > 3 GHz (pg.18 prescaler) - adf4350_regs_t::prescaler_t prescaler = target_freq > 3e9 ? adf4350_regs_t::PRESCALER_8_9 : adf4350_regs_t::PRESCALER_4_5; - - adf435x_tuning_constraints tuning_constraints; - tuning_constraints.force_frac0 = is_int_n; - tuning_constraints.band_sel_freq_max = 100e3; - tuning_constraints.ref_doubler_threshold = 12.5e6; - tuning_constraints.int_range = uhd::range_t(prescaler_to_min_int_div[prescaler], 4095); //INT is a 12-bit field - tuning_constraints.pfd_freq_max = 25e6; - tuning_constraints.rf_divider_range = uhd::range_t(1, 16); - tuning_constraints.feedback_after_divider = true; + //Select the LO + adf435x_iface::sptr& lo_iface = unit == dboard_iface::UNIT_RX ? _rxlo : _txlo; + lo_iface->set_feedback_select(adf435x_iface::FB_SEL_DIVIDED); + lo_iface->set_reference_freq(self_base->get_iface()->get_clock_rate(unit)); - double actual_freq; - adf435x_tuning_settings tuning_settings = tune_adf435x_synth( - target_freq, self_base->get_iface()->get_clock_rate(unit), - tuning_constraints, actual_freq); + //Use 8/9 prescaler for vco_freq > 3 GHz (pg.18 prescaler) + lo_iface->set_prescaler(target_freq > 3e9 ? adf435x_iface::PRESCALER_8_9 : adf435x_iface::PRESCALER_4_5); - //load the register values - adf4350_regs_t regs; + //Configure the LO + double actual_freq = 0.0; + actual_freq = lo_iface->set_frequency(sbx_freq_range.clip(target_freq), is_int_n); - if ((unit == dboard_iface::UNIT_TX) and (actual_freq == sbx_tx_lo_2dbm.clip(actual_freq))) - regs.output_power = adf4350_regs_t::OUTPUT_POWER_2DBM; - else - regs.output_power = adf4350_regs_t::OUTPUT_POWER_5DBM; - - regs.frac_12_bit = tuning_settings.frac_12_bit; - regs.int_16_bit = tuning_settings.int_16_bit; - regs.mod_12_bit = tuning_settings.mod_12_bit; - regs.clock_divider_12_bit = tuning_settings.clock_divider_12_bit; - regs.feedback_select = tuning_constraints.feedback_after_divider ? - adf4350_regs_t::FEEDBACK_SELECT_DIVIDED : - adf4350_regs_t::FEEDBACK_SELECT_FUNDAMENTAL; - regs.clock_div_mode = adf4350_regs_t::CLOCK_DIV_MODE_RESYNC_ENABLE; - regs.prescaler = prescaler; - regs.r_counter_10_bit = tuning_settings.r_counter_10_bit; - regs.reference_divide_by_2 = tuning_settings.r_divide_by_2_en ? - adf4350_regs_t::REFERENCE_DIVIDE_BY_2_ENABLED : - adf4350_regs_t::REFERENCE_DIVIDE_BY_2_DISABLED; - regs.reference_doubler = tuning_settings.r_doubler_en ? - adf4350_regs_t::REFERENCE_DOUBLER_ENABLED : - adf4350_regs_t::REFERENCE_DOUBLER_DISABLED; - regs.band_select_clock_div = tuning_settings.band_select_clock_div; - UHD_ASSERT_THROW(rfdivsel_to_enum.has_key(tuning_settings.rf_divider)); - regs.rf_divider_select = rfdivsel_to_enum[tuning_settings.rf_divider]; - regs.ldf = is_int_n ? - adf4350_regs_t::LDF_INT_N : - adf4350_regs_t::LDF_FRAC_N; - - //reset the N and R counter - regs.counter_reset = adf4350_regs_t::COUNTER_RESET_ENABLED; - self_base->get_iface()->write_spi(unit, spi_config_t::EDGE_RISE, regs.get_reg(2), 32); - regs.counter_reset = adf4350_regs_t::COUNTER_RESET_DISABLED; - - //write the registers - //correct power-up sequence to write registers (5, 4, 3, 2, 1, 0) - int addr; - - for(addr=5; addr>=0; addr--){ - UHD_LOGV(often) << boost::format( - "SBX SPI Reg (0x%02x): 0x%08x" - ) % addr % regs.get_reg(addr) << std::endl; - self_base->get_iface()->write_spi( - unit, spi_config_t::EDGE_RISE, - regs.get_reg(addr), 32 - ); + if ((unit == dboard_iface::UNIT_TX) and (actual_freq == sbx_tx_lo_2dbm.clip(actual_freq))) { + lo_iface->set_output_power(adf435x_iface::OUTPUT_POWER_2DBM); + } else { + lo_iface->set_output_power(adf435x_iface::OUTPUT_POWER_5DBM); } - //return the actual frequency - UHD_LOGV(often) << boost::format( - "SBX tune: actual frequency %f MHz" - ) % (actual_freq/1e6) << std::endl; + //Write to hardware + lo_iface->commit(); + return actual_freq; } diff --git a/host/lib/usrp/dboard/db_sbx_version4.cpp b/host/lib/usrp/dboard/db_sbx_version4.cpp index 8f7e747bc..639bce250 100644 --- a/host/lib/usrp/dboard/db_sbx_version4.cpp +++ b/host/lib/usrp/dboard/db_sbx_version4.cpp @@ -16,9 +16,7 @@ // -#include "adf4351_regs.hpp" #include "db_sbx_common.hpp" -#include "../common/adf435x_common.hpp" #include <uhd/types/tune_request.hpp> #include <boost/algorithm/string.hpp> @@ -32,6 +30,8 @@ using namespace boost::assign; sbx_xcvr::sbx_version4::sbx_version4(sbx_xcvr *_self_sbx_xcvr) { //register the handle to our base SBX class self_base = _self_sbx_xcvr; + _txlo = adf435x_iface::make_adf4351(boost::bind(&sbx_xcvr::sbx_version4::write_lo_regs, this, dboard_iface::UNIT_TX, _1)); + _rxlo = adf435x_iface::make_adf4351(boost::bind(&sbx_xcvr::sbx_version4::write_lo_regs, this, dboard_iface::UNIT_RX, _1)); } @@ -39,6 +39,14 @@ sbx_xcvr::sbx_version4::~sbx_version4(void){ /* NOP */ } +void sbx_xcvr::sbx_version4::write_lo_regs(dboard_iface::unit_t unit, const std::vector<boost::uint32_t> ®s) +{ + BOOST_FOREACH(boost::uint32_t reg, regs) + { + self_base->get_iface()->write_spi(unit, spi_config_t::EDGE_RISE, reg, 32); + } +} + /*********************************************************************** * Tuning @@ -58,99 +66,27 @@ double sbx_xcvr::sbx_version4::set_lo_freq(dboard_iface::unit_t unit, double tar device_addr_t tune_args = subtree->access<device_addr_t>("tune_args").get(); bool is_int_n = boost::iequals(tune_args.get("mode_n",""), "integer"); - //clip the input - target_freq = sbx_freq_range.clip(target_freq); - - //map prescaler setting to mininmum integer divider (N) values (pg.18 prescaler) - static const uhd::dict<int, int> prescaler_to_min_int_div = map_list_of - (0,23) //adf4351_regs_t::PRESCALER_4_5 - (1,75) //adf4351_regs_t::PRESCALER_8_9 - ; - - //map rf divider select output dividers to enums - static const uhd::dict<int, adf4351_regs_t::rf_divider_select_t> rfdivsel_to_enum = map_list_of - (1, adf4351_regs_t::RF_DIVIDER_SELECT_DIV1) - (2, adf4351_regs_t::RF_DIVIDER_SELECT_DIV2) - (4, adf4351_regs_t::RF_DIVIDER_SELECT_DIV4) - (8, adf4351_regs_t::RF_DIVIDER_SELECT_DIV8) - (16, adf4351_regs_t::RF_DIVIDER_SELECT_DIV16) - (32, adf4351_regs_t::RF_DIVIDER_SELECT_DIV32) - (64, adf4351_regs_t::RF_DIVIDER_SELECT_DIV64) - ; - - //use 8/9 prescaler for vco_freq > 3 GHz (pg.18 prescaler) - adf4351_regs_t::prescaler_t prescaler = target_freq > 3.6e9 ? adf4351_regs_t::PRESCALER_8_9 : adf4351_regs_t::PRESCALER_4_5; - - adf435x_tuning_constraints tuning_constraints; - tuning_constraints.force_frac0 = is_int_n; - tuning_constraints.band_sel_freq_max = 100e3; - tuning_constraints.ref_doubler_threshold = 12.5e6; - tuning_constraints.int_range = uhd::range_t(prescaler_to_min_int_div[prescaler], 4095); //INT is a 12-bit field - tuning_constraints.pfd_freq_max = 25e6; - tuning_constraints.rf_divider_range = uhd::range_t(1, 64); - tuning_constraints.feedback_after_divider = true; - - double actual_freq; - adf435x_tuning_settings tuning_settings = tune_adf435x_synth( - target_freq, self_base->get_iface()->get_clock_rate(unit), - tuning_constraints, actual_freq); - - //load the register values - adf4351_regs_t regs; - - if ((unit == dboard_iface::UNIT_TX) and (actual_freq == sbx_tx_lo_2dbm.clip(actual_freq))) - regs.output_power = adf4351_regs_t::OUTPUT_POWER_2DBM; - else - regs.output_power = adf4351_regs_t::OUTPUT_POWER_5DBM; - - regs.frac_12_bit = tuning_settings.frac_12_bit; - regs.int_16_bit = tuning_settings.int_16_bit; - regs.mod_12_bit = tuning_settings.mod_12_bit; - regs.clock_divider_12_bit = tuning_settings.clock_divider_12_bit; - regs.feedback_select = tuning_constraints.feedback_after_divider ? - adf4351_regs_t::FEEDBACK_SELECT_DIVIDED : - adf4351_regs_t::FEEDBACK_SELECT_FUNDAMENTAL; - regs.clock_div_mode = adf4351_regs_t::CLOCK_DIV_MODE_RESYNC_ENABLE; - regs.prescaler = prescaler; - regs.r_counter_10_bit = tuning_settings.r_counter_10_bit; - regs.reference_divide_by_2 = tuning_settings.r_divide_by_2_en ? - adf4351_regs_t::REFERENCE_DIVIDE_BY_2_ENABLED : - adf4351_regs_t::REFERENCE_DIVIDE_BY_2_DISABLED; - regs.reference_doubler = tuning_settings.r_doubler_en ? - adf4351_regs_t::REFERENCE_DOUBLER_ENABLED : - adf4351_regs_t::REFERENCE_DOUBLER_DISABLED; - regs.band_select_clock_div = tuning_settings.band_select_clock_div; - UHD_ASSERT_THROW(rfdivsel_to_enum.has_key(tuning_settings.rf_divider)); - regs.rf_divider_select = rfdivsel_to_enum[tuning_settings.rf_divider]; - regs.ldf = is_int_n ? - adf4351_regs_t::LDF_INT_N : - adf4351_regs_t::LDF_FRAC_N; - - //reset the N and R counter - regs.counter_reset = adf4351_regs_t::COUNTER_RESET_ENABLED; - self_base->get_iface()->write_spi(unit, spi_config_t::EDGE_RISE, regs.get_reg(2), 32); - regs.counter_reset = adf4351_regs_t::COUNTER_RESET_DISABLED; - - //write the registers - //correct power-up sequence to write registers (5, 4, 3, 2, 1, 0) - int addr; - - boost::uint16_t rx_id = self_base->get_rx_id().to_uint16(); - std::string board_name = (rx_id == 0x0083) ? "SBX-120" : "SBX"; - for(addr=5; addr>=0; addr--){ - UHD_LOGV(often) << boost::format( - "%s SPI Reg (0x%02x): 0x%08x" - ) % board_name.c_str() % addr % regs.get_reg(addr) << std::endl; - self_base->get_iface()->write_spi( - unit, spi_config_t::EDGE_RISE, - regs.get_reg(addr), 32 - ); + //Select the LO + adf435x_iface::sptr& lo_iface = unit == dboard_iface::UNIT_RX ? _rxlo : _txlo; + lo_iface->set_feedback_select(adf435x_iface::FB_SEL_DIVIDED); + lo_iface->set_reference_freq(self_base->get_iface()->get_clock_rate(unit)); + + //Use 8/9 prescaler for vco_freq > 3 GHz (pg.18 prescaler) + lo_iface->set_prescaler(target_freq > 3.6e9 ? adf435x_iface::PRESCALER_8_9 : adf435x_iface::PRESCALER_4_5); + + //Configure the LO + double actual_freq = 0.0; + actual_freq = lo_iface->set_frequency(sbx_freq_range.clip(target_freq), is_int_n); + + if ((unit == dboard_iface::UNIT_TX) and (actual_freq == sbx_tx_lo_2dbm.clip(actual_freq))) { + lo_iface->set_output_power(adf435x_iface::OUTPUT_POWER_2DBM); + } else { + lo_iface->set_output_power(adf435x_iface::OUTPUT_POWER_5DBM); } - //return the actual frequency - UHD_LOGV(often) << boost::format( - "%s tune: actual frequency %f MHz" - ) % board_name.c_str() % (actual_freq/1e6) << std::endl; + //Write to hardware + lo_iface->commit(); + return actual_freq; } diff --git a/host/lib/usrp/dboard/db_tvrx.cpp b/host/lib/usrp/dboard/db_tvrx.cpp index e9f60f765..0f84cd68a 100644 --- a/host/lib/usrp/dboard/db_tvrx.cpp +++ b/host/lib/usrp/dboard/db_tvrx.cpp @@ -134,7 +134,7 @@ static const double reference_freq = 4.0e6; class tvrx : public rx_dboard_base{ public: tvrx(ctor_args_t args); - ~tvrx(void); + virtual ~tvrx(void); private: uhd::dict<std::string, double> _gains; @@ -190,12 +190,12 @@ tvrx::tvrx(ctor_args_t args) : rx_dboard_base(args){ this->get_rx_subtree()->create<int>("sensors"); //phony property so this dir exists BOOST_FOREACH(const std::string &name, get_tvrx_gain_ranges().keys()){ this->get_rx_subtree()->create<double>("gains/"+name+"/value") - .coerce(boost::bind(&tvrx::set_gain, this, _1, name)); + .set_coercer(boost::bind(&tvrx::set_gain, this, _1, name)); this->get_rx_subtree()->create<meta_range_t>("gains/"+name+"/range") .set(get_tvrx_gain_ranges()[name]); } this->get_rx_subtree()->create<double>("freq/value") - .coerce(boost::bind(&tvrx::set_freq, this, _1)); + .set_coercer(boost::bind(&tvrx::set_freq, this, _1)); this->get_rx_subtree()->create<meta_range_t>("freq/range") .set(tvrx_freq_range); this->get_rx_subtree()->create<std::string>("antenna/value") diff --git a/host/lib/usrp/dboard/db_tvrx2.cpp b/host/lib/usrp/dboard/db_tvrx2.cpp index ccbac0b5d..6f0604f72 100644 --- a/host/lib/usrp/dboard/db_tvrx2.cpp +++ b/host/lib/usrp/dboard/db_tvrx2.cpp @@ -751,7 +751,7 @@ static const uhd::dict<std::string, gain_range_t> tvrx2_gain_ranges = map_list_o class tvrx2 : public rx_dboard_base{ public: tvrx2(ctor_args_t args); - ~tvrx2(void); + virtual ~tvrx2(void); private: double _freq_scalar; @@ -957,19 +957,19 @@ tvrx2::tvrx2(ctor_args_t args) : rx_dboard_base(args){ this->get_rx_subtree()->create<std::string>("name") .set("TVRX2"); this->get_rx_subtree()->create<sensor_value_t>("sensors/lo_locked") - .publish(boost::bind(&tvrx2::get_locked, this)); + .set_publisher(boost::bind(&tvrx2::get_locked, this)); this->get_rx_subtree()->create<sensor_value_t>("sensors/rssi") - .publish(boost::bind(&tvrx2::get_rssi, this)); + .set_publisher(boost::bind(&tvrx2::get_rssi, this)); this->get_rx_subtree()->create<sensor_value_t>("sensors/temperature") - .publish(boost::bind(&tvrx2::get_temp, this)); + .set_publisher(boost::bind(&tvrx2::get_temp, this)); BOOST_FOREACH(const std::string &name, tvrx2_gain_ranges.keys()){ this->get_rx_subtree()->create<double>("gains/"+name+"/value") - .coerce(boost::bind(&tvrx2::set_gain, this, _1, name)); + .set_coercer(boost::bind(&tvrx2::set_gain, this, _1, name)); this->get_rx_subtree()->create<meta_range_t>("gains/"+name+"/range") .set(tvrx2_gain_ranges[name]); } this->get_rx_subtree()->create<double>("freq/value") - .coerce(boost::bind(&tvrx2::set_lo_freq, this, _1)); + .set_coercer(boost::bind(&tvrx2::set_lo_freq, this, _1)); this->get_rx_subtree()->create<meta_range_t>("freq/range") .set(tvrx2_freq_range); this->get_rx_subtree()->create<std::string>("antenna/value") @@ -979,12 +979,12 @@ tvrx2::tvrx2(ctor_args_t args) : rx_dboard_base(args){ this->get_rx_subtree()->create<std::string>("connection") .set(tvrx2_sd_name_to_conn[get_subdev_name()]); this->get_rx_subtree()->create<bool>("enabled") - .coerce(boost::bind(&tvrx2::set_enabled, this, _1)) + .set_coercer(boost::bind(&tvrx2::set_enabled, this, _1)) .set(_enabled); this->get_rx_subtree()->create<bool>("use_lo_offset") .set(false); this->get_rx_subtree()->create<double>("bandwidth/value") - .coerce(boost::bind(&tvrx2::set_bandwidth, this, _1)) + .set_coercer(boost::bind(&tvrx2::set_bandwidth, this, _1)) .set(_bandwidth); this->get_rx_subtree()->create<meta_range_t>("bandwidth/range") .set(tvrx2_bandwidth_range); diff --git a/host/lib/usrp/dboard/db_twinrx.cpp b/host/lib/usrp/dboard/db_twinrx.cpp new file mode 100644 index 000000000..e960f134f --- /dev/null +++ b/host/lib/usrp/dboard/db_twinrx.cpp @@ -0,0 +1,337 @@ +// +// Copyright 2014-15 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 +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +#include "twinrx/twinrx_experts.hpp" +#include "twinrx/twinrx_ctrl.hpp" +#include "twinrx/twinrx_io.hpp" +#include <expert_factory.hpp> +#include <uhd/types/device_addr.hpp> +#include <uhd/types/ranges.hpp> +#include <uhd/types/sensors.hpp> +#include <uhd/usrp/dboard_base.hpp> +#include <uhd/usrp/dboard_manager.hpp> +#include <uhd/utils/log.hpp> +#include <uhd/utils/static.hpp> +#include "dboard_ctor_args.hpp" +#include <boost/assign/list_of.hpp> +#include <boost/make_shared.hpp> +#include <boost/thread.hpp> +#include <boost/thread/mutex.hpp> +//#include <fstream> //Needed for _expert->to_dot() below + +using namespace uhd; +using namespace uhd::usrp; +using namespace uhd::usrp::dboard::twinrx; +using namespace uhd::experts; + +static const dboard_id_t TWINRX_V100_000_ID(0x91); + +/*! + * twinrx_rcvr_fe is the dbaord class (dboard_base) that + * represents each front-end of a TwinRX board. UHD will + * create and hold two instances of this class per TwinRX + * dboard. + * + */ +class twinrx_rcvr_fe : public rx_dboard_base +{ +public: + twinrx_rcvr_fe( + ctor_args_t args, + expert_container::sptr expert, + twinrx_ctrl::sptr ctrl + ) : + rx_dboard_base(args), _expert(expert), _ctrl(ctrl), + _ch_name(dboard_ctor_args_t::cast(args).sd_name) + { + //--------------------------------------------------------- + // Add user-visible, channel specific properties to front-end tree + //--------------------------------------------------------- + + //Generic + get_rx_subtree()->create<std::string>("name") + .set("TwinRX RX" + _ch_name); + get_rx_subtree()->create<bool>("use_lo_offset") + .set(false); + get_rx_subtree()->create<std::string>("connection") + .set(_ch_name == "0" ? "II" : "QQ"); //Ch->ADC port mapping + static const double BW = 80e6; + get_rx_subtree()->create<double>("bandwidth/value") + .set(BW); + get_rx_subtree()->create<meta_range_t>("bandwidth/range") + .set(freq_range_t(BW, BW)); + + //Frequency Specific + get_rx_subtree()->create<meta_range_t>("freq/range") + .set(freq_range_t(10e6, 6.0e9)); + expert_factory::add_dual_prop_node<double>(_expert, get_rx_subtree(), + "freq/value", prepend_ch("freq/desired", _ch_name), prepend_ch("freq/coerced", _ch_name), + 1.0e9, AUTO_RESOLVE_ON_READ_WRITE); + get_rx_subtree()->create<device_addr_t>("tune_args") + .set(device_addr_t()); + + static const double DEFAULT_IF_FREQ = 150e6; + meta_range_t if_freq_range; + if_freq_range.push_back(range_t(-DEFAULT_IF_FREQ-(BW/2), -DEFAULT_IF_FREQ+(BW/2))); + if_freq_range.push_back(range_t( DEFAULT_IF_FREQ-(BW/2), DEFAULT_IF_FREQ+(BW/2))); + get_rx_subtree()->create<meta_range_t>("if_freq/range") + .set(if_freq_range); + expert_factory::add_dual_prop_node<double>(_expert, get_rx_subtree(), + "if_freq/value", prepend_ch("if_freq/desired", _ch_name), prepend_ch("if_freq/coerced", _ch_name), + DEFAULT_IF_FREQ, AUTO_RESOLVE_ON_WRITE); + + //LO Specific + get_rx_subtree()->create<meta_range_t>("los/LO1/freq/range") + .set(freq_range_t(2.0e9, 6.8e9)); + expert_factory::add_dual_prop_node<double>(_expert, get_rx_subtree(), + "los/LO1/freq/value", prepend_ch("los/LO1/freq/desired", _ch_name), prepend_ch("los/LO1/freq/coerced", _ch_name), + 0.0, AUTO_RESOLVE_ON_READ_WRITE); + get_rx_subtree()->create<meta_range_t>("los/LO2/freq/range") + .set(freq_range_t(1.0e9, 3.0e9)); + expert_factory::add_dual_prop_node<double>(_expert, get_rx_subtree(), + "los/LO2/freq/value", prepend_ch("los/LO2/freq/desired", _ch_name), prepend_ch("los/LO2/freq/coerced", _ch_name), + 0.0, AUTO_RESOLVE_ON_READ_WRITE); + get_rx_subtree()->create<std::vector<std::string> >("los/all/source/options") + .set(boost::assign::list_of("internal")("external")("companion")("disabled")); + expert_factory::add_prop_node<std::string>(_expert, get_rx_subtree(), + "los/all/source/value", prepend_ch("los/all/source", _ch_name), + "internal", AUTO_RESOLVE_ON_WRITE); + expert_factory::add_prop_node<bool>(_expert, get_rx_subtree(), + "los/all/export", prepend_ch("los/all/export", _ch_name), + false, AUTO_RESOLVE_ON_WRITE); + + //Gain Specific + get_rx_subtree()->create<meta_range_t>("gains/all/range") + .set(gain_range_t(0, 95, double(1.0))); + expert_factory::add_prop_node<double>(_expert, get_rx_subtree(), + "gains/all/value", prepend_ch("gain", _ch_name), + 0.0, AUTO_RESOLVE_ON_WRITE); + get_rx_subtree()->create<std::vector<std::string> >("gains/all/profile/options") + .set(boost::assign::list_of("low-noise")("low-distortion")("default")); + expert_factory::add_prop_node<std::string>(_expert, get_rx_subtree(), + "gains/all/profile/value", prepend_ch("gain_profile", _ch_name), + "default", AUTO_RESOLVE_ON_WRITE); + + //Antenna Specific + get_rx_subtree()->create<std::vector<std::string> >("antenna/options") + .set(boost::assign::list_of("RX1")("RX2")); + expert_factory::add_prop_node<std::string>(_expert, get_rx_subtree(), + "antenna/value", prepend_ch("antenna", _ch_name), + (_ch_name == "0" ? "RX1" : "RX2"), AUTO_RESOLVE_ON_WRITE); + expert_factory::add_prop_node<bool>(_expert, get_rx_subtree(), + "enabled", prepend_ch("enabled", _ch_name), + false, AUTO_RESOLVE_ON_WRITE); + + //Readback + get_rx_subtree()->create<sensor_value_t>("sensors/lo_locked") + .set_publisher(boost::bind(&twinrx_rcvr_fe::get_lo_locked, this)); + + //--------------------------------------------------------- + // Add internal channel-specific data nodes to expert + //--------------------------------------------------------- + expert_factory::add_data_node<lo_inj_side_t>(_expert, + prepend_ch("ch/LO1/inj_side", _ch_name), INJ_LOW_SIDE); + expert_factory::add_data_node<lo_inj_side_t>(_expert, + prepend_ch("ch/LO2/inj_side", _ch_name), INJ_LOW_SIDE); + expert_factory::add_data_node<twinrx_ctrl::signal_path_t>(_expert, + prepend_ch("ch/signal_path", _ch_name), twinrx_ctrl::PATH_LOWBAND); + expert_factory::add_data_node<twinrx_ctrl::preselector_path_t>(_expert, + prepend_ch("ch/lb_presel", _ch_name), twinrx_ctrl::PRESEL_PATH1); + expert_factory::add_data_node<twinrx_ctrl::preselector_path_t>(_expert, + prepend_ch("ch/hb_presel", _ch_name), twinrx_ctrl::PRESEL_PATH1); + expert_factory::add_data_node<bool>(_expert, + prepend_ch("ch/lb_preamp_presel", _ch_name), false); + expert_factory::add_data_node<bool>(_expert, + prepend_ch("ant/lb_preamp_presel", _ch_name), false); + expert_factory::add_data_node<twinrx_ctrl::preamp_state_t>(_expert, + prepend_ch("ch/preamp1", _ch_name), twinrx_ctrl::PREAMP_BYPASS); + expert_factory::add_data_node<twinrx_ctrl::preamp_state_t>(_expert, + prepend_ch("ant/preamp1", _ch_name), twinrx_ctrl::PREAMP_BYPASS); + expert_factory::add_data_node<bool>(_expert, + prepend_ch("ch/preamp2", _ch_name), false); + expert_factory::add_data_node<bool>(_expert, + prepend_ch("ant/preamp2", _ch_name), false); + expert_factory::add_data_node<boost::uint8_t>(_expert, + prepend_ch("ch/input_atten", _ch_name), 0); + expert_factory::add_data_node<boost::uint8_t>(_expert, + prepend_ch("ant/input_atten", _ch_name), 0); + expert_factory::add_data_node<boost::uint8_t>(_expert, + prepend_ch("ch/lb_atten", _ch_name), 0); + expert_factory::add_data_node<boost::uint8_t>(_expert, + prepend_ch("ch/hb_atten", _ch_name), 0); + expert_factory::add_data_node<twinrx_ctrl::lo_source_t>(_expert, + prepend_ch("ch/LO1/source", _ch_name), twinrx_ctrl::LO_INTERNAL); + expert_factory::add_data_node<twinrx_ctrl::lo_source_t>(_expert, + prepend_ch("ch/LO2/source", _ch_name), twinrx_ctrl::LO_INTERNAL); + expert_factory::add_data_node<lo_synth_mapping_t>(_expert, + prepend_ch("synth/LO1/mapping", _ch_name), MAPPING_NONE); + expert_factory::add_data_node<lo_synth_mapping_t>(_expert, + prepend_ch("synth/LO2/mapping", _ch_name), MAPPING_NONE); + + } + + virtual ~twinrx_rcvr_fe(void) + { + } + + sensor_value_t get_lo_locked() + { + bool locked = true; + twinrx_ctrl::channel_t ch = (_ch_name == "0") ? twinrx_ctrl::CH1 : twinrx_ctrl::CH2; + locked &= _ctrl->read_lo1_locked(ch); + locked &= _ctrl->read_lo2_locked(ch); + return sensor_value_t("LO", locked, "locked", "unlocked"); + } + +private: + expert_container::sptr _expert; + twinrx_ctrl::sptr _ctrl; + const std::string _ch_name; +}; + +/*! + * twinrx_rcvr is the top-level container for each + * TwinRX board. UHD will hold one instance of this + * class per TwinRX dboard. This class is responsible + * for owning all the control classes for the board. + * + */ +class twinrx_rcvr : public rx_dboard_base +{ +public: + typedef boost::shared_ptr<twinrx_rcvr> sptr; + + twinrx_rcvr(ctor_args_t args) : rx_dboard_base(args) + { + _db_iface = get_iface(); + twinrx_gpio::sptr gpio_iface = boost::make_shared<twinrx_gpio>(_db_iface); + twinrx_cpld_regmap::sptr cpld_regs = boost::make_shared<twinrx_cpld_regmap>(); + cpld_regs->initialize(*gpio_iface, false); + _ctrl = twinrx_ctrl::make(_db_iface, gpio_iface, cpld_regs); + _expert = expert_factory::create_container("twinrx_expert"); + } + + virtual ~twinrx_rcvr(void) + { + } + + inline expert_container::sptr get_expert() { + return _expert; + } + + inline twinrx_ctrl::sptr get_ctrl() { + return _ctrl; + } + + virtual void initialize() + { + //--------------------------------------------------------- + // Add internal channel-agnostic data nodes to expert + //--------------------------------------------------------- + expert_factory::add_data_node<twinrx_ctrl::lo_export_source_t>(_expert, + "com/LO1/export_source", twinrx_ctrl::LO_EXPORT_DISABLED); + expert_factory::add_data_node<twinrx_ctrl::lo_export_source_t>(_expert, + "com/LO2/export_source", twinrx_ctrl::LO_EXPORT_DISABLED); + expert_factory::add_data_node<twinrx_ctrl::antenna_mapping_t>(_expert, + "com/ant_mapping", twinrx_ctrl::ANTX_NATIVE); + expert_factory::add_data_node<twinrx_ctrl::cal_mode_t>(_expert, + "com/cal_mode", twinrx_ctrl::CAL_DISABLED); + expert_factory::add_data_node<bool>(_expert, + "com/synth/LO1/hopping_enabled", false); + expert_factory::add_data_node<bool>(_expert, + "com/synth/LO2/hopping_enabled", false); + + //--------------------------------------------------------- + // Add workers to expert + //--------------------------------------------------------- + //Channel (front-end) specific + BOOST_FOREACH(const std::string& fe, _fe_names) { + expert_factory::add_worker_node<twinrx_freq_path_expert>(_expert, _expert->node_retriever(), fe); + expert_factory::add_worker_node<twinrx_freq_coercion_expert>(_expert, _expert->node_retriever(), fe); + expert_factory::add_worker_node<twinrx_chan_gain_expert>(_expert, _expert->node_retriever(), fe); + expert_factory::add_worker_node<twinrx_nyquist_expert>(_expert, _expert->node_retriever(), fe, _db_iface); + } + + //Channel (front-end) agnostic + expert_factory::add_worker_node<twinrx_lo_config_expert>(_expert, _expert->node_retriever()); + expert_factory::add_worker_node<twinrx_lo_mapping_expert>(_expert, _expert->node_retriever(), STAGE_LO1); + expert_factory::add_worker_node<twinrx_lo_mapping_expert>(_expert, _expert->node_retriever(), STAGE_LO2); + expert_factory::add_worker_node<twinrx_antenna_expert>(_expert, _expert->node_retriever()); + expert_factory::add_worker_node<twinrx_ant_gain_expert>(_expert, _expert->node_retriever()); + expert_factory::add_worker_node<twinrx_settings_expert>(_expert, _expert->node_retriever(), _ctrl); + + /*//Expert debug code + std::ofstream dot_file("/tmp/twinrx.dot", std::ios::out); + dot_file << _expert->to_dot(); + dot_file.close(); + */ + + _expert->debug_audit(); + _expert->resolve_all(true); + } + + static dboard_base::sptr make_twinrx_fe(dboard_base::ctor_args_t args) + { + const dboard_ctor_args_t& db_args = dboard_ctor_args_t::cast(args); + sptr container = boost::dynamic_pointer_cast<twinrx_rcvr>(db_args.rx_container); + if (container) { + dboard_base::sptr fe = dboard_base::sptr( + new twinrx_rcvr_fe(args, container->get_expert(), container->get_ctrl())); + container->add_twinrx_fe(db_args.sd_name); + return fe; + } else { + throw uhd::assertion_error("error creating twinrx frontend"); + } + } + +protected: + inline void add_twinrx_fe(const std::string& name) { + _fe_names.push_back(name); + } + +private: + typedef std::map<std::string, dboard_base::sptr> twinrx_fe_map_t; + + dboard_iface::sptr _db_iface; + twinrx_ctrl::sptr _ctrl; + std::vector<std::string> _fe_names; + expert_container::sptr _expert; +}; + +/*! + * Initialization Sequence for each TwinRX board: + * - make_twinrx_container is called which creates an instance of twinrx_rcvr + * - twinrx_rcvr::make_twinrx_fe is called with channel "0" which creates an instance of twinrx_rcvr_fe + * - twinrx_rcvr::make_twinrx_fe is called with channel "1" which creates an instance of twinrx_rcvr_fe + * - twinrx_rcvr::initialize is called with finishes the init sequence + * + */ +static dboard_base::sptr make_twinrx_container(dboard_base::ctor_args_t args) +{ + return dboard_base::sptr(new twinrx_rcvr(args)); +} + +UHD_STATIC_BLOCK(reg_twinrx_dboards) +{ + dboard_manager::register_dboard_restricted( + TWINRX_V100_000_ID, + &twinrx_rcvr::make_twinrx_fe, + "TwinRX v1.0", + boost::assign::list_of("0")("1"), + &make_twinrx_container + ); +} diff --git a/host/lib/usrp/dboard/db_ubx.cpp b/host/lib/usrp/dboard/db_ubx.cpp index 6b15f28dd..7416de735 100644 --- a/host/lib/usrp/dboard/db_ubx.cpp +++ b/host/lib/usrp/dboard/db_ubx.cpp @@ -27,7 +27,9 @@ #include <uhd/usrp/dboard_manager.hpp> #include <uhd/utils/assert_has.hpp> #include <uhd/utils/log.hpp> +#include <uhd/utils/msg.hpp> #include <uhd/utils/static.hpp> +#include <uhd/utils/safe_call.hpp> #include <boost/assign/list_of.hpp> #include <boost/shared_ptr.hpp> #include <boost/math/special_functions/round.hpp> @@ -341,14 +343,14 @@ public: write_gpio(); // Configure ATR - _iface->set_atr_reg(dboard_iface::UNIT_TX, dboard_iface::ATR_REG_IDLE, _tx_gpio_reg.atr_idle); - _iface->set_atr_reg(dboard_iface::UNIT_TX, dboard_iface::ATR_REG_TX_ONLY, _tx_gpio_reg.atr_tx); - _iface->set_atr_reg(dboard_iface::UNIT_TX, dboard_iface::ATR_REG_RX_ONLY, _tx_gpio_reg.atr_rx); - _iface->set_atr_reg(dboard_iface::UNIT_TX, dboard_iface::ATR_REG_FULL_DUPLEX, _tx_gpio_reg.atr_full_duplex); - _iface->set_atr_reg(dboard_iface::UNIT_RX, dboard_iface::ATR_REG_IDLE, _rx_gpio_reg.atr_idle); - _iface->set_atr_reg(dboard_iface::UNIT_RX, dboard_iface::ATR_REG_TX_ONLY, _rx_gpio_reg.atr_tx); - _iface->set_atr_reg(dboard_iface::UNIT_RX, dboard_iface::ATR_REG_RX_ONLY, _rx_gpio_reg.atr_rx); - _iface->set_atr_reg(dboard_iface::UNIT_RX, dboard_iface::ATR_REG_FULL_DUPLEX, _rx_gpio_reg.atr_full_duplex); + _iface->set_atr_reg(dboard_iface::UNIT_TX, gpio_atr::ATR_REG_IDLE, _tx_gpio_reg.atr_idle); + _iface->set_atr_reg(dboard_iface::UNIT_TX, gpio_atr::ATR_REG_TX_ONLY, _tx_gpio_reg.atr_tx); + _iface->set_atr_reg(dboard_iface::UNIT_TX, gpio_atr::ATR_REG_RX_ONLY, _tx_gpio_reg.atr_rx); + _iface->set_atr_reg(dboard_iface::UNIT_TX, gpio_atr::ATR_REG_FULL_DUPLEX, _tx_gpio_reg.atr_full_duplex); + _iface->set_atr_reg(dboard_iface::UNIT_RX, gpio_atr::ATR_REG_IDLE, _rx_gpio_reg.atr_idle); + _iface->set_atr_reg(dboard_iface::UNIT_RX, gpio_atr::ATR_REG_TX_ONLY, _rx_gpio_reg.atr_tx); + _iface->set_atr_reg(dboard_iface::UNIT_RX, gpio_atr::ATR_REG_RX_ONLY, _rx_gpio_reg.atr_rx); + _iface->set_atr_reg(dboard_iface::UNIT_RX, gpio_atr::ATR_REG_FULL_DUPLEX, _rx_gpio_reg.atr_full_duplex); // Engage ATR control (1 is ATR control, 0 is manual control) _iface->set_pin_ctrl(dboard_iface::UNIT_TX, _tx_gpio_reg.atr_mask); @@ -407,23 +409,23 @@ public: get_rx_subtree()->create<std::vector<std::string> >("power_mode/options") .set(ubx_power_modes); get_rx_subtree()->create<std::string>("power_mode/value") - .subscribe(boost::bind(&ubx_xcvr::set_power_mode, this, _1)) + .add_coerced_subscriber(boost::bind(&ubx_xcvr::set_power_mode, this, _1)) .set("performance"); get_rx_subtree()->create<std::vector<std::string> >("xcvr_mode/options") .set(ubx_xcvr_modes); get_rx_subtree()->create<std::string>("xcvr_mode/value") - .subscribe(boost::bind(&ubx_xcvr::set_xcvr_mode, this, _1)) + .add_coerced_subscriber(boost::bind(&ubx_xcvr::set_xcvr_mode, this, _1)) .set("FDX"); get_tx_subtree()->create<std::vector<std::string> >("power_mode/options") .set(ubx_power_modes); get_tx_subtree()->create<std::string>("power_mode/value") - .subscribe(boost::bind(&uhd::property<std::string>::set, &get_rx_subtree()->access<std::string>("power_mode/value"), _1)) - .publish(boost::bind(&uhd::property<std::string>::get, &get_rx_subtree()->access<std::string>("power_mode/value"))); + .add_coerced_subscriber(boost::bind(&uhd::property<std::string>::set, &get_rx_subtree()->access<std::string>("power_mode/value"), _1)) + .set_publisher(boost::bind(&uhd::property<std::string>::get, &get_rx_subtree()->access<std::string>("power_mode/value"))); get_tx_subtree()->create<std::vector<std::string> >("xcvr_mode/options") .set(ubx_xcvr_modes); get_tx_subtree()->create<std::string>("xcvr_mode/value") - .subscribe(boost::bind(&uhd::property<std::string>::set, &get_rx_subtree()->access<std::string>("xcvr_mode/value"), _1)) - .publish(boost::bind(&uhd::property<std::string>::get, &get_rx_subtree()->access<std::string>("xcvr_mode/value"))); + .add_coerced_subscriber(boost::bind(&uhd::property<std::string>::set, &get_rx_subtree()->access<std::string>("xcvr_mode/value"), _1)) + .set_publisher(boost::bind(&uhd::property<std::string>::get, &get_rx_subtree()->access<std::string>("xcvr_mode/value"))); //////////////////////////////////////////////////////////////////// // Register TX properties @@ -432,20 +434,20 @@ public: get_tx_subtree()->create<device_addr_t>("tune_args") .set(device_addr_t()); get_tx_subtree()->create<sensor_value_t>("sensors/lo_locked") - .publish(boost::bind(&ubx_xcvr::get_locked, this, "TXLO")); + .set_publisher(boost::bind(&ubx_xcvr::get_locked, this, "TXLO")); get_tx_subtree()->create<double>("gains/PGA0/value") - .coerce(boost::bind(&ubx_xcvr::set_tx_gain, this, _1)).set(0); + .set_coercer(boost::bind(&ubx_xcvr::set_tx_gain, this, _1)).set(0); get_tx_subtree()->create<meta_range_t>("gains/PGA0/range") .set(ubx_tx_gain_range); get_tx_subtree()->create<double>("freq/value") - .coerce(boost::bind(&ubx_xcvr::set_tx_freq, this, _1)) + .set_coercer(boost::bind(&ubx_xcvr::set_tx_freq, this, _1)) .set(ubx_freq_range.start()); get_tx_subtree()->create<meta_range_t>("freq/range") .set(ubx_freq_range); get_tx_subtree()->create<std::vector<std::string> >("antenna/options") .set(ubx_tx_antennas); get_tx_subtree()->create<std::string>("antenna/value") - .subscribe(boost::bind(&ubx_xcvr::set_tx_ant, this, _1)) + .add_coerced_subscriber(boost::bind(&ubx_xcvr::set_tx_ant, this, _1)) .set(ubx_tx_antennas.at(0)); get_tx_subtree()->create<std::string>("connection") .set("QI"); @@ -458,7 +460,7 @@ public: get_tx_subtree()->create<meta_range_t>("bandwidth/range") .set(freq_range_t(bw, bw)); get_tx_subtree()->create<int64_t>("sync_delay") - .subscribe(boost::bind(&ubx_xcvr::set_sync_delay, this, true, _1)) + .add_coerced_subscriber(boost::bind(&ubx_xcvr::set_sync_delay, this, true, _1)) .set(-8); //////////////////////////////////////////////////////////////////// @@ -468,21 +470,21 @@ public: get_rx_subtree()->create<device_addr_t>("tune_args") .set(device_addr_t()); get_rx_subtree()->create<sensor_value_t>("sensors/lo_locked") - .publish(boost::bind(&ubx_xcvr::get_locked, this, "RXLO")); + .set_publisher(boost::bind(&ubx_xcvr::get_locked, this, "RXLO")); get_rx_subtree()->create<double>("gains/PGA0/value") - .coerce(boost::bind(&ubx_xcvr::set_rx_gain, this, _1)) + .set_coercer(boost::bind(&ubx_xcvr::set_rx_gain, this, _1)) .set(0); get_rx_subtree()->create<meta_range_t>("gains/PGA0/range") .set(ubx_rx_gain_range); get_rx_subtree()->create<double>("freq/value") - .coerce(boost::bind(&ubx_xcvr::set_rx_freq, this, _1)) + .set_coercer(boost::bind(&ubx_xcvr::set_rx_freq, this, _1)) .set(ubx_freq_range.start()); get_rx_subtree()->create<meta_range_t>("freq/range") .set(ubx_freq_range); get_rx_subtree()->create<std::vector<std::string> >("antenna/options") .set(ubx_rx_antennas); get_rx_subtree()->create<std::string>("antenna/value") - .subscribe(boost::bind(&ubx_xcvr::set_rx_ant, this, _1)).set("RX2"); + .add_coerced_subscriber(boost::bind(&ubx_xcvr::set_rx_ant, this, _1)).set("RX2"); get_rx_subtree()->create<std::string>("connection") .set("IQ"); get_rx_subtree()->create<bool>("enabled") @@ -494,35 +496,38 @@ public: get_rx_subtree()->create<meta_range_t>("bandwidth/range") .set(freq_range_t(bw, bw)); get_rx_subtree()->create<int64_t>("sync_delay") - .subscribe(boost::bind(&ubx_xcvr::set_sync_delay, this, false, _1)) + .add_coerced_subscriber(boost::bind(&ubx_xcvr::set_sync_delay, this, false, _1)) .set(-8); } - ~ubx_xcvr(void) + virtual ~ubx_xcvr(void) { - // Shutdown synthesizers - _txlo1->shutdown(); - _txlo2->shutdown(); - _rxlo1->shutdown(); - _rxlo2->shutdown(); + UHD_SAFE_CALL + ( + // Shutdown synthesizers + _txlo1->shutdown(); + _txlo2->shutdown(); + _rxlo1->shutdown(); + _rxlo2->shutdown(); - // Reset CPLD values - _cpld_reg.value = 0; - write_cpld_reg(); + // Reset CPLD values + _cpld_reg.value = 0; + write_cpld_reg(); - // Reset GPIO values - set_gpio_field(TX_GAIN, 0); - set_gpio_field(CPLD_RST_N, 0); - set_gpio_field(RX_ANT, 1); - set_gpio_field(TX_EN_N, 1); - set_gpio_field(RX_EN_N, 1); - set_gpio_field(SPI_ADDR, 0x7); - set_gpio_field(RX_GAIN, 0); - set_gpio_field(TXLO1_SYNC, 0); - set_gpio_field(TXLO2_SYNC, 0); - set_gpio_field(RXLO1_SYNC, 0); - set_gpio_field(RXLO1_SYNC, 0); - write_gpio(); + // Reset GPIO values + set_gpio_field(TX_GAIN, 0); + set_gpio_field(CPLD_RST_N, 0); + set_gpio_field(RX_ANT, 1); + set_gpio_field(TX_EN_N, 1); + set_gpio_field(RX_EN_N, 1); + set_gpio_field(SPI_ADDR, 0x7); + set_gpio_field(RX_GAIN, 0); + set_gpio_field(TXLO1_SYNC, 0); + set_gpio_field(TXLO2_SYNC, 0); + set_gpio_field(RXLO1_SYNC, 0); + set_gpio_field(RXLO1_SYNC, 0); + write_gpio(); + ) } private: @@ -660,23 +665,23 @@ private: uint16_t mask = lo1_field_info.mask | lo2_field_info.mask; dboard_iface::unit_t unit = lo1_field_info.unit; UHD_ASSERT_THROW(lo1_field_info.unit == lo2_field_info.unit); - _iface->set_atr_reg(unit, dboard_iface::ATR_REG_IDLE, value, mask); + _iface->set_atr_reg(unit, gpio_atr::ATR_REG_IDLE, value, mask); cmd_time += uhd::time_spec_t(1/pfd_freq); _iface->set_command_time(cmd_time); - _iface->set_atr_reg(unit, dboard_iface::ATR_REG_TX_ONLY, value, mask); + _iface->set_atr_reg(unit, gpio_atr::ATR_REG_TX_ONLY, value, mask); cmd_time += uhd::time_spec_t(1/pfd_freq); _iface->set_command_time(cmd_time); - _iface->set_atr_reg(unit, dboard_iface::ATR_REG_RX_ONLY, value, mask); + _iface->set_atr_reg(unit, gpio_atr::ATR_REG_RX_ONLY, value, mask); cmd_time += uhd::time_spec_t(1/pfd_freq); _iface->set_command_time(cmd_time); - _iface->set_atr_reg(unit, dboard_iface::ATR_REG_FULL_DUPLEX, value, mask); + _iface->set_atr_reg(unit, gpio_atr::ATR_REG_FULL_DUPLEX, value, mask); // De-assert SYNC // Head of line blocking means the command time does not need to be set. - _iface->set_atr_reg(unit, dboard_iface::ATR_REG_IDLE, 0, mask); - _iface->set_atr_reg(unit, dboard_iface::ATR_REG_TX_ONLY, 0, mask); - _iface->set_atr_reg(unit, dboard_iface::ATR_REG_RX_ONLY, 0, mask); - _iface->set_atr_reg(unit, dboard_iface::ATR_REG_FULL_DUPLEX, 0, mask); + _iface->set_atr_reg(unit, gpio_atr::ATR_REG_IDLE, 0, mask); + _iface->set_atr_reg(unit, gpio_atr::ATR_REG_TX_ONLY, 0, mask); + _iface->set_atr_reg(unit, gpio_atr::ATR_REG_RX_ONLY, 0, mask); + _iface->set_atr_reg(unit, gpio_atr::ATR_REG_FULL_DUPLEX, 0, mask); } } @@ -782,6 +787,20 @@ private: device_addr_t tune_args = subtree->access<device_addr_t>("tune_args").get(); is_int_n = boost::iequals(tune_args.get("mode_n",""), "integer"); UHD_LOGV(rarely) << boost::format("UBX TX: the requested frequency is %f MHz") % (freq/1e6) << std::endl; + double target_pfd_freq = _tx_target_pfd_freq; + if (is_int_n and tune_args.has_key("int_n_step")) + { + target_pfd_freq = tune_args.cast<double>("int_n_step", _tx_target_pfd_freq); + if (target_pfd_freq > _tx_target_pfd_freq) + { + UHD_MSG(warning) + << boost::format("Requested int_n_step of %f MHz too large, clipping to %f MHz") + % (target_pfd_freq/1e6) + % (_tx_target_pfd_freq/1e6) + << std::endl; + target_pfd_freq = _tx_target_pfd_freq; + } + } // Clip the frequency to the valid range freq = ubx_freq_range.clip(freq); @@ -818,10 +837,10 @@ private: set_cpld_field(TXLB_SEL, 1); set_cpld_field(TXHB_SEL, 0); // Set LO1 to IF of 2100 MHz (offset from RX IF to reduce leakage) - freq_lo1 = _txlo1->set_frequency(2100*fMHz, ref_freq, _tx_target_pfd_freq, is_int_n); + freq_lo1 = _txlo1->set_frequency(2100*fMHz, ref_freq, target_pfd_freq, is_int_n); _txlo1->set_output_power(max287x_iface::OUTPUT_POWER_5DBM); // Set LO2 to IF minus desired frequency - freq_lo2 = _txlo2->set_frequency(freq_lo1 - freq, ref_freq, _tx_target_pfd_freq, is_int_n); + freq_lo2 = _txlo2->set_frequency(freq_lo1 - freq, ref_freq, target_pfd_freq, is_int_n); _txlo2->set_output_power(max287x_iface::OUTPUT_POWER_2DBM); } else if ((freq >= (500*fMHz)) && (freq <= (800*fMHz))) @@ -831,7 +850,7 @@ private: set_cpld_field(TXLO1_FSEL1, 1); set_cpld_field(TXLB_SEL, 0); set_cpld_field(TXHB_SEL, 1); - freq_lo1 = _txlo1->set_frequency(freq, ref_freq, _tx_target_pfd_freq, is_int_n); + freq_lo1 = _txlo1->set_frequency(freq, ref_freq, target_pfd_freq, is_int_n); _txlo1->set_output_power(max287x_iface::OUTPUT_POWER_2DBM); } else if ((freq > (800*fMHz)) && (freq <= (1000*fMHz))) @@ -841,7 +860,7 @@ private: set_cpld_field(TXLO1_FSEL1, 1); set_cpld_field(TXLB_SEL, 0); set_cpld_field(TXHB_SEL, 1); - freq_lo1 = _txlo1->set_frequency(freq, ref_freq, _tx_target_pfd_freq, is_int_n); + freq_lo1 = _txlo1->set_frequency(freq, ref_freq, target_pfd_freq, is_int_n); _txlo1->set_output_power(max287x_iface::OUTPUT_POWER_5DBM); } else if ((freq > (1000*fMHz)) && (freq <= (2200*fMHz))) @@ -851,7 +870,7 @@ private: set_cpld_field(TXLO1_FSEL1, 0); set_cpld_field(TXLB_SEL, 0); set_cpld_field(TXHB_SEL, 1); - freq_lo1 = _txlo1->set_frequency(freq, ref_freq, _tx_target_pfd_freq, is_int_n); + freq_lo1 = _txlo1->set_frequency(freq, ref_freq, target_pfd_freq, is_int_n); _txlo1->set_output_power(max287x_iface::OUTPUT_POWER_2DBM); } else if ((freq > (2200*fMHz)) && (freq <= (2500*fMHz))) @@ -861,7 +880,7 @@ private: set_cpld_field(TXLO1_FSEL1, 0); set_cpld_field(TXLB_SEL, 0); set_cpld_field(TXHB_SEL, 1); - freq_lo1 = _txlo1->set_frequency(freq, ref_freq, _tx_target_pfd_freq, is_int_n); + freq_lo1 = _txlo1->set_frequency(freq, ref_freq, target_pfd_freq, is_int_n); _txlo1->set_output_power(max287x_iface::OUTPUT_POWER_2DBM); } else if ((freq > (2500*fMHz)) && (freq <= (6000*fMHz))) @@ -871,7 +890,7 @@ private: set_cpld_field(TXLO1_FSEL1, 0); set_cpld_field(TXLB_SEL, 0); set_cpld_field(TXHB_SEL, 1); - freq_lo1 = _txlo1->set_frequency(freq, ref_freq, _tx_target_pfd_freq, is_int_n); + freq_lo1 = _txlo1->set_frequency(freq, ref_freq, target_pfd_freq, is_int_n); _txlo1->set_output_power(max287x_iface::OUTPUT_POWER_5DBM); } @@ -924,6 +943,20 @@ private: property_tree::sptr subtree = this->get_rx_subtree(); device_addr_t tune_args = subtree->access<device_addr_t>("tune_args").get(); is_int_n = boost::iequals(tune_args.get("mode_n",""), "integer"); + double target_pfd_freq = _rx_target_pfd_freq; + if (is_int_n and tune_args.has_key("int_n_step")) + { + target_pfd_freq = tune_args.cast<double>("int_n_step", _rx_target_pfd_freq); + if (target_pfd_freq > _rx_target_pfd_freq) + { + UHD_MSG(warning) + << boost::format("Requested int_n_step of %f Mhz too large, clipping to %f MHz") + % (target_pfd_freq/1e6) + % (_rx_target_pfd_freq/1e6) + << std::endl; + target_pfd_freq = _rx_target_pfd_freq; + } + } // Clip the frequency to the valid range freq = ubx_freq_range.clip(freq); @@ -962,10 +995,10 @@ private: set_cpld_field(RXLB_SEL, 1); set_cpld_field(RXHB_SEL, 0); // Set LO1 to IF of 2380 MHz (2440 MHz filter center minus 60 MHz offset to minimize LO leakage) - freq_lo1 = _rxlo1->set_frequency(2380*fMHz, ref_freq, _rx_target_pfd_freq, is_int_n); + freq_lo1 = _rxlo1->set_frequency(2380*fMHz, ref_freq, target_pfd_freq, is_int_n); _rxlo1->set_output_power(max287x_iface::OUTPUT_POWER_5DBM); // Set LO2 to IF minus desired frequency - freq_lo2 = _rxlo2->set_frequency(freq_lo1 - freq, ref_freq, _rx_target_pfd_freq, is_int_n); + freq_lo2 = _rxlo2->set_frequency(freq_lo1 - freq, ref_freq, target_pfd_freq, is_int_n); _rxlo2->set_output_power(max287x_iface::OUTPUT_POWER_2DBM); } else if ((freq >= 100*fMHz) && (freq < 500*fMHz)) @@ -978,10 +1011,10 @@ private: set_cpld_field(RXLB_SEL, 1); set_cpld_field(RXHB_SEL, 0); // Set LO1 to IF of 2440 (center of filter) - freq_lo1 = _rxlo1->set_frequency(2440*fMHz, ref_freq, _rx_target_pfd_freq, is_int_n); + freq_lo1 = _rxlo1->set_frequency(2440*fMHz, ref_freq, target_pfd_freq, is_int_n); _rxlo1->set_output_power(max287x_iface::OUTPUT_POWER_5DBM); // Set LO2 to IF minus desired frequency - freq_lo2 = _rxlo2->set_frequency(freq_lo1 - freq, ref_freq, _rx_target_pfd_freq, is_int_n); + freq_lo2 = _rxlo2->set_frequency(freq_lo1 - freq, ref_freq, target_pfd_freq, is_int_n); _rxlo1->set_output_power(max287x_iface::OUTPUT_POWER_2DBM); } else if ((freq >= 500*fMHz) && (freq < 800*fMHz)) @@ -993,7 +1026,7 @@ private: set_cpld_field(RXLO1_FSEL1, 1); set_cpld_field(RXLB_SEL, 0); set_cpld_field(RXHB_SEL, 1); - freq_lo1 = _rxlo1->set_frequency(freq, ref_freq, _rx_target_pfd_freq, is_int_n); + freq_lo1 = _rxlo1->set_frequency(freq, ref_freq, target_pfd_freq, is_int_n); _rxlo1->set_output_power(max287x_iface::OUTPUT_POWER_2DBM); } else if ((freq >= 800*fMHz) && (freq < 1000*fMHz)) @@ -1005,7 +1038,7 @@ private: set_cpld_field(RXLO1_FSEL1, 1); set_cpld_field(RXLB_SEL, 0); set_cpld_field(RXHB_SEL, 1); - freq_lo1 = _rxlo1->set_frequency(freq, ref_freq, _rx_target_pfd_freq, is_int_n); + freq_lo1 = _rxlo1->set_frequency(freq, ref_freq, target_pfd_freq, is_int_n); _rxlo1->set_output_power(max287x_iface::OUTPUT_POWER_5DBM); } else if ((freq >= 1000*fMHz) && (freq < 1500*fMHz)) @@ -1017,7 +1050,7 @@ private: set_cpld_field(RXLO1_FSEL1, 0); set_cpld_field(RXLB_SEL, 0); set_cpld_field(RXHB_SEL, 1); - freq_lo1 = _rxlo1->set_frequency(freq, ref_freq, _rx_target_pfd_freq, is_int_n); + freq_lo1 = _rxlo1->set_frequency(freq, ref_freq, target_pfd_freq, is_int_n); _rxlo1->set_output_power(max287x_iface::OUTPUT_POWER_2DBM); } else if ((freq >= 1500*fMHz) && (freq < 2200*fMHz)) @@ -1029,7 +1062,7 @@ private: set_cpld_field(RXLO1_FSEL1, 0); set_cpld_field(RXLB_SEL, 0); set_cpld_field(RXHB_SEL, 1); - freq_lo1 = _rxlo1->set_frequency(freq, ref_freq, _rx_target_pfd_freq, is_int_n); + freq_lo1 = _rxlo1->set_frequency(freq, ref_freq, target_pfd_freq, is_int_n); _rxlo1->set_output_power(max287x_iface::OUTPUT_POWER_2DBM); } else if ((freq >= 2200*fMHz) && (freq < 2500*fMHz)) @@ -1041,7 +1074,7 @@ private: set_cpld_field(RXLO1_FSEL1, 0); set_cpld_field(RXLB_SEL, 0); set_cpld_field(RXHB_SEL, 1); - freq_lo1 = _rxlo1->set_frequency(freq, ref_freq, _rx_target_pfd_freq, is_int_n); + freq_lo1 = _rxlo1->set_frequency(freq, ref_freq, target_pfd_freq, is_int_n); _rxlo1->set_output_power(max287x_iface::OUTPUT_POWER_2DBM); } else if ((freq >= 2500*fMHz) && (freq <= 6000*fMHz)) @@ -1053,7 +1086,7 @@ private: set_cpld_field(RXLO1_FSEL1, 0); set_cpld_field(RXLB_SEL, 0); set_cpld_field(RXHB_SEL, 1); - freq_lo1 = _rxlo1->set_frequency(freq, ref_freq, _rx_target_pfd_freq, is_int_n); + freq_lo1 = _rxlo1->set_frequency(freq, ref_freq, target_pfd_freq, is_int_n); _rxlo1->set_output_power(max287x_iface::OUTPUT_POWER_5DBM); } diff --git a/host/lib/usrp/dboard/db_wbx_common.cpp b/host/lib/usrp/dboard/db_wbx_common.cpp index 97357bc90..6539e798a 100644 --- a/host/lib/usrp/dboard/db_wbx_common.cpp +++ b/host/lib/usrp/dboard/db_wbx_common.cpp @@ -69,17 +69,17 @@ wbx_base::wbx_base(ctor_args_t args) : xcvr_dboard_base(args){ this->get_rx_subtree()->create<device_addr_t>("tune_args").set(device_addr_t()); this->get_rx_subtree()->create<sensor_value_t>("sensors/lo_locked") - .publish(boost::bind(&wbx_base::get_locked, this, dboard_iface::UNIT_RX)); + .set_publisher(boost::bind(&wbx_base::get_locked, this, dboard_iface::UNIT_RX)); BOOST_FOREACH(const std::string &name, wbx_rx_gain_ranges.keys()){ this->get_rx_subtree()->create<double>("gains/"+name+"/value") - .coerce(boost::bind(&wbx_base::set_rx_gain, this, _1, name)) + .set_coercer(boost::bind(&wbx_base::set_rx_gain, this, _1, name)) .set(wbx_rx_gain_ranges[name].start()); this->get_rx_subtree()->create<meta_range_t>("gains/"+name+"/range") .set(wbx_rx_gain_ranges[name]); } this->get_rx_subtree()->create<std::string>("connection").set("IQ"); this->get_rx_subtree()->create<bool>("enabled") - .subscribe(boost::bind(&wbx_base::set_rx_enabled, this, _1)) + .add_coerced_subscriber(boost::bind(&wbx_base::set_rx_enabled, this, _1)) .set(true); //start enabled this->get_rx_subtree()->create<bool>("use_lo_offset").set(false); @@ -94,7 +94,7 @@ wbx_base::wbx_base(ctor_args_t args) : xcvr_dboard_base(args){ this->get_tx_subtree()->create<device_addr_t>("tune_args").set(device_addr_t()); this->get_tx_subtree()->create<sensor_value_t>("sensors/lo_locked") - .publish(boost::bind(&wbx_base::get_locked, this, dboard_iface::UNIT_TX)); + .set_publisher(boost::bind(&wbx_base::get_locked, this, dboard_iface::UNIT_TX)); this->get_tx_subtree()->create<std::string>("connection").set("IQ"); this->get_tx_subtree()->create<bool>("use_lo_offset").set(false); @@ -156,3 +156,9 @@ sensor_value_t wbx_base::get_locked(dboard_iface::unit_t unit){ const bool locked = (this->get_iface()->read_gpio(unit) & LOCKDET_MASK) != 0; return sensor_value_t("LO", locked, "locked", "unlocked"); } + +void wbx_base::wbx_versionx::write_lo_regs(dboard_iface::unit_t unit, const std::vector<boost::uint32_t> ®s) { + BOOST_FOREACH(boost::uint32_t reg, regs) { + self_base->get_iface()->write_spi(unit, spi_config_t::EDGE_RISE, reg, 32); + } +} diff --git a/host/lib/usrp/dboard/db_wbx_common.hpp b/host/lib/usrp/dboard/db_wbx_common.hpp index 6a4224048..0e339e4a3 100644 --- a/host/lib/usrp/dboard/db_wbx_common.hpp +++ b/host/lib/usrp/dboard/db_wbx_common.hpp @@ -19,8 +19,13 @@ #define INCLUDED_LIBUHD_USRP_DBOARD_DB_WBX_COMMON_HPP #include <uhd/types/device_addr.hpp> +#include "adf435x.hpp" -#include "../common/adf435x_common.hpp" +// LO Related +#define ADF435X_CE (1 << 3) +#define ADF435X_PDBRF (1 << 2) +#define ADF435X_MUXOUT (1 << 1) // INPUT!!! +#define LOCKDET_MASK (1 << 0) // INPUT!!! // TX IO Pins #define TX_PUP_5V (1 << 7) // enables 5.0V power supply @@ -40,6 +45,9 @@ #define TX_ATTN_1 (1 << 1) #define TX_ATTN_MASK (TX_ATTN_16|TX_ATTN_8|TX_ATTN_4|TX_ATTN_2|TX_ATTN_1) // valid bits of TX Attenuator Control +#define RX_ATTN_SHIFT 8 //lsb of RX Attenuator Control +#define RX_ATTN_MASK (63 << RX_ATTN_SHIFT) //valid bits of RX Attenuator Control + // Mixer functions #define TX_MIXER_ENB (TXMOD_EN|ADF435X_PDBRF) // for v3, TXMOD_EN tied to ADF435X_PDBRF rather than separate #define TX_MIXER_DIS 0 @@ -142,6 +150,10 @@ protected: property_tree::sptr get_tx_subtree(void){ return self_base->get_tx_subtree(); } + + adf435x_iface::sptr _txlo; + adf435x_iface::sptr _rxlo; + void write_lo_regs(dboard_iface::unit_t unit, const std::vector<boost::uint32_t> ®s); }; @@ -153,7 +165,7 @@ protected: class wbx_version2 : public wbx_versionx { public: wbx_version2(wbx_base *_self_wbx_base); - ~wbx_version2(void); + virtual ~wbx_version2(void); double set_tx_gain(double gain, const std::string &name); void set_tx_enabled(bool enb); @@ -168,7 +180,7 @@ protected: class wbx_version3 : public wbx_versionx { public: wbx_version3(wbx_base *_self_wbx_base); - ~wbx_version3(void); + virtual ~wbx_version3(void); double set_tx_gain(double gain, const std::string &name); void set_tx_enabled(bool enb); @@ -183,7 +195,7 @@ protected: class wbx_version4 : public wbx_versionx { public: wbx_version4(wbx_base *_self_wbx_base); - ~wbx_version4(void); + virtual ~wbx_version4(void); double set_tx_gain(double gain, const std::string &name); void set_tx_enabled(bool enb); diff --git a/host/lib/usrp/dboard/db_wbx_simple.cpp b/host/lib/usrp/dboard/db_wbx_simple.cpp index c8f2be155..062e1294b 100644 --- a/host/lib/usrp/dboard/db_wbx_simple.cpp +++ b/host/lib/usrp/dboard/db_wbx_simple.cpp @@ -46,7 +46,7 @@ static const std::vector<std::string> wbx_rx_antennas = list_of("TX/RX")("RX2")( class wbx_simple : public wbx_base{ public: wbx_simple(ctor_args_t args); - ~wbx_simple(void); + virtual ~wbx_simple(void); private: void set_rx_ant(const std::string &ant); @@ -88,7 +88,7 @@ wbx_simple::wbx_simple(ctor_args_t args) : wbx_base(args){ std::string(str(boost::format("%s+GDB") % this->get_rx_subtree()->access<std::string>("name").get() ))); this->get_rx_subtree()->create<std::string>("antenna/value") - .subscribe(boost::bind(&wbx_simple::set_rx_ant, this, _1)) + .add_coerced_subscriber(boost::bind(&wbx_simple::set_rx_ant, this, _1)) .set("RX2"); this->get_rx_subtree()->create<std::vector<std::string> >("antenna/options") .set(wbx_rx_antennas); @@ -100,7 +100,7 @@ wbx_simple::wbx_simple(ctor_args_t args) : wbx_base(args){ std::string(str(boost::format("%s+GDB") % this->get_tx_subtree()->access<std::string>("name").get() ))); this->get_tx_subtree()->create<std::string>("antenna/value") - .subscribe(boost::bind(&wbx_simple::set_tx_ant, this, _1)) + .add_coerced_subscriber(boost::bind(&wbx_simple::set_tx_ant, this, _1)) .set(wbx_tx_antennas.at(0)); this->get_tx_subtree()->create<std::vector<std::string> >("antenna/options") .set(wbx_tx_antennas); @@ -112,14 +112,14 @@ wbx_simple::wbx_simple(ctor_args_t args) : wbx_base(args){ this->get_iface()->set_gpio_ddr(dboard_iface::UNIT_RX, ANTSW_IO, ANTSW_IO); //setup ATR for the antenna switches (constant) - this->get_iface()->set_atr_reg(dboard_iface::UNIT_TX, dboard_iface::ATR_REG_IDLE, ANT_RX, ANTSW_IO); - this->get_iface()->set_atr_reg(dboard_iface::UNIT_TX, dboard_iface::ATR_REG_RX_ONLY, ANT_RX, ANTSW_IO); - this->get_iface()->set_atr_reg(dboard_iface::UNIT_TX, dboard_iface::ATR_REG_TX_ONLY, ANT_TX, ANTSW_IO); - this->get_iface()->set_atr_reg(dboard_iface::UNIT_TX, dboard_iface::ATR_REG_FULL_DUPLEX, ANT_TX, ANTSW_IO); - - this->get_iface()->set_atr_reg(dboard_iface::UNIT_RX, dboard_iface::ATR_REG_IDLE, ANT_TXRX, ANTSW_IO); - this->get_iface()->set_atr_reg(dboard_iface::UNIT_RX, dboard_iface::ATR_REG_TX_ONLY, ANT_RX2, ANTSW_IO); - this->get_iface()->set_atr_reg(dboard_iface::UNIT_RX, dboard_iface::ATR_REG_FULL_DUPLEX, ANT_RX2, ANTSW_IO); + this->get_iface()->set_atr_reg(dboard_iface::UNIT_TX, gpio_atr::ATR_REG_IDLE, ANT_RX, ANTSW_IO); + this->get_iface()->set_atr_reg(dboard_iface::UNIT_TX, gpio_atr::ATR_REG_RX_ONLY, ANT_RX, ANTSW_IO); + this->get_iface()->set_atr_reg(dboard_iface::UNIT_TX, gpio_atr::ATR_REG_TX_ONLY, ANT_TX, ANTSW_IO); + this->get_iface()->set_atr_reg(dboard_iface::UNIT_TX, gpio_atr::ATR_REG_FULL_DUPLEX, ANT_TX, ANTSW_IO); + + this->get_iface()->set_atr_reg(dboard_iface::UNIT_RX, gpio_atr::ATR_REG_IDLE, ANT_TXRX, ANTSW_IO); + this->get_iface()->set_atr_reg(dboard_iface::UNIT_RX, gpio_atr::ATR_REG_TX_ONLY, ANT_RX2, ANTSW_IO); + this->get_iface()->set_atr_reg(dboard_iface::UNIT_RX, gpio_atr::ATR_REG_FULL_DUPLEX, ANT_RX2, ANTSW_IO); } wbx_simple::~wbx_simple(void){ @@ -138,14 +138,14 @@ void wbx_simple::set_rx_ant(const std::string &ant){ //write the new antenna setting to atr regs if (_rx_ant == "CAL") { - this->get_iface()->set_atr_reg(dboard_iface::UNIT_RX, dboard_iface::ATR_REG_TX_ONLY, ANT_TXRX, ANTSW_IO); - this->get_iface()->set_atr_reg(dboard_iface::UNIT_RX, dboard_iface::ATR_REG_FULL_DUPLEX, ANT_TXRX, ANTSW_IO); - this->get_iface()->set_atr_reg(dboard_iface::UNIT_RX, dboard_iface::ATR_REG_RX_ONLY, ANT_TXRX, ANTSW_IO); + this->get_iface()->set_atr_reg(dboard_iface::UNIT_RX, gpio_atr::ATR_REG_TX_ONLY, ANT_TXRX, ANTSW_IO); + this->get_iface()->set_atr_reg(dboard_iface::UNIT_RX, gpio_atr::ATR_REG_FULL_DUPLEX, ANT_TXRX, ANTSW_IO); + this->get_iface()->set_atr_reg(dboard_iface::UNIT_RX, gpio_atr::ATR_REG_RX_ONLY, ANT_TXRX, ANTSW_IO); } else { - this->get_iface()->set_atr_reg(dboard_iface::UNIT_RX, dboard_iface::ATR_REG_TX_ONLY, ANT_RX2, ANTSW_IO); - this->get_iface()->set_atr_reg(dboard_iface::UNIT_RX, dboard_iface::ATR_REG_FULL_DUPLEX, ANT_RX2, ANTSW_IO); - this->get_iface()->set_atr_reg(dboard_iface::UNIT_RX, dboard_iface::ATR_REG_RX_ONLY, ((_rx_ant == "TX/RX")? ANT_TXRX : ANT_RX2), ANTSW_IO); + this->get_iface()->set_atr_reg(dboard_iface::UNIT_RX, gpio_atr::ATR_REG_TX_ONLY, ANT_RX2, ANTSW_IO); + this->get_iface()->set_atr_reg(dboard_iface::UNIT_RX, gpio_atr::ATR_REG_FULL_DUPLEX, ANT_RX2, ANTSW_IO); + this->get_iface()->set_atr_reg(dboard_iface::UNIT_RX, gpio_atr::ATR_REG_RX_ONLY, ((_rx_ant == "TX/RX")? ANT_TXRX : ANT_RX2), ANTSW_IO); } } @@ -154,11 +154,11 @@ void wbx_simple::set_tx_ant(const std::string &ant){ //write the new antenna setting to atr regs if (ant == "CAL") { - this->get_iface()->set_atr_reg(dboard_iface::UNIT_TX, dboard_iface::ATR_REG_TX_ONLY, ANT_RX, ANTSW_IO); - this->get_iface()->set_atr_reg(dboard_iface::UNIT_TX, dboard_iface::ATR_REG_FULL_DUPLEX, ANT_RX, ANTSW_IO); + this->get_iface()->set_atr_reg(dboard_iface::UNIT_TX, gpio_atr::ATR_REG_TX_ONLY, ANT_RX, ANTSW_IO); + this->get_iface()->set_atr_reg(dboard_iface::UNIT_TX, gpio_atr::ATR_REG_FULL_DUPLEX, ANT_RX, ANTSW_IO); } else { - this->get_iface()->set_atr_reg(dboard_iface::UNIT_TX, dboard_iface::ATR_REG_TX_ONLY, ANT_TX, ANTSW_IO); - this->get_iface()->set_atr_reg(dboard_iface::UNIT_TX, dboard_iface::ATR_REG_FULL_DUPLEX, ANT_TX, ANTSW_IO); + this->get_iface()->set_atr_reg(dboard_iface::UNIT_TX, gpio_atr::ATR_REG_TX_ONLY, ANT_TX, ANTSW_IO); + this->get_iface()->set_atr_reg(dboard_iface::UNIT_TX, gpio_atr::ATR_REG_FULL_DUPLEX, ANT_TX, ANTSW_IO); } } diff --git a/host/lib/usrp/dboard/db_wbx_version2.cpp b/host/lib/usrp/dboard/db_wbx_version2.cpp index 03c8f37e9..489291881 100644 --- a/host/lib/usrp/dboard/db_wbx_version2.cpp +++ b/host/lib/usrp/dboard/db_wbx_version2.cpp @@ -16,8 +16,6 @@ // #include "db_wbx_common.hpp" -#include "adf4350_regs.hpp" -#include "../common/adf435x_common.hpp" #include <uhd/types/tune_request.hpp> #include <uhd/utils/log.hpp> #include <uhd/types/dict.hpp> @@ -77,13 +75,15 @@ static double tx_pga0_gain_to_dac_volts(double &gain){ wbx_base::wbx_version2::wbx_version2(wbx_base *_self_wbx_base) { //register our handle on the primary wbx_base instance self_base = _self_wbx_base; + _txlo = adf435x_iface::make_adf4350(boost::bind(&wbx_base::wbx_versionx::write_lo_regs, this, dboard_iface::UNIT_TX, _1)); + _rxlo = adf435x_iface::make_adf4350(boost::bind(&wbx_base::wbx_versionx::write_lo_regs, this, dboard_iface::UNIT_RX, _1)); //////////////////////////////////////////////////////////////////// // Register RX properties //////////////////////////////////////////////////////////////////// this->get_rx_subtree()->create<std::string>("name").set("WBXv2 RX"); this->get_rx_subtree()->create<double>("freq/value") - .coerce(boost::bind(&wbx_base::wbx_version2::set_lo_freq, this, dboard_iface::UNIT_RX, _1)) + .set_coercer(boost::bind(&wbx_base::wbx_version2::set_lo_freq, this, dboard_iface::UNIT_RX, _1)) .set((wbx_v2_freq_range.start() + wbx_v2_freq_range.stop())/2.0); this->get_rx_subtree()->create<meta_range_t>("freq/range").set(wbx_v2_freq_range); @@ -93,17 +93,17 @@ wbx_base::wbx_version2::wbx_version2(wbx_base *_self_wbx_base) { this->get_tx_subtree()->create<std::string>("name").set("WBXv2 TX"); BOOST_FOREACH(const std::string &name, wbx_v2_tx_gain_ranges.keys()){ self_base->get_tx_subtree()->create<double>("gains/"+name+"/value") - .coerce(boost::bind(&wbx_base::wbx_version2::set_tx_gain, this, _1, name)) + .set_coercer(boost::bind(&wbx_base::wbx_version2::set_tx_gain, this, _1, name)) .set(wbx_v2_tx_gain_ranges[name].start()); self_base->get_tx_subtree()->create<meta_range_t>("gains/"+name+"/range") .set(wbx_v2_tx_gain_ranges[name]); } this->get_tx_subtree()->create<double>("freq/value") - .coerce(boost::bind(&wbx_base::wbx_version2::set_lo_freq, this, dboard_iface::UNIT_TX, _1)) + .set_coercer(boost::bind(&wbx_base::wbx_version2::set_lo_freq, this, dboard_iface::UNIT_TX, _1)) .set((wbx_v2_freq_range.start() + wbx_v2_freq_range.stop())/2.0); this->get_tx_subtree()->create<meta_range_t>("freq/range").set(wbx_v2_freq_range); this->get_tx_subtree()->create<bool>("enabled") - .subscribe(boost::bind(&wbx_base::wbx_version2::set_tx_enabled, this, _1)) + .add_coerced_subscriber(boost::bind(&wbx_base::wbx_version2::set_tx_enabled, this, _1)) .set(true); //start enabled //set attenuator control bits @@ -117,15 +117,15 @@ wbx_base::wbx_version2::wbx_version2(wbx_base *_self_wbx_base) { self_base->get_iface()->set_gpio_ddr(dboard_iface::UNIT_RX, RX_PUP_5V|RX_PUP_3V|ADF435X_CE|RXBB_PDB|ADF435X_PDBRF|RX_ATTN_MASK); //setup ATR for the mixer enables (always enabled to prevent phase slip between bursts) - self_base->get_iface()->set_atr_reg(dboard_iface::UNIT_TX, dboard_iface::ATR_REG_IDLE, v2_tx_mod, TX_MIXER_DIS | v2_tx_mod); - self_base->get_iface()->set_atr_reg(dboard_iface::UNIT_TX, dboard_iface::ATR_REG_RX_ONLY, v2_tx_mod, TX_MIXER_DIS | v2_tx_mod); - self_base->get_iface()->set_atr_reg(dboard_iface::UNIT_TX, dboard_iface::ATR_REG_TX_ONLY, v2_tx_mod, TX_MIXER_DIS | v2_tx_mod); - self_base->get_iface()->set_atr_reg(dboard_iface::UNIT_TX, dboard_iface::ATR_REG_FULL_DUPLEX, v2_tx_mod, TX_MIXER_DIS | v2_tx_mod); - - self_base->get_iface()->set_atr_reg(dboard_iface::UNIT_RX, dboard_iface::ATR_REG_IDLE, RX_MIXER_ENB, RX_MIXER_DIS | RX_MIXER_ENB); - self_base->get_iface()->set_atr_reg(dboard_iface::UNIT_RX, dboard_iface::ATR_REG_TX_ONLY, RX_MIXER_ENB, RX_MIXER_DIS | RX_MIXER_ENB); - self_base->get_iface()->set_atr_reg(dboard_iface::UNIT_RX, dboard_iface::ATR_REG_RX_ONLY, RX_MIXER_ENB, RX_MIXER_DIS | RX_MIXER_ENB); - self_base->get_iface()->set_atr_reg(dboard_iface::UNIT_RX, dboard_iface::ATR_REG_FULL_DUPLEX, RX_MIXER_ENB, RX_MIXER_DIS | RX_MIXER_ENB); + self_base->get_iface()->set_atr_reg(dboard_iface::UNIT_TX, gpio_atr::ATR_REG_IDLE, v2_tx_mod, TX_MIXER_DIS | v2_tx_mod); + self_base->get_iface()->set_atr_reg(dboard_iface::UNIT_TX, gpio_atr::ATR_REG_RX_ONLY, v2_tx_mod, TX_MIXER_DIS | v2_tx_mod); + self_base->get_iface()->set_atr_reg(dboard_iface::UNIT_TX, gpio_atr::ATR_REG_TX_ONLY, v2_tx_mod, TX_MIXER_DIS | v2_tx_mod); + self_base->get_iface()->set_atr_reg(dboard_iface::UNIT_TX, gpio_atr::ATR_REG_FULL_DUPLEX, v2_tx_mod, TX_MIXER_DIS | v2_tx_mod); + + self_base->get_iface()->set_atr_reg(dboard_iface::UNIT_RX, gpio_atr::ATR_REG_IDLE, RX_MIXER_ENB, RX_MIXER_DIS | RX_MIXER_ENB); + self_base->get_iface()->set_atr_reg(dboard_iface::UNIT_RX, gpio_atr::ATR_REG_TX_ONLY, RX_MIXER_ENB, RX_MIXER_DIS | RX_MIXER_ENB); + self_base->get_iface()->set_atr_reg(dboard_iface::UNIT_RX, gpio_atr::ATR_REG_RX_ONLY, RX_MIXER_ENB, RX_MIXER_DIS | RX_MIXER_ENB); + self_base->get_iface()->set_atr_reg(dboard_iface::UNIT_RX, gpio_atr::ATR_REG_FULL_DUPLEX, RX_MIXER_ENB, RX_MIXER_DIS | RX_MIXER_ENB); } wbx_base::wbx_version2::~wbx_version2(void){ @@ -178,111 +178,45 @@ double wbx_base::wbx_version2::set_lo_freq(dboard_iface::unit_t unit, double tar : self_base->get_tx_subtree(); device_addr_t tune_args = subtree->access<device_addr_t>("tune_args").get(); bool is_int_n = boost::iequals(tune_args.get("mode_n",""), "integer"); + double reference_freq = self_base->get_iface()->get_clock_rate(unit); - //map prescaler setting to mininmum integer divider (N) values (pg.18 prescaler) - static const uhd::dict<int, int> prescaler_to_min_int_div = map_list_of - (0,23) //adf4350_regs_t::PRESCALER_4_5 - (1,75) //adf4350_regs_t::PRESCALER_8_9 - ; - - //map rf divider select output dividers to enums - static const uhd::dict<int, adf4350_regs_t::rf_divider_select_t> rfdivsel_to_enum = map_list_of - (1, adf4350_regs_t::RF_DIVIDER_SELECT_DIV1) - (2, adf4350_regs_t::RF_DIVIDER_SELECT_DIV2) - (4, adf4350_regs_t::RF_DIVIDER_SELECT_DIV4) - (8, adf4350_regs_t::RF_DIVIDER_SELECT_DIV8) - (16, adf4350_regs_t::RF_DIVIDER_SELECT_DIV16) - ; + //Select the LO + adf435x_iface::sptr& lo_iface = unit == dboard_iface::UNIT_RX ? _rxlo : _txlo; + lo_iface->set_reference_freq(reference_freq); - double reference_freq = self_base->get_iface()->get_clock_rate(unit); //The mixer has a divide-by-2 stage on the LO port so the synthesizer - //frequency must 2x the target frequency. This introduces a 180 degree - //phase ambiguity + //frequency must 2x the target frequency double synth_target_freq = target_freq * 2; - adf4350_regs_t::prescaler_t prescaler = - synth_target_freq > 3e9 ? adf4350_regs_t::PRESCALER_8_9 : adf4350_regs_t::PRESCALER_4_5; + //Use 8/9 prescaler for vco_freq > 3 GHz (pg.18 prescaler) + lo_iface->set_prescaler(synth_target_freq > 3e9 ? + adf435x_iface::PRESCALER_8_9 : adf435x_iface::PRESCALER_4_5); - adf435x_tuning_constraints tuning_constraints; - tuning_constraints.force_frac0 = is_int_n; - tuning_constraints.band_sel_freq_max = 100e3; - tuning_constraints.ref_doubler_threshold = 12.5e6; - tuning_constraints.int_range = uhd::range_t(prescaler_to_min_int_div[prescaler], 4095); - tuning_constraints.pfd_freq_max = 25e6; - tuning_constraints.rf_divider_range = uhd::range_t(1, 16); //The feedback of the divided frequency must be disabled whenever the target frequency //divided by the minimum PFD frequency cannot meet the minimum integer divider (N) value. //If it is disabled, additional phase ambiguity will be introduced. With a minimum PFD //frequency of 10 MHz, synthesizer frequencies below 230 MHz (LO frequencies below 115 MHz) //will have too much ambiguity to synchronize. - tuning_constraints.feedback_after_divider = - (int(synth_target_freq / 10e6) >= prescaler_to_min_int_div[prescaler]); + lo_iface->set_feedback_select( + (int(synth_target_freq / 10e6) >= lo_iface->get_int_range().start() ? + adf435x_iface::FB_SEL_DIVIDED : adf435x_iface::FB_SEL_FUNDAMENTAL)); - double synth_actual_freq = 0; - adf435x_tuning_settings tuning_settings = tune_adf435x_synth( - synth_target_freq, reference_freq, tuning_constraints, synth_actual_freq); + double synth_actual_freq = lo_iface->set_frequency(synth_target_freq, is_int_n); //The mixer has a divide-by-2 stage on the LO port so the synthesizer //actual_freq must /2 the synth_actual_freq double actual_freq = synth_actual_freq / 2; - //load the register values - adf4350_regs_t regs; - - if (unit == dboard_iface::UNIT_RX) - regs.output_power = (actual_freq == wbx_rx_lo_5dbm.clip(actual_freq)) ? adf4350_regs_t::OUTPUT_POWER_5DBM - : adf4350_regs_t::OUTPUT_POWER_2DBM; - else - regs.output_power = (actual_freq == wbx_tx_lo_5dbm.clip(actual_freq)) ? adf4350_regs_t::OUTPUT_POWER_5DBM - : adf4350_regs_t::OUTPUT_POWER_M1DBM; - - regs.frac_12_bit = tuning_settings.frac_12_bit; - regs.int_16_bit = tuning_settings.int_16_bit; - regs.mod_12_bit = tuning_settings.mod_12_bit; - regs.clock_divider_12_bit = tuning_settings.clock_divider_12_bit; - regs.feedback_select = tuning_constraints.feedback_after_divider ? - adf4350_regs_t::FEEDBACK_SELECT_DIVIDED : - adf4350_regs_t::FEEDBACK_SELECT_FUNDAMENTAL; - regs.clock_div_mode = tuning_constraints.feedback_after_divider ? - adf4350_regs_t::CLOCK_DIV_MODE_RESYNC_ENABLE : - adf4350_regs_t::CLOCK_DIV_MODE_FAST_LOCK; - regs.prescaler = prescaler; - regs.r_counter_10_bit = tuning_settings.r_counter_10_bit; - regs.reference_divide_by_2 = tuning_settings.r_divide_by_2_en ? - adf4350_regs_t::REFERENCE_DIVIDE_BY_2_ENABLED : - adf4350_regs_t::REFERENCE_DIVIDE_BY_2_DISABLED; - regs.reference_doubler = tuning_settings.r_doubler_en ? - adf4350_regs_t::REFERENCE_DOUBLER_ENABLED : - adf4350_regs_t::REFERENCE_DOUBLER_DISABLED; - regs.band_select_clock_div = tuning_settings.band_select_clock_div; - UHD_ASSERT_THROW(rfdivsel_to_enum.has_key(tuning_settings.rf_divider)); - regs.rf_divider_select = rfdivsel_to_enum[tuning_settings.rf_divider]; - regs.ldf = is_int_n ? - adf4350_regs_t::LDF_INT_N : - adf4350_regs_t::LDF_FRAC_N; - - //reset the N and R counter - regs.counter_reset = adf4350_regs_t::COUNTER_RESET_ENABLED; - self_base->get_iface()->write_spi(unit, spi_config_t::EDGE_RISE, regs.get_reg(2), 32); - regs.counter_reset = adf4350_regs_t::COUNTER_RESET_DISABLED; - - //write the registers - //correct power-up sequence to write registers (5, 4, 3, 2, 1, 0) - int addr; - - for(addr=5; addr>=0; addr--){ - UHD_LOGV(often) << boost::format( - "WBX SPI Reg (0x%02x): 0x%08x" - ) % addr % regs.get_reg(addr) << std::endl; - self_base->get_iface()->write_spi( - unit, spi_config_t::EDGE_RISE, - regs.get_reg(addr), 32 - ); + if (unit == dboard_iface::UNIT_RX) { + lo_iface->set_output_power((actual_freq == wbx_rx_lo_5dbm.clip(actual_freq)) ? + adf435x_iface::OUTPUT_POWER_5DBM : adf435x_iface::OUTPUT_POWER_2DBM); + } else { + lo_iface->set_output_power((actual_freq == wbx_tx_lo_5dbm.clip(actual_freq)) ? + adf435x_iface::OUTPUT_POWER_5DBM : adf435x_iface::OUTPUT_POWER_M1DBM); } - //return the actual frequency - UHD_LOGV(often) << boost::format( - "WBX tune: actual frequency %f MHz" - ) % (actual_freq/1e6) << std::endl; + //Write to hardware + lo_iface->commit(); + return actual_freq; } diff --git a/host/lib/usrp/dboard/db_wbx_version3.cpp b/host/lib/usrp/dboard/db_wbx_version3.cpp index a559a7b14..4dcf3bb71 100644 --- a/host/lib/usrp/dboard/db_wbx_version3.cpp +++ b/host/lib/usrp/dboard/db_wbx_version3.cpp @@ -16,8 +16,6 @@ // #include "db_wbx_common.hpp" -#include "adf4350_regs.hpp" -#include "../common/adf435x_common.hpp" #include <uhd/utils/log.hpp> #include <uhd/types/dict.hpp> #include <uhd/types/ranges.hpp> @@ -82,13 +80,15 @@ static int tx_pga0_gain_to_iobits(double &gain){ wbx_base::wbx_version3::wbx_version3(wbx_base *_self_wbx_base) { //register our handle on the primary wbx_base instance self_base = _self_wbx_base; + _txlo = adf435x_iface::make_adf4350(boost::bind(&wbx_base::wbx_versionx::write_lo_regs, this, dboard_iface::UNIT_TX, _1)); + _rxlo = adf435x_iface::make_adf4350(boost::bind(&wbx_base::wbx_versionx::write_lo_regs, this, dboard_iface::UNIT_RX, _1)); //////////////////////////////////////////////////////////////////// // Register RX properties //////////////////////////////////////////////////////////////////// this->get_rx_subtree()->create<std::string>("name").set("WBXv3 RX"); this->get_rx_subtree()->create<double>("freq/value") - .coerce(boost::bind(&wbx_base::wbx_version3::set_lo_freq, this, dboard_iface::UNIT_RX, _1)) + .set_coercer(boost::bind(&wbx_base::wbx_version3::set_lo_freq, this, dboard_iface::UNIT_RX, _1)) .set((wbx_v3_freq_range.start() + wbx_v3_freq_range.stop())/2.0); this->get_rx_subtree()->create<meta_range_t>("freq/range").set(wbx_v3_freq_range); @@ -98,17 +98,17 @@ wbx_base::wbx_version3::wbx_version3(wbx_base *_self_wbx_base) { this->get_tx_subtree()->create<std::string>("name").set("WBXv3 TX"); BOOST_FOREACH(const std::string &name, wbx_v3_tx_gain_ranges.keys()){ self_base->get_tx_subtree()->create<double>("gains/"+name+"/value") - .coerce(boost::bind(&wbx_base::wbx_version3::set_tx_gain, this, _1, name)) + .set_coercer(boost::bind(&wbx_base::wbx_version3::set_tx_gain, this, _1, name)) .set(wbx_v3_tx_gain_ranges[name].start()); self_base->get_tx_subtree()->create<meta_range_t>("gains/"+name+"/range") .set(wbx_v3_tx_gain_ranges[name]); } this->get_tx_subtree()->create<double>("freq/value") - .coerce(boost::bind(&wbx_base::wbx_version3::set_lo_freq, this, dboard_iface::UNIT_TX, _1)) + .set_coercer(boost::bind(&wbx_base::wbx_version3::set_lo_freq, this, dboard_iface::UNIT_TX, _1)) .set((wbx_v3_freq_range.start() + wbx_v3_freq_range.stop())/2.0); this->get_tx_subtree()->create<meta_range_t>("freq/range").set(wbx_v3_freq_range); this->get_tx_subtree()->create<bool>("enabled") - .subscribe(boost::bind(&wbx_base::wbx_version3::set_tx_enabled, this, _1)) + .add_coerced_subscriber(boost::bind(&wbx_base::wbx_version3::set_tx_enabled, this, _1)) .set(true); //start enabled //set attenuator control bits @@ -129,29 +129,29 @@ wbx_base::wbx_version3::wbx_version3(wbx_base *_self_wbx_base) { //slip between bursts). set TX gain iobits to min gain (max attenuation) //when RX_ONLY or IDLE to suppress LO leakage self_base->get_iface()->set_atr_reg(dboard_iface::UNIT_TX, \ - dboard_iface::ATR_REG_IDLE, v3_tx_mod, \ + gpio_atr::ATR_REG_IDLE, v3_tx_mod, \ TX_ATTN_MASK | TX_MIXER_DIS | v3_tx_mod); self_base->get_iface()->set_atr_reg(dboard_iface::UNIT_TX, \ - dboard_iface::ATR_REG_RX_ONLY, v3_tx_mod, \ + gpio_atr::ATR_REG_RX_ONLY, v3_tx_mod, \ TX_ATTN_MASK | TX_MIXER_DIS | v3_tx_mod); self_base->get_iface()->set_atr_reg(dboard_iface::UNIT_TX, \ - dboard_iface::ATR_REG_TX_ONLY, v3_tx_mod, \ + gpio_atr::ATR_REG_TX_ONLY, v3_tx_mod, \ TX_ATTN_MASK | TX_MIXER_DIS | v3_tx_mod); self_base->get_iface()->set_atr_reg(dboard_iface::UNIT_TX, \ - dboard_iface::ATR_REG_FULL_DUPLEX, v3_tx_mod, \ + gpio_atr::ATR_REG_FULL_DUPLEX, v3_tx_mod, \ TX_ATTN_MASK | TX_MIXER_DIS | v3_tx_mod); self_base->get_iface()->set_atr_reg(dboard_iface::UNIT_RX, \ - dboard_iface::ATR_REG_IDLE, \ + gpio_atr::ATR_REG_IDLE, \ RX_MIXER_ENB, RX_MIXER_DIS | RX_MIXER_ENB); self_base->get_iface()->set_atr_reg(dboard_iface::UNIT_RX, \ - dboard_iface::ATR_REG_TX_ONLY, \ + gpio_atr::ATR_REG_TX_ONLY, \ RX_MIXER_ENB, RX_MIXER_DIS | RX_MIXER_ENB); self_base->get_iface()->set_atr_reg(dboard_iface::UNIT_RX, \ - dboard_iface::ATR_REG_RX_ONLY, \ + gpio_atr::ATR_REG_RX_ONLY, \ RX_MIXER_ENB, RX_MIXER_DIS | RX_MIXER_ENB); self_base->get_iface()->set_atr_reg(dboard_iface::UNIT_RX, \ - dboard_iface::ATR_REG_FULL_DUPLEX, \ + gpio_atr::ATR_REG_FULL_DUPLEX, \ RX_MIXER_ENB, RX_MIXER_DIS | RX_MIXER_ENB); } @@ -181,8 +181,8 @@ double wbx_base::wbx_version3::set_tx_gain(double gain, const std::string &name) //write the new gain to tx gpio outputs //Update ATR with gain io_bits, only update for TX_ONLY and FULL_DUPLEX ATR states - self_base->get_iface()->set_atr_reg(dboard_iface::UNIT_TX, dboard_iface::ATR_REG_TX_ONLY, io_bits, TX_ATTN_MASK); - self_base->get_iface()->set_atr_reg(dboard_iface::UNIT_TX, dboard_iface::ATR_REG_FULL_DUPLEX, io_bits, TX_ATTN_MASK); + self_base->get_iface()->set_atr_reg(dboard_iface::UNIT_TX, gpio_atr::ATR_REG_TX_ONLY, io_bits, TX_ATTN_MASK); + self_base->get_iface()->set_atr_reg(dboard_iface::UNIT_TX, gpio_atr::ATR_REG_FULL_DUPLEX, io_bits, TX_ATTN_MASK); } else UHD_THROW_INVALID_CODE_PATH(); return self_base->_tx_gains[name]; //shadow @@ -209,111 +209,45 @@ double wbx_base::wbx_version3::set_lo_freq(dboard_iface::unit_t unit, double tar : self_base->get_tx_subtree(); device_addr_t tune_args = subtree->access<device_addr_t>("tune_args").get(); bool is_int_n = boost::iequals(tune_args.get("mode_n",""), "integer"); + double reference_freq = self_base->get_iface()->get_clock_rate(unit); - //map prescaler setting to mininmum integer divider (N) values (pg.18 prescaler) - static const uhd::dict<int, int> prescaler_to_min_int_div = map_list_of - (0,23) //adf4350_regs_t::PRESCALER_4_5 - (1,75) //adf4350_regs_t::PRESCALER_8_9 - ; - - //map rf divider select output dividers to enums - static const uhd::dict<int, adf4350_regs_t::rf_divider_select_t> rfdivsel_to_enum = map_list_of - (1, adf4350_regs_t::RF_DIVIDER_SELECT_DIV1) - (2, adf4350_regs_t::RF_DIVIDER_SELECT_DIV2) - (4, adf4350_regs_t::RF_DIVIDER_SELECT_DIV4) - (8, adf4350_regs_t::RF_DIVIDER_SELECT_DIV8) - (16, adf4350_regs_t::RF_DIVIDER_SELECT_DIV16) - ; + //Select the LO + adf435x_iface::sptr& lo_iface = unit == dboard_iface::UNIT_RX ? _rxlo : _txlo; + lo_iface->set_reference_freq(reference_freq); - double reference_freq = self_base->get_iface()->get_clock_rate(unit); //The mixer has a divide-by-2 stage on the LO port so the synthesizer - //frequency must 2x the target frequency. This introduces a 180 degree - //phase ambiguity + //frequency must 2x the target frequency double synth_target_freq = target_freq * 2; - adf4350_regs_t::prescaler_t prescaler = - synth_target_freq > 3e9 ? adf4350_regs_t::PRESCALER_8_9 : adf4350_regs_t::PRESCALER_4_5; + //Use 8/9 prescaler for vco_freq > 3 GHz (pg.18 prescaler) + lo_iface->set_prescaler(synth_target_freq > 3e9 ? + adf435x_iface::PRESCALER_8_9 : adf435x_iface::PRESCALER_4_5); - adf435x_tuning_constraints tuning_constraints; - tuning_constraints.force_frac0 = is_int_n; - tuning_constraints.band_sel_freq_max = 100e3; - tuning_constraints.ref_doubler_threshold = 12.5e6; - tuning_constraints.int_range = uhd::range_t(prescaler_to_min_int_div[prescaler], 4095); - tuning_constraints.pfd_freq_max = 25e6; - tuning_constraints.rf_divider_range = uhd::range_t(1, 16); //The feedback of the divided frequency must be disabled whenever the target frequency //divided by the minimum PFD frequency cannot meet the minimum integer divider (N) value. //If it is disabled, additional phase ambiguity will be introduced. With a minimum PFD //frequency of 10 MHz, synthesizer frequencies below 230 MHz (LO frequencies below 115 MHz) //will have too much ambiguity to synchronize. - tuning_constraints.feedback_after_divider = - (int(synth_target_freq / 10e6) >= prescaler_to_min_int_div[prescaler]); + lo_iface->set_feedback_select( + (int(synth_target_freq / 10e6) >= lo_iface->get_int_range().start() ? + adf435x_iface::FB_SEL_DIVIDED : adf435x_iface::FB_SEL_FUNDAMENTAL)); - double synth_actual_freq = 0; - adf435x_tuning_settings tuning_settings = tune_adf435x_synth( - synth_target_freq, reference_freq, tuning_constraints, synth_actual_freq); + double synth_actual_freq = lo_iface->set_frequency(synth_target_freq, is_int_n); //The mixer has a divide-by-2 stage on the LO port so the synthesizer //actual_freq must /2 the synth_actual_freq double actual_freq = synth_actual_freq / 2; - //load the register values - adf4350_regs_t regs; - - if (unit == dboard_iface::UNIT_RX) - regs.output_power = (actual_freq == wbx_rx_lo_5dbm.clip(actual_freq)) ? adf4350_regs_t::OUTPUT_POWER_5DBM - : adf4350_regs_t::OUTPUT_POWER_2DBM; - else - regs.output_power = (actual_freq == wbx_tx_lo_5dbm.clip(actual_freq)) ? adf4350_regs_t::OUTPUT_POWER_5DBM - : adf4350_regs_t::OUTPUT_POWER_M1DBM; - - regs.frac_12_bit = tuning_settings.frac_12_bit; - regs.int_16_bit = tuning_settings.int_16_bit; - regs.mod_12_bit = tuning_settings.mod_12_bit; - regs.clock_divider_12_bit = tuning_settings.clock_divider_12_bit; - regs.feedback_select = tuning_constraints.feedback_after_divider ? - adf4350_regs_t::FEEDBACK_SELECT_DIVIDED : - adf4350_regs_t::FEEDBACK_SELECT_FUNDAMENTAL; - regs.clock_div_mode = tuning_constraints.feedback_after_divider ? - adf4350_regs_t::CLOCK_DIV_MODE_RESYNC_ENABLE : - adf4350_regs_t::CLOCK_DIV_MODE_FAST_LOCK; - regs.prescaler = prescaler; - regs.r_counter_10_bit = tuning_settings.r_counter_10_bit; - regs.reference_divide_by_2 = tuning_settings.r_divide_by_2_en ? - adf4350_regs_t::REFERENCE_DIVIDE_BY_2_ENABLED : - adf4350_regs_t::REFERENCE_DIVIDE_BY_2_DISABLED; - regs.reference_doubler = tuning_settings.r_doubler_en ? - adf4350_regs_t::REFERENCE_DOUBLER_ENABLED : - adf4350_regs_t::REFERENCE_DOUBLER_DISABLED; - regs.band_select_clock_div = tuning_settings.band_select_clock_div; - UHD_ASSERT_THROW(rfdivsel_to_enum.has_key(tuning_settings.rf_divider)); - regs.rf_divider_select = rfdivsel_to_enum[tuning_settings.rf_divider]; - regs.ldf = is_int_n ? - adf4350_regs_t::LDF_INT_N : - adf4350_regs_t::LDF_FRAC_N; - - //reset the N and R counter - regs.counter_reset = adf4350_regs_t::COUNTER_RESET_ENABLED; - self_base->get_iface()->write_spi(unit, spi_config_t::EDGE_RISE, regs.get_reg(2), 32); - regs.counter_reset = adf4350_regs_t::COUNTER_RESET_DISABLED; - - //write the registers - //correct power-up sequence to write registers (5, 4, 3, 2, 1, 0) - int addr; - - for(addr=5; addr>=0; addr--){ - UHD_LOGV(often) << boost::format( - "WBX SPI Reg (0x%02x): 0x%08x" - ) % addr % regs.get_reg(addr) << std::endl; - self_base->get_iface()->write_spi( - unit, spi_config_t::EDGE_RISE, - regs.get_reg(addr), 32 - ); + if (unit == dboard_iface::UNIT_RX) { + lo_iface->set_output_power((actual_freq == wbx_rx_lo_5dbm.clip(actual_freq)) ? + adf435x_iface::OUTPUT_POWER_5DBM : adf435x_iface::OUTPUT_POWER_2DBM); + } else { + lo_iface->set_output_power((actual_freq == wbx_tx_lo_5dbm.clip(actual_freq)) ? + adf435x_iface::OUTPUT_POWER_5DBM : adf435x_iface::OUTPUT_POWER_M1DBM); } - //return the actual frequency - UHD_LOGV(often) << boost::format( - "WBX tune: actual frequency %f MHz" - ) % (actual_freq/1e6) << std::endl; + //Write to hardware + lo_iface->commit(); + return actual_freq; } diff --git a/host/lib/usrp/dboard/db_wbx_version4.cpp b/host/lib/usrp/dboard/db_wbx_version4.cpp index 81cdaefac..dc351af1d 100644 --- a/host/lib/usrp/dboard/db_wbx_version4.cpp +++ b/host/lib/usrp/dboard/db_wbx_version4.cpp @@ -16,8 +16,6 @@ // #include "db_wbx_common.hpp" -#include "adf4351_regs.hpp" -#include "../common/adf435x_common.hpp" #include <uhd/utils/log.hpp> #include <uhd/types/dict.hpp> #include <uhd/types/ranges.hpp> @@ -83,6 +81,8 @@ static int tx_pga0_gain_to_iobits(double &gain){ wbx_base::wbx_version4::wbx_version4(wbx_base *_self_wbx_base) { //register our handle on the primary wbx_base instance self_base = _self_wbx_base; + _txlo = adf435x_iface::make_adf4351(boost::bind(&wbx_base::wbx_versionx::write_lo_regs, this, dboard_iface::UNIT_TX, _1)); + _rxlo = adf435x_iface::make_adf4351(boost::bind(&wbx_base::wbx_versionx::write_lo_regs, this, dboard_iface::UNIT_RX, _1)); //////////////////////////////////////////////////////////////////// // Register RX properties @@ -92,7 +92,7 @@ wbx_base::wbx_version4::wbx_version4(wbx_base *_self_wbx_base) { if(rx_id == 0x0063) this->get_rx_subtree()->create<std::string>("name").set("WBXv4 RX"); else if(rx_id == 0x0081) this->get_rx_subtree()->create<std::string>("name").set("WBX-120 RX"); this->get_rx_subtree()->create<double>("freq/value") - .coerce(boost::bind(&wbx_base::wbx_version4::set_lo_freq, this, dboard_iface::UNIT_RX, _1)) + .set_coercer(boost::bind(&wbx_base::wbx_version4::set_lo_freq, this, dboard_iface::UNIT_RX, _1)) .set((wbx_v4_freq_range.start() + wbx_v4_freq_range.stop())/2.0); this->get_rx_subtree()->create<meta_range_t>("freq/range").set(wbx_v4_freq_range); @@ -105,17 +105,17 @@ wbx_base::wbx_version4::wbx_version4(wbx_base *_self_wbx_base) { else if(rx_id == 0x0081) this->get_tx_subtree()->create<std::string>("name").set("WBX-120 TX"); BOOST_FOREACH(const std::string &name, wbx_v4_tx_gain_ranges.keys()){ self_base->get_tx_subtree()->create<double>("gains/"+name+"/value") - .coerce(boost::bind(&wbx_base::wbx_version4::set_tx_gain, this, _1, name)) + .set_coercer(boost::bind(&wbx_base::wbx_version4::set_tx_gain, this, _1, name)) .set(wbx_v4_tx_gain_ranges[name].start()); self_base->get_tx_subtree()->create<meta_range_t>("gains/"+name+"/range") .set(wbx_v4_tx_gain_ranges[name]); } this->get_tx_subtree()->create<double>("freq/value") - .coerce(boost::bind(&wbx_base::wbx_version4::set_lo_freq, this, dboard_iface::UNIT_TX, _1)) + .set_coercer(boost::bind(&wbx_base::wbx_version4::set_lo_freq, this, dboard_iface::UNIT_TX, _1)) .set((wbx_v4_freq_range.start() + wbx_v4_freq_range.stop())/2.0); this->get_tx_subtree()->create<meta_range_t>("freq/range").set(wbx_v4_freq_range); this->get_tx_subtree()->create<bool>("enabled") - .subscribe(boost::bind(&wbx_base::wbx_version4::set_tx_enabled, this, _1)) + .add_coerced_subscriber(boost::bind(&wbx_base::wbx_version4::set_tx_enabled, this, _1)) .set(true); //start enabled //set attenuator control bits @@ -136,29 +136,29 @@ wbx_base::wbx_version4::wbx_version4(wbx_base *_self_wbx_base) { //between bursts) set TX gain iobits to min gain (max attenuation) when //RX_ONLY or IDLE to suppress LO leakage self_base->get_iface()->set_atr_reg(dboard_iface::UNIT_TX, \ - dboard_iface::ATR_REG_IDLE, v4_tx_mod, \ + gpio_atr::ATR_REG_IDLE, v4_tx_mod, \ TX_ATTN_MASK | TX_MIXER_DIS | v4_tx_mod); self_base->get_iface()->set_atr_reg(dboard_iface::UNIT_TX, \ - dboard_iface::ATR_REG_RX_ONLY, v4_tx_mod, \ + gpio_atr::ATR_REG_RX_ONLY, v4_tx_mod, \ TX_ATTN_MASK | TX_MIXER_DIS | v4_tx_mod); self_base->get_iface()->set_atr_reg(dboard_iface::UNIT_TX, \ - dboard_iface::ATR_REG_TX_ONLY, v4_tx_mod, \ + gpio_atr::ATR_REG_TX_ONLY, v4_tx_mod, \ TX_ATTN_MASK | TX_MIXER_DIS | v4_tx_mod); self_base->get_iface()->set_atr_reg(dboard_iface::UNIT_TX, \ - dboard_iface::ATR_REG_FULL_DUPLEX, v4_tx_mod, \ + gpio_atr::ATR_REG_FULL_DUPLEX, v4_tx_mod, \ TX_ATTN_MASK | TX_MIXER_DIS | v4_tx_mod); self_base->get_iface()->set_atr_reg(dboard_iface::UNIT_RX, \ - dboard_iface::ATR_REG_IDLE, \ + gpio_atr::ATR_REG_IDLE, \ RX_MIXER_ENB, RX_MIXER_DIS | RX_MIXER_ENB); self_base->get_iface()->set_atr_reg(dboard_iface::UNIT_RX, \ - dboard_iface::ATR_REG_TX_ONLY, \ + gpio_atr::ATR_REG_TX_ONLY, \ RX_MIXER_ENB, RX_MIXER_DIS | RX_MIXER_ENB); self_base->get_iface()->set_atr_reg(dboard_iface::UNIT_RX, \ - dboard_iface::ATR_REG_RX_ONLY, \ + gpio_atr::ATR_REG_RX_ONLY, \ RX_MIXER_ENB, RX_MIXER_DIS | RX_MIXER_ENB); self_base->get_iface()->set_atr_reg(dboard_iface::UNIT_RX, \ - dboard_iface::ATR_REG_FULL_DUPLEX, \ + gpio_atr::ATR_REG_FULL_DUPLEX, \ RX_MIXER_ENB, RX_MIXER_DIS | RX_MIXER_ENB); } @@ -188,8 +188,8 @@ double wbx_base::wbx_version4::set_tx_gain(double gain, const std::string &name) //write the new gain to tx gpio outputs //Update ATR with gain io_bits, only update for TX_ONLY and FULL_DUPLEX ATR states - self_base->get_iface()->set_atr_reg(dboard_iface::UNIT_TX, dboard_iface::ATR_REG_TX_ONLY, io_bits, TX_ATTN_MASK); - self_base->get_iface()->set_atr_reg(dboard_iface::UNIT_TX, dboard_iface::ATR_REG_FULL_DUPLEX, io_bits, TX_ATTN_MASK); + self_base->get_iface()->set_atr_reg(dboard_iface::UNIT_TX, gpio_atr::ATR_REG_TX_ONLY, io_bits, TX_ATTN_MASK); + self_base->get_iface()->set_atr_reg(dboard_iface::UNIT_TX, gpio_atr::ATR_REG_FULL_DUPLEX, io_bits, TX_ATTN_MASK); } else UHD_THROW_INVALID_CODE_PATH(); @@ -217,116 +217,46 @@ double wbx_base::wbx_version4::set_lo_freq(dboard_iface::unit_t unit, double tar : self_base->get_tx_subtree(); device_addr_t tune_args = subtree->access<device_addr_t>("tune_args").get(); bool is_int_n = boost::iequals(tune_args.get("mode_n",""), "integer"); + double reference_freq = self_base->get_iface()->get_clock_rate(unit); - //map prescaler setting to mininmum integer divider (N) values (pg.18 prescaler) - static const uhd::dict<int, int> prescaler_to_min_int_div = map_list_of - (adf4351_regs_t::PRESCALER_4_5, 23) - (adf4351_regs_t::PRESCALER_8_9, 75) - ; - - //map rf divider select output dividers to enums - static const uhd::dict<int, adf4351_regs_t::rf_divider_select_t> rfdivsel_to_enum = map_list_of - (1, adf4351_regs_t::RF_DIVIDER_SELECT_DIV1) - (2, adf4351_regs_t::RF_DIVIDER_SELECT_DIV2) - (4, adf4351_regs_t::RF_DIVIDER_SELECT_DIV4) - (8, adf4351_regs_t::RF_DIVIDER_SELECT_DIV8) - (16, adf4351_regs_t::RF_DIVIDER_SELECT_DIV16) - (32, adf4351_regs_t::RF_DIVIDER_SELECT_DIV32) - (64, adf4351_regs_t::RF_DIVIDER_SELECT_DIV64) - ; + //Select the LO + adf435x_iface::sptr& lo_iface = unit == dboard_iface::UNIT_RX ? _rxlo : _txlo; + lo_iface->set_reference_freq(reference_freq); - double reference_freq = self_base->get_iface()->get_clock_rate(unit); //The mixer has a divide-by-2 stage on the LO port so the synthesizer //frequency must 2x the target frequency. This introduces a 180 degree phase //ambiguity when trying to synchronize the phase of multiple boards. double synth_target_freq = target_freq * 2; - adf4351_regs_t::prescaler_t prescaler = - synth_target_freq > 3.6e9 ? adf4351_regs_t::PRESCALER_8_9 : adf4351_regs_t::PRESCALER_4_5; + //Use 8/9 prescaler for vco_freq > 3 GHz (pg.18 prescaler) + lo_iface->set_prescaler(synth_target_freq > 3.6e9 ? + adf435x_iface::PRESCALER_8_9 : adf435x_iface::PRESCALER_4_5); - adf435x_tuning_constraints tuning_constraints; - tuning_constraints.force_frac0 = is_int_n; - tuning_constraints.band_sel_freq_max = 100e3; - tuning_constraints.ref_doubler_threshold = 12.5e6; - tuning_constraints.int_range = uhd::range_t(prescaler_to_min_int_div[prescaler], 4095); - tuning_constraints.pfd_freq_max = 25e6; - tuning_constraints.rf_divider_range = uhd::range_t(1, 64); //The feedback of the divided frequency must be disabled whenever the target frequency //divided by the minimum PFD frequency cannot meet the minimum integer divider (N) value. //If it is disabled, additional phase ambiguity will be introduced. With a minimum PFD //frequency of 10 MHz, synthesizer frequencies below 230 MHz (LO frequencies below 115 MHz) //will have too much ambiguity to synchronize. - tuning_constraints.feedback_after_divider = - (int(synth_target_freq / 10e6) >= prescaler_to_min_int_div[prescaler]); + lo_iface->set_feedback_select( + (int(synth_target_freq / 10e6) >= lo_iface->get_int_range().start() ? + adf435x_iface::FB_SEL_DIVIDED : adf435x_iface::FB_SEL_FUNDAMENTAL)); - double synth_actual_freq = 0; - adf435x_tuning_settings tuning_settings = tune_adf435x_synth( - synth_target_freq, reference_freq, tuning_constraints, synth_actual_freq); + double synth_actual_freq = lo_iface->set_frequency(synth_target_freq, is_int_n); //The mixer has a divide-by-2 stage on the LO port so the synthesizer //actual_freq must /2 the synth_actual_freq double actual_freq = synth_actual_freq / 2; - //load the register values - adf4351_regs_t regs; - - if (unit == dboard_iface::UNIT_RX) - regs.output_power = (actual_freq == wbx_rx_lo_5dbm.clip(actual_freq)) ? adf4351_regs_t::OUTPUT_POWER_5DBM - : adf4351_regs_t::OUTPUT_POWER_2DBM; - else - regs.output_power = (actual_freq == wbx_tx_lo_5dbm.clip(actual_freq)) ? adf4351_regs_t::OUTPUT_POWER_5DBM - : adf4351_regs_t::OUTPUT_POWER_M1DBM; - - regs.frac_12_bit = tuning_settings.frac_12_bit; - regs.int_16_bit = tuning_settings.int_16_bit; - regs.mod_12_bit = tuning_settings.mod_12_bit; - regs.clock_divider_12_bit = tuning_settings.clock_divider_12_bit; - regs.feedback_select = tuning_constraints.feedback_after_divider ? - adf4351_regs_t::FEEDBACK_SELECT_DIVIDED : - adf4351_regs_t::FEEDBACK_SELECT_FUNDAMENTAL; - regs.clock_div_mode = tuning_constraints.feedback_after_divider ? - adf4351_regs_t::CLOCK_DIV_MODE_RESYNC_ENABLE : - adf4351_regs_t::CLOCK_DIV_MODE_FAST_LOCK; - regs.prescaler = prescaler; - regs.r_counter_10_bit = tuning_settings.r_counter_10_bit; - regs.reference_divide_by_2 = tuning_settings.r_divide_by_2_en ? - adf4351_regs_t::REFERENCE_DIVIDE_BY_2_ENABLED : - adf4351_regs_t::REFERENCE_DIVIDE_BY_2_DISABLED; - regs.reference_doubler = tuning_settings.r_doubler_en ? - adf4351_regs_t::REFERENCE_DOUBLER_ENABLED : - adf4351_regs_t::REFERENCE_DOUBLER_DISABLED; - regs.band_select_clock_div = tuning_settings.band_select_clock_div; - UHD_ASSERT_THROW(rfdivsel_to_enum.has_key(tuning_settings.rf_divider)); - regs.rf_divider_select = rfdivsel_to_enum[tuning_settings.rf_divider]; - regs.ldf = is_int_n ? - adf4351_regs_t::LDF_INT_N : - adf4351_regs_t::LDF_FRAC_N; - - //reset the N and R counter - regs.counter_reset = adf4351_regs_t::COUNTER_RESET_ENABLED; - self_base->get_iface()->write_spi(unit, spi_config_t::EDGE_RISE, regs.get_reg(2), 32); - regs.counter_reset = adf4351_regs_t::COUNTER_RESET_DISABLED; - - //write the registers - //correct power-up sequence to write registers (5, 4, 3, 2, 1, 0) - int addr; - - boost::uint16_t rx_id = self_base->get_rx_id().to_uint16(); - std::string board_name = (rx_id == 0x0081) ? "WBX-120" : "WBX"; - for(addr=5; addr>=0; addr--){ - UHD_LOGV(often) << boost::format( - "%s SPI Reg (0x%02x): 0x%08x" - ) % board_name.c_str() % addr % regs.get_reg(addr) << std::endl; - self_base->get_iface()->write_spi( - unit, spi_config_t::EDGE_RISE, - regs.get_reg(addr), 32 - ); + if (unit == dboard_iface::UNIT_RX) { + lo_iface->set_output_power((actual_freq == wbx_rx_lo_5dbm.clip(actual_freq)) ? + adf435x_iface::OUTPUT_POWER_5DBM : adf435x_iface::OUTPUT_POWER_2DBM); + } else { + lo_iface->set_output_power((actual_freq == wbx_tx_lo_5dbm.clip(actual_freq)) ? + adf435x_iface::OUTPUT_POWER_5DBM : adf435x_iface::OUTPUT_POWER_M1DBM); } - //return the actual frequency - UHD_LOGV(often) << boost::format( - "%s tune: actual frequency %f MHz" - ) % board_name.c_str() % (actual_freq/1e6) << std::endl; + //Write to hardware + lo_iface->commit(); return actual_freq; } diff --git a/host/lib/usrp/dboard/db_xcvr2450.cpp b/host/lib/usrp/dboard/db_xcvr2450.cpp index 50c67991a..4a3f69f69 100644 --- a/host/lib/usrp/dboard/db_xcvr2450.cpp +++ b/host/lib/usrp/dboard/db_xcvr2450.cpp @@ -112,7 +112,7 @@ static const uhd::dict<std::string, gain_range_t> xcvr_rx_gain_ranges = map_list class xcvr2450 : public xcvr_dboard_base{ public: xcvr2450(ctor_args_t args); - ~xcvr2450(void); + virtual ~xcvr2450(void); private: double _lo_freq; @@ -231,23 +231,23 @@ xcvr2450::xcvr2450(ctor_args_t args) : xcvr_dboard_base(args){ this->get_rx_subtree()->create<std::string>("name") .set("XCVR2450 RX"); this->get_rx_subtree()->create<sensor_value_t>("sensors/lo_locked") - .publish(boost::bind(&xcvr2450::get_locked, this)); + .set_publisher(boost::bind(&xcvr2450::get_locked, this)); this->get_rx_subtree()->create<sensor_value_t>("sensors/rssi") - .publish(boost::bind(&xcvr2450::get_rssi, this)); + .set_publisher(boost::bind(&xcvr2450::get_rssi, this)); BOOST_FOREACH(const std::string &name, xcvr_rx_gain_ranges.keys()){ this->get_rx_subtree()->create<double>("gains/"+name+"/value") - .coerce(boost::bind(&xcvr2450::set_rx_gain, this, _1, name)) + .set_coercer(boost::bind(&xcvr2450::set_rx_gain, this, _1, name)) .set(xcvr_rx_gain_ranges[name].start()); this->get_rx_subtree()->create<meta_range_t>("gains/"+name+"/range") .set(xcvr_rx_gain_ranges[name]); } this->get_rx_subtree()->create<double>("freq/value") - .coerce(boost::bind(&xcvr2450::set_lo_freq, this, _1)) + .set_coercer(boost::bind(&xcvr2450::set_lo_freq, this, _1)) .set(double(2.45e9)); this->get_rx_subtree()->create<meta_range_t>("freq/range") .set(xcvr_freq_range); this->get_rx_subtree()->create<std::string>("antenna/value") - .subscribe(boost::bind(&xcvr2450::set_rx_ant, this, _1)) + .add_coerced_subscriber(boost::bind(&xcvr2450::set_rx_ant, this, _1)) .set(xcvr_antennas.at(0)); this->get_rx_subtree()->create<std::vector<std::string> >("antenna/options") .set(xcvr_antennas); @@ -258,7 +258,7 @@ xcvr2450::xcvr2450(ctor_args_t args) : xcvr_dboard_base(args){ this->get_rx_subtree()->create<bool>("use_lo_offset") .set(false); this->get_rx_subtree()->create<double>("bandwidth/value") - .coerce(boost::bind(&xcvr2450::set_rx_bandwidth, this, _1)) //complex bandpass bandwidth + .set_coercer(boost::bind(&xcvr2450::set_rx_bandwidth, this, _1)) //complex bandpass bandwidth .set(2.0*_rx_bandwidth); //_rx_bandwidth in lowpass, convert to complex bandpass this->get_rx_subtree()->create<meta_range_t>("bandwidth/range") .set(xcvr_rx_bandwidth_range); @@ -269,21 +269,21 @@ xcvr2450::xcvr2450(ctor_args_t args) : xcvr_dboard_base(args){ this->get_tx_subtree()->create<std::string>("name") .set("XCVR2450 TX"); this->get_tx_subtree()->create<sensor_value_t>("sensors/lo_locked") - .publish(boost::bind(&xcvr2450::get_locked, this)); + .set_publisher(boost::bind(&xcvr2450::get_locked, this)); BOOST_FOREACH(const std::string &name, xcvr_tx_gain_ranges.keys()){ this->get_tx_subtree()->create<double>("gains/"+name+"/value") - .coerce(boost::bind(&xcvr2450::set_tx_gain, this, _1, name)) + .set_coercer(boost::bind(&xcvr2450::set_tx_gain, this, _1, name)) .set(xcvr_tx_gain_ranges[name].start()); this->get_tx_subtree()->create<meta_range_t>("gains/"+name+"/range") .set(xcvr_tx_gain_ranges[name]); } this->get_tx_subtree()->create<double>("freq/value") - .coerce(boost::bind(&xcvr2450::set_lo_freq, this, _1)) + .set_coercer(boost::bind(&xcvr2450::set_lo_freq, this, _1)) .set(double(2.45e9)); this->get_tx_subtree()->create<meta_range_t>("freq/range") .set(xcvr_freq_range); this->get_tx_subtree()->create<std::string>("antenna/value") - .subscribe(boost::bind(&xcvr2450::set_tx_ant, this, _1)) + .add_coerced_subscriber(boost::bind(&xcvr2450::set_tx_ant, this, _1)) .set(xcvr_antennas.at(1)); this->get_tx_subtree()->create<std::vector<std::string> >("antenna/options") .set(xcvr_antennas); @@ -294,7 +294,7 @@ xcvr2450::xcvr2450(ctor_args_t args) : xcvr_dboard_base(args){ this->get_tx_subtree()->create<bool>("use_lo_offset") .set(false); this->get_tx_subtree()->create<double>("bandwidth/value") - .coerce(boost::bind(&xcvr2450::set_tx_bandwidth, this, _1)) //complex bandpass bandwidth + .set_coercer(boost::bind(&xcvr2450::set_tx_bandwidth, this, _1)) //complex bandpass bandwidth .set(2.0*_tx_bandwidth); //_tx_bandwidth in lowpass, convert to complex bandpass this->get_tx_subtree()->create<meta_range_t>("bandwidth/range") .set(xcvr_tx_bandwidth_range); @@ -315,12 +315,12 @@ xcvr2450::~xcvr2450(void){ void xcvr2450::spi_reset(void){ //spi reset mode: global enable = off, tx and rx enable = on - this->get_iface()->set_atr_reg(dboard_iface::UNIT_TX, dboard_iface::ATR_REG_IDLE, TX_ENB_TXIO); - this->get_iface()->set_atr_reg(dboard_iface::UNIT_RX, dboard_iface::ATR_REG_IDLE, RX_ENB_RXIO | POWER_DOWN_RXIO); + this->get_iface()->set_atr_reg(dboard_iface::UNIT_TX, gpio_atr::ATR_REG_IDLE, TX_ENB_TXIO); + this->get_iface()->set_atr_reg(dboard_iface::UNIT_RX, gpio_atr::ATR_REG_IDLE, RX_ENB_RXIO | POWER_DOWN_RXIO); boost::this_thread::sleep(boost::posix_time::milliseconds(10)); //take it back out of spi reset mode and wait a bit - this->get_iface()->set_atr_reg(dboard_iface::UNIT_RX, dboard_iface::ATR_REG_IDLE, RX_DIS_RXIO | POWER_UP_RXIO); + this->get_iface()->set_atr_reg(dboard_iface::UNIT_RX, gpio_atr::ATR_REG_IDLE, RX_DIS_RXIO | POWER_UP_RXIO); boost::this_thread::sleep(boost::posix_time::milliseconds(10)); } @@ -337,16 +337,16 @@ void xcvr2450::update_atr(void){ int ad9515div = (_ad9515div == 3)? AD9515DIV_3_TXIO : AD9515DIV_2_TXIO; //set the tx registers - this->get_iface()->set_atr_reg(dboard_iface::UNIT_TX, dboard_iface::ATR_REG_IDLE, band_sel | ad9515div | TX_DIS_TXIO); - this->get_iface()->set_atr_reg(dboard_iface::UNIT_TX, dboard_iface::ATR_REG_RX_ONLY, band_sel | ad9515div | TX_DIS_TXIO | rx_ant_sel); - this->get_iface()->set_atr_reg(dboard_iface::UNIT_TX, dboard_iface::ATR_REG_TX_ONLY, band_sel | ad9515div | TX_ENB_TXIO | tx_ant_sel); - this->get_iface()->set_atr_reg(dboard_iface::UNIT_TX, dboard_iface::ATR_REG_FULL_DUPLEX, band_sel | ad9515div | TX_ENB_TXIO | xx_ant_sel); + this->get_iface()->set_atr_reg(dboard_iface::UNIT_TX, gpio_atr::ATR_REG_IDLE, band_sel | ad9515div | TX_DIS_TXIO); + this->get_iface()->set_atr_reg(dboard_iface::UNIT_TX, gpio_atr::ATR_REG_RX_ONLY, band_sel | ad9515div | TX_DIS_TXIO | rx_ant_sel); + this->get_iface()->set_atr_reg(dboard_iface::UNIT_TX, gpio_atr::ATR_REG_TX_ONLY, band_sel | ad9515div | TX_ENB_TXIO | tx_ant_sel); + this->get_iface()->set_atr_reg(dboard_iface::UNIT_TX, gpio_atr::ATR_REG_FULL_DUPLEX, band_sel | ad9515div | TX_ENB_TXIO | xx_ant_sel); //set the rx registers - this->get_iface()->set_atr_reg(dboard_iface::UNIT_RX, dboard_iface::ATR_REG_IDLE, POWER_UP_RXIO | RX_DIS_RXIO); - this->get_iface()->set_atr_reg(dboard_iface::UNIT_RX, dboard_iface::ATR_REG_RX_ONLY, POWER_UP_RXIO | RX_ENB_RXIO); - this->get_iface()->set_atr_reg(dboard_iface::UNIT_RX, dboard_iface::ATR_REG_TX_ONLY, POWER_UP_RXIO | RX_DIS_RXIO); - this->get_iface()->set_atr_reg(dboard_iface::UNIT_RX, dboard_iface::ATR_REG_FULL_DUPLEX, POWER_UP_RXIO | RX_DIS_RXIO); + this->get_iface()->set_atr_reg(dboard_iface::UNIT_RX, gpio_atr::ATR_REG_IDLE, POWER_UP_RXIO | RX_DIS_RXIO); + this->get_iface()->set_atr_reg(dboard_iface::UNIT_RX, gpio_atr::ATR_REG_RX_ONLY, POWER_UP_RXIO | RX_ENB_RXIO); + this->get_iface()->set_atr_reg(dboard_iface::UNIT_RX, gpio_atr::ATR_REG_TX_ONLY, POWER_UP_RXIO | RX_DIS_RXIO); + this->get_iface()->set_atr_reg(dboard_iface::UNIT_RX, gpio_atr::ATR_REG_FULL_DUPLEX, POWER_UP_RXIO | RX_DIS_RXIO); } /*********************************************************************** diff --git a/host/lib/usrp/dboard/twinrx/table_to_cpp.py b/host/lib/usrp/dboard/twinrx/table_to_cpp.py new file mode 100644 index 000000000..ea0e3bf66 --- /dev/null +++ b/host/lib/usrp/dboard/twinrx/table_to_cpp.py @@ -0,0 +1,26 @@ +#!/usr/bin/python + +import sys + +if len(sys.argv) != 3: + print 'Usage: ' + sys.argv[0] + ' <table filename> <table name>' + sys.exit(1) + +with open(sys.argv[1], 'rb') as f: + print ('static const std::vector<twinrx_gain_config_t> ' + + sys.argv[2] + ' = boost::assign::list_of') + i = -1 + for line in f.readlines(): + if (i != -1): + row = line.strip().split(',') + print (' ( twinrx_gain_config_t( %s, %6.1f, %s, %s, %s, %s ) )' % ( + str(i).rjust(5), + float(row[0].rjust(6)), + row[1].rjust(6), row[2].rjust(6), + ('true' if int(row[3]) == 1 else 'false').rjust(5), + ('true' if int(row[4]) == 1 else 'false').rjust(5))) + else: + print (' // %s, %s, %s, %s, %s, %s' % ( + 'Index', ' Gain', 'Atten1', 'Atten2', ' Amp1', ' Amp2')) + i += 1 + print ';' diff --git a/host/lib/usrp/dboard/twinrx/twinrx_ctrl.cpp b/host/lib/usrp/dboard/twinrx/twinrx_ctrl.cpp new file mode 100644 index 000000000..172992f0a --- /dev/null +++ b/host/lib/usrp/dboard/twinrx/twinrx_ctrl.cpp @@ -0,0 +1,643 @@ +// +// Copyright 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 +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +#include <boost/thread/thread.hpp> +#include <uhd/utils/math.hpp> +#include <uhd/utils/safe_call.hpp> +#include "twinrx_ctrl.hpp" +#include "adf435x.hpp" +#include "adf5355.hpp" + +using namespace uhd; +using namespace usrp; +using namespace dboard::twinrx; +typedef twinrx_cpld_regmap rm; + +static boost::uint32_t bool2bin(bool x) { return x ? 1 : 0; } + +static const double TWINRX_DESIRED_REFERENCE_FREQ = 50e6; + +class twinrx_ctrl_impl : public twinrx_ctrl { +public: + twinrx_ctrl_impl( + dboard_iface::sptr db_iface, + twinrx_gpio::sptr gpio_iface, + twinrx_cpld_regmap::sptr cpld_regmap + ) : _db_iface(db_iface), _gpio_iface(gpio_iface), _cpld_regs(cpld_regmap) + { + + //Turn on switcher and wait for power good + _gpio_iface->set_field(twinrx_gpio::FIELD_SWPS_EN, 1); + size_t timeout_ms = 100; + while (_gpio_iface->get_field(twinrx_gpio::FIELD_SWPS_PWR_GOOD) == 0) { + boost::this_thread::sleep(boost::posix_time::microsec(1000)); + if (--timeout_ms == 0) { + throw uhd::runtime_error("power supply failure"); + } + } + //Initialize synthesizer objects + _lo1_iface[size_t(CH1)] = adf5355_iface::make( + boost::bind(&twinrx_ctrl_impl::_write_lo_spi, this, dboard_iface::UNIT_TX, _1)); + _lo1_iface[size_t(CH2)] = adf5355_iface::make( + boost::bind(&twinrx_ctrl_impl::_write_lo_spi, this, dboard_iface::UNIT_TX, _1)); + + _lo2_iface[size_t(CH1)] = adf435x_iface::make_adf4351( + boost::bind(&twinrx_ctrl_impl::_write_lo_spi, this, dboard_iface::UNIT_RX, _1)); + _lo2_iface[size_t(CH2)] = adf435x_iface::make_adf4351( + boost::bind(&twinrx_ctrl_impl::_write_lo_spi, this, dboard_iface::UNIT_RX, _1)); + + // Assert synthesizer chip enables + _gpio_iface->set_field(twinrx_gpio::FIELD_LO1_CE_CH1, 1); + _gpio_iface->set_field(twinrx_gpio::FIELD_LO1_CE_CH2, 1); + _gpio_iface->set_field(twinrx_gpio::FIELD_LO2_CE_CH1, 1); + _gpio_iface->set_field(twinrx_gpio::FIELD_LO2_CE_CH2, 1); + + //Initialize default state + set_chan_enabled(BOTH, false, false); + set_preamp1(BOTH, PREAMP_BYPASS, false); + set_preamp2(BOTH, false, false); + set_lb_preamp_preselector(BOTH, false, false); + set_signal_path(BOTH, PATH_LOWBAND, false); + set_lb_preselector(BOTH, PRESEL_PATH3, false); + set_hb_preselector(BOTH, PRESEL_PATH1, false); + set_input_atten(BOTH, 31, false); + set_lb_atten(BOTH, 31, false); + set_hb_atten(BOTH, 31, false); + set_lo1_source(BOTH, LO_INTERNAL, false); + set_lo2_source(BOTH, LO_INTERNAL, false); + set_lo1_export_source(LO_EXPORT_DISABLED, false); + set_lo2_export_source(LO_EXPORT_DISABLED, false); + set_antenna_mapping(ANTX_NATIVE, false); + set_crossover_cal_mode(CAL_DISABLED, false); + commit(); + + //Initialize clocks and LO + bool found_rate = false; + BOOST_FOREACH(double rate, _db_iface->get_clock_rates(dboard_iface::UNIT_TX)) { + found_rate |= uhd::math::frequencies_are_equal(rate, TWINRX_DESIRED_REFERENCE_FREQ); + } + BOOST_FOREACH(double rate, _db_iface->get_clock_rates(dboard_iface::UNIT_RX)) { + found_rate |= uhd::math::frequencies_are_equal(rate, TWINRX_DESIRED_REFERENCE_FREQ); + } + if (not found_rate) { + throw uhd::runtime_error("TwinRX not supported on this motherboard"); + } + _db_iface->set_clock_rate(dboard_iface::UNIT_TX, TWINRX_DESIRED_REFERENCE_FREQ); + _db_iface->set_clock_rate(dboard_iface::UNIT_RX, TWINRX_DESIRED_REFERENCE_FREQ); + + _db_iface->set_clock_enabled(dboard_iface::UNIT_TX, true); + _db_iface->set_clock_enabled(dboard_iface::UNIT_RX, true); + for (size_t i = 0; i < NUM_CHANS; i++) { + _config_lo1_route(i==0?LO_CONFIG_CH1:LO_CONFIG_CH2); + _config_lo2_route(i==0?LO_CONFIG_CH1:LO_CONFIG_CH2); + _lo1_iface[i]->set_output_power(adf5355_iface::OUTPUT_POWER_5DBM); + _lo1_iface[i]->set_reference_freq(TWINRX_DESIRED_REFERENCE_FREQ); + _lo1_iface[i]->set_muxout_mode(adf5355_iface::MUXOUT_DLD); + _lo1_iface[i]->set_frequency(3e9, 1.0e3); + _lo2_iface[i]->set_feedback_select(adf435x_iface::FB_SEL_DIVIDED); + _lo2_iface[i]->set_output_power(adf435x_iface::OUTPUT_POWER_5DBM); + _lo2_iface[i]->set_reference_freq(TWINRX_DESIRED_REFERENCE_FREQ); + _lo2_iface[i]->set_muxout_mode(adf435x_iface::MUXOUT_DLD); + _lo1_iface[i]->commit(); + _lo2_iface[i]->commit(); + } + _config_lo1_route(LO_CONFIG_NONE); + _config_lo2_route(LO_CONFIG_NONE); + } + + ~twinrx_ctrl_impl() + { + UHD_SAFE_CALL( + boost::lock_guard<boost::mutex> lock(_mutex); + _gpio_iface->set_field(twinrx_gpio::FIELD_SWPS_EN, 0); + ) + } + + void commit() + { + boost::lock_guard<boost::mutex> lock(_mutex); + _commit(); + } + + void set_chan_enabled(channel_t ch, bool enabled, bool commit = true) + { + boost::lock_guard<boost::mutex> lock(_mutex); + if (ch == CH1 or ch == BOTH) { + _cpld_regs->rf1_reg1.set(rm::rf1_reg1_t::AMP_LO1_EN_CH1, bool2bin(enabled)); + _cpld_regs->if0_reg3.set(rm::if0_reg3_t::IF1_IF2_EN_CH1, bool2bin(enabled)); + _cpld_regs->if0_reg0.set(rm::if0_reg0_t::AMP_LO2_EN_CH1, bool2bin(enabled)); + } + if (ch == CH2 or ch == BOTH) { + _cpld_regs->rf1_reg5.set(rm::rf1_reg5_t::AMP_LO1_EN_CH2, bool2bin(enabled)); + _cpld_regs->if0_reg4.set(rm::if0_reg4_t::IF1_IF2_EN_CH2, bool2bin(enabled)); + _cpld_regs->if0_reg0.set(rm::if0_reg0_t::AMP_LO2_EN_CH2, bool2bin(enabled)); + } + if (commit) _commit(); + } + + void set_preamp1(channel_t ch, preamp_state_t value, bool commit = true) + { + boost::lock_guard<boost::mutex> lock(_mutex); + if (ch == CH1 or ch == BOTH) { + _cpld_regs->rf0_reg1.set(rm::rf0_reg1_t::SWPA1_CTL_CH1, bool2bin(value==PREAMP_HIGHBAND)); + _cpld_regs->rf2_reg2.set(rm::rf2_reg2_t::SWPA2_CTRL_CH1, bool2bin(value==PREAMP_BYPASS)); + _cpld_regs->rf0_reg1.set(rm::rf0_reg1_t::HB_PREAMP_EN_CH1, bool2bin(value==PREAMP_HIGHBAND)); + _cpld_regs->rf0_reg1.set(rm::rf0_reg1_t::LB_PREAMP_EN_CH1, bool2bin(value==PREAMP_LOWBAND)); + } + if (ch == CH2 or ch == BOTH) { + _cpld_regs->rf0_reg7.set(rm::rf0_reg7_t::SWPA1_CTRL_CH2, bool2bin(value==PREAMP_HIGHBAND)); + _cpld_regs->rf2_reg5.set(rm::rf2_reg5_t::SWPA2_CTRL_CH2, bool2bin(value==PREAMP_BYPASS)); + _cpld_regs->rf0_reg5.set(rm::rf0_reg5_t::HB_PREAMP_EN_CH2, bool2bin(value==PREAMP_HIGHBAND)); + _cpld_regs->rf2_reg6.set(rm::rf2_reg6_t::LB_PREAMP_EN_CH2, bool2bin(value==PREAMP_LOWBAND)); + } + if (commit) _commit(); + } + + void set_preamp2(channel_t ch, bool enabled, bool commit = true) + { + boost::lock_guard<boost::mutex> lock(_mutex); + if (ch == CH1 or ch == BOTH) { + _cpld_regs->rf2_reg7.set(rm::rf2_reg7_t::SWPA4_CTRL_CH1, bool2bin(not enabled)); + _cpld_regs->rf2_reg3.set(rm::rf2_reg3_t::PREAMP2_EN_CH1, bool2bin(enabled)); + } + if (ch == CH2 or ch == BOTH) { + _cpld_regs->rf0_reg6.set(rm::rf0_reg6_t::SWPA4_CTRL_CH2, bool2bin(not enabled)); + _cpld_regs->rf1_reg6.set(rm::rf1_reg6_t::PREAMP2_EN_CH2, bool2bin(enabled)); + } + if (commit) _commit(); + } + + void set_lb_preamp_preselector(channel_t ch, bool enabled, bool commit = true) + { + boost::lock_guard<boost::mutex> lock(_mutex); + if (ch == CH1 or ch == BOTH) { + _cpld_regs->rf0_reg7.set(rm::rf0_reg7_t::SWPA3_CTRL_CH1, bool2bin(not enabled)); + } + if (ch == CH2 or ch == BOTH) { + _cpld_regs->rf0_reg1.set(rm::rf0_reg1_t::SWPA3_CTRL_CH2, bool2bin(not enabled)); + } + if (commit) _commit(); + } + + void set_signal_path(channel_t ch, signal_path_t path, bool commit = true) + { + boost::lock_guard<boost::mutex> lock(_mutex); + if (ch == CH1 or ch == BOTH) { + _cpld_regs->rf2_reg2.set(rm::rf2_reg2_t::SW11_CTRL_CH1, bool2bin(path==PATH_LOWBAND)); + _cpld_regs->rf1_reg2.set(rm::rf1_reg2_t::SW12_CTRL_CH1, bool2bin(path==PATH_LOWBAND)); + _cpld_regs->rf1_reg6.set(rm::rf1_reg6_t::HB_PRESEL_PGA_EN_CH1, bool2bin(path==PATH_HIGHBAND)); + _cpld_regs->rf0_reg2.set(rm::rf0_reg2_t::SW6_CTRL_CH1, bool2bin(path==PATH_LOWBAND)); + _cpld_regs->if0_reg3.set(rm::if0_reg3_t::SW13_CTRL_CH1, bool2bin(path==PATH_LOWBAND)); + _cpld_regs->if0_reg2.set(rm::if0_reg2_t::AMP_LB_IF1_EN_CH1, bool2bin(path==PATH_LOWBAND)); + _cpld_regs->if0_reg0.set(rm::if0_reg0_t::AMP_HB_IF1_EN_CH1, bool2bin(path==PATH_HIGHBAND)); + _cpld_regs->rf1_reg2.set(rm::rf1_reg2_t::AMP_HB_EN_CH1, bool2bin(path==PATH_HIGHBAND)); + _cpld_regs->rf2_reg2.set(rm::rf2_reg2_t::AMP_LB_EN_CH1, bool2bin(path==PATH_LOWBAND)); + } + if (ch == CH2 or ch == BOTH) { + _cpld_regs->rf2_reg7.set(rm::rf2_reg7_t::SW11_CTRL_CH2, bool2bin(path==PATH_LOWBAND)); + _cpld_regs->rf1_reg7.set(rm::rf1_reg7_t::SW12_CTRL_CH2, bool2bin(path==PATH_LOWBAND)); + _cpld_regs->rf1_reg2.set(rm::rf1_reg2_t::HB_PRESEL_PGA_EN_CH2, bool2bin(path==PATH_HIGHBAND)); + _cpld_regs->rf0_reg6.set(rm::rf0_reg6_t::SW6_CTRL_CH2, bool2bin(path==PATH_HIGHBAND)); + _cpld_regs->if0_reg6.set(rm::if0_reg6_t::SW13_CTRL_CH2, bool2bin(path==PATH_HIGHBAND)); + _cpld_regs->if0_reg2.set(rm::if0_reg2_t::AMP_LB_IF1_EN_CH2, bool2bin(path==PATH_LOWBAND)); + _cpld_regs->if0_reg6.set(rm::if0_reg6_t::AMP_HB_IF1_EN_CH2, bool2bin(path==PATH_HIGHBAND)); + _cpld_regs->rf1_reg7.set(rm::rf1_reg7_t::AMP_HB_EN_CH2, bool2bin(path==PATH_HIGHBAND)); + _cpld_regs->rf2_reg7.set(rm::rf2_reg7_t::AMP_LB_EN_CH2, bool2bin(path==PATH_LOWBAND)); + } + if (commit) _commit(); + } + + void set_lb_preselector(channel_t ch, preselector_path_t path, bool commit = true) + { + boost::lock_guard<boost::mutex> lock(_mutex); + boost::uint32_t sw7val = 0, sw8val = 0; + switch (path) { + case PRESEL_PATH1: sw7val = 3; sw8val = 1; break; + case PRESEL_PATH2: sw7val = 2; sw8val = 0; break; + case PRESEL_PATH3: sw7val = 0; sw8val = 2; break; + case PRESEL_PATH4: sw7val = 1; sw8val = 3; break; + default: UHD_THROW_INVALID_CODE_PATH(); + } + if (ch == CH1 or ch == BOTH) { + _cpld_regs->rf0_reg3.set(rm::rf0_reg3_t::SW7_CTRL_CH1, sw7val); + _cpld_regs->rf2_reg3.set(rm::rf2_reg3_t::SW8_CTRL_CH1, sw8val); + } + if (ch == CH2 or ch == BOTH) { + _cpld_regs->rf0_reg7.set(rm::rf0_reg7_t::SW7_CTRL_CH2, sw7val); + _cpld_regs->rf2_reg7.set(rm::rf2_reg7_t::SW8_CTRL_CH2, sw8val); + } + if (commit) _commit(); + } + + void set_hb_preselector(channel_t ch, preselector_path_t path, bool commit = true) + { + boost::lock_guard<boost::mutex> lock(_mutex); + boost::uint32_t sw9ch1val = 0, sw10ch1val = 0, sw9ch2val = 0, sw10ch2val = 0; + switch (path) { + case PRESEL_PATH1: sw9ch1val = 3; sw10ch1val = 0; sw9ch2val = 0; sw10ch2val = 3; break; + case PRESEL_PATH2: sw9ch1val = 1; sw10ch1val = 2; sw9ch2val = 1; sw10ch2val = 1; break; + case PRESEL_PATH3: sw9ch1val = 2; sw10ch1val = 1; sw9ch2val = 2; sw10ch2val = 2; break; + case PRESEL_PATH4: sw9ch1val = 0; sw10ch1val = 3; sw9ch2val = 3; sw10ch2val = 0; break; + default: UHD_THROW_INVALID_CODE_PATH(); + } + if (ch == CH1 or ch == BOTH) { + _cpld_regs->rf0_reg5.set(rm::rf0_reg5_t::SW9_CTRL_CH1, sw9ch1val); + _cpld_regs->rf1_reg3.set(rm::rf1_reg3_t::SW10_CTRL_CH1, sw10ch1val); + } + if (ch == CH2 or ch == BOTH) { + _cpld_regs->rf0_reg3.set(rm::rf0_reg3_t::SW9_CTRL_CH2, sw9ch2val); + _cpld_regs->rf1_reg7.set(rm::rf1_reg7_t::SW10_CTRL_CH2, sw10ch2val); + } + if (commit) _commit(); + + } + + void set_input_atten(channel_t ch, boost::uint8_t atten, bool commit = true) + { + boost::lock_guard<boost::mutex> lock(_mutex); + if (ch == CH1 or ch == BOTH) { + _cpld_regs->rf0_reg0.set(rm::rf0_reg0_t::ATTEN_IN_CH1, atten&0x1F); + } + if (ch == CH2 or ch == BOTH) { + _cpld_regs->rf0_reg4.set(rm::rf0_reg4_t::ATTEN_IN_CH2, atten&0x1F); + } + if (commit) _commit(); + } + + void set_lb_atten(channel_t ch, boost::uint8_t atten, bool commit = true) + { + boost::lock_guard<boost::mutex> lock(_mutex); + if (ch == CH1 or ch == BOTH) { + _cpld_regs->rf2_reg0.set(rm::rf2_reg0_t::ATTEN_LB_CH1, atten&0x1F); + } + if (ch == CH2 or ch == BOTH) { + _cpld_regs->rf2_reg4.set(rm::rf2_reg4_t::ATTEN_LB_CH2, atten&0x1F); + } + if (commit) _commit(); + } + + void set_hb_atten(channel_t ch, boost::uint8_t atten, bool commit = true) + { + boost::lock_guard<boost::mutex> lock(_mutex); + if (ch == CH1 or ch == BOTH) { + _cpld_regs->rf1_reg0.set(rm::rf1_reg0_t::ATTEN_HB_CH1, atten&0x1F); + } + if (ch == CH2 or ch == BOTH) { + _cpld_regs->rf1_reg4.set(rm::rf1_reg4_t::ATTEN_HB_CH2, atten&0x1F); + } + if (commit) _commit(); + } + + void set_lo1_source(channel_t ch, lo_source_t source, bool commit = true) + { + boost::lock_guard<boost::mutex> lock(_mutex); + if (ch == CH1 or ch == BOTH) { + _cpld_regs->rf1_reg5.set(rm::rf1_reg5_t::SW14_CTRL_CH2, bool2bin(source!=LO_COMPANION)); + _cpld_regs->rf1_reg1.set(rm::rf1_reg1_t::SW15_CTRL_CH1, bool2bin(source==LO_EXTERNAL)); + _cpld_regs->rf1_reg1.set(rm::rf1_reg1_t::SW16_CTRL_CH1, bool2bin(source!=LO_INTERNAL)); + _lo1_src[size_t(CH1)] = source; + } + if (ch == CH2 or ch == BOTH) { + _cpld_regs->rf1_reg1.set(rm::rf1_reg1_t::SW14_CTRL_CH1, bool2bin(source==LO_COMPANION)); + _cpld_regs->rf1_reg5.set(rm::rf1_reg5_t::SW15_CTRL_CH2, bool2bin(source!=LO_INTERNAL)); + _cpld_regs->rf1_reg6.set(rm::rf1_reg6_t::SW16_CTRL_CH2, bool2bin(source==LO_INTERNAL)); + _lo1_src[size_t(CH2)] = source; + } + if (commit) _commit(); + } + + void set_lo2_source(channel_t ch, lo_source_t source, bool commit = true) + { + boost::lock_guard<boost::mutex> lock(_mutex); + if (ch == CH1 or ch == BOTH) { + _cpld_regs->if0_reg0.set(rm::if0_reg0_t::SW19_CTRL_CH2, bool2bin(source==LO_COMPANION)); + _cpld_regs->if0_reg1.set(rm::if0_reg1_t::SW20_CTRL_CH1, bool2bin(source==LO_COMPANION)); + _cpld_regs->if0_reg4.set(rm::if0_reg4_t::SW21_CTRL_CH1, bool2bin(source==LO_INTERNAL)); + _lo2_src[size_t(CH1)] = source; + } + if (ch == CH2 or ch == BOTH) { + _cpld_regs->if0_reg4.set(rm::if0_reg4_t::SW19_CTRL_CH1, bool2bin(source==LO_EXTERNAL)); + _cpld_regs->if0_reg0.set(rm::if0_reg0_t::SW20_CTRL_CH2, bool2bin(source==LO_INTERNAL||source==LO_DISABLED)); + _cpld_regs->if0_reg4.set(rm::if0_reg4_t::SW21_CTRL_CH2, bool2bin(source==LO_INTERNAL)); + _lo2_src[size_t(CH2)] = source; + } + if (commit) _commit(); + } + + void set_lo1_export_source(lo_export_source_t source, bool commit = true) + { + boost::lock_guard<boost::mutex> lock(_mutex); + //SW22 may conflict with the cal switch but this attr takes priority and we assume + //that the cal switch is disabled (by disabling it!) + _set_cal_mode(CAL_DISABLED, source); + _cpld_regs->rf1_reg3.set(rm::rf1_reg3_t::SW23_CTRL, bool2bin(source!=LO_CH1_SYNTH)); + _lo1_export = source; + + if (commit) _commit(); + } + + void set_lo2_export_source(lo_export_source_t source, bool commit = true) + { + boost::lock_guard<boost::mutex> lock(_mutex); + _cpld_regs->if0_reg7.set(rm::if0_reg7_t::SW24_CTRL_CH2, bool2bin(source==LO_CH2_SYNTH)); + _cpld_regs->if0_reg4.set(rm::if0_reg4_t::SW25_CTRL, bool2bin(source!=LO_CH1_SYNTH)); + _cpld_regs->if0_reg3.set(rm::if0_reg3_t::SW24_CTRL_CH1, bool2bin(source!=LO_CH1_SYNTH)); + _lo2_export = source; + + if (commit) _commit(); + } + + void set_antenna_mapping(antenna_mapping_t mapping, bool commit = true) + { + boost::lock_guard<boost::mutex> lock(_mutex); + + enum switch_path_t { CONNECT, TERM, EXPORT, IMPORT, SWAP }; + switch_path_t path1, path2; + + switch (mapping) { + case ANTX_NATIVE: + path1 = CONNECT; path2 = CONNECT; break; + case ANT1_SHARED: + path1 = EXPORT; path2 = IMPORT; break; + case ANT2_SHARED: + path1 = IMPORT; path2 = EXPORT; break; + case ANTX_SWAPPED: + path1 = SWAP; path2 = SWAP; break; + default: + path1 = TERM; path2 = TERM; break; + } + + _cpld_regs->rf0_reg5.set(rm::rf0_reg5_t::SW3_CTRL_CH1, bool2bin(path1==EXPORT||path1==SWAP)); + _cpld_regs->rf0_reg2.set(rm::rf0_reg2_t::SW4_CTRL_CH1, bool2bin(!(path1==IMPORT||path1==SWAP))); + _cpld_regs->rf0_reg2.set(rm::rf0_reg2_t::SW5_CTRL_CH1, bool2bin(path1==CONNECT)); + _cpld_regs->rf0_reg7.set(rm::rf0_reg7_t::SW3_CTRL_CH2, bool2bin(path2==EXPORT||path2==SWAP)); + _cpld_regs->rf0_reg6.set(rm::rf0_reg6_t::SW4_CTRL_CH2, bool2bin(path2==IMPORT||path2==SWAP)); + _cpld_regs->rf0_reg6.set(rm::rf0_reg6_t::SW5_CTRL_CH2, bool2bin(path2==CONNECT)); + + if (commit) _commit(); + } + + void set_crossover_cal_mode(cal_mode_t cal_mode, bool commit = true) + { + boost::lock_guard<boost::mutex> lock(_mutex); + if (_lo1_export == LO_CH1_SYNTH && cal_mode == CAL_CH2) { + throw uhd::runtime_error("cannot enable cal crossover on CH2 when LO1 in CH1 is exported"); + } + if (_lo1_export == LO_CH2_SYNTH && cal_mode == CAL_CH1) { + throw uhd::runtime_error("cannot enable cal crossover on CH1 when LO1 in CH2 is exported"); + } + _set_cal_mode(cal_mode, _lo1_export); + + if (commit) _commit(); + } + + double set_lo1_synth_freq(channel_t ch, double freq, bool commit = true) + { + boost::lock_guard<boost::mutex> lock(_mutex); + static const double RESOLUTION = 1e3; + + double coerced_freq = 0.0; + if (ch == CH1 or ch == BOTH) { + coerced_freq = _lo1_iface[size_t(CH1)]->set_frequency(freq, RESOLUTION, false); + _lo1_freq[size_t(CH1)] = tune_freq_t(freq); + } + if (ch == CH2 or ch == BOTH) { + coerced_freq = _lo1_iface[size_t(CH2)]->set_frequency(freq, RESOLUTION, false); + _lo1_freq[size_t(CH2)] = tune_freq_t(freq); + } + + if (commit) _commit(); + return coerced_freq; + } + + double set_lo2_synth_freq(channel_t ch, double freq, bool commit = true) + { + boost::lock_guard<boost::mutex> lock(_mutex); + static const double PRESCALER_THRESH = 3.6e9; + + double coerced_freq = 0.0; + if (ch == CH1 or ch == BOTH) { + _lo2_iface[size_t(CH1)]->set_prescaler(freq > PRESCALER_THRESH ? + adf435x_iface::PRESCALER_8_9 : adf435x_iface::PRESCALER_4_5); + coerced_freq = _lo2_iface[size_t(CH1)]->set_frequency(freq, false, false); + _lo2_freq[size_t(CH1)] = tune_freq_t(freq); + } + if (ch == CH2 or ch == BOTH) { + _lo2_iface[size_t(CH2)]->set_prescaler(freq > PRESCALER_THRESH ? + adf435x_iface::PRESCALER_8_9 : adf435x_iface::PRESCALER_4_5); + coerced_freq = _lo2_iface[size_t(CH2)]->set_frequency(freq, false, false); + _lo2_freq[size_t(CH2)] = tune_freq_t(freq); + } + + if (commit) _commit(); + return coerced_freq; + } + + bool read_lo1_locked(channel_t ch) + { + boost::lock_guard<boost::mutex> lock(_mutex); + + bool locked = true; + if (ch == CH1 or ch == BOTH) { + locked = locked && (_gpio_iface->get_field(twinrx_gpio::FIELD_LO1_MUXOUT_CH1) == 1); + } + if (ch == CH2 or ch == BOTH) { + locked = locked && (_gpio_iface->get_field(twinrx_gpio::FIELD_LO1_MUXOUT_CH2) == 1); + } + return locked; + } + + bool read_lo2_locked(channel_t ch) + { + boost::lock_guard<boost::mutex> lock(_mutex); + + bool locked = true; + if (ch == CH1 or ch == BOTH) { + locked = locked && (_gpio_iface->get_field(twinrx_gpio::FIELD_LO2_MUXOUT_CH1) == 1); + } + if (ch == CH2 or ch == BOTH) { + locked = locked && (_gpio_iface->get_field(twinrx_gpio::FIELD_LO2_MUXOUT_CH2) == 1); + } + return locked; + } + +private: //Functions + void _set_cal_mode(cal_mode_t cal_mode, lo_export_source_t lo1_export_src) + { + _cpld_regs->rf1_reg1.set(rm::rf1_reg1_t::SW17_CTRL_CH1, bool2bin(cal_mode!=CAL_CH1)); + _cpld_regs->rf1_reg6.set(rm::rf1_reg6_t::SW17_CTRL_CH2, bool2bin(cal_mode!=CAL_CH2)); + _cpld_regs->rf1_reg5.set(rm::rf1_reg5_t::SW18_CTRL_CH1, bool2bin(cal_mode!=CAL_CH1)); + _cpld_regs->rf2_reg3.set(rm::rf2_reg3_t::SW18_CTRL_CH2, bool2bin(cal_mode!=CAL_CH2)); + _cpld_regs->rf1_reg3.set(rm::rf1_reg3_t::SW22_CTRL_CH1, bool2bin((lo1_export_src!=LO_CH1_SYNTH)||(cal_mode==CAL_CH1))); + _cpld_regs->rf1_reg7.set(rm::rf1_reg7_t::SW22_CTRL_CH2, bool2bin((lo1_export_src!=LO_CH2_SYNTH)||(cal_mode==CAL_CH2))); + } + + void _config_lo1_route(lo_config_route_t source) + { + //Route SPI LEs through CPLD (will not assert them) + _cpld_regs->rf0_reg2.set(rm::rf0_reg2_t::LO1_LE_CH1, bool2bin(source==LO_CONFIG_CH1||source==LO_CONFIG_BOTH)); + _cpld_regs->rf0_reg2.set(rm::rf0_reg2_t::LO1_LE_CH2, bool2bin(source==LO_CONFIG_CH2||source==LO_CONFIG_BOTH)); + _cpld_regs->rf0_reg2.flush(); + } + + void _config_lo2_route(lo_config_route_t source) + { + //Route SPI LEs through CPLD (will not assert them) + _cpld_regs->if0_reg2.set(rm::if0_reg2_t::LO2_LE_CH1, bool2bin(source==LO_CONFIG_CH1||source==LO_CONFIG_BOTH)); + _cpld_regs->if0_reg2.set(rm::if0_reg2_t::LO2_LE_CH2, bool2bin(source==LO_CONFIG_CH2||source==LO_CONFIG_BOTH)); + _cpld_regs->if0_reg2.flush(); + } + + void _write_lo_spi(dboard_iface::unit_t unit, const std::vector<boost::uint32_t> ®s) + { + BOOST_FOREACH(boost::uint32_t reg, regs) { + spi_config_t spi_config = spi_config_t(spi_config_t::EDGE_RISE); + spi_config.use_custom_divider = true; + spi_config.divider = 67; + _db_iface->write_spi(unit, spi_config, reg, 32); + } + } + + void _commit() + { + //Commit everything except the LO synthesizers + _cpld_regs->flush(); + + // Disable unused LO synthesizers + _lo1_enable[size_t(CH1)] = _lo1_src[size_t(CH1)] == LO_INTERNAL || + _lo1_src[size_t(CH2)] == LO_COMPANION || + _lo1_export == LO_CH1_SYNTH; + + _lo1_enable[size_t(CH2)] = _lo1_src[size_t(CH2)] == LO_INTERNAL || + _lo1_src[size_t(CH1)] == LO_COMPANION || + _lo1_export == LO_CH2_SYNTH; + _lo2_enable[size_t(CH1)] = _lo2_src[size_t(CH1)] == LO_INTERNAL || + _lo2_src[size_t(CH2)] == LO_COMPANION || + _lo2_export == LO_CH1_SYNTH; + + _lo2_enable[size_t(CH2)] = _lo2_src[size_t(CH2)] == LO_INTERNAL || + _lo2_src[size_t(CH1)] == LO_COMPANION || + _lo2_export == LO_CH2_SYNTH; + + _lo1_iface[size_t(CH1)]->set_output_enable(adf5355_iface::RF_OUTPUT_A, _lo1_enable[size_t(CH1)].get()); + _lo1_iface[size_t(CH2)]->set_output_enable(adf5355_iface::RF_OUTPUT_A, _lo1_enable[size_t(CH2)].get()); + + _lo2_iface[size_t(CH1)]->set_output_enable(adf435x_iface::RF_OUTPUT_A, _lo2_enable[size_t(CH1)].get()); + _lo2_iface[size_t(CH2)]->set_output_enable(adf435x_iface::RF_OUTPUT_A, _lo2_enable[size_t(CH2)].get()); + + //Commit LO1 frequency + // Commit Channel 1's settings to both channels simultaneously if the frequency is the same. + bool simultaneous_commit_lo1 = _lo1_freq[size_t(CH1)].is_dirty() and + _lo1_freq[size_t(CH2)].is_dirty() and + _lo1_freq[size_t(CH1)].get() == _lo1_freq[size_t(CH2)].get() and + _lo1_enable[size_t(CH1)].get() == _lo1_enable[size_t(CH2)].get(); + + if (simultaneous_commit_lo1) { + _config_lo1_route(LO_CONFIG_BOTH); + //Only commit one of the channels. The route LO_CONFIG_BOTH + //will ensure that the LEs for both channels are enabled + _lo1_iface[size_t(CH1)]->commit(); + _lo1_freq[size_t(CH1)].mark_clean(); + _lo1_freq[size_t(CH2)].mark_clean(); + _lo1_enable[size_t(CH1)].mark_clean(); + _lo1_enable[size_t(CH2)].mark_clean(); + _config_lo1_route(LO_CONFIG_NONE); + } else { + if (_lo1_freq[size_t(CH1)].is_dirty() || _lo1_enable[size_t(CH1)].is_dirty()) { + _config_lo1_route(LO_CONFIG_CH1); + _lo1_iface[size_t(CH1)]->commit(); + _lo1_freq[size_t(CH1)].mark_clean(); + _lo1_enable[size_t(CH1)].mark_clean(); + _config_lo1_route(LO_CONFIG_NONE); + } + if (_lo1_freq[size_t(CH2)].is_dirty() || _lo1_enable[size_t(CH2)].is_dirty()) { + _config_lo1_route(LO_CONFIG_CH2); + _lo1_iface[size_t(CH2)]->commit(); + _lo1_freq[size_t(CH2)].mark_clean(); + _lo1_enable[size_t(CH2)].mark_clean(); + _config_lo1_route(LO_CONFIG_NONE); + } + } + + //Commit LO2 frequency + bool simultaneous_commit_lo2 = _lo2_freq[size_t(CH1)].is_dirty() and + _lo2_freq[size_t(CH2)].is_dirty() and + _lo2_freq[size_t(CH1)].get() == _lo2_freq[size_t(CH2)].get() and + _lo2_enable[size_t(CH1)].get() == _lo2_enable[size_t(CH2)].get(); + + if (simultaneous_commit_lo2) { + _config_lo2_route(LO_CONFIG_BOTH); + //Only commit one of the channels. The route LO_CONFIG_BOTH + //will ensure that the LEs for both channels are enabled + _lo2_iface[size_t(CH1)]->commit(); + _lo2_freq[size_t(CH1)].mark_clean(); + _lo2_freq[size_t(CH2)].mark_clean(); + _lo2_enable[size_t(CH1)].mark_clean(); + _lo2_enable[size_t(CH2)].mark_clean(); + _config_lo2_route(LO_CONFIG_NONE); + } else { + if (_lo2_freq[size_t(CH1)].is_dirty() || _lo2_enable[size_t(CH1)].is_dirty()) { + _config_lo2_route(LO_CONFIG_CH1); + _lo2_iface[size_t(CH1)]->commit(); + _lo2_freq[size_t(CH1)].mark_clean(); + _lo2_enable[size_t(CH1)].mark_clean(); + _config_lo2_route(LO_CONFIG_NONE); + } + if (_lo2_freq[size_t(CH2)].is_dirty() || _lo2_enable[size_t(CH2)].is_dirty()) { + _config_lo2_route(LO_CONFIG_CH2); + _lo2_iface[size_t(CH2)]->commit(); + _lo2_freq[size_t(CH2)].mark_clean(); + _lo2_enable[size_t(CH2)].mark_clean(); + _config_lo2_route(LO_CONFIG_NONE); + } + } + } + +private: //Members + static const size_t NUM_CHANS = 2; + + struct tune_freq_t : public uhd::math::fp_compare::fp_compare_delta<double> { + tune_freq_t() : uhd::math::fp_compare::fp_compare_delta<double>( + 0.0, uhd::math::FREQ_COMPARISON_DELTA_HZ) {} + + tune_freq_t(double freq) : uhd::math::fp_compare::fp_compare_delta<double>( + freq, uhd::math::FREQ_COMPARISON_DELTA_HZ) {} + }; + + boost::mutex _mutex; + dboard_iface::sptr _db_iface; + twinrx_gpio::sptr _gpio_iface; + twinrx_cpld_regmap::sptr _cpld_regs; + adf5355_iface::sptr _lo1_iface[NUM_CHANS]; + adf435x_iface::sptr _lo2_iface[NUM_CHANS]; + lo_source_t _lo1_src[NUM_CHANS]; + lo_source_t _lo2_src[NUM_CHANS]; + dirty_tracked<tune_freq_t> _lo1_freq[NUM_CHANS]; + dirty_tracked<tune_freq_t> _lo2_freq[NUM_CHANS]; + dirty_tracked<bool> _lo1_enable[NUM_CHANS]; + dirty_tracked<bool> _lo2_enable[NUM_CHANS]; + lo_export_source_t _lo1_export; + lo_export_source_t _lo2_export; +}; + +twinrx_ctrl::sptr twinrx_ctrl::make( + dboard_iface::sptr db_iface, + twinrx_gpio::sptr gpio_iface, + twinrx_cpld_regmap::sptr cpld_regmap +) { + return sptr(new twinrx_ctrl_impl(db_iface, gpio_iface, cpld_regmap)); +} diff --git a/host/lib/usrp/dboard/twinrx/twinrx_ctrl.hpp b/host/lib/usrp/dboard/twinrx/twinrx_ctrl.hpp new file mode 100644 index 000000000..521e27ae9 --- /dev/null +++ b/host/lib/usrp/dboard/twinrx/twinrx_ctrl.hpp @@ -0,0 +1,101 @@ +// +// Copyright 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 +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +#ifndef INCLUDED_DBOARD_TWINRX_CTRL_HPP +#define INCLUDED_DBOARD_TWINRX_CTRL_HPP + +#include <boost/noncopyable.hpp> +#include <uhd/types/wb_iface.hpp> +#include "twinrx_io.hpp" + +namespace uhd { namespace usrp { namespace dboard { namespace twinrx { + +class twinrx_ctrl : public boost::noncopyable { +public: + typedef boost::shared_ptr<twinrx_ctrl> sptr; + + static sptr make( + dboard_iface::sptr db_iface, + twinrx_gpio::sptr gpio_iface, + twinrx_cpld_regmap::sptr cpld_regmap); + + virtual ~twinrx_ctrl() {} + + enum channel_t { CH1 = 0, CH2 = 1, BOTH = 2}; + + enum preamp_state_t { PREAMP_LOWBAND, PREAMP_HIGHBAND, PREAMP_BYPASS }; + + enum signal_path_t { PATH_LOWBAND, PATH_HIGHBAND }; + + enum preselector_path_t { PRESEL_PATH1, PRESEL_PATH2, PRESEL_PATH3, PRESEL_PATH4 }; + + enum lo_source_t { LO_INTERNAL, LO_EXTERNAL, LO_COMPANION, LO_DISABLED }; + + enum lo_export_source_t { LO_CH1_SYNTH, LO_CH2_SYNTH, LO_EXPORT_DISABLED }; + + enum antenna_mapping_t { ANTX_NATIVE, ANT1_SHARED, ANT2_SHARED, ANTX_SWAPPED, ANTX_DISABLED }; + + enum lo_config_route_t { LO_CONFIG_CH1, LO_CONFIG_CH2, LO_CONFIG_BOTH, LO_CONFIG_NONE }; + + enum cal_mode_t { CAL_DISABLED, CAL_CH1, CAL_CH2 }; + + virtual void commit() = 0; + + virtual void set_chan_enabled(channel_t ch, bool enabled, bool commit = true) = 0; + + virtual void set_preamp1(channel_t ch, preamp_state_t value, bool commit = true) = 0; + + virtual void set_preamp2(channel_t ch, bool enabled, bool commit = true) = 0; + + virtual void set_lb_preamp_preselector(channel_t ch, bool enabled, bool commit = true) = 0; + + virtual void set_signal_path(channel_t ch, signal_path_t path, bool commit = true) = 0; + + virtual void set_lb_preselector(channel_t ch, preselector_path_t path, bool commit = true) = 0; + + virtual void set_hb_preselector(channel_t ch, preselector_path_t path, bool commit = true) = 0; + + virtual void set_input_atten(channel_t ch, boost::uint8_t atten, bool commit = true) = 0; + + virtual void set_lb_atten(channel_t ch, boost::uint8_t atten, bool commit = true) = 0; + + virtual void set_hb_atten(channel_t ch, boost::uint8_t atten, bool commit = true) = 0; + + virtual void set_lo1_source(channel_t ch, lo_source_t source, bool commit = true) = 0; + + virtual void set_lo2_source(channel_t ch, lo_source_t source, bool commit = true) = 0; + + virtual void set_lo1_export_source(lo_export_source_t source, bool commit = true) = 0; + + virtual void set_lo2_export_source(lo_export_source_t source, bool commit = true) = 0; + + virtual void set_antenna_mapping(antenna_mapping_t mapping, bool commit = true) = 0; + + virtual void set_crossover_cal_mode(cal_mode_t cal_mode, bool commit = true) = 0; + + virtual double set_lo1_synth_freq(channel_t ch, double freq, bool commit = true) = 0; + + virtual double set_lo2_synth_freq(channel_t ch, double freq, bool commit = true) = 0; + + virtual bool read_lo1_locked(channel_t ch) = 0; + + virtual bool read_lo2_locked(channel_t ch) = 0; +}; + +}}}} //namespaces + +#endif /* INCLUDED_DBOARD_TWINRX_CTRL_HPP */ diff --git a/host/lib/usrp/dboard/twinrx/twinrx_experts.cpp b/host/lib/usrp/dboard/twinrx/twinrx_experts.cpp new file mode 100644 index 000000000..ddaa4211e --- /dev/null +++ b/host/lib/usrp/dboard/twinrx/twinrx_experts.cpp @@ -0,0 +1,637 @@ +// +// Copyright 2016 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 +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +#include "twinrx_experts.hpp" +#include "twinrx_gain_tables.hpp" +#include <uhd/utils/math.hpp> +#include <uhd/exception.hpp> +#include <uhd/types/dict.hpp> +#include <uhd/types/ranges.hpp> +#include <uhd/types/dict.hpp> +#include <boost/assign/list_of.hpp> +#include <boost/math/special_functions/round.hpp> + +using namespace uhd::experts; +using namespace uhd::math; +using namespace uhd::usrp::dboard::twinrx; + +/*!--------------------------------------------------------- + * twinrx_freq_path_expert::resolve + * --------------------------------------------------------- + */ +void twinrx_freq_path_expert::resolve() +{ + //Lowband/highband switch point + static const double LB_HB_THRESHOLD_FREQ = 1.8e9; + static const double LB_TARGET_IF1_FREQ = 2.345e9; + static const double HB_TARGET_IF1_FREQ = 1.25e9; + static const double INJ_SIDE_THRESHOLD_FREQ = 5.1e9; + + static const double FIXED_LO1_THRESHOLD_FREQ= 50e6; + + //Preselector filter switch point + static const double LB_FILT1_THRESHOLD_FREQ = 0.5e9; + static const double LB_FILT2_THRESHOLD_FREQ = 0.8e9; + static const double LB_FILT3_THRESHOLD_FREQ = 1.2e9; + static const double LB_FILT4_THRESHOLD_FREQ = 1.8e9; + static const double HB_FILT1_THRESHOLD_FREQ = 3.0e9; + static const double HB_FILT2_THRESHOLD_FREQ = 4.1e9; + static const double HB_FILT3_THRESHOLD_FREQ = 5.1e9; + static const double HB_FILT4_THRESHOLD_FREQ = 6.0e9; + + static const double LB_PREAMP_PRESEL_THRESHOLD_FREQ = 0.8e9; + + //Misc + static const double INST_BANDWIDTH = 80e6; + static const double MANUAL_LO_HYSTERESIS_PPM = 1.0; + + static const freq_range_t FREQ_RANGE(10e6, 6e9); + rf_freq_abs_t rf_freq(FREQ_RANGE.clip(_rf_freq_d)); + + // Choose low-band vs high-band depending on frequency + _signal_path = (rf_freq > LB_HB_THRESHOLD_FREQ) ? + twinrx_ctrl::PATH_HIGHBAND : twinrx_ctrl::PATH_LOWBAND; + if (_signal_path == twinrx_ctrl::PATH_LOWBAND) { + // Choose low-band preselector filter + if (rf_freq < LB_FILT1_THRESHOLD_FREQ) { + _lb_presel = twinrx_ctrl::PRESEL_PATH1; + } else if (rf_freq < LB_FILT2_THRESHOLD_FREQ) { + _lb_presel = twinrx_ctrl::PRESEL_PATH2; + } else if (rf_freq < LB_FILT3_THRESHOLD_FREQ) { + _lb_presel = twinrx_ctrl::PRESEL_PATH3; + } else if (rf_freq < LB_FILT4_THRESHOLD_FREQ) { + _lb_presel = twinrx_ctrl::PRESEL_PATH4; + } else { + _lb_presel = twinrx_ctrl::PRESEL_PATH4; + } + } else if (_signal_path == twinrx_ctrl::PATH_HIGHBAND) { + // Choose high-band preselector filter + if (rf_freq < HB_FILT1_THRESHOLD_FREQ) { + _hb_presel = twinrx_ctrl::PRESEL_PATH1; + } else if (rf_freq < HB_FILT2_THRESHOLD_FREQ) { + _hb_presel = twinrx_ctrl::PRESEL_PATH2; + } else if (rf_freq < HB_FILT3_THRESHOLD_FREQ) { + _hb_presel = twinrx_ctrl::PRESEL_PATH3; + } else if (rf_freq < HB_FILT4_THRESHOLD_FREQ) { + _hb_presel = twinrx_ctrl::PRESEL_PATH4; + } else { + _hb_presel = twinrx_ctrl::PRESEL_PATH4; + } + } else { + UHD_THROW_INVALID_CODE_PATH(); + } + + //Choose low-band preamp preselector + _lb_preamp_presel = (rf_freq > LB_PREAMP_PRESEL_THRESHOLD_FREQ); + + //Choose LO frequencies + const double target_if1_freq = (_signal_path == twinrx_ctrl::PATH_HIGHBAND) ? + HB_TARGET_IF1_FREQ : LB_TARGET_IF1_FREQ; + const double target_if2_freq = _if_freq_d; + + // LO1 + double lo1_freq_ideal = 0.0, lo2_freq_ideal = 0.0; + if (rf_freq <= FIXED_LO1_THRESHOLD_FREQ) { + //LO1 Freq static + lo1_freq_ideal = target_if1_freq + FIXED_LO1_THRESHOLD_FREQ; + } else if (rf_freq <= INJ_SIDE_THRESHOLD_FREQ) { + //High-side LO1 Injection + lo1_freq_ideal = rf_freq.get() + target_if1_freq; + } else { + //Low-side LO1 Injection + lo1_freq_ideal = rf_freq.get() - target_if1_freq; + } + + if (_lo1_freq_d.get_author() == experts::AUTHOR_USER) { + if (_lo1_freq_d.is_dirty()) { //Are we here because the LO frequency was set? + // The user explicitly requested to set the LO freq so don't touch it! + } else { + // Something else changed which may cause the LO frequency to update. + // Only commit if the frequency is stale. If the user's value is stale + // reset the author to expert. + if (rf_freq_ppm_t(lo1_freq_ideal, MANUAL_LO_HYSTERESIS_PPM) != _lo1_freq_d.get()) { + _lo1_freq_d = lo1_freq_ideal; //Reset author + } + } + } else { + // The LO frequency was never set by the user. Let the expert take care of it + _lo1_freq_d = lo1_freq_ideal; //Reset author + } + + // LO2 + lo_inj_side_t lo2_inj_side_ideal = _compute_lo2_inj_side( + lo1_freq_ideal, target_if1_freq, target_if2_freq, INST_BANDWIDTH); + if (lo2_inj_side_ideal == INJ_HIGH_SIDE) { + lo2_freq_ideal = target_if1_freq + target_if2_freq; + } else { + lo2_freq_ideal = target_if1_freq - target_if2_freq; + } + + if (_lo2_freq_d.get_author() == experts::AUTHOR_USER) { + if (_lo2_freq_d.is_dirty()) { //Are we here because the LO frequency was set? + // The user explicitly requested to set the LO freq so don't touch it! + } else { + // Something else changed which may cause the LO frequency to update. + // Only commit if the frequency is stale. If the user's value is stale + // reset the author to expert. + if (rf_freq_ppm_t(lo2_freq_ideal, MANUAL_LO_HYSTERESIS_PPM) != _lo2_freq_d.get()) { + _lo2_freq_d = lo2_freq_ideal; //Reset author + } + } + } else { + // The LO frequency was never set by the user. Let the expert take care of it + _lo2_freq_d = lo2_freq_ideal; //Reset author + } + + // Determine injection side using the final LO frequency + _lo1_inj_side = (_lo1_freq_d > rf_freq.get()) ? INJ_HIGH_SIDE : INJ_LOW_SIDE; + _lo2_inj_side = (_lo2_freq_d > target_if1_freq) ? INJ_HIGH_SIDE : INJ_LOW_SIDE; +} + +lo_inj_side_t twinrx_freq_path_expert::_compute_lo2_inj_side( + double lo1_freq, double if1_freq, double if2_freq, double bandwidth +) { + static const int MAX_SPUR_ORDER = 5; + for (int ord = MAX_SPUR_ORDER; ord >= 1; ord--) { + // Check high-side injection first + if (not _has_mixer_spurs(lo1_freq, if1_freq + if2_freq, if2_freq, bandwidth, ord)) { + return INJ_HIGH_SIDE; + } + // Check low-side injection second + if (not _has_mixer_spurs(lo1_freq, if1_freq - if2_freq, if2_freq, bandwidth, ord)) { + return INJ_LOW_SIDE; + } + } + // If we reached here, then there are spurs everywhere. Pick high-side as the default + return INJ_HIGH_SIDE; +} + +bool twinrx_freq_path_expert::_has_mixer_spurs( + double lo1_freq, double lo2_freq, double if2_freq, + double bandwidth, int spur_order +) { + // Iterate through all N-th order harmomic combinations + // of LOs... + for (int lo1h_i = 1; lo1h_i <= spur_order; lo1h_i++) { + double lo1harm_freq = lo1_freq * lo1h_i; + for (int lo2h_i = 1; lo2h_i <= spur_order; lo2h_i++) { + double lo2harm_freq = lo2_freq * lo2h_i; + double hdelta = lo1harm_freq - lo2harm_freq; + // .. and check if there is a mixer spur in the IF band + if (std::abs(hdelta + if2_freq) < bandwidth/2 or + std::abs(hdelta - if2_freq) < bandwidth/2) { + return true; + } + } + } + // No spurs were found after NxN search + return false; +} + +/*!--------------------------------------------------------- + * twinrx_freq_coercion_expert::resolve + * --------------------------------------------------------- + */ +void twinrx_freq_coercion_expert::resolve() +{ + const double actual_if2_freq = _if_freq_d; + const double actual_if1_freq = (_lo2_inj_side == INJ_LOW_SIDE) ? + (_lo2_freq_c + actual_if2_freq) : (_lo2_freq_c - actual_if2_freq); + + _rf_freq_c = (_lo1_inj_side == INJ_LOW_SIDE) ? + (_lo1_freq_c + actual_if1_freq) : (_lo1_freq_c - actual_if1_freq); +} + +/*!--------------------------------------------------------- + * twinrx_nyquist_expert::resolve + * --------------------------------------------------------- + */ +void twinrx_nyquist_expert::resolve() +{ + double if_freq_sign = 1.0; + if (_lo1_inj_side == INJ_HIGH_SIDE) if_freq_sign *= -1.0; + if (_lo2_inj_side == INJ_HIGH_SIDE) if_freq_sign *= -1.0; + _if_freq_c = _if_freq_d * if_freq_sign; + + _db_iface->set_fe_connection(dboard_iface::UNIT_RX, _channel, + usrp::fe_connection_t(_codec_conn, _if_freq_c)); +} + +/*!--------------------------------------------------------- + * twinrx_chan_gain_expert::resolve + * --------------------------------------------------------- + */ +void twinrx_chan_gain_expert::resolve() +{ + if (_gain_profile != "default") { + //TODO: Implement me! + throw uhd::not_implemented_error("custom gain strategies not implemeted yet"); + } + + //Lookup table using settings + const twinrx_gain_table table = twinrx_gain_table::lookup_table( + _signal_path, + (_signal_path==twinrx_ctrl::PATH_HIGHBAND) ? _hb_presel : _lb_presel, + _gain_profile); + + //Compute minimum gain. The user-specified gain value will be interpreted as + //the gain applied on top of the minimum gain state. + //If antennas are shared or swapped, the switch has 6dB of loss + size_t gain_index = std::min(static_cast<size_t>(boost::math::round(_gain.get())), table.get_num_entries()-1); + + //Translate gain to an index in the gain table + const twinrx_gain_config_t& config = table.find_by_index(gain_index); + + _input_atten = config.atten1; + if (_signal_path == twinrx_ctrl::PATH_HIGHBAND) { + _hb_atten = config.atten2; + } else { + _lb_atten = config.atten2; + } + + // Preamp 1 should use the Highband amp for frequencies above 3 GHz + if (_signal_path == twinrx_ctrl::PATH_HIGHBAND && _hb_presel != twinrx_ctrl::PRESEL_PATH1) { + _preamp1 = config.amp1 ? twinrx_ctrl::PREAMP_HIGHBAND : twinrx_ctrl::PREAMP_BYPASS; + } else { + _preamp1 = config.amp1 ? twinrx_ctrl::PREAMP_LOWBAND : twinrx_ctrl::PREAMP_BYPASS; + } + + _preamp2 = config.amp2; +} + +/*!--------------------------------------------------------- + * twinrx_lo_config_expert::resolve + * --------------------------------------------------------- + */ +void twinrx_lo_config_expert::resolve() +{ + static const uhd::dict<std::string, twinrx_ctrl::lo_source_t> src_lookup = + boost::assign::map_list_of + ("internal", twinrx_ctrl::LO_INTERNAL) + ("external", twinrx_ctrl::LO_EXTERNAL) + ("companion", twinrx_ctrl::LO_COMPANION) + ("disabled", twinrx_ctrl::LO_DISABLED); + + if (src_lookup.has_key(_lo_source_ch0)) { + _lo1_src_ch0 = _lo2_src_ch0 = src_lookup[_lo_source_ch0]; + } else { + throw uhd::value_error("Invalid LO source for channel 0.Choose from {internal, external, companion}"); + } + if (src_lookup.has_key(_lo_source_ch1)) { + _lo1_src_ch1 = _lo2_src_ch1 = src_lookup[_lo_source_ch1]; + } else { + throw uhd::value_error("Invalid LO source for channel 1.Choose from {internal, external, companion}"); + } + + twinrx_ctrl::lo_export_source_t export_src = twinrx_ctrl::LO_EXPORT_DISABLED; + if (_lo_export_ch0 and (_lo_source_ch0 == "external")) { + throw uhd::value_error("Cannot export an external LO for channel 0"); + } + if (_lo_export_ch1 and (_lo_source_ch1 == "external")) { + throw uhd::value_error("Cannot export an external LO for channel 1"); + } + if (_lo_export_ch0 and _lo_export_ch1) { + throw uhd::value_error("Cannot export LOs for both channels"); + } else if (_lo_export_ch0) { + export_src = (_lo1_src_ch0 == twinrx_ctrl::LO_INTERNAL) ? + twinrx_ctrl::LO_CH1_SYNTH : twinrx_ctrl::LO_CH2_SYNTH; + } else if (_lo_export_ch1) { + export_src = (_lo1_src_ch1 == twinrx_ctrl::LO_INTERNAL) ? + twinrx_ctrl::LO_CH2_SYNTH : twinrx_ctrl::LO_CH1_SYNTH; + } + _lo1_export_src = _lo2_export_src = export_src; +} + +/*!--------------------------------------------------------- + * twinrx_lo_freq_expert::resolve + * --------------------------------------------------------- + */ +void twinrx_lo_mapping_expert::resolve() +{ + static const size_t CH0_MSK = 0x1; + static const size_t CH1_MSK = 0x2; + + // Determine which channels are "driving" each synthesizer + // First check for explicit requests i.e. lo_source "internal" or "companion" + size_t synth_map[] = {0, 0}; + if (_lox_src_ch0 == twinrx_ctrl::LO_INTERNAL) { + synth_map[0] = synth_map[0] | CH0_MSK; + } else if (_lox_src_ch0 == twinrx_ctrl::LO_COMPANION) { + synth_map[1] = synth_map[1] | CH0_MSK; + } + if (_lox_src_ch1 == twinrx_ctrl::LO_INTERNAL) { + synth_map[1] = synth_map[1] | CH1_MSK; + } else if (_lox_src_ch1 == twinrx_ctrl::LO_COMPANION) { + synth_map[0] = synth_map[0] | CH1_MSK; + } + + // If a particular channel has its LO source disabled then the other + // channel is automatically put in hop mode i.e. the synthesizer that + // belongs to the disabled channel can be re-purposed as a redundant LO + // to overlap tuning with signal dwell time. + bool hopping_enabled = false; + if (_lox_src_ch0 == twinrx_ctrl::LO_DISABLED) { + if (_lox_src_ch1 == twinrx_ctrl::LO_INTERNAL) { + synth_map[0] = synth_map[0] | CH0_MSK; + hopping_enabled = true; + } else if (_lox_src_ch1 == twinrx_ctrl::LO_COMPANION) { + synth_map[1] = synth_map[1] | CH0_MSK; + hopping_enabled = true; + } + } + if (_lox_src_ch1 == twinrx_ctrl::LO_DISABLED) { + if (_lox_src_ch0 == twinrx_ctrl::LO_INTERNAL) { + synth_map[1] = synth_map[1] | CH1_MSK; + hopping_enabled = true; + } else if (_lox_src_ch0 == twinrx_ctrl::LO_COMPANION) { + synth_map[0] = synth_map[0] | CH1_MSK; + hopping_enabled = true; + } + } + + // For each synthesizer come up with the final mapping + for (size_t synth = 0; synth < 2; synth++) { + experts::data_writer_t<lo_synth_mapping_t>& lox_mapping = + (synth == 0) ? _lox_mapping_synth0 : _lox_mapping_synth1; + if (synth_map[synth] == (CH0_MSK|CH1_MSK)) { + lox_mapping = MAPPING_SHARED; + } else if (synth_map[synth] == CH0_MSK) { + lox_mapping = MAPPING_CH0; + } else if (synth_map[synth] == CH1_MSK) { + lox_mapping = MAPPING_CH1; + } else { + lox_mapping = MAPPING_NONE; + } + } + _lox_hopping_enabled = hopping_enabled; +} + +/*!--------------------------------------------------------- + * twinrx_antenna_expert::resolve + * --------------------------------------------------------- + */ +void twinrx_antenna_expert::resolve() +{ + static const std::string ANT0 = "RX1", ANT1 = "RX2"; + + if (_antenna_ch0 == ANT0 and _antenna_ch1 == ANT1) { + _ant_mapping = twinrx_ctrl::ANTX_NATIVE; + } else if (_antenna_ch0 == ANT0 and _antenna_ch1 == ANT0) { + if (_enabled_ch0 and _enabled_ch1) { + _ant_mapping = twinrx_ctrl::ANT1_SHARED; + } else if (_enabled_ch0) { + _ant_mapping = twinrx_ctrl::ANTX_NATIVE; + } else if (_enabled_ch1) { + _ant_mapping = twinrx_ctrl::ANTX_SWAPPED; + } + } else if (_antenna_ch0 == ANT1 and _antenna_ch1 == ANT1) { + if (_enabled_ch0 and _enabled_ch1) { + _ant_mapping = twinrx_ctrl::ANT2_SHARED; + } else if (_enabled_ch0) { + _ant_mapping = twinrx_ctrl::ANTX_SWAPPED; + } else if (_enabled_ch1) { + _ant_mapping = twinrx_ctrl::ANTX_NATIVE; + } + } else if (_antenna_ch0 == ANT1 and _antenna_ch1 == ANT0) { + _ant_mapping = twinrx_ctrl::ANTX_SWAPPED; + } else if (_antenna_ch0 != ANT0 and _antenna_ch0 != ANT1) { + throw uhd::value_error("Invalid antenna selection " + _antenna_ch0.get() + " for channel 0. Must be " + ANT0 + " or " + ANT1); + } else if (_antenna_ch1 != ANT0 and _antenna_ch1 != ANT1) { + throw uhd::value_error("Invalid antenna selection " + _antenna_ch1.get() + " for channel 1. Must be " + ANT0 + " or " + ANT1); + } + + //TODO: Implement hooks for the calibration switch + _cal_mode = twinrx_ctrl::CAL_DISABLED; + + if (_cal_mode == twinrx_ctrl::CAL_CH1 and _lo_export_ch1) { + throw uhd::value_error("Cannot calibrate channel 0 and export the LO for channel 1."); + } else if (_cal_mode == twinrx_ctrl::CAL_CH2 and _lo_export_ch0) { + throw uhd::value_error("Cannot calibrate channel 1 and export the LO for channel 0."); + } +} + +/*!--------------------------------------------------------- + * twinrx_ant_gain_expert::resolve + * --------------------------------------------------------- + */ +void twinrx_ant_gain_expert::resolve() +{ + switch (_ant_mapping) { + case twinrx_ctrl::ANTX_NATIVE: + _ant0_input_atten = _ch0_input_atten; + _ant0_preamp1 = _ch0_preamp1; + _ant0_preamp2 = _ch0_preamp2; + _ant0_lb_preamp_presel = _ch0_lb_preamp_presel; + _ant1_input_atten = _ch1_input_atten; + _ant1_preamp1 = _ch1_preamp1; + _ant1_preamp2 = _ch1_preamp2; + _ant1_lb_preamp_presel = _ch1_lb_preamp_presel; + break; + case twinrx_ctrl::ANTX_SWAPPED: + _ant0_input_atten = _ch1_input_atten; + _ant0_preamp1 = _ch1_preamp1; + _ant0_preamp2 = _ch1_preamp2; + _ant0_lb_preamp_presel = _ch1_lb_preamp_presel; + _ant1_input_atten = _ch0_input_atten; + _ant1_preamp1 = _ch0_preamp1; + _ant1_preamp2 = _ch0_preamp2; + _ant1_lb_preamp_presel = _ch0_lb_preamp_presel; + break; + case twinrx_ctrl::ANT1_SHARED: + if ((_ch0_input_atten != _ch1_input_atten) or + (_ch0_preamp1 != _ch1_preamp1) or + (_ch0_preamp2 != _ch1_preamp2) or + (_ch0_lb_preamp_presel != _ch1_lb_preamp_presel)) + { + UHD_MSG(warning) << "incompatible gain settings for antenna sharing. temporarily using Ch0 settings for Ch1."; + } + _ant0_input_atten = _ch0_input_atten; + _ant0_preamp1 = _ch0_preamp1; + _ant0_preamp2 = _ch0_preamp2; + _ant0_lb_preamp_presel = _ch0_lb_preamp_presel; + + _ant1_input_atten = 0; + _ant1_preamp1 = twinrx_ctrl::PREAMP_BYPASS; + _ant1_preamp2 = false; + _ant1_lb_preamp_presel = false; + break; + case twinrx_ctrl::ANT2_SHARED: + if ((_ch0_input_atten != _ch1_input_atten) or + (_ch0_preamp1 != _ch1_preamp1) or + (_ch0_preamp2 != _ch1_preamp2) or + (_ch0_lb_preamp_presel != _ch1_lb_preamp_presel)) + { + UHD_MSG(warning) << "incompatible gain settings for antenna sharing. temporarily using Ch0 settings for Ch1."; + } + _ant1_input_atten = _ch0_input_atten; + _ant1_preamp1 = _ch0_preamp1; + _ant1_preamp2 = _ch0_preamp2; + _ant1_lb_preamp_presel = _ch0_lb_preamp_presel; + + _ant0_input_atten = 0; + _ant0_preamp1 = twinrx_ctrl::PREAMP_BYPASS; + _ant0_preamp2 = false; + _ant0_lb_preamp_presel = false; + break; + default: + _ant0_input_atten = 0; + _ant0_preamp1 = twinrx_ctrl::PREAMP_BYPASS; + _ant0_preamp2 = false; + _ant0_lb_preamp_presel = false; + _ant1_input_atten = 0; + _ant1_preamp1 = twinrx_ctrl::PREAMP_BYPASS; + _ant1_preamp2 = false; + _ant1_lb_preamp_presel = false; + break; + } +} + +/*!--------------------------------------------------------- + * twinrx_settings_expert::resolve + * --------------------------------------------------------- + */ +const bool twinrx_settings_expert::FORCE_COMMIT = false; + +void twinrx_settings_expert::resolve() +{ + for (size_t i = 0; i < 2; i++) { + ch_settings& ch_set = (i == 1) ? _ch1 : _ch0; + twinrx_ctrl::channel_t ch = (i == 1) ? twinrx_ctrl::CH2 : twinrx_ctrl::CH1; + _ctrl->set_chan_enabled(ch, ch_set.chan_enabled, FORCE_COMMIT); + _ctrl->set_preamp1(ch, ch_set.preamp1, FORCE_COMMIT); + _ctrl->set_preamp2(ch, ch_set.preamp2, FORCE_COMMIT); + _ctrl->set_lb_preamp_preselector(ch, ch_set.lb_preamp_presel, FORCE_COMMIT); + _ctrl->set_signal_path(ch, ch_set.signal_path, FORCE_COMMIT); + _ctrl->set_lb_preselector(ch, ch_set.lb_presel, FORCE_COMMIT); + _ctrl->set_hb_preselector(ch, ch_set.hb_presel, FORCE_COMMIT); + _ctrl->set_input_atten(ch, ch_set.input_atten, FORCE_COMMIT); + _ctrl->set_lb_atten(ch, ch_set.lb_atten, FORCE_COMMIT); + _ctrl->set_hb_atten(ch, ch_set.hb_atten, FORCE_COMMIT); + _ctrl->set_lo1_source(ch, ch_set.lo1_source, FORCE_COMMIT); + _ctrl->set_lo2_source(ch, ch_set.lo2_source, FORCE_COMMIT); + } + + _resolve_lox_freq(STAGE_LO1, + _ch0.lo1_freq_d, _ch1.lo1_freq_d, _ch0.lo1_freq_c, _ch1.lo1_freq_c, + _ch0.lo1_source, _ch1.lo1_source, _lo1_synth0_mapping, _lo1_synth1_mapping, + _lo1_hopping_enabled); + _resolve_lox_freq(STAGE_LO2, + _ch0.lo2_freq_d, _ch1.lo2_freq_d, _ch0.lo2_freq_c, _ch1.lo2_freq_c, + _ch0.lo2_source, _ch1.lo2_source, _lo2_synth0_mapping, _lo2_synth1_mapping, + _lo2_hopping_enabled); + + _ctrl->set_lo1_export_source(_lo1_export_src, FORCE_COMMIT); + _ctrl->set_lo2_export_source(_lo2_export_src, FORCE_COMMIT); + _ctrl->set_antenna_mapping(_ant_mapping, FORCE_COMMIT); + //TODO: Re-enable this when we support this mode + //_ctrl->set_crossover_cal_mode(_cal_mode, FORCE_COMMIT); + + _ctrl->commit(); +} + +void twinrx_settings_expert::_resolve_lox_freq( + lo_stage_t lo_stage, + uhd::experts::data_reader_t<double>& ch0_freq_d, + uhd::experts::data_reader_t<double>& ch1_freq_d, + uhd::experts::data_writer_t<double>& ch0_freq_c, + uhd::experts::data_writer_t<double>& ch1_freq_c, + twinrx_ctrl::lo_source_t ch0_lo_source, + twinrx_ctrl::lo_source_t ch1_lo_source, + lo_synth_mapping_t synth0_mapping, + lo_synth_mapping_t synth1_mapping, + bool hopping_enabled) +{ + if (ch0_lo_source == twinrx_ctrl::LO_EXTERNAL) { + // If the LO is external then we don't need to program any synthesizers + ch0_freq_c = ch0_freq_d; + } else { + // When in hopping mode, only attempt to write the LO frequency if it is actually + // dirty to avoid reconfiguring the LO if it is being "double-buffered". If not + // hopping, then always write the frequency because other inputs might require + // an LO re-commit + const bool freq_update_request = (not hopping_enabled) or ch0_freq_d.is_dirty(); + if (synth0_mapping == MAPPING_CH0 and freq_update_request) { + ch0_freq_c = _set_lox_synth_freq(lo_stage, twinrx_ctrl::CH1, ch0_freq_d); + } else if (synth1_mapping == MAPPING_CH0 and freq_update_request) { + ch0_freq_c = _set_lox_synth_freq(lo_stage, twinrx_ctrl::CH2, ch0_freq_d); + } else if (synth0_mapping == MAPPING_SHARED or synth1_mapping == MAPPING_SHARED) { + // If any synthesizer is being shared then we are not in hopping mode + if (rf_freq_ppm_t(ch0_freq_d) != ch1_freq_d) { + UHD_MSG(warning) << + "Incompatible RF/LO frequencies for LO sharing. Using Ch0 settings for both channels."; + } + twinrx_ctrl::channel_t ch = (synth0_mapping == MAPPING_SHARED) ? twinrx_ctrl::CH1 : twinrx_ctrl::CH2; + ch0_freq_c = _set_lox_synth_freq(lo_stage, ch, ch0_freq_d); + ch1_freq_c = ch0_freq_c; + } + } + + if (ch1_lo_source == twinrx_ctrl::LO_EXTERNAL) { + // If the LO is external then we don't need to program any synthesizers + ch1_freq_c = ch1_freq_d; + } else { + // When in hopping mode, only attempt to write the LO frequency if it is actually + // dirty to avoid reconfiguring the LO if it is being "double-buffered". If not + // hopping, then always write the frequency because other inputs might require + // an LO re-commit + const bool freq_update_request = (not hopping_enabled) or ch1_freq_d.is_dirty(); + // As an additional layer of protection from unnecessarily committing the LO, check + // if the frequency has actually changed. + if (synth0_mapping == MAPPING_CH1 and freq_update_request) { + ch1_freq_c = _set_lox_synth_freq(lo_stage, twinrx_ctrl::CH1, ch1_freq_d); + } else if (synth1_mapping == MAPPING_CH1 and freq_update_request) { + ch1_freq_c = _set_lox_synth_freq(lo_stage, twinrx_ctrl::CH2, ch1_freq_d); + } else if (synth0_mapping == MAPPING_SHARED or synth1_mapping == MAPPING_SHARED) { + // If any synthesizer is being shared then we are not in hopping mode + if (rf_freq_ppm_t(ch0_freq_d) != ch1_freq_d) { + UHD_MSG(warning) << + "Incompatible RF/LO frequencies for LO sharing. Using Ch0 settings for both channels."; + } + twinrx_ctrl::channel_t ch = (synth0_mapping == MAPPING_SHARED) ? twinrx_ctrl::CH1 : twinrx_ctrl::CH2; + ch0_freq_c = _set_lox_synth_freq(lo_stage, ch, ch0_freq_d); + ch1_freq_c = ch0_freq_c; + } + } +} + +double twinrx_settings_expert::_set_lox_synth_freq(lo_stage_t stage, twinrx_ctrl::channel_t ch, double freq) +{ + lo_freq_cache_t* freq_cache = NULL; + if (stage == STAGE_LO1) { + freq_cache = (ch == twinrx_ctrl::CH1) ? &_cached_lo1_synth0_freq : &_cached_lo1_synth1_freq; + } else if (stage == STAGE_LO2) { + freq_cache = (ch == twinrx_ctrl::CH1) ? &_cached_lo2_synth0_freq : &_cached_lo2_synth1_freq; + } else { + throw uhd::assertion_error("Invalid LO stage"); + } + + // Check if the frequency has actually changed before configuring synthesizers + double coerced_freq = 0.0; + if (freq_cache->desired != freq) { + if (stage == STAGE_LO1) { + coerced_freq = _ctrl->set_lo1_synth_freq(ch, freq, FORCE_COMMIT); + } else { + coerced_freq = _ctrl->set_lo2_synth_freq(ch, freq, FORCE_COMMIT); + } + freq_cache->desired = rf_freq_ppm_t(freq); + freq_cache->coerced = coerced_freq; + } else { + coerced_freq = freq_cache->coerced; + } + return coerced_freq; +} + diff --git a/host/lib/usrp/dboard/twinrx/twinrx_experts.hpp b/host/lib/usrp/dboard/twinrx/twinrx_experts.hpp new file mode 100644 index 000000000..1617c9c80 --- /dev/null +++ b/host/lib/usrp/dboard/twinrx/twinrx_experts.hpp @@ -0,0 +1,636 @@ +// +// Copyright 2016 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 +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +#ifndef INCLUDED_DBOARD_TWINRX_EXPERTS_HPP +#define INCLUDED_DBOARD_TWINRX_EXPERTS_HPP + +#include "twinrx_ctrl.hpp" +#include "expert_nodes.hpp" +#include <uhd/utils/math.hpp> + +namespace uhd { namespace usrp { namespace dboard { namespace twinrx { + +//--------------------------------------------------------- +// Misc types and definitions +//--------------------------------------------------------- + +struct rf_freq_abs_t : public uhd::math::fp_compare::fp_compare_delta<double> { + rf_freq_abs_t(double freq = 0.0, double epsilon = 1.0 /* 1Hz epsilon */) : + uhd::math::fp_compare::fp_compare_delta<double>(freq, epsilon) {} + inline double get() const { return _value; } +}; + +struct rf_freq_ppm_t : public rf_freq_abs_t { + rf_freq_ppm_t(double freq = 0.0, double epsilon_ppm = 0.1 /* 1PPM epsilon */) : + rf_freq_abs_t(freq, 1e-6 * freq * epsilon_ppm) {} +}; + +enum lo_stage_t { STAGE_LO1, STAGE_LO2 }; +enum lo_inj_side_t { INJ_LOW_SIDE, INJ_HIGH_SIDE }; +enum lo_synth_mapping_t { MAPPING_NONE, MAPPING_CH0, MAPPING_CH1, MAPPING_SHARED }; + +static const std::string prepend_ch(std::string name, const std::string& ch) { + return ch + "/" + name; +} + +static const std::string lo_stage_str(lo_stage_t stage, bool lower = false) { + std::string prefix = lower ? "lo" : "LO"; + return prefix + ((stage == STAGE_LO1) ? "1" : "2"); +} + +/*!--------------------------------------------------------- + * twinrx_freq_path_expert + * + * This expert is responsble for translating a user-specified + * RF and IF center frequency into TwinRX specific settings + * like band, preselector path, LO frequency and injection + * sides for both the LO stages. + * + * One instance of this expert is required for each channel + * --------------------------------------------------------- + */ +class twinrx_freq_path_expert : public experts::worker_node_t { +public: + twinrx_freq_path_expert(const experts::node_retriever_t& db, std::string ch) + : experts::worker_node_t(prepend_ch("twinrx_freq_path_expert", ch)), + _rf_freq_d (db, prepend_ch("freq/desired", ch)), + _if_freq_d (db, prepend_ch("if_freq/desired", ch)), + _signal_path (db, prepend_ch("ch/signal_path", ch)), + _lb_presel (db, prepend_ch("ch/lb_presel", ch)), + _hb_presel (db, prepend_ch("ch/hb_presel", ch)), + _lb_preamp_presel (db, prepend_ch("ch/lb_preamp_presel", ch)), + _lo1_freq_d (db, prepend_ch("los/LO1/freq/desired", ch)), + _lo2_freq_d (db, prepend_ch("los/LO2/freq/desired", ch)), + _lo1_inj_side (db, prepend_ch("ch/LO1/inj_side", ch)), + _lo2_inj_side (db, prepend_ch("ch/LO2/inj_side", ch)) + { + bind_accessor(_rf_freq_d); + bind_accessor(_if_freq_d); + bind_accessor(_signal_path); + bind_accessor(_lb_presel); + bind_accessor(_hb_presel); + bind_accessor(_lb_preamp_presel); + bind_accessor(_lo1_freq_d); + bind_accessor(_lo2_freq_d); + bind_accessor(_lo1_inj_side); + bind_accessor(_lo2_inj_side); + } + +private: + virtual void resolve(); + static lo_inj_side_t _compute_lo2_inj_side( + double lo1_freq, double if1_freq, double if2_freq, double bandwidth); + static bool _has_mixer_spurs( + double lo1_freq, double lo2_freq, double if2_freq, + double bandwidth, int spur_order); + + //Inputs + experts::data_reader_t<double> _rf_freq_d; + experts::data_reader_t<double> _if_freq_d; + //Outputs + experts::data_writer_t<twinrx_ctrl::signal_path_t> _signal_path; + experts::data_writer_t<twinrx_ctrl::preselector_path_t> _lb_presel; + experts::data_writer_t<twinrx_ctrl::preselector_path_t> _hb_presel; + experts::data_writer_t<bool> _lb_preamp_presel; + experts::data_writer_t<double> _lo1_freq_d; + experts::data_writer_t<double> _lo2_freq_d; + experts::data_writer_t<lo_inj_side_t> _lo1_inj_side; + experts::data_writer_t<lo_inj_side_t> _lo2_inj_side; +}; + +/*!--------------------------------------------------------- + * twinrx_lo_config_expert + * + * This expert is responsible for translating high level + * channel-scoped LO source and export settings to low-level + * channel-scoped settings. The expert only deals with + * the source and export attributes, not frequency. + * + * One instance of this expert is required for all channels + * --------------------------------------------------------- + */ +class twinrx_lo_config_expert : public experts::worker_node_t { +public: + twinrx_lo_config_expert(const experts::node_retriever_t& db) + : experts::worker_node_t("twinrx_lo_config_expert"), + _lo_source_ch0 (db, prepend_ch("los/all/source", "0")), + _lo_source_ch1 (db, prepend_ch("los/all/source", "1")), + _lo_export_ch0 (db, prepend_ch("los/all/export", "0")), + _lo_export_ch1 (db, prepend_ch("los/all/export", "1")), + _lo1_src_ch0 (db, prepend_ch("ch/LO1/source", "0")), + _lo1_src_ch1 (db, prepend_ch("ch/LO1/source", "1")), + _lo2_src_ch0 (db, prepend_ch("ch/LO2/source", "0")), + _lo2_src_ch1 (db, prepend_ch("ch/LO2/source", "1")), + _lo1_export_src (db, "com/LO1/export_source"), + _lo2_export_src (db, "com/LO2/export_source") + { + bind_accessor(_lo_source_ch0); + bind_accessor(_lo_source_ch1); + bind_accessor(_lo_export_ch0); + bind_accessor(_lo_export_ch1); + bind_accessor(_lo1_src_ch0); + bind_accessor(_lo1_src_ch1); + bind_accessor(_lo2_src_ch0); + bind_accessor(_lo2_src_ch1); + bind_accessor(_lo1_export_src); + bind_accessor(_lo2_export_src); + } + +private: + virtual void resolve(); + + //Inputs + experts::data_reader_t<std::string> _lo_source_ch0; + experts::data_reader_t<std::string> _lo_source_ch1; + experts::data_reader_t<bool> _lo_export_ch0; + experts::data_reader_t<bool> _lo_export_ch1; + //Outputs + experts::data_writer_t<twinrx_ctrl::lo_source_t> _lo1_src_ch0; + experts::data_writer_t<twinrx_ctrl::lo_source_t> _lo1_src_ch1; + experts::data_writer_t<twinrx_ctrl::lo_source_t> _lo2_src_ch0; + experts::data_writer_t<twinrx_ctrl::lo_source_t> _lo2_src_ch1; + experts::data_writer_t<twinrx_ctrl::lo_export_source_t> _lo1_export_src; + experts::data_writer_t<twinrx_ctrl::lo_export_source_t> _lo2_export_src; +}; + +/*!--------------------------------------------------------- + * twinrx_lo_mapping_expert + * + * This expert is responsible for translating low-level + * channel-scoped LO source and export settings to low-level + * synthesizer-scoped settings. The expert deals with the + * extremely flexible channel->synthesizer mapping and handles + * frequency hopping modes. + * + * One instance of this expert is required for each LO stage + * --------------------------------------------------------- + */ +class twinrx_lo_mapping_expert : public experts::worker_node_t { +public: + twinrx_lo_mapping_expert(const experts::node_retriever_t& db, lo_stage_t stage) + : experts::worker_node_t("twinrx_" + lo_stage_str(stage, true) + "_mapping_expert"), + _lox_src_ch0 (db, prepend_ch("ch/" + lo_stage_str(stage) + "/source", "0")), + _lox_src_ch1 (db, prepend_ch("ch/" + lo_stage_str(stage) + "/source", "1")), + _lox_mapping_synth0 (db, prepend_ch("synth/" + lo_stage_str(stage) + "/mapping", "0")), + _lox_mapping_synth1 (db, prepend_ch("synth/" + lo_stage_str(stage) + "/mapping", "1")), + _lox_hopping_enabled (db, "com/synth/" + lo_stage_str(stage) + "/hopping_enabled") + { + bind_accessor(_lox_src_ch0); + bind_accessor(_lox_src_ch1); + bind_accessor(_lox_mapping_synth0); + bind_accessor(_lox_mapping_synth1); + bind_accessor(_lox_hopping_enabled); + } + +private: + virtual void resolve(); + + //Inputs + experts::data_reader_t<twinrx_ctrl::lo_source_t> _lox_src_ch0; + experts::data_reader_t<twinrx_ctrl::lo_source_t> _lox_src_ch1; + //Outputs + experts::data_writer_t<lo_synth_mapping_t> _lox_mapping_synth0; + experts::data_writer_t<lo_synth_mapping_t> _lox_mapping_synth1; + experts::data_writer_t<bool> _lox_hopping_enabled; +}; + +/*!--------------------------------------------------------- + * twinrx_freq_coercion_expert + * + * This expert is responsible for calculating the coerced + * RF frequency after most settings and modes have been + * resolved. + * + * One instance of this expert is required for each channel + * --------------------------------------------------------- + */ +class twinrx_freq_coercion_expert : public experts::worker_node_t { +public: + twinrx_freq_coercion_expert(const experts::node_retriever_t& db, std::string ch) + : experts::worker_node_t(prepend_ch("twinrx_freq_coercion_expert", ch)), + _lo1_freq_c (db, prepend_ch("los/LO1/freq/coerced", ch)), + _lo2_freq_c (db, prepend_ch("los/LO2/freq/coerced", ch)), + _if_freq_d (db, prepend_ch("if_freq/desired", ch)), + _lo1_inj_side (db, prepend_ch("ch/LO1/inj_side", ch)), + _lo2_inj_side (db, prepend_ch("ch/LO2/inj_side", ch)), + _rf_freq_c (db, prepend_ch("freq/coerced", ch)) + { + bind_accessor(_lo1_freq_c); + bind_accessor(_lo2_freq_c); + bind_accessor(_if_freq_d); + bind_accessor(_lo1_inj_side); + bind_accessor(_lo2_inj_side); + bind_accessor(_rf_freq_c); + } + +private: + virtual void resolve(); + + //Inputs + experts::data_reader_t<double> _lo1_freq_c; + experts::data_reader_t<double> _lo2_freq_c; + experts::data_reader_t<double> _if_freq_d; + experts::data_reader_t<lo_inj_side_t> _lo1_inj_side; + experts::data_reader_t<lo_inj_side_t> _lo2_inj_side; + //Outputs + experts::data_writer_t<double> _rf_freq_c; +}; + +/*!--------------------------------------------------------- + * twinrx_nyquist_expert + * + * This expert is responsible for figuring out the DSP + * front-end settings required for each channel + * + * One instance of this expert is required for each channel + * --------------------------------------------------------- + */ +class twinrx_nyquist_expert : public experts::worker_node_t { +public: + twinrx_nyquist_expert(const experts::node_retriever_t& db, std::string ch, + dboard_iface::sptr db_iface) + : experts::worker_node_t(prepend_ch("twinrx_nyquist_expert", ch)), + _channel (ch), + _codec_conn (ch=="0"?"II":"QQ"), //Ch->ADC Port mapping + _lo1_freq_d (db, prepend_ch("los/LO1/freq/desired", ch)), + _lo2_freq_d (db, prepend_ch("los/LO2/freq/desired", ch)), + _if_freq_d (db, prepend_ch("if_freq/desired", ch)), + _lo1_inj_side (db, prepend_ch("ch/LO1/inj_side", ch)), + _lo2_inj_side (db, prepend_ch("ch/LO2/inj_side", ch)), + _if_freq_c (db, prepend_ch("if_freq/coerced", ch)), + _db_iface (db_iface) + { + bind_accessor(_lo1_freq_d); + bind_accessor(_lo2_freq_d); + bind_accessor(_if_freq_d); + bind_accessor(_lo1_inj_side); + bind_accessor(_lo2_inj_side); + bind_accessor(_if_freq_c); + } + +private: + virtual void resolve(); + + //Inputs + const std::string _channel; + const std::string _codec_conn; + experts::data_reader_t<double> _lo1_freq_d; + experts::data_reader_t<double> _lo2_freq_d; + experts::data_reader_t<double> _if_freq_d; + experts::data_reader_t<lo_inj_side_t> _lo1_inj_side; + experts::data_reader_t<lo_inj_side_t> _lo2_inj_side; + //Outputs + experts::data_writer_t<double> _if_freq_c; + dboard_iface::sptr _db_iface; +}; + +/*!--------------------------------------------------------- + * twinrx_antenna_expert + * + * This expert is responsible for translating high-level + * antenna selection settings and channel enables to low-level + * switch configurations. + * + * One instance of this expert is required for all channels + * --------------------------------------------------------- + */ +class twinrx_antenna_expert : public experts::worker_node_t { +public: + twinrx_antenna_expert(const experts::node_retriever_t& db) + : experts::worker_node_t("twinrx_antenna_expert"), + _antenna_ch0 (db, prepend_ch("antenna", "0")), + _antenna_ch1 (db, prepend_ch("antenna", "1")), + _enabled_ch0 (db, prepend_ch("enabled", "0")), + _enabled_ch1 (db, prepend_ch("enabled", "1")), + _lo_export_ch0 (db, prepend_ch("los/all/export", "0")), + _lo_export_ch1 (db, prepend_ch("los/all/export", "1")), + _ant_mapping (db, "com/ant_mapping"), + _cal_mode (db, "com/cal_mode") + { + bind_accessor(_antenna_ch0); + bind_accessor(_antenna_ch1); + bind_accessor(_enabled_ch0); + bind_accessor(_enabled_ch1); + bind_accessor(_lo_export_ch0); + bind_accessor(_lo_export_ch1); + bind_accessor(_ant_mapping); + bind_accessor(_cal_mode); + } + +private: + virtual void resolve(); + + //Inputs + experts::data_reader_t<std::string> _antenna_ch0; + experts::data_reader_t<std::string> _antenna_ch1; + experts::data_reader_t<bool> _enabled_ch0; + experts::data_reader_t<bool> _enabled_ch1; + experts::data_reader_t<bool> _lo_export_ch0; + experts::data_reader_t<bool> _lo_export_ch1; + //Outputs + experts::data_writer_t<twinrx_ctrl::antenna_mapping_t> _ant_mapping; + experts::data_writer_t<twinrx_ctrl::cal_mode_t> _cal_mode; +}; + +/*!--------------------------------------------------------- + * twinrx_chan_gain_expert + * + * This expert is responsible for mapping high-level channel + * gain settings to individual attenuator and amp configurations + * that are also channel-scoped. This expert will implement + * the gain distribution strategy. + * + * One instance of this expert is required for each channel + * --------------------------------------------------------- + */ +class twinrx_chan_gain_expert : public experts::worker_node_t { +public: + twinrx_chan_gain_expert(const experts::node_retriever_t& db, std::string ch) + : experts::worker_node_t(prepend_ch("twinrx_chan_gain_expert", ch)), + _gain (db, prepend_ch("gain", ch)), + _gain_profile (db, prepend_ch("gain_profile", ch)), + _signal_path (db, prepend_ch("ch/signal_path", ch)), + _lb_presel (db, prepend_ch("ch/lb_presel", ch)), + _hb_presel (db, prepend_ch("ch/hb_presel", ch)), + _ant_mapping (db, "com/ant_mapping"), + _input_atten (db, prepend_ch("ch/input_atten", ch)), + _lb_atten (db, prepend_ch("ch/lb_atten", ch)), + _hb_atten (db, prepend_ch("ch/hb_atten", ch)), + _preamp1 (db, prepend_ch("ch/preamp1", ch)), + _preamp2 (db, prepend_ch("ch/preamp2", ch)) + { + bind_accessor(_gain); + bind_accessor(_gain_profile); + bind_accessor(_signal_path); + bind_accessor(_lb_presel); + bind_accessor(_hb_presel); + bind_accessor(_ant_mapping); + bind_accessor(_input_atten); + bind_accessor(_lb_atten); + bind_accessor(_hb_atten); + bind_accessor(_preamp1); + bind_accessor(_preamp2); + } + +private: + virtual void resolve(); + + //Inputs + experts::data_reader_t<double> _gain; + experts::data_reader_t<std::string> _gain_profile; + experts::data_reader_t<twinrx_ctrl::signal_path_t> _signal_path; + experts::data_reader_t<twinrx_ctrl::preselector_path_t> _lb_presel; + experts::data_reader_t<twinrx_ctrl::preselector_path_t> _hb_presel; + experts::data_reader_t<twinrx_ctrl::antenna_mapping_t> _ant_mapping; + //Outputs + experts::data_writer_t<boost::uint8_t> _input_atten; + experts::data_writer_t<boost::uint8_t> _lb_atten; + experts::data_writer_t<boost::uint8_t> _hb_atten; + experts::data_writer_t<twinrx_ctrl::preamp_state_t> _preamp1; + experts::data_writer_t<bool> _preamp2; +}; + +/*!--------------------------------------------------------- + * twinrx_ant_gain_expert + * + * This expert is responsible for translating between the + * channel-scoped low-level gain settings to antenna-scoped + * gain settings. + * + * One instance of this expert is required for all channels + * --------------------------------------------------------- + */ +class twinrx_ant_gain_expert : public experts::worker_node_t { +public: + twinrx_ant_gain_expert(const experts::node_retriever_t& db) + : experts::worker_node_t("twinrx_ant_gain_expert"), + _ant_mapping (db, "com/ant_mapping"), + _ch0_input_atten (db, prepend_ch("ch/input_atten", "0")), + _ch0_preamp1 (db, prepend_ch("ch/preamp1", "0")), + _ch0_preamp2 (db, prepend_ch("ch/preamp2", "0")), + _ch0_lb_preamp_presel (db, prepend_ch("ch/lb_preamp_presel", "0")), + _ch1_input_atten (db, prepend_ch("ch/input_atten", "1")), + _ch1_preamp1 (db, prepend_ch("ch/preamp1", "1")), + _ch1_preamp2 (db, prepend_ch("ch/preamp2", "1")), + _ch1_lb_preamp_presel (db, prepend_ch("ch/lb_preamp_presel", "1")), + _ant0_input_atten (db, prepend_ch("ant/input_atten", "0")), + _ant0_preamp1 (db, prepend_ch("ant/preamp1", "0")), + _ant0_preamp2 (db, prepend_ch("ant/preamp2", "0")), + _ant0_lb_preamp_presel(db, prepend_ch("ant/lb_preamp_presel", "0")), + _ant1_input_atten (db, prepend_ch("ant/input_atten", "1")), + _ant1_preamp1 (db, prepend_ch("ant/preamp1", "1")), + _ant1_preamp2 (db, prepend_ch("ant/preamp2", "1")), + _ant1_lb_preamp_presel(db, prepend_ch("ant/lb_preamp_presel", "1")) + { + bind_accessor(_ant_mapping); + bind_accessor(_ch0_input_atten); + bind_accessor(_ch0_preamp1); + bind_accessor(_ch0_preamp2); + bind_accessor(_ch0_lb_preamp_presel); + bind_accessor(_ch1_input_atten); + bind_accessor(_ch1_preamp1); + bind_accessor(_ch1_preamp2); + bind_accessor(_ch1_lb_preamp_presel); + bind_accessor(_ant0_input_atten); + bind_accessor(_ant0_preamp1); + bind_accessor(_ant0_preamp2); + bind_accessor(_ant0_lb_preamp_presel); + bind_accessor(_ant1_input_atten); + bind_accessor(_ant1_preamp1); + bind_accessor(_ant1_preamp2); + bind_accessor(_ant1_lb_preamp_presel); + } + +private: + virtual void resolve(); + + //Inputs + experts::data_reader_t<twinrx_ctrl::antenna_mapping_t> _ant_mapping; + experts::data_reader_t<boost::uint8_t> _ch0_input_atten; + experts::data_reader_t<twinrx_ctrl::preamp_state_t> _ch0_preamp1; + experts::data_reader_t<bool> _ch0_preamp2; + experts::data_reader_t<bool> _ch0_lb_preamp_presel; + experts::data_reader_t<boost::uint8_t> _ch1_input_atten; + experts::data_reader_t<twinrx_ctrl::preamp_state_t> _ch1_preamp1; + experts::data_reader_t<bool> _ch1_preamp2; + experts::data_reader_t<bool> _ch1_lb_preamp_presel; + + //Outputs + experts::data_writer_t<boost::uint8_t> _ant0_input_atten; + experts::data_writer_t<twinrx_ctrl::preamp_state_t> _ant0_preamp1; + experts::data_writer_t<bool> _ant0_preamp2; + experts::data_writer_t<bool> _ant0_lb_preamp_presel; + experts::data_writer_t<boost::uint8_t> _ant1_input_atten; + experts::data_writer_t<twinrx_ctrl::preamp_state_t> _ant1_preamp1; + experts::data_writer_t<bool> _ant1_preamp2; + experts::data_writer_t<bool> _ant1_lb_preamp_presel; +}; + +/*!--------------------------------------------------------- + * twinrx_settings_expert + * + * This expert is responsible for gathering all low-level + * settings and writing them to hardware. All LO frequency + * settings are cached with a hysteresis. All other settings + * are always written to twinrx_ctrl and rely on register + * level caching. + * + * One instance of this expert is required for all channels + * --------------------------------------------------------- + */ +class twinrx_settings_expert : public experts::worker_node_t { +public: + twinrx_settings_expert(const experts::node_retriever_t& db, twinrx_ctrl::sptr ctrl) + : experts::worker_node_t("twinrx_settings_expert"), _ctrl(ctrl), + _ch0 (db, "0"), + _ch1 (db, "1"), + _lo1_synth0_mapping(db, "0/synth/LO1/mapping"), + _lo1_synth1_mapping(db, "1/synth/LO1/mapping"), + _lo2_synth0_mapping(db, "0/synth/LO2/mapping"), + _lo2_synth1_mapping(db, "1/synth/LO2/mapping"), + _lo1_hopping_enabled(db, "com/synth/LO1/hopping_enabled"), + _lo2_hopping_enabled(db, "com/synth/LO2/hopping_enabled"), + _lo1_export_src (db, "com/LO1/export_source"), + _lo2_export_src (db, "com/LO2/export_source"), + _ant_mapping (db, "com/ant_mapping"), + _cal_mode (db, "com/cal_mode") + { + for (size_t i = 0; i < 2; i++) { + ch_settings& ch = (i==1) ? _ch1 : _ch0; + bind_accessor(ch.chan_enabled); + bind_accessor(ch.preamp1); + bind_accessor(ch.preamp2); + bind_accessor(ch.lb_preamp_presel); + bind_accessor(ch.signal_path); + bind_accessor(ch.lb_presel); + bind_accessor(ch.hb_presel); + bind_accessor(ch.input_atten); + bind_accessor(ch.lb_atten); + bind_accessor(ch.hb_atten); + bind_accessor(ch.lo1_source); + bind_accessor(ch.lo2_source); + bind_accessor(ch.lo1_freq_d); + bind_accessor(ch.lo2_freq_d); + bind_accessor(ch.lo1_freq_c); + bind_accessor(ch.lo2_freq_c); + } + bind_accessor(_lo1_synth0_mapping); + bind_accessor(_lo1_synth1_mapping); + bind_accessor(_lo2_synth0_mapping); + bind_accessor(_lo2_synth1_mapping); + bind_accessor(_lo1_hopping_enabled); + bind_accessor(_lo2_hopping_enabled); + bind_accessor(_lo1_export_src); + bind_accessor(_lo2_export_src); + bind_accessor(_ant_mapping); + bind_accessor(_cal_mode); + } + +private: + virtual void resolve(); + void _resolve_lox_freq( + lo_stage_t lo_stage, + experts::data_reader_t<double>& ch0_freq_d, + experts::data_reader_t<double>& ch1_freq_d, + experts::data_writer_t<double>& ch0_freq_c, + experts::data_writer_t<double>& ch1_freq_c, + twinrx_ctrl::lo_source_t ch0_lo_source, + twinrx_ctrl::lo_source_t ch1_lo_source, + lo_synth_mapping_t synth0_mapping, + lo_synth_mapping_t synth1_mapping, + bool hopping_enabled); + double _set_lox_synth_freq(lo_stage_t stage, twinrx_ctrl::channel_t ch, double freq); + + class ch_settings { + public: + ch_settings(const experts::node_retriever_t& db, const std::string& ch) : + chan_enabled (db, prepend_ch("enabled", ch)), + preamp1 (db, prepend_ch("ant/preamp1", ch)), + preamp2 (db, prepend_ch("ant/preamp2", ch)), + lb_preamp_presel(db, prepend_ch("ant/lb_preamp_presel", ch)), + signal_path (db, prepend_ch("ch/signal_path", ch)), + lb_presel (db, prepend_ch("ch/lb_presel", ch)), + hb_presel (db, prepend_ch("ch/hb_presel", ch)), + input_atten (db, prepend_ch("ant/input_atten", ch)), + lb_atten (db, prepend_ch("ch/lb_atten", ch)), + hb_atten (db, prepend_ch("ch/hb_atten", ch)), + lo1_source (db, prepend_ch("ch/LO1/source", ch)), + lo2_source (db, prepend_ch("ch/LO2/source", ch)), + lo1_freq_d (db, prepend_ch("los/LO1/freq/desired", ch)), + lo2_freq_d (db, prepend_ch("los/LO2/freq/desired", ch)), + lo1_freq_c (db, prepend_ch("los/LO1/freq/coerced", ch)), + lo2_freq_c (db, prepend_ch("los/LO2/freq/coerced", ch)) + {} + + //Inputs (channel specific) + experts::data_reader_t<bool> chan_enabled; + experts::data_reader_t<twinrx_ctrl::preamp_state_t> preamp1; + experts::data_reader_t<bool> preamp2; + experts::data_reader_t<bool> lb_preamp_presel; + experts::data_reader_t<twinrx_ctrl::signal_path_t> signal_path; + experts::data_reader_t<twinrx_ctrl::preselector_path_t> lb_presel; + experts::data_reader_t<twinrx_ctrl::preselector_path_t> hb_presel; + experts::data_reader_t<boost::uint8_t> input_atten; + experts::data_reader_t<boost::uint8_t> lb_atten; + experts::data_reader_t<boost::uint8_t> hb_atten; + experts::data_reader_t<twinrx_ctrl::lo_source_t> lo1_source; + experts::data_reader_t<twinrx_ctrl::lo_source_t> lo2_source; + experts::data_reader_t<double> lo1_freq_d; + experts::data_reader_t<double> lo2_freq_d; + + //Output (channel specific) + experts::data_writer_t<double> lo1_freq_c; + experts::data_writer_t<double> lo2_freq_c; + }; + + //External interface + twinrx_ctrl::sptr _ctrl; + + //Inputs (channel agnostic) + ch_settings _ch0; + ch_settings _ch1; + experts::data_reader_t<lo_synth_mapping_t> _lo1_synth0_mapping; + experts::data_reader_t<lo_synth_mapping_t> _lo1_synth1_mapping; + experts::data_reader_t<lo_synth_mapping_t> _lo2_synth0_mapping; + experts::data_reader_t<lo_synth_mapping_t> _lo2_synth1_mapping; + experts::data_reader_t<bool> _lo1_hopping_enabled; + experts::data_reader_t<bool> _lo2_hopping_enabled; + experts::data_reader_t<twinrx_ctrl::lo_export_source_t> _lo1_export_src; + experts::data_reader_t<twinrx_ctrl::lo_export_source_t> _lo2_export_src; + experts::data_reader_t<twinrx_ctrl::antenna_mapping_t> _ant_mapping; + experts::data_reader_t<twinrx_ctrl::cal_mode_t> _cal_mode; + + //Outputs (channel agnostic) + //None + + //Misc + struct lo_freq_cache_t { + rf_freq_ppm_t desired; + double coerced; + }; + lo_freq_cache_t _cached_lo1_synth0_freq; + lo_freq_cache_t _cached_lo2_synth0_freq; + lo_freq_cache_t _cached_lo1_synth1_freq; + lo_freq_cache_t _cached_lo2_synth1_freq; + + static const bool FORCE_COMMIT; +}; + + +}}}} //namespaces + +#endif /* INCLUDED_DBOARD_TWINRX_EXPERTS_HPP */ diff --git a/host/lib/usrp/dboard/twinrx/twinrx_gain_tables.cpp b/host/lib/usrp/dboard/twinrx/twinrx_gain_tables.cpp new file mode 100644 index 000000000..5cc8b49f3 --- /dev/null +++ b/host/lib/usrp/dboard/twinrx/twinrx_gain_tables.cpp @@ -0,0 +1,860 @@ +// +// Copyright 2016 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 +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +#include "twinrx_gain_tables.hpp" +#include <uhd/exception.hpp> +#include <boost/assign/list_of.hpp> + +using namespace uhd::usrp::dboard::twinrx; + +static const std::vector<twinrx_gain_config_t> HIGHBAND1_TABLE = boost::assign::list_of + // Index, Gain, Atten1, Atten2, Amp1, Amp2 + ( twinrx_gain_config_t( 0, -28.3, 31, 31, false, false ) ) + ( twinrx_gain_config_t( 1, -28.3, 31, 31, false, false ) ) + ( twinrx_gain_config_t( 2, -28.3, 31, 31, false, false ) ) + ( twinrx_gain_config_t( 3, -28.3, 31, 31, false, false ) ) + ( twinrx_gain_config_t( 4, -28.3, 31, 31, false, false ) ) + ( twinrx_gain_config_t( 5, -28.3, 31, 31, false, false ) ) + ( twinrx_gain_config_t( 6, -28.3, 31, 31, false, false ) ) + ( twinrx_gain_config_t( 7, -27.3, 31, 30, false, false ) ) + ( twinrx_gain_config_t( 8, -26.3, 31, 29, false, false ) ) + ( twinrx_gain_config_t( 9, -25.3, 31, 28, false, false ) ) + ( twinrx_gain_config_t( 10, -24.3, 31, 27, false, false ) ) + ( twinrx_gain_config_t( 11, -23.3, 31, 26, false, false ) ) + ( twinrx_gain_config_t( 12, -22.3, 31, 25, false, false ) ) + ( twinrx_gain_config_t( 13, -21.3, 31, 24, false, false ) ) + ( twinrx_gain_config_t( 14, -20.3, 31, 23, false, false ) ) + ( twinrx_gain_config_t( 15, -19.3, 31, 22, false, false ) ) + ( twinrx_gain_config_t( 16, -18.3, 31, 21, false, false ) ) + ( twinrx_gain_config_t( 17, -17.3, 31, 20, false, false ) ) + ( twinrx_gain_config_t( 18, -16.3, 31, 19, false, false ) ) + ( twinrx_gain_config_t( 19, -15.3, 31, 18, false, false ) ) + ( twinrx_gain_config_t( 20, -14.3, 31, 17, false, false ) ) + ( twinrx_gain_config_t( 21, -13.3, 31, 16, false, false ) ) + ( twinrx_gain_config_t( 22, -12.3, 31, 15, false, false ) ) + ( twinrx_gain_config_t( 23, -11.3, 31, 14, false, false ) ) + ( twinrx_gain_config_t( 24, -10.3, 31, 13, false, false ) ) + ( twinrx_gain_config_t( 25, -9.3, 31, 12, false, false ) ) + ( twinrx_gain_config_t( 26, -8.3, 30, 12, false, false ) ) + ( twinrx_gain_config_t( 27, -7.3, 30, 11, false, false ) ) + ( twinrx_gain_config_t( 28, -6.3, 29, 11, false, false ) ) + ( twinrx_gain_config_t( 29, -5.3, 28, 11, false, false ) ) + ( twinrx_gain_config_t( 30, -4.3, 27, 11, false, false ) ) + ( twinrx_gain_config_t( 31, -3.3, 27, 10, false, false ) ) + ( twinrx_gain_config_t( 32, -2.3, 26, 10, false, false ) ) + ( twinrx_gain_config_t( 33, -1.3, 25, 10, false, false ) ) + ( twinrx_gain_config_t( 34, -0.3, 24, 10, false, false ) ) + ( twinrx_gain_config_t( 35, 0.7, 23, 10, false, false ) ) + ( twinrx_gain_config_t( 36, 1.7, 22, 10, false, false ) ) + ( twinrx_gain_config_t( 37, 2.7, 21, 10, false, false ) ) + ( twinrx_gain_config_t( 38, 3.7, 21, 9, false, false ) ) + ( twinrx_gain_config_t( 39, 4.7, 20, 9, false, false ) ) + ( twinrx_gain_config_t( 40, 5.7, 19, 9, false, false ) ) + ( twinrx_gain_config_t( 41, 6.7, 18, 9, false, false ) ) + ( twinrx_gain_config_t( 42, 7.7, 17, 9, false, false ) ) + ( twinrx_gain_config_t( 43, 8.7, 16, 9, false, false ) ) + ( twinrx_gain_config_t( 44, 9.7, 15, 9, false, false ) ) + ( twinrx_gain_config_t( 45, 10.7, 14, 9, false, false ) ) + ( twinrx_gain_config_t( 46, 11.7, 13, 9, false, false ) ) + ( twinrx_gain_config_t( 47, 12.7, 12, 9, false, false ) ) + ( twinrx_gain_config_t( 48, 13.7, 11, 9, false, false ) ) + ( twinrx_gain_config_t( 49, 14.7, 10, 9, false, false ) ) + ( twinrx_gain_config_t( 50, 15.7, 9, 9, false, false ) ) + ( twinrx_gain_config_t( 51, 16.7, 8, 9, false, false ) ) + ( twinrx_gain_config_t( 52, 17.7, 7, 9, false, false ) ) + ( twinrx_gain_config_t( 53, 18.7, 6, 9, false, false ) ) + ( twinrx_gain_config_t( 54, 19.7, 5, 9, false, false ) ) + ( twinrx_gain_config_t( 55, 20.7, 4, 9, false, false ) ) + ( twinrx_gain_config_t( 56, 21.7, 3, 9, false, false ) ) + ( twinrx_gain_config_t( 57, 22.7, 2, 9, false, false ) ) + ( twinrx_gain_config_t( 58, 23.7, 1, 9, false, false ) ) + ( twinrx_gain_config_t( 59, 24.7, 0, 9, false, false ) ) + ( twinrx_gain_config_t( 60, 25.7, 0, 8, false, false ) ) + ( twinrx_gain_config_t( 61, 26.7, 0, 7, false, false ) ) + ( twinrx_gain_config_t( 62, 27.7, 0, 6, false, false ) ) + ( twinrx_gain_config_t( 63, 28.7, 0, 5, false, false ) ) + ( twinrx_gain_config_t( 64, 29.7, 0, 4, false, false ) ) + ( twinrx_gain_config_t( 65, 30.7, 0, 3, false, false ) ) + ( twinrx_gain_config_t( 66, 31.7, 0, 2, false, false ) ) + ( twinrx_gain_config_t( 67, 32.7, 0, 1, false, false ) ) + ( twinrx_gain_config_t( 68, 33.7, 0, 0, false, false ) ) + ( twinrx_gain_config_t( 69, 33.9, 3, 9, true, false ) ) + ( twinrx_gain_config_t( 70, 34.9, 2, 9, true, false ) ) + ( twinrx_gain_config_t( 71, 35.9, 1, 9, true, false ) ) + ( twinrx_gain_config_t( 72, 36.9, 0, 9, true, false ) ) + ( twinrx_gain_config_t( 73, 37.9, 0, 8, true, false ) ) + ( twinrx_gain_config_t( 74, 38.9, 0, 7, true, false ) ) + ( twinrx_gain_config_t( 75, 39.9, 0, 6, true, false ) ) + ( twinrx_gain_config_t( 76, 40.9, 0, 5, true, false ) ) + ( twinrx_gain_config_t( 77, 41.9, 0, 4, true, false ) ) + ( twinrx_gain_config_t( 78, 42.9, 0, 3, true, false ) ) + ( twinrx_gain_config_t( 79, 43.9, 0, 2, true, false ) ) + ( twinrx_gain_config_t( 80, 44.9, 0, 1, true, false ) ) + ( twinrx_gain_config_t( 81, 45.9, 0, 0, true, false ) ) + ( twinrx_gain_config_t( 82, 47.3, 1, 10, true, true ) ) + ( twinrx_gain_config_t( 83, 48.3, 0, 10, true, true ) ) + ( twinrx_gain_config_t( 84, 49.3, 0, 9, true, true ) ) + ( twinrx_gain_config_t( 85, 50.3, 0, 8, true, true ) ) + ( twinrx_gain_config_t( 86, 51.3, 0, 7, true, true ) ) + ( twinrx_gain_config_t( 87, 52.3, 0, 6, true, true ) ) + ( twinrx_gain_config_t( 88, 53.3, 0, 5, true, true ) ) + ( twinrx_gain_config_t( 89, 54.3, 0, 4, true, true ) ) + ( twinrx_gain_config_t( 90, 55.3, 0, 3, true, true ) ) + ( twinrx_gain_config_t( 91, 56.3, 0, 2, true, true ) ) + ( twinrx_gain_config_t( 92, 57.3, 0, 1, true, true ) ) + ( twinrx_gain_config_t( 93, 58.3, 0, 0, true, true ) ) +; + +static const std::vector<twinrx_gain_config_t> HIGHBAND2_TABLE = boost::assign::list_of + // Index, Gain, Atten1, Atten2, Amp1, Amp2 + ( twinrx_gain_config_t( 0, -30.9, 31, 31, false, false ) ) + ( twinrx_gain_config_t( 1, -30.9, 31, 31, false, false ) ) + ( twinrx_gain_config_t( 2, -30.9, 31, 31, false, false ) ) + ( twinrx_gain_config_t( 3, -30.9, 31, 31, false, false ) ) + ( twinrx_gain_config_t( 4, -30.9, 31, 31, false, false ) ) + ( twinrx_gain_config_t( 5, -30.9, 31, 31, false, false ) ) + ( twinrx_gain_config_t( 6, -30.9, 31, 31, false, false ) ) + ( twinrx_gain_config_t( 7, -30.9, 31, 31, false, false ) ) + ( twinrx_gain_config_t( 8, -29.9, 31, 30, false, false ) ) + ( twinrx_gain_config_t( 9, -28.9, 31, 29, false, false ) ) + ( twinrx_gain_config_t( 10, -27.9, 31, 28, false, false ) ) + ( twinrx_gain_config_t( 11, -26.9, 31, 27, false, false ) ) + ( twinrx_gain_config_t( 12, -25.9, 31, 26, false, false ) ) + ( twinrx_gain_config_t( 13, -24.9, 31, 25, false, false ) ) + ( twinrx_gain_config_t( 14, -23.9, 31, 24, false, false ) ) + ( twinrx_gain_config_t( 15, -22.9, 31, 23, false, false ) ) + ( twinrx_gain_config_t( 16, -21.9, 31, 22, false, false ) ) + ( twinrx_gain_config_t( 17, -20.9, 31, 21, false, false ) ) + ( twinrx_gain_config_t( 18, -19.9, 31, 20, false, false ) ) + ( twinrx_gain_config_t( 19, -18.9, 31, 19, false, false ) ) + ( twinrx_gain_config_t( 20, -17.9, 31, 18, false, false ) ) + ( twinrx_gain_config_t( 21, -16.9, 31, 17, false, false ) ) + ( twinrx_gain_config_t( 22, -15.9, 31, 16, false, false ) ) + ( twinrx_gain_config_t( 23, -14.9, 31, 15, false, false ) ) + ( twinrx_gain_config_t( 24, -13.9, 31, 14, false, false ) ) + ( twinrx_gain_config_t( 25, -12.9, 31, 13, false, false ) ) + ( twinrx_gain_config_t( 26, -11.9, 31, 12, false, false ) ) + ( twinrx_gain_config_t( 27, -10.9, 31, 11, false, false ) ) + ( twinrx_gain_config_t( 28, -9.9, 30, 11, false, false ) ) + ( twinrx_gain_config_t( 29, -8.9, 29, 11, false, false ) ) + ( twinrx_gain_config_t( 30, -7.9, 29, 10, false, false ) ) + ( twinrx_gain_config_t( 31, -6.9, 28, 10, false, false ) ) + ( twinrx_gain_config_t( 32, -5.9, 27, 10, false, false ) ) + ( twinrx_gain_config_t( 33, -4.9, 27, 9, false, false ) ) + ( twinrx_gain_config_t( 34, -3.9, 26, 9, false, false ) ) + ( twinrx_gain_config_t( 35, -2.9, 25, 9, false, false ) ) + ( twinrx_gain_config_t( 36, -1.9, 24, 9, false, false ) ) + ( twinrx_gain_config_t( 37, -0.9, 23, 9, false, false ) ) + ( twinrx_gain_config_t( 38, 0.1, 23, 8, false, false ) ) + ( twinrx_gain_config_t( 39, 1.1, 22, 8, false, false ) ) + ( twinrx_gain_config_t( 40, 2.1, 21, 8, false, false ) ) + ( twinrx_gain_config_t( 41, 3.1, 20, 8, false, false ) ) + ( twinrx_gain_config_t( 42, 4.1, 19, 8, false, false ) ) + ( twinrx_gain_config_t( 43, 5.1, 18, 8, false, false ) ) + ( twinrx_gain_config_t( 44, 6.1, 17, 8, false, false ) ) + ( twinrx_gain_config_t( 45, 7.1, 16, 8, false, false ) ) + ( twinrx_gain_config_t( 46, 8.1, 15, 8, false, false ) ) + ( twinrx_gain_config_t( 47, 9.1, 14, 8, false, false ) ) + ( twinrx_gain_config_t( 48, 10.1, 13, 8, false, false ) ) + ( twinrx_gain_config_t( 49, 11.1, 12, 8, false, false ) ) + ( twinrx_gain_config_t( 50, 12.1, 11, 8, false, false ) ) + ( twinrx_gain_config_t( 51, 13.1, 10, 8, false, false ) ) + ( twinrx_gain_config_t( 52, 14.1, 9, 8, false, false ) ) + ( twinrx_gain_config_t( 53, 15.1, 8, 8, false, false ) ) + ( twinrx_gain_config_t( 54, 16.1, 7, 8, false, false ) ) + ( twinrx_gain_config_t( 55, 17.1, 6, 8, false, false ) ) + ( twinrx_gain_config_t( 56, 18.1, 5, 8, false, false ) ) + ( twinrx_gain_config_t( 57, 19.1, 4, 8, false, false ) ) + ( twinrx_gain_config_t( 58, 20.1, 3, 8, false, false ) ) + ( twinrx_gain_config_t( 59, 21.1, 2, 8, false, false ) ) + ( twinrx_gain_config_t( 60, 22.1, 1, 8, false, false ) ) + ( twinrx_gain_config_t( 61, 23.1, 0, 8, false, false ) ) + ( twinrx_gain_config_t( 62, 24.1, 0, 7, false, false ) ) + ( twinrx_gain_config_t( 63, 25.1, 0, 6, false, false ) ) + ( twinrx_gain_config_t( 64, 26.1, 0, 5, false, false ) ) + ( twinrx_gain_config_t( 65, 27.1, 0, 4, false, false ) ) + ( twinrx_gain_config_t( 66, 28.1, 0, 3, false, false ) ) + ( twinrx_gain_config_t( 67, 29.1, 0, 2, false, false ) ) + ( twinrx_gain_config_t( 68, 30.1, 0, 1, false, false ) ) + ( twinrx_gain_config_t( 69, 31.9, 0, 10, false, true ) ) + ( twinrx_gain_config_t( 70, 31.9, 0, 10, false, true ) ) + ( twinrx_gain_config_t( 71, 32.9, 0, 9, false, true ) ) + ( twinrx_gain_config_t( 72, 33.9, 0, 8, false, true ) ) + ( twinrx_gain_config_t( 73, 34.9, 0, 7, false, true ) ) + ( twinrx_gain_config_t( 74, 35.9, 0, 6, false, true ) ) + ( twinrx_gain_config_t( 75, 36.9, 0, 5, false, true ) ) + ( twinrx_gain_config_t( 76, 38.6, 0, 6, true, false ) ) + ( twinrx_gain_config_t( 77, 39.6, 0, 5, true, false ) ) + ( twinrx_gain_config_t( 78, 40.6, 0, 4, true, false ) ) + ( twinrx_gain_config_t( 79, 41.6, 0, 3, true, false ) ) + ( twinrx_gain_config_t( 80, 42.6, 0, 2, true, false ) ) + ( twinrx_gain_config_t( 81, 43.6, 0, 1, true, false ) ) + ( twinrx_gain_config_t( 82, 44.4, 2, 9, true, true ) ) + ( twinrx_gain_config_t( 83, 45.4, 1, 9, true, true ) ) + ( twinrx_gain_config_t( 84, 46.4, 0, 9, true, true ) ) + ( twinrx_gain_config_t( 85, 47.4, 0, 8, true, true ) ) + ( twinrx_gain_config_t( 86, 48.4, 0, 7, true, true ) ) + ( twinrx_gain_config_t( 87, 49.4, 0, 6, true, true ) ) + ( twinrx_gain_config_t( 88, 50.4, 0, 5, true, true ) ) + ( twinrx_gain_config_t( 89, 51.4, 0, 4, true, true ) ) + ( twinrx_gain_config_t( 90, 52.4, 0, 3, true, true ) ) + ( twinrx_gain_config_t( 91, 53.4, 0, 2, true, true ) ) + ( twinrx_gain_config_t( 92, 54.4, 0, 1, true, true ) ) + ( twinrx_gain_config_t( 93, 55.4, 0, 0, true, true ) ) +; + +static const std::vector<twinrx_gain_config_t> HIGHBAND3_TABLE = boost::assign::list_of + // Index, Gain, Atten1, Atten2, Amp1, Amp2 + ( twinrx_gain_config_t( 0, -31.1, 31, 31, false, false ) ) + ( twinrx_gain_config_t( 1, -31.1, 31, 31, false, false ) ) + ( twinrx_gain_config_t( 2, -31.1, 31, 31, false, false ) ) + ( twinrx_gain_config_t( 3, -31.1, 31, 31, false, false ) ) + ( twinrx_gain_config_t( 4, -31.1, 31, 31, false, false ) ) + ( twinrx_gain_config_t( 5, -31.1, 31, 31, false, false ) ) + ( twinrx_gain_config_t( 6, -31.1, 31, 31, false, false ) ) + ( twinrx_gain_config_t( 7, -30.1, 31, 30, false, false ) ) + ( twinrx_gain_config_t( 8, -29.1, 31, 29, false, false ) ) + ( twinrx_gain_config_t( 9, -28.1, 31, 28, false, false ) ) + ( twinrx_gain_config_t( 10, -27.1, 31, 27, false, false ) ) + ( twinrx_gain_config_t( 11, -26.1, 31, 26, false, false ) ) + ( twinrx_gain_config_t( 12, -25.1, 31, 25, false, false ) ) + ( twinrx_gain_config_t( 13, -24.1, 31, 24, false, false ) ) + ( twinrx_gain_config_t( 14, -23.1, 31, 23, false, false ) ) + ( twinrx_gain_config_t( 15, -22.1, 31, 22, false, false ) ) + ( twinrx_gain_config_t( 16, -21.1, 31, 21, false, false ) ) + ( twinrx_gain_config_t( 17, -20.1, 31, 20, false, false ) ) + ( twinrx_gain_config_t( 18, -19.1, 31, 19, false, false ) ) + ( twinrx_gain_config_t( 19, -18.1, 31, 18, false, false ) ) + ( twinrx_gain_config_t( 20, -17.1, 31, 17, false, false ) ) + ( twinrx_gain_config_t( 21, -16.1, 31, 16, false, false ) ) + ( twinrx_gain_config_t( 22, -15.1, 31, 15, false, false ) ) + ( twinrx_gain_config_t( 23, -14.1, 31, 14, false, false ) ) + ( twinrx_gain_config_t( 24, -13.1, 31, 13, false, false ) ) + ( twinrx_gain_config_t( 25, -12.1, 30, 13, false, false ) ) + ( twinrx_gain_config_t( 26, -11.1, 30, 12, false, false ) ) + ( twinrx_gain_config_t( 27, -10.1, 29, 12, false, false ) ) + ( twinrx_gain_config_t( 28, -9.1, 28, 12, false, false ) ) + ( twinrx_gain_config_t( 29, -8.1, 28, 11, false, false ) ) + ( twinrx_gain_config_t( 30, -7.1, 27, 11, false, false ) ) + ( twinrx_gain_config_t( 31, -6.1, 26, 11, false, false ) ) + ( twinrx_gain_config_t( 32, -5.1, 26, 10, false, false ) ) + ( twinrx_gain_config_t( 33, -4.1, 25, 10, false, false ) ) + ( twinrx_gain_config_t( 34, -3.1, 24, 10, false, false ) ) + ( twinrx_gain_config_t( 35, -2.1, 23, 10, false, false ) ) + ( twinrx_gain_config_t( 36, -1.1, 22, 10, false, false ) ) + ( twinrx_gain_config_t( 37, -0.1, 21, 10, false, false ) ) + ( twinrx_gain_config_t( 38, 0.9, 21, 9, false, false ) ) + ( twinrx_gain_config_t( 39, 1.9, 20, 9, false, false ) ) + ( twinrx_gain_config_t( 40, 2.9, 19, 9, false, false ) ) + ( twinrx_gain_config_t( 41, 3.9, 18, 9, false, false ) ) + ( twinrx_gain_config_t( 42, 4.9, 17, 9, false, false ) ) + ( twinrx_gain_config_t( 43, 5.9, 16, 9, false, false ) ) + ( twinrx_gain_config_t( 44, 6.9, 15, 9, false, false ) ) + ( twinrx_gain_config_t( 45, 7.9, 14, 9, false, false ) ) + ( twinrx_gain_config_t( 46, 8.9, 13, 9, false, false ) ) + ( twinrx_gain_config_t( 47, 9.9, 12, 9, false, false ) ) + ( twinrx_gain_config_t( 48, 10.9, 11, 9, false, false ) ) + ( twinrx_gain_config_t( 49, 11.9, 10, 9, false, false ) ) + ( twinrx_gain_config_t( 50, 12.9, 9, 9, false, false ) ) + ( twinrx_gain_config_t( 51, 13.9, 8, 9, false, false ) ) + ( twinrx_gain_config_t( 52, 14.9, 7, 9, false, false ) ) + ( twinrx_gain_config_t( 53, 15.9, 6, 9, false, false ) ) + ( twinrx_gain_config_t( 54, 16.9, 5, 9, false, false ) ) + ( twinrx_gain_config_t( 55, 17.9, 4, 9, false, false ) ) + ( twinrx_gain_config_t( 56, 18.9, 3, 9, false, false ) ) + ( twinrx_gain_config_t( 57, 19.9, 2, 9, false, false ) ) + ( twinrx_gain_config_t( 58, 20.9, 1, 9, false, false ) ) + ( twinrx_gain_config_t( 59, 21.9, 0, 9, false, false ) ) + ( twinrx_gain_config_t( 60, 22.9, 0, 8, false, false ) ) + ( twinrx_gain_config_t( 61, 23.9, 0, 7, false, false ) ) + ( twinrx_gain_config_t( 62, 24.9, 0, 6, false, false ) ) + ( twinrx_gain_config_t( 63, 25.9, 0, 5, false, false ) ) + ( twinrx_gain_config_t( 64, 26.9, 0, 4, false, false ) ) + ( twinrx_gain_config_t( 65, 27.9, 0, 3, false, false ) ) + ( twinrx_gain_config_t( 66, 28.9, 0, 2, false, false ) ) + ( twinrx_gain_config_t( 67, 29.9, 0, 1, false, false ) ) + ( twinrx_gain_config_t( 68, 31.3, 0, 9, false, true ) ) + ( twinrx_gain_config_t( 69, 32.3, 0, 8, false, true ) ) + ( twinrx_gain_config_t( 70, 33.3, 0, 7, false, true ) ) + ( twinrx_gain_config_t( 71, 34.3, 0, 6, false, true ) ) + ( twinrx_gain_config_t( 72, 35.3, 0, 5, false, true ) ) + ( twinrx_gain_config_t( 73, 36.3, 0, 4, false, true ) ) + ( twinrx_gain_config_t( 74, 37.3, 0, 3, false, true ) ) + ( twinrx_gain_config_t( 75, 37.6, 0, 9, true, false ) ) + ( twinrx_gain_config_t( 76, 38.6, 0, 8, true, false ) ) + ( twinrx_gain_config_t( 77, 39.6, 0, 7, true, false ) ) + ( twinrx_gain_config_t( 78, 40.6, 0, 6, true, false ) ) + ( twinrx_gain_config_t( 79, 41.6, 0, 5, true, false ) ) + ( twinrx_gain_config_t( 80, 42.6, 0, 4, true, false ) ) + ( twinrx_gain_config_t( 81, 43.6, 0, 3, true, false ) ) + ( twinrx_gain_config_t( 82, 44.6, 0, 2, true, false ) ) + ( twinrx_gain_config_t( 83, 45.6, 0, 1, true, false ) ) + ( twinrx_gain_config_t( 84, 47.0, 0, 9, true, true ) ) + ( twinrx_gain_config_t( 85, 48.0, 0, 8, true, true ) ) + ( twinrx_gain_config_t( 86, 49.0, 0, 7, true, true ) ) + ( twinrx_gain_config_t( 87, 50.0, 0, 6, true, true ) ) + ( twinrx_gain_config_t( 88, 51.0, 0, 5, true, true ) ) + ( twinrx_gain_config_t( 89, 52.0, 0, 4, true, true ) ) + ( twinrx_gain_config_t( 90, 53.0, 0, 3, true, true ) ) + ( twinrx_gain_config_t( 91, 54.0, 0, 2, true, true ) ) + ( twinrx_gain_config_t( 92, 55.0, 0, 1, true, true ) ) + ( twinrx_gain_config_t( 93, 56.0, 0, 0, true, true ) ) +; + +static const std::vector<twinrx_gain_config_t> HIGHBAND4_TABLE = boost::assign::list_of + // Index, Gain, Atten1, Atten2, Amp1, Amp2 + ( twinrx_gain_config_t( 0, -37.2, 31, 31, false, false ) ) + ( twinrx_gain_config_t( 1, -37.2, 31, 31, false, false ) ) + ( twinrx_gain_config_t( 2, -37.2, 31, 31, false, false ) ) + ( twinrx_gain_config_t( 3, -37.2, 31, 31, false, false ) ) + ( twinrx_gain_config_t( 4, -37.2, 31, 31, false, false ) ) + ( twinrx_gain_config_t( 5, -37.2, 31, 31, false, false ) ) + ( twinrx_gain_config_t( 6, -37.2, 31, 31, false, false ) ) + ( twinrx_gain_config_t( 7, -37.2, 31, 31, false, false ) ) + ( twinrx_gain_config_t( 8, -37.2, 31, 31, false, false ) ) + ( twinrx_gain_config_t( 9, -36.2, 31, 30, false, false ) ) + ( twinrx_gain_config_t( 10, -35.2, 31, 29, false, false ) ) + ( twinrx_gain_config_t( 11, -34.2, 31, 28, false, false ) ) + ( twinrx_gain_config_t( 12, -33.2, 31, 27, false, false ) ) + ( twinrx_gain_config_t( 13, -32.2, 31, 26, false, false ) ) + ( twinrx_gain_config_t( 14, -31.2, 31, 25, false, false ) ) + ( twinrx_gain_config_t( 15, -30.2, 31, 24, false, false ) ) + ( twinrx_gain_config_t( 16, -29.2, 31, 23, false, false ) ) + ( twinrx_gain_config_t( 17, -28.2, 31, 22, false, false ) ) + ( twinrx_gain_config_t( 18, -27.2, 31, 21, false, false ) ) + ( twinrx_gain_config_t( 19, -26.2, 31, 20, false, false ) ) + ( twinrx_gain_config_t( 20, -25.2, 31, 19, false, false ) ) + ( twinrx_gain_config_t( 21, -24.2, 31, 18, false, false ) ) + ( twinrx_gain_config_t( 22, -23.2, 31, 17, false, false ) ) + ( twinrx_gain_config_t( 23, -22.2, 31, 16, false, false ) ) + ( twinrx_gain_config_t( 24, -21.2, 31, 15, false, false ) ) + ( twinrx_gain_config_t( 25, -20.2, 31, 14, false, false ) ) + ( twinrx_gain_config_t( 26, -19.2, 31, 13, false, false ) ) + ( twinrx_gain_config_t( 27, -18.2, 31, 12, false, false ) ) + ( twinrx_gain_config_t( 28, -17.2, 31, 11, false, false ) ) + ( twinrx_gain_config_t( 29, -16.2, 31, 10, false, false ) ) + ( twinrx_gain_config_t( 30, -15.2, 30, 10, false, false ) ) + ( twinrx_gain_config_t( 31, -14.2, 30, 9, false, false ) ) + ( twinrx_gain_config_t( 32, -13.2, 29, 9, false, false ) ) + ( twinrx_gain_config_t( 33, -12.2, 28, 9, false, false ) ) + ( twinrx_gain_config_t( 34, -11.2, 28, 8, false, false ) ) + ( twinrx_gain_config_t( 35, -10.2, 27, 8, false, false ) ) + ( twinrx_gain_config_t( 36, -9.2, 27, 7, false, false ) ) + ( twinrx_gain_config_t( 37, -8.2, 26, 7, false, false ) ) + ( twinrx_gain_config_t( 38, -7.2, 25, 7, false, false ) ) + ( twinrx_gain_config_t( 39, -6.2, 24, 7, false, false ) ) + ( twinrx_gain_config_t( 40, -5.2, 24, 6, false, false ) ) + ( twinrx_gain_config_t( 41, -4.2, 23, 6, false, false ) ) + ( twinrx_gain_config_t( 42, -3.2, 22, 6, false, false ) ) + ( twinrx_gain_config_t( 43, -2.2, 21, 6, false, false ) ) + ( twinrx_gain_config_t( 44, -1.2, 20, 6, false, false ) ) + ( twinrx_gain_config_t( 45, -0.2, 19, 6, false, false ) ) + ( twinrx_gain_config_t( 46, 0.8, 18, 6, false, false ) ) + ( twinrx_gain_config_t( 47, 1.8, 17, 6, false, false ) ) + ( twinrx_gain_config_t( 48, 2.8, 16, 6, false, false ) ) + ( twinrx_gain_config_t( 49, 3.8, 16, 5, false, false ) ) + ( twinrx_gain_config_t( 50, 4.8, 15, 5, false, false ) ) + ( twinrx_gain_config_t( 51, 5.8, 14, 5, false, false ) ) + ( twinrx_gain_config_t( 52, 6.8, 13, 5, false, false ) ) + ( twinrx_gain_config_t( 53, 7.8, 12, 5, false, false ) ) + ( twinrx_gain_config_t( 54, 8.8, 11, 5, false, false ) ) + ( twinrx_gain_config_t( 55, 9.8, 10, 5, false, false ) ) + ( twinrx_gain_config_t( 56, 10.8, 9, 5, false, false ) ) + ( twinrx_gain_config_t( 57, 11.8, 8, 5, false, false ) ) + ( twinrx_gain_config_t( 58, 12.8, 7, 5, false, false ) ) + ( twinrx_gain_config_t( 59, 13.8, 6, 5, false, false ) ) + ( twinrx_gain_config_t( 60, 14.8, 5, 5, false, false ) ) + ( twinrx_gain_config_t( 61, 15.8, 4, 5, false, false ) ) + ( twinrx_gain_config_t( 62, 16.8, 3, 5, false, false ) ) + ( twinrx_gain_config_t( 63, 17.8, 2, 5, false, false ) ) + ( twinrx_gain_config_t( 64, 18.8, 1, 5, false, false ) ) + ( twinrx_gain_config_t( 65, 19.8, 0, 5, false, false ) ) + ( twinrx_gain_config_t( 66, 20.8, 0, 4, false, false ) ) + ( twinrx_gain_config_t( 67, 21.8, 0, 3, false, false ) ) + ( twinrx_gain_config_t( 68, 22.8, 0, 2, false, false ) ) + ( twinrx_gain_config_t( 69, 23.8, 0, 1, false, false ) ) + ( twinrx_gain_config_t( 70, 24.8, 0, 0, false, false ) ) + ( twinrx_gain_config_t( 71, 26.1, 0, 6, false, true ) ) + ( twinrx_gain_config_t( 72, 26.1, 0, 6, false, true ) ) + ( twinrx_gain_config_t( 73, 27.1, 0, 5, false, true ) ) + ( twinrx_gain_config_t( 74, 28.1, 0, 4, false, true ) ) + ( twinrx_gain_config_t( 75, 29.1, 0, 3, false, true ) ) + ( twinrx_gain_config_t( 76, 30.1, 0, 2, false, true ) ) + ( twinrx_gain_config_t( 77, 31.1, 0, 1, false, true ) ) + ( twinrx_gain_config_t( 78, 32.1, 0, 0, false, true ) ) + ( twinrx_gain_config_t( 79, 33.3, 0, 7, true, false ) ) + ( twinrx_gain_config_t( 80, 34.3, 0, 6, true, false ) ) + ( twinrx_gain_config_t( 81, 35.3, 0, 5, true, false ) ) + ( twinrx_gain_config_t( 82, 36.3, 0, 4, true, false ) ) + ( twinrx_gain_config_t( 83, 37.3, 0, 3, true, false ) ) + ( twinrx_gain_config_t( 84, 38.3, 0, 2, true, false ) ) + ( twinrx_gain_config_t( 85, 39.3, 0, 1, true, false ) ) + ( twinrx_gain_config_t( 86, 40.3, 0, 0, true, false ) ) + ( twinrx_gain_config_t( 87, 41.6, 0, 6, true, true ) ) + ( twinrx_gain_config_t( 88, 42.6, 0, 5, true, true ) ) + ( twinrx_gain_config_t( 89, 43.6, 0, 4, true, true ) ) + ( twinrx_gain_config_t( 90, 44.6, 0, 3, true, true ) ) + ( twinrx_gain_config_t( 91, 45.6, 0, 2, true, true ) ) + ( twinrx_gain_config_t( 92, 46.6, 0, 1, true, true ) ) + ( twinrx_gain_config_t( 93, 47.6, 0, 0, true, true ) ) +; + +static const std::vector<twinrx_gain_config_t> LOWBAND1_TABLE = boost::assign::list_of + // Index, Gain, Atten1, Atten2, Amp1, Amp2 + ( twinrx_gain_config_t( 0, -31.1, 31, 31, false, false ) ) + ( twinrx_gain_config_t( 1, -30.1, 31, 30, false, false ) ) + ( twinrx_gain_config_t( 2, -29.1, 31, 29, false, false ) ) + ( twinrx_gain_config_t( 3, -28.1, 31, 28, false, false ) ) + ( twinrx_gain_config_t( 4, -27.1, 31, 27, false, false ) ) + ( twinrx_gain_config_t( 5, -26.1, 31, 26, false, false ) ) + ( twinrx_gain_config_t( 6, -25.1, 31, 25, false, false ) ) + ( twinrx_gain_config_t( 7, -24.1, 31, 24, false, false ) ) + ( twinrx_gain_config_t( 8, -23.1, 31, 23, false, false ) ) + ( twinrx_gain_config_t( 9, -22.1, 31, 22, false, false ) ) + ( twinrx_gain_config_t( 10, -21.1, 31, 21, false, false ) ) + ( twinrx_gain_config_t( 11, -20.1, 31, 20, false, false ) ) + ( twinrx_gain_config_t( 12, -19.1, 31, 19, false, false ) ) + ( twinrx_gain_config_t( 13, -18.1, 31, 18, false, false ) ) + ( twinrx_gain_config_t( 14, -17.1, 31, 17, false, false ) ) + ( twinrx_gain_config_t( 15, -16.1, 31, 16, false, false ) ) + ( twinrx_gain_config_t( 16, -15.1, 31, 15, false, false ) ) + ( twinrx_gain_config_t( 17, -14.1, 31, 14, false, false ) ) + ( twinrx_gain_config_t( 18, -13.1, 31, 13, false, false ) ) + ( twinrx_gain_config_t( 19, -12.1, 31, 12, false, false ) ) + ( twinrx_gain_config_t( 20, -11.1, 31, 11, false, false ) ) + ( twinrx_gain_config_t( 21, -10.1, 31, 10, false, false ) ) + ( twinrx_gain_config_t( 22, -9.1, 31, 9, false, false ) ) + ( twinrx_gain_config_t( 23, -8.1, 31, 8, false, false ) ) + ( twinrx_gain_config_t( 24, -7.1, 31, 7, false, false ) ) + ( twinrx_gain_config_t( 25, -6.1, 31, 6, false, false ) ) + ( twinrx_gain_config_t( 26, -5.1, 31, 5, false, false ) ) + ( twinrx_gain_config_t( 27, -4.1, 31, 4, false, false ) ) + ( twinrx_gain_config_t( 28, -3.1, 31, 3, false, false ) ) + ( twinrx_gain_config_t( 29, -2.1, 31, 2, false, false ) ) + ( twinrx_gain_config_t( 30, -1.1, 31, 1, false, false ) ) + ( twinrx_gain_config_t( 31, -0.1, 31, 0, false, false ) ) + ( twinrx_gain_config_t( 32, 0.9, 30, 0, false, false ) ) + ( twinrx_gain_config_t( 33, 1.9, 29, 0, false, false ) ) + ( twinrx_gain_config_t( 34, 2.9, 28, 0, false, false ) ) + ( twinrx_gain_config_t( 35, 3.9, 27, 0, false, false ) ) + ( twinrx_gain_config_t( 36, 4.9, 26, 0, false, false ) ) + ( twinrx_gain_config_t( 37, 5.9, 25, 0, false, false ) ) + ( twinrx_gain_config_t( 38, 6.9, 24, 0, false, false ) ) + ( twinrx_gain_config_t( 39, 7.9, 23, 0, false, false ) ) + ( twinrx_gain_config_t( 40, 8.9, 22, 0, false, false ) ) + ( twinrx_gain_config_t( 41, 9.9, 21, 0, false, false ) ) + ( twinrx_gain_config_t( 42, 10.9, 20, 0, false, false ) ) + ( twinrx_gain_config_t( 43, 11.9, 19, 0, false, false ) ) + ( twinrx_gain_config_t( 44, 12.9, 18, 0, false, false ) ) + ( twinrx_gain_config_t( 45, 13.9, 17, 0, false, false ) ) + ( twinrx_gain_config_t( 46, 14.9, 16, 0, false, false ) ) + ( twinrx_gain_config_t( 47, 15.9, 15, 0, false, false ) ) + ( twinrx_gain_config_t( 48, 16.9, 14, 0, false, false ) ) + ( twinrx_gain_config_t( 49, 17.9, 13, 0, false, false ) ) + ( twinrx_gain_config_t( 50, 18.9, 12, 0, false, false ) ) + ( twinrx_gain_config_t( 51, 19.9, 11, 0, false, false ) ) + ( twinrx_gain_config_t( 52, 20.9, 10, 0, false, false ) ) + ( twinrx_gain_config_t( 53, 21.9, 9, 0, false, false ) ) + ( twinrx_gain_config_t( 54, 22.9, 8, 0, false, false ) ) + ( twinrx_gain_config_t( 55, 23.9, 7, 0, false, false ) ) + ( twinrx_gain_config_t( 56, 24.9, 6, 0, false, false ) ) + ( twinrx_gain_config_t( 57, 25.9, 5, 0, false, false ) ) + ( twinrx_gain_config_t( 58, 26.9, 4, 0, false, false ) ) + ( twinrx_gain_config_t( 59, 27.9, 3, 0, false, false ) ) + ( twinrx_gain_config_t( 60, 28.9, 2, 0, false, false ) ) + ( twinrx_gain_config_t( 61, 29.9, 1, 0, false, false ) ) + ( twinrx_gain_config_t( 62, 30.9, 0, 0, false, false ) ) + ( twinrx_gain_config_t( 63, 31.2, 4, 11, false, true ) ) + ( twinrx_gain_config_t( 64, 32.2, 3, 11, false, true ) ) + ( twinrx_gain_config_t( 65, 33.2, 2, 11, false, true ) ) + ( twinrx_gain_config_t( 66, 34.2, 1, 11, false, true ) ) + ( twinrx_gain_config_t( 67, 35.2, 0, 11, false, true ) ) + ( twinrx_gain_config_t( 68, 36.2, 10, 0, true, false ) ) + ( twinrx_gain_config_t( 69, 37.2, 9, 0, true, false ) ) + ( twinrx_gain_config_t( 70, 38.2, 8, 0, true, false ) ) + ( twinrx_gain_config_t( 71, 39.2, 7, 0, true, false ) ) + ( twinrx_gain_config_t( 72, 40.2, 6, 0, true, false ) ) + ( twinrx_gain_config_t( 73, 41.2, 5, 0, true, false ) ) + ( twinrx_gain_config_t( 74, 42.2, 4, 0, true, false ) ) + ( twinrx_gain_config_t( 75, 43.2, 3, 0, true, false ) ) + ( twinrx_gain_config_t( 76, 44.2, 2, 0, true, false ) ) + ( twinrx_gain_config_t( 77, 45.2, 1, 0, true, false ) ) + ( twinrx_gain_config_t( 78, 46.2, 0, 0, true, false ) ) + ( twinrx_gain_config_t( 79, 47.5, 4, 10, true, true ) ) + ( twinrx_gain_config_t( 80, 48.5, 3, 10, true, true ) ) + ( twinrx_gain_config_t( 81, 49.5, 3, 9, true, true ) ) + ( twinrx_gain_config_t( 82, 50.5, 2, 9, true, true ) ) + ( twinrx_gain_config_t( 83, 51.5, 1, 9, true, true ) ) + ( twinrx_gain_config_t( 84, 52.5, 1, 8, true, true ) ) + ( twinrx_gain_config_t( 85, 53.5, 0, 8, true, true ) ) + ( twinrx_gain_config_t( 86, 54.5, 0, 7, true, true ) ) + ( twinrx_gain_config_t( 87, 55.5, 0, 6, true, true ) ) + ( twinrx_gain_config_t( 88, 56.5, 0, 5, true, true ) ) + ( twinrx_gain_config_t( 89, 57.5, 0, 4, true, true ) ) + ( twinrx_gain_config_t( 90, 58.5, 0, 3, true, true ) ) + ( twinrx_gain_config_t( 91, 59.5, 0, 2, true, true ) ) + ( twinrx_gain_config_t( 92, 60.5, 0, 1, true, true ) ) + ( twinrx_gain_config_t( 93, 61.5, 0, 0, true, true ) ) +; + +static const std::vector<twinrx_gain_config_t> LOWBAND2_TABLE = boost::assign::list_of + // Index, Gain, Atten1, Atten2, Amp1, Amp2 + ( twinrx_gain_config_t( 0, -33.4, 31, 31, false, false ) ) + ( twinrx_gain_config_t( 1, -33.4, 31, 31, false, false ) ) + ( twinrx_gain_config_t( 2, -32.4, 31, 30, false, false ) ) + ( twinrx_gain_config_t( 3, -31.4, 31, 29, false, false ) ) + ( twinrx_gain_config_t( 4, -30.4, 31, 28, false, false ) ) + ( twinrx_gain_config_t( 5, -29.4, 31, 27, false, false ) ) + ( twinrx_gain_config_t( 6, -28.4, 31, 26, false, false ) ) + ( twinrx_gain_config_t( 7, -27.4, 31, 25, false, false ) ) + ( twinrx_gain_config_t( 8, -26.4, 31, 24, false, false ) ) + ( twinrx_gain_config_t( 9, -25.4, 31, 23, false, false ) ) + ( twinrx_gain_config_t( 10, -24.4, 31, 22, false, false ) ) + ( twinrx_gain_config_t( 11, -23.4, 31, 21, false, false ) ) + ( twinrx_gain_config_t( 12, -22.4, 31, 20, false, false ) ) + ( twinrx_gain_config_t( 13, -21.4, 31, 19, false, false ) ) + ( twinrx_gain_config_t( 14, -20.4, 31, 18, false, false ) ) + ( twinrx_gain_config_t( 15, -19.4, 31, 17, false, false ) ) + ( twinrx_gain_config_t( 16, -18.4, 31, 16, false, false ) ) + ( twinrx_gain_config_t( 17, -17.4, 31, 15, false, false ) ) + ( twinrx_gain_config_t( 18, -16.4, 31, 14, false, false ) ) + ( twinrx_gain_config_t( 19, -15.4, 31, 13, false, false ) ) + ( twinrx_gain_config_t( 20, -14.4, 31, 12, false, false ) ) + ( twinrx_gain_config_t( 21, -13.4, 31, 11, false, false ) ) + ( twinrx_gain_config_t( 22, -12.4, 31, 10, false, false ) ) + ( twinrx_gain_config_t( 23, -11.4, 31, 9, false, false ) ) + ( twinrx_gain_config_t( 24, -10.4, 31, 8, false, false ) ) + ( twinrx_gain_config_t( 25, -9.4, 31, 7, false, false ) ) + ( twinrx_gain_config_t( 26, -8.4, 31, 6, false, false ) ) + ( twinrx_gain_config_t( 27, -7.4, 31, 5, false, false ) ) + ( twinrx_gain_config_t( 28, -6.4, 31, 4, false, false ) ) + ( twinrx_gain_config_t( 29, -5.4, 31, 3, false, false ) ) + ( twinrx_gain_config_t( 30, -4.4, 31, 2, false, false ) ) + ( twinrx_gain_config_t( 31, -3.4, 31, 1, false, false ) ) + ( twinrx_gain_config_t( 32, -2.4, 31, 0, false, false ) ) + ( twinrx_gain_config_t( 33, -1.4, 30, 0, false, false ) ) + ( twinrx_gain_config_t( 34, -0.4, 29, 0, false, false ) ) + ( twinrx_gain_config_t( 35, 0.6, 28, 0, false, false ) ) + ( twinrx_gain_config_t( 36, 1.6, 27, 0, false, false ) ) + ( twinrx_gain_config_t( 37, 2.6, 26, 0, false, false ) ) + ( twinrx_gain_config_t( 38, 3.6, 25, 0, false, false ) ) + ( twinrx_gain_config_t( 39, 4.6, 24, 0, false, false ) ) + ( twinrx_gain_config_t( 40, 5.6, 23, 0, false, false ) ) + ( twinrx_gain_config_t( 41, 6.6, 22, 0, false, false ) ) + ( twinrx_gain_config_t( 42, 7.6, 21, 0, false, false ) ) + ( twinrx_gain_config_t( 43, 8.6, 20, 0, false, false ) ) + ( twinrx_gain_config_t( 44, 9.6, 19, 0, false, false ) ) + ( twinrx_gain_config_t( 45, 10.6, 18, 0, false, false ) ) + ( twinrx_gain_config_t( 46, 11.6, 17, 0, false, false ) ) + ( twinrx_gain_config_t( 47, 12.6, 16, 0, false, false ) ) + ( twinrx_gain_config_t( 48, 13.6, 15, 0, false, false ) ) + ( twinrx_gain_config_t( 49, 14.6, 14, 0, false, false ) ) + ( twinrx_gain_config_t( 50, 15.6, 13, 0, false, false ) ) + ( twinrx_gain_config_t( 51, 16.6, 12, 0, false, false ) ) + ( twinrx_gain_config_t( 52, 17.6, 11, 0, false, false ) ) + ( twinrx_gain_config_t( 53, 18.6, 10, 0, false, false ) ) + ( twinrx_gain_config_t( 54, 19.6, 9, 0, false, false ) ) + ( twinrx_gain_config_t( 55, 20.6, 8, 0, false, false ) ) + ( twinrx_gain_config_t( 56, 21.6, 7, 0, false, false ) ) + ( twinrx_gain_config_t( 57, 22.6, 6, 0, false, false ) ) + ( twinrx_gain_config_t( 58, 23.6, 5, 0, false, false ) ) + ( twinrx_gain_config_t( 59, 24.6, 4, 0, false, false ) ) + ( twinrx_gain_config_t( 60, 25.6, 3, 0, false, false ) ) + ( twinrx_gain_config_t( 61, 26.6, 2, 0, false, false ) ) + ( twinrx_gain_config_t( 62, 27.6, 1, 0, false, false ) ) + ( twinrx_gain_config_t( 63, 28.6, 0, 0, false, false ) ) + ( twinrx_gain_config_t( 64, 29.7, 5, 9, false, true ) ) + ( twinrx_gain_config_t( 65, 30.7, 4, 9, false, true ) ) + ( twinrx_gain_config_t( 66, 31.7, 3, 9, false, true ) ) + ( twinrx_gain_config_t( 67, 32.7, 2, 9, false, true ) ) + ( twinrx_gain_config_t( 68, 33.7, 1, 9, false, true ) ) + ( twinrx_gain_config_t( 69, 34.7, 0, 9, false, true ) ) + ( twinrx_gain_config_t( 70, 35.7, 0, 8, false, true ) ) + ( twinrx_gain_config_t( 71, 36.7, 7, 0, true, false ) ) + ( twinrx_gain_config_t( 72, 37.7, 6, 0, true, false ) ) + ( twinrx_gain_config_t( 73, 38.7, 5, 0, true, false ) ) + ( twinrx_gain_config_t( 74, 39.7, 4, 0, true, false ) ) + ( twinrx_gain_config_t( 75, 40.7, 3, 0, true, false ) ) + ( twinrx_gain_config_t( 76, 41.7, 2, 0, true, false ) ) + ( twinrx_gain_config_t( 77, 42.7, 1, 0, true, false ) ) + ( twinrx_gain_config_t( 78, 43.7, 0, 0, true, false ) ) + ( twinrx_gain_config_t( 79, 44.8, 6, 8, true, true ) ) + ( twinrx_gain_config_t( 80, 45.8, 5, 8, true, true ) ) + ( twinrx_gain_config_t( 81, 46.8, 4, 8, true, true ) ) + ( twinrx_gain_config_t( 82, 47.8, 4, 7, true, true ) ) + ( twinrx_gain_config_t( 83, 48.8, 3, 7, true, true ) ) + ( twinrx_gain_config_t( 84, 49.8, 2, 7, true, true ) ) + ( twinrx_gain_config_t( 85, 50.8, 1, 7, true, true ) ) + ( twinrx_gain_config_t( 86, 51.8, 1, 6, true, true ) ) + ( twinrx_gain_config_t( 87, 52.8, 0, 6, true, true ) ) + ( twinrx_gain_config_t( 88, 53.8, 0, 5, true, true ) ) + ( twinrx_gain_config_t( 89, 54.8, 0, 4, true, true ) ) + ( twinrx_gain_config_t( 90, 55.8, 0, 3, true, true ) ) + ( twinrx_gain_config_t( 91, 56.8, 0, 2, true, true ) ) + ( twinrx_gain_config_t( 92, 57.8, 0, 1, true, true ) ) + ( twinrx_gain_config_t( 93, 58.8, 0, 0, true, true ) ) +; + +static const std::vector<twinrx_gain_config_t> LOWBAND3_TABLE = boost::assign::list_of + // Index, Gain, Atten1, Atten2, Amp1, Amp2 + ( twinrx_gain_config_t( 0, -34.0, 31, 31, false, false ) ) + ( twinrx_gain_config_t( 1, -34.0, 31, 31, false, false ) ) + ( twinrx_gain_config_t( 2, -34.0, 31, 31, false, false ) ) + ( twinrx_gain_config_t( 3, -33.0, 31, 30, false, false ) ) + ( twinrx_gain_config_t( 4, -32.0, 31, 29, false, false ) ) + ( twinrx_gain_config_t( 5, -31.0, 31, 28, false, false ) ) + ( twinrx_gain_config_t( 6, -30.0, 31, 27, false, false ) ) + ( twinrx_gain_config_t( 7, -29.0, 31, 26, false, false ) ) + ( twinrx_gain_config_t( 8, -28.0, 31, 25, false, false ) ) + ( twinrx_gain_config_t( 9, -27.0, 31, 24, false, false ) ) + ( twinrx_gain_config_t( 10, -26.0, 31, 23, false, false ) ) + ( twinrx_gain_config_t( 11, -25.0, 31, 22, false, false ) ) + ( twinrx_gain_config_t( 12, -24.0, 31, 21, false, false ) ) + ( twinrx_gain_config_t( 13, -23.0, 31, 20, false, false ) ) + ( twinrx_gain_config_t( 14, -22.0, 31, 19, false, false ) ) + ( twinrx_gain_config_t( 15, -21.0, 31, 18, false, false ) ) + ( twinrx_gain_config_t( 16, -20.0, 31, 17, false, false ) ) + ( twinrx_gain_config_t( 17, -19.0, 31, 16, false, false ) ) + ( twinrx_gain_config_t( 18, -18.0, 31, 15, false, false ) ) + ( twinrx_gain_config_t( 19, -17.0, 31, 14, false, false ) ) + ( twinrx_gain_config_t( 20, -16.0, 31, 13, false, false ) ) + ( twinrx_gain_config_t( 21, -15.0, 31, 12, false, false ) ) + ( twinrx_gain_config_t( 22, -14.0, 31, 11, false, false ) ) + ( twinrx_gain_config_t( 23, -13.0, 31, 10, false, false ) ) + ( twinrx_gain_config_t( 24, -12.0, 31, 9, false, false ) ) + ( twinrx_gain_config_t( 25, -11.0, 31, 8, false, false ) ) + ( twinrx_gain_config_t( 26, -10.0, 31, 7, false, false ) ) + ( twinrx_gain_config_t( 27, -9.0, 31, 6, false, false ) ) + ( twinrx_gain_config_t( 28, -8.0, 31, 5, false, false ) ) + ( twinrx_gain_config_t( 29, -7.0, 31, 4, false, false ) ) + ( twinrx_gain_config_t( 30, -6.0, 31, 3, false, false ) ) + ( twinrx_gain_config_t( 31, -5.0, 31, 2, false, false ) ) + ( twinrx_gain_config_t( 32, -4.0, 31, 1, false, false ) ) + ( twinrx_gain_config_t( 33, -3.0, 31, 0, false, false ) ) + ( twinrx_gain_config_t( 34, -2.0, 30, 0, false, false ) ) + ( twinrx_gain_config_t( 35, -1.0, 29, 0, false, false ) ) + ( twinrx_gain_config_t( 36, -0.0, 28, 0, false, false ) ) + ( twinrx_gain_config_t( 37, 1.0, 27, 0, false, false ) ) + ( twinrx_gain_config_t( 38, 2.0, 26, 0, false, false ) ) + ( twinrx_gain_config_t( 39, 3.0, 25, 0, false, false ) ) + ( twinrx_gain_config_t( 40, 4.0, 24, 0, false, false ) ) + ( twinrx_gain_config_t( 41, 5.0, 23, 0, false, false ) ) + ( twinrx_gain_config_t( 42, 6.0, 22, 0, false, false ) ) + ( twinrx_gain_config_t( 43, 7.0, 21, 0, false, false ) ) + ( twinrx_gain_config_t( 44, 8.0, 20, 0, false, false ) ) + ( twinrx_gain_config_t( 45, 9.0, 19, 0, false, false ) ) + ( twinrx_gain_config_t( 46, 10.0, 18, 0, false, false ) ) + ( twinrx_gain_config_t( 47, 11.0, 17, 0, false, false ) ) + ( twinrx_gain_config_t( 48, 12.0, 16, 0, false, false ) ) + ( twinrx_gain_config_t( 49, 13.0, 15, 0, false, false ) ) + ( twinrx_gain_config_t( 50, 14.0, 14, 0, false, false ) ) + ( twinrx_gain_config_t( 51, 15.0, 13, 0, false, false ) ) + ( twinrx_gain_config_t( 52, 16.0, 12, 0, false, false ) ) + ( twinrx_gain_config_t( 53, 17.0, 11, 0, false, false ) ) + ( twinrx_gain_config_t( 54, 18.0, 10, 0, false, false ) ) + ( twinrx_gain_config_t( 55, 19.0, 9, 0, false, false ) ) + ( twinrx_gain_config_t( 56, 20.0, 8, 0, false, false ) ) + ( twinrx_gain_config_t( 57, 21.0, 7, 0, false, false ) ) + ( twinrx_gain_config_t( 58, 22.0, 6, 0, false, false ) ) + ( twinrx_gain_config_t( 59, 23.0, 5, 0, false, false ) ) + ( twinrx_gain_config_t( 60, 24.0, 4, 0, false, false ) ) + ( twinrx_gain_config_t( 61, 25.0, 3, 0, false, false ) ) + ( twinrx_gain_config_t( 62, 26.0, 2, 0, false, false ) ) + ( twinrx_gain_config_t( 63, 27.0, 1, 0, false, false ) ) + ( twinrx_gain_config_t( 64, 28.0, 0, 0, false, false ) ) + ( twinrx_gain_config_t( 65, 29.5, 5, 8, false, true ) ) + ( twinrx_gain_config_t( 66, 30.5, 4, 8, false, true ) ) + ( twinrx_gain_config_t( 67, 31.5, 3, 8, false, true ) ) + ( twinrx_gain_config_t( 68, 32.5, 2, 8, false, true ) ) + ( twinrx_gain_config_t( 69, 33.5, 1, 8, false, true ) ) + ( twinrx_gain_config_t( 70, 34.5, 0, 8, false, true ) ) + ( twinrx_gain_config_t( 71, 34.5, 0, 8, false, true ) ) + ( twinrx_gain_config_t( 72, 36.5, 6, 0, true, false ) ) + ( twinrx_gain_config_t( 73, 36.5, 6, 0, true, false ) ) + ( twinrx_gain_config_t( 74, 37.5, 5, 0, true, false ) ) + ( twinrx_gain_config_t( 75, 38.5, 4, 0, true, false ) ) + ( twinrx_gain_config_t( 76, 39.5, 3, 0, true, false ) ) + ( twinrx_gain_config_t( 77, 40.5, 2, 0, true, false ) ) + ( twinrx_gain_config_t( 78, 41.5, 1, 0, true, false ) ) + ( twinrx_gain_config_t( 79, 42.5, 0, 0, true, false ) ) + ( twinrx_gain_config_t( 80, 44.0, 6, 7, true, true ) ) + ( twinrx_gain_config_t( 81, 45.0, 5, 7, true, true ) ) + ( twinrx_gain_config_t( 82, 46.0, 4, 7, true, true ) ) + ( twinrx_gain_config_t( 83, 47.0, 3, 7, true, true ) ) + ( twinrx_gain_config_t( 84, 48.0, 3, 6, true, true ) ) + ( twinrx_gain_config_t( 85, 49.0, 2, 6, true, true ) ) + ( twinrx_gain_config_t( 86, 50.0, 1, 6, true, true ) ) + ( twinrx_gain_config_t( 87, 51.0, 0, 6, true, true ) ) + ( twinrx_gain_config_t( 88, 52.0, 0, 5, true, true ) ) + ( twinrx_gain_config_t( 89, 53.0, 0, 4, true, true ) ) + ( twinrx_gain_config_t( 90, 54.0, 0, 3, true, true ) ) + ( twinrx_gain_config_t( 91, 55.0, 0, 2, true, true ) ) + ( twinrx_gain_config_t( 92, 56.0, 0, 1, true, true ) ) + ( twinrx_gain_config_t( 93, 57.0, 0, 0, true, true ) ) +; + +static const std::vector<twinrx_gain_config_t> LOWBAND4_TABLE = boost::assign::list_of + // Index, Gain, Atten1, Atten2, Amp1, Amp2 + ( twinrx_gain_config_t( 0, -32.8, 31, 31, false, false ) ) + ( twinrx_gain_config_t( 1, -32.8, 31, 31, false, false ) ) + ( twinrx_gain_config_t( 2, -32.8, 31, 31, false, false ) ) + ( twinrx_gain_config_t( 3, -32.8, 31, 31, false, false ) ) + ( twinrx_gain_config_t( 4, -31.8, 31, 30, false, false ) ) + ( twinrx_gain_config_t( 5, -30.8, 31, 29, false, false ) ) + ( twinrx_gain_config_t( 6, -29.8, 31, 28, false, false ) ) + ( twinrx_gain_config_t( 7, -28.8, 31, 27, false, false ) ) + ( twinrx_gain_config_t( 8, -27.8, 31, 26, false, false ) ) + ( twinrx_gain_config_t( 9, -26.8, 31, 25, false, false ) ) + ( twinrx_gain_config_t( 10, -25.8, 31, 24, false, false ) ) + ( twinrx_gain_config_t( 11, -24.8, 31, 23, false, false ) ) + ( twinrx_gain_config_t( 12, -23.8, 31, 22, false, false ) ) + ( twinrx_gain_config_t( 13, -22.8, 31, 21, false, false ) ) + ( twinrx_gain_config_t( 14, -21.8, 31, 20, false, false ) ) + ( twinrx_gain_config_t( 15, -20.8, 31, 19, false, false ) ) + ( twinrx_gain_config_t( 16, -19.8, 31, 18, false, false ) ) + ( twinrx_gain_config_t( 17, -18.8, 31, 17, false, false ) ) + ( twinrx_gain_config_t( 18, -17.8, 31, 16, false, false ) ) + ( twinrx_gain_config_t( 19, -16.8, 31, 15, false, false ) ) + ( twinrx_gain_config_t( 20, -15.8, 31, 14, false, false ) ) + ( twinrx_gain_config_t( 21, -14.8, 31, 13, false, false ) ) + ( twinrx_gain_config_t( 22, -13.8, 31, 12, false, false ) ) + ( twinrx_gain_config_t( 23, -12.8, 31, 11, false, false ) ) + ( twinrx_gain_config_t( 24, -11.8, 31, 10, false, false ) ) + ( twinrx_gain_config_t( 25, -10.8, 31, 9, false, false ) ) + ( twinrx_gain_config_t( 26, -9.8, 31, 8, false, false ) ) + ( twinrx_gain_config_t( 27, -8.8, 31, 7, false, false ) ) + ( twinrx_gain_config_t( 28, -7.8, 31, 6, false, false ) ) + ( twinrx_gain_config_t( 29, -6.8, 31, 5, false, false ) ) + ( twinrx_gain_config_t( 30, -5.8, 31, 4, false, false ) ) + ( twinrx_gain_config_t( 31, -4.8, 31, 3, false, false ) ) + ( twinrx_gain_config_t( 32, -3.8, 31, 2, false, false ) ) + ( twinrx_gain_config_t( 33, -2.8, 31, 1, false, false ) ) + ( twinrx_gain_config_t( 34, -1.8, 31, 0, false, false ) ) + ( twinrx_gain_config_t( 35, -0.8, 30, 0, false, false ) ) + ( twinrx_gain_config_t( 36, 0.2, 29, 0, false, false ) ) + ( twinrx_gain_config_t( 37, 1.2, 28, 0, false, false ) ) + ( twinrx_gain_config_t( 38, 2.2, 27, 0, false, false ) ) + ( twinrx_gain_config_t( 39, 3.2, 26, 0, false, false ) ) + ( twinrx_gain_config_t( 40, 4.2, 25, 0, false, false ) ) + ( twinrx_gain_config_t( 41, 5.2, 24, 0, false, false ) ) + ( twinrx_gain_config_t( 42, 6.2, 23, 0, false, false ) ) + ( twinrx_gain_config_t( 43, 7.2, 22, 0, false, false ) ) + ( twinrx_gain_config_t( 44, 8.2, 21, 0, false, false ) ) + ( twinrx_gain_config_t( 45, 9.2, 20, 0, false, false ) ) + ( twinrx_gain_config_t( 46, 10.2, 19, 0, false, false ) ) + ( twinrx_gain_config_t( 47, 11.2, 18, 0, false, false ) ) + ( twinrx_gain_config_t( 48, 12.2, 17, 0, false, false ) ) + ( twinrx_gain_config_t( 49, 13.2, 16, 0, false, false ) ) + ( twinrx_gain_config_t( 50, 14.2, 15, 0, false, false ) ) + ( twinrx_gain_config_t( 51, 15.2, 14, 0, false, false ) ) + ( twinrx_gain_config_t( 52, 16.2, 13, 0, false, false ) ) + ( twinrx_gain_config_t( 53, 17.2, 12, 0, false, false ) ) + ( twinrx_gain_config_t( 54, 18.2, 11, 0, false, false ) ) + ( twinrx_gain_config_t( 55, 19.2, 10, 0, false, false ) ) + ( twinrx_gain_config_t( 56, 20.2, 9, 0, false, false ) ) + ( twinrx_gain_config_t( 57, 21.2, 8, 0, false, false ) ) + ( twinrx_gain_config_t( 58, 22.2, 7, 0, false, false ) ) + ( twinrx_gain_config_t( 59, 23.2, 6, 0, false, false ) ) + ( twinrx_gain_config_t( 60, 24.2, 5, 0, false, false ) ) + ( twinrx_gain_config_t( 61, 25.2, 4, 0, false, false ) ) + ( twinrx_gain_config_t( 62, 26.2, 3, 0, false, false ) ) + ( twinrx_gain_config_t( 63, 27.2, 2, 0, false, false ) ) + ( twinrx_gain_config_t( 64, 28.2, 1, 0, false, false ) ) + ( twinrx_gain_config_t( 65, 29.2, 0, 0, false, false ) ) + ( twinrx_gain_config_t( 66, 30.4, 4, 9, false, true ) ) + ( twinrx_gain_config_t( 67, 31.4, 3, 9, false, true ) ) + ( twinrx_gain_config_t( 68, 32.4, 2, 9, false, true ) ) + ( twinrx_gain_config_t( 69, 33.4, 1, 9, false, true ) ) + ( twinrx_gain_config_t( 70, 34.4, 0, 9, false, true ) ) + ( twinrx_gain_config_t( 71, 35.4, 8, 0, true, false ) ) + ( twinrx_gain_config_t( 72, 36.4, 7, 0, true, false ) ) + ( twinrx_gain_config_t( 73, 37.4, 6, 0, true, false ) ) + ( twinrx_gain_config_t( 74, 38.4, 5, 0, true, false ) ) + ( twinrx_gain_config_t( 75, 39.4, 4, 0, true, false ) ) + ( twinrx_gain_config_t( 76, 40.4, 3, 0, true, false ) ) + ( twinrx_gain_config_t( 77, 41.4, 2, 0, true, false ) ) + ( twinrx_gain_config_t( 78, 42.4, 1, 0, true, false ) ) + ( twinrx_gain_config_t( 79, 43.4, 0, 0, true, false ) ) + ( twinrx_gain_config_t( 80, 44.6, 4, 9, true, true ) ) + ( twinrx_gain_config_t( 81, 45.6, 4, 8, true, true ) ) + ( twinrx_gain_config_t( 82, 46.6, 3, 8, true, true ) ) + ( twinrx_gain_config_t( 83, 47.6, 2, 8, true, true ) ) + ( twinrx_gain_config_t( 84, 48.6, 1, 8, true, true ) ) + ( twinrx_gain_config_t( 85, 49.6, 1, 7, true, true ) ) + ( twinrx_gain_config_t( 86, 50.6, 0, 7, true, true ) ) + ( twinrx_gain_config_t( 87, 51.6, 0, 6, true, true ) ) + ( twinrx_gain_config_t( 88, 52.6, 0, 5, true, true ) ) + ( twinrx_gain_config_t( 89, 53.6, 0, 4, true, true ) ) + ( twinrx_gain_config_t( 90, 54.6, 0, 3, true, true ) ) + ( twinrx_gain_config_t( 91, 55.6, 0, 2, true, true ) ) + ( twinrx_gain_config_t( 92, 56.6, 0, 1, true, true ) ) + ( twinrx_gain_config_t( 93, 57.6, 0, 0, true, true ) ) +; + +const twinrx_gain_table twinrx_gain_table::lookup_table +( + twinrx_ctrl::signal_path_t signal_path, + twinrx_ctrl::preselector_path_t preselector_path, + std::string +) { + + if (signal_path == twinrx_ctrl::PATH_HIGHBAND) { + switch (preselector_path) { + case twinrx_ctrl::PRESEL_PATH1: + return twinrx_gain_table(HIGHBAND1_TABLE); + case twinrx_ctrl::PRESEL_PATH2: + return twinrx_gain_table(HIGHBAND2_TABLE); + case twinrx_ctrl::PRESEL_PATH3: + return twinrx_gain_table(HIGHBAND3_TABLE); + case twinrx_ctrl::PRESEL_PATH4: + return twinrx_gain_table(HIGHBAND4_TABLE); + } + } else { + switch (preselector_path) { + case twinrx_ctrl::PRESEL_PATH1: + return twinrx_gain_table(LOWBAND1_TABLE); + case twinrx_ctrl::PRESEL_PATH2: + return twinrx_gain_table(LOWBAND2_TABLE); + case twinrx_ctrl::PRESEL_PATH3: + return twinrx_gain_table(LOWBAND3_TABLE); + case twinrx_ctrl::PRESEL_PATH4: + return twinrx_gain_table(LOWBAND4_TABLE); + } + } + throw runtime_error("NO GAIN TABLE SELECTED"); + return twinrx_gain_table(HIGHBAND1_TABLE); +} + +const twinrx_gain_config_t& twinrx_gain_table::find_by_index(size_t index) const { + if (index >= get_num_entries()) throw uhd::value_error("invalid gain table index"); + return _tbl.at(index); +} + +uhd::gain_range_t twinrx_gain_table::get_gain_range() const { + double max = std::numeric_limits<double>::min(); + double min = std::numeric_limits<double>::max(); + for (size_t i = 0; i < get_num_entries(); i++) { + const twinrx_gain_config_t& config = find_by_index(i); + if (config.sys_gain > max) { + max = config.sys_gain; + } + if (config.sys_gain < min) { + min = config.sys_gain; + } + } + return uhd::gain_range_t(min, max, 1.0); +} diff --git a/host/lib/usrp/dboard/twinrx/twinrx_gain_tables.hpp b/host/lib/usrp/dboard/twinrx/twinrx_gain_tables.hpp new file mode 100644 index 000000000..0148965da --- /dev/null +++ b/host/lib/usrp/dboard/twinrx/twinrx_gain_tables.hpp @@ -0,0 +1,83 @@ +// +// Copyright 2016 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 +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +#ifndef INCLUDED_DBOARD_TWINRX_GAIN_TABLES_HPP +#define INCLUDED_DBOARD_TWINRX_GAIN_TABLES_HPP + +#include <uhd/config.hpp> +#include <boost/cstdint.hpp> +#include <uhd/types/ranges.hpp> +#include "twinrx_ctrl.hpp" + +namespace uhd { namespace usrp { namespace dboard { namespace twinrx { + +class twinrx_gain_config_t { +public: + twinrx_gain_config_t( + size_t index_, double sys_gain_, + boost::uint8_t atten1_, boost::uint8_t atten2_, + bool amp1_, bool amp2_ + ): index(index_), sys_gain(sys_gain_), atten1(atten1_), atten2(atten2_), + amp1(amp1_), amp2(amp2_) + {} + + twinrx_gain_config_t& operator=(const twinrx_gain_config_t& src) { + if (this != &src) { + this->index = src.index; + this->sys_gain = src.sys_gain; + this->atten1 = src.atten1; + this->atten2 = src.atten2; + this->amp1 = src.amp1; + this->amp2 = src.amp2; + } + return *this; + } + + size_t index; + double sys_gain; + boost::uint8_t atten1; + boost::uint8_t atten2; + bool amp1; + bool amp2; +}; + +class twinrx_gain_table { +public: + static const twinrx_gain_table lookup_table( + twinrx_ctrl::signal_path_t signal_path, + twinrx_ctrl::preselector_path_t presel_path, + std::string profile); + + twinrx_gain_table(const std::vector<twinrx_gain_config_t>& tbl) + : _tbl(tbl) {} + + const twinrx_gain_config_t& find_by_index(size_t index) const; + + inline size_t get_num_entries() const { + return _tbl.size(); + } + + uhd::gain_range_t get_gain_range() const; + +private: + const std::vector<twinrx_gain_config_t>& _tbl; +}; + + +}}}} //namespaces + +#endif /* INCLUDED_DBOARD_TWINRX_GAIN_TABLES_HPP */ diff --git a/host/lib/usrp/dboard/twinrx/twinrx_io.hpp b/host/lib/usrp/dboard/twinrx/twinrx_io.hpp new file mode 100644 index 000000000..5d099e361 --- /dev/null +++ b/host/lib/usrp/dboard/twinrx/twinrx_io.hpp @@ -0,0 +1,523 @@ +// +// Copyright 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 +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +#ifndef INCLUDED_DBOARD_TWINRX_IO_HPP +#define INCLUDED_DBOARD_TWINRX_IO_HPP + +#include <uhd/types/wb_iface.hpp> +#include <uhd/usrp/dboard_base.hpp> +#include <uhd/utils/soft_register.hpp> +#include <boost/thread.hpp> +#include "gpio_atr_3000.hpp" + +namespace uhd { namespace usrp { namespace dboard { namespace twinrx { + +static const boost::uint32_t SET_ALL_BITS = 0xFFFFFFFF; + +namespace cpld { +static wb_iface::wb_addr_type addr(boost::uint8_t cpld_num, boost::uint8_t cpld_addr) { + //Decode CPLD addressing for the following bitmap: + // {CPLD1_EN, CPLD2_EN, CPLD3_EN, CPLD4_EN, CPLD_ADDR[2:0]} + boost::uint8_t addr = 0; + switch (cpld_num) { + case 1: addr = 0x8 << 3; break; + case 2: addr = 0x4 << 3; break; + case 3: addr = 0x2 << 3; break; + case 4: addr = 0x1 << 3; break; + default: UHD_THROW_INVALID_CODE_PATH(); + } + return static_cast<wb_iface::wb_addr_type>(addr | (cpld_addr & 0x7)); +} + +static boost::uint32_t get_reg(wb_iface::wb_addr_type addr) { + return static_cast<boost::uint32_t>(addr) & 0x7; +} +} + +class twinrx_gpio : public timed_wb_iface { +public: + typedef boost::shared_ptr<twinrx_gpio> sptr; + + //---------------------------------------------- + //Public GPIO fields + UHD_DEFINE_SOFT_REG_FIELD(FIELD_LO2_CE_CH1, /*width*/ 1, /*shift*/ 0); //GPIO[0] OUT + UHD_DEFINE_SOFT_REG_FIELD(FIELD_LO2_CE_CH2, /*width*/ 1, /*shift*/ 1); //GPIO[1] OUT + UHD_DEFINE_SOFT_REG_FIELD(FIELD_LO2_MUXOUT_CH1, /*width*/ 1, /*shift*/ 2); //GPIO[2] IN + UHD_DEFINE_SOFT_REG_FIELD(FIELD_LO2_MUXOUT_CH2, /*width*/ 1, /*shift*/ 3); //GPIO[3] IN + UHD_DEFINE_SOFT_REG_FIELD(FIELD_LO2_LD_CH1, /*width*/ 1, /*shift*/ 4); //GPIO[4] IN + UHD_DEFINE_SOFT_REG_FIELD(FIELD_LO2_LD_CH2, /*width*/ 1, /*shift*/ 5); //GPIO[5] IN + // NO CONNECT //GPIO[8:6] + // PRIVATE //GPIO[15:9] + // NO CONNECT //GPIO[16] + UHD_DEFINE_SOFT_REG_FIELD(FIELD_LO1_CE_CH1, /*width*/ 1, /*shift*/ 17); //GPIO[17] OUT + UHD_DEFINE_SOFT_REG_FIELD(FIELD_LO1_CE_CH2, /*width*/ 1, /*shift*/ 18); //GPIO[18] OUT + UHD_DEFINE_SOFT_REG_FIELD(FIELD_LO1_MUXOUT_CH1, /*width*/ 1, /*shift*/ 19); //GPIO[19] IN + UHD_DEFINE_SOFT_REG_FIELD(FIELD_LO1_MUXOUT_CH2, /*width*/ 1, /*shift*/ 20); //GPIO[20] IN + // NO CONNECT //GPIO[21:23] + UHD_DEFINE_SOFT_REG_FIELD(FIELD_SWPS_CLK, /*width*/ 1, /*shift*/ 24); //GPIO[24] IN + UHD_DEFINE_SOFT_REG_FIELD(FIELD_SWPS_PWR_GOOD, /*width*/ 1, /*shift*/ 25); //GPIO[25] IN + UHD_DEFINE_SOFT_REG_FIELD(FIELD_SWPS_EN, /*width*/ 1, /*shift*/ 26); //GPIO[26] OUT + // PRIVATE //GPIO[27:31] + //---------------------------------------------- + + twinrx_gpio(dboard_iface::sptr iface) : _db_iface(iface) { + _db_iface->set_gpio_ddr(dboard_iface::UNIT_BOTH, GPIO_OUTPUT_MASK, SET_ALL_BITS); + _db_iface->set_pin_ctrl(dboard_iface::UNIT_BOTH, GPIO_PINCTRL_MASK, SET_ALL_BITS); + _db_iface->set_gpio_out(dboard_iface::UNIT_BOTH, 0, ~GPIO_PINCTRL_MASK); + } + + ~twinrx_gpio() { + _db_iface->set_gpio_ddr(dboard_iface::UNIT_BOTH, ~GPIO_OUTPUT_MASK, SET_ALL_BITS); + } + + void set_field(const uhd::soft_reg_field_t field, const boost::uint32_t value) { + boost::lock_guard<boost::mutex> lock(_mutex); + using namespace soft_reg_field; + + _db_iface->set_gpio_out(dboard_iface::UNIT_BOTH, + (value << shift(field)), + mask<boost::uint32_t>(field)); + } + + boost::uint32_t get_field(const uhd::soft_reg_field_t field) { + boost::lock_guard<boost::mutex> lock(_mutex); + using namespace soft_reg_field; + return (_db_iface->read_gpio(dboard_iface::UNIT_BOTH) & mask<boost::uint32_t>(field)) >> shift(field); + } + + // CPLD register write-only interface + void poke32(const wb_addr_type addr, const boost::uint32_t data) { + boost::lock_guard<boost::mutex> lock(_mutex); + using namespace soft_reg_field; + + //Step 1: Write the reg offset and data to the GPIO bus and de-assert all enables + _db_iface->set_gpio_out(dboard_iface::UNIT_BOTH, + (cpld::get_reg(addr) << shift(CPLD_FULL_ADDR)) | (data << shift(CPLD_DATA)), + mask<boost::uint32_t>(CPLD_FULL_ADDR)|mask<boost::uint32_t>(CPLD_DATA)); + //Sleep for 166ns to ensure that we don't toggle the enables too quickly + //The underlying sleep function rounds to microsecond precision. + _db_iface->sleep(boost::chrono::nanoseconds(166)); + //Step 2: Write the reg offset and data, and assert the necessary enable + _db_iface->set_gpio_out(dboard_iface::UNIT_BOTH, + (static_cast<boost::uint32_t>(addr) << shift(CPLD_FULL_ADDR)) | (data << shift(CPLD_DATA)), + mask<boost::uint32_t>(CPLD_FULL_ADDR)|mask<boost::uint32_t>(CPLD_DATA)); + } + + // Timed command interface + inline time_spec_t get_time() { + return _db_iface->get_command_time(); + } + + void set_time(const time_spec_t& t) { + boost::lock_guard<boost::mutex> lock(_mutex); + _db_iface->set_command_time(t); + } + +private: //Members/definitions + static const boost::uint32_t GPIO_OUTPUT_MASK = 0xFC06FE03; + static const boost::uint32_t GPIO_PINCTRL_MASK = 0x00000000; + + //Private GPIO fields + UHD_DEFINE_SOFT_REG_FIELD(CPLD_FULL_ADDR, /*width*/ 7, /*shift*/ 9); //GPIO[15:9] + UHD_DEFINE_SOFT_REG_FIELD(CPLD_DATA, /*width*/ 5, /*shift*/ 27); //GPIO[31:27] + + //Members + dboard_iface::sptr _db_iface; + boost::mutex _mutex; +}; + +class twinrx_cpld_regmap : public uhd::soft_regmap_t { +public: + typedef boost::shared_ptr<twinrx_cpld_regmap> sptr; + + //---------------------------------------------- + // IF CCA: CPLD 1 + //---------------------------------------------- + class if0_reg0_t : public uhd::soft_reg32_wo_t { + public: + UHD_DEFINE_SOFT_REG_FIELD(AMP_HB_IF1_EN_CH1, /*width*/ 1, /*shift*/ 0); + UHD_DEFINE_SOFT_REG_FIELD(AMP_LO2_EN_CH1, /*width*/ 1, /*shift*/ 1); + UHD_DEFINE_SOFT_REG_FIELD(AMP_LO2_EN_CH2, /*width*/ 1, /*shift*/ 2); + UHD_DEFINE_SOFT_REG_FIELD(SW19_CTRL_CH2, /*width*/ 1, /*shift*/ 3); + UHD_DEFINE_SOFT_REG_FIELD(SW20_CTRL_CH2, /*width*/ 1, /*shift*/ 4); + + if0_reg0_t(): uhd::soft_reg32_wo_t(cpld::addr(/*cpld*/ 1, /*reg*/ 0), OPTIMIZED_FLUSH) { + set(REGISTER, 0); + } + } if0_reg0; + + class if0_reg1_t : public uhd::soft_reg32_wo_t { + public: + UHD_DEFINE_SOFT_REG_FIELD(SW20_CTRL_CH1, /*width*/ 1, /*shift*/ 2); + + if0_reg1_t(): uhd::soft_reg32_wo_t(cpld::addr(/*cpld*/ 1, /*reg*/ 1), OPTIMIZED_FLUSH) { + set(REGISTER, 0); + } + } if0_reg1; + + class if0_reg2_t : public uhd::soft_reg32_wo_t { + public: + UHD_DEFINE_SOFT_REG_FIELD(AMP_LB_IF1_EN_CH2, /*width*/ 1, /*shift*/ 0); + UHD_DEFINE_SOFT_REG_FIELD(AMP_LB_IF1_EN_CH1, /*width*/ 1, /*shift*/ 1); + UHD_DEFINE_SOFT_REG_FIELD(LO2_LE_CH1, /*width*/ 1, /*shift*/ 3); + UHD_DEFINE_SOFT_REG_FIELD(LO2_LE_CH2, /*width*/ 1, /*shift*/ 4); + + if0_reg2_t(): uhd::soft_reg32_wo_t(cpld::addr(/*cpld*/ 1, /*reg*/ 2), OPTIMIZED_FLUSH) { + set(REGISTER, 0); + } + } if0_reg2; + + class if0_reg3_t : public uhd::soft_reg32_wo_t { + public: + UHD_DEFINE_SOFT_REG_FIELD(SW24_CTRL_CH1, /*width*/ 1, /*shift*/ 1); + UHD_DEFINE_SOFT_REG_FIELD(SW13_CTRL_CH1, /*width*/ 1, /*shift*/ 2); + UHD_DEFINE_SOFT_REG_FIELD(IF1_IF2_EN_CH1, /*width*/ 1, /*shift*/ 3); + + if0_reg3_t(): uhd::soft_reg32_wo_t(cpld::addr(/*cpld*/ 1, /*reg*/ 3), OPTIMIZED_FLUSH) { + set(REGISTER, 0); + } + } if0_reg3; + + class if0_reg4_t : public uhd::soft_reg32_wo_t { + public: + UHD_DEFINE_SOFT_REG_FIELD(SW21_CTRL_CH2, /*width*/ 1, /*shift*/ 0); + UHD_DEFINE_SOFT_REG_FIELD(SW25_CTRL, /*width*/ 1, /*shift*/ 1); + UHD_DEFINE_SOFT_REG_FIELD(IF1_IF2_EN_CH2, /*width*/ 1, /*shift*/ 2); + UHD_DEFINE_SOFT_REG_FIELD(SW19_CTRL_CH1, /*width*/ 1, /*shift*/ 3); + UHD_DEFINE_SOFT_REG_FIELD(SW21_CTRL_CH1, /*width*/ 1, /*shift*/ 4); + + if0_reg4_t(): uhd::soft_reg32_wo_t(cpld::addr(/*cpld*/ 1, /*reg*/ 4), OPTIMIZED_FLUSH) { + set(REGISTER, 0); + } + } if0_reg4; + + class if0_reg6_t : public uhd::soft_reg32_wo_t { + public: + UHD_DEFINE_SOFT_REG_FIELD(AMP_HB_IF1_EN_CH2, /*width*/ 1, /*shift*/ 0); + UHD_DEFINE_SOFT_REG_FIELD(SW13_CTRL_CH2, /*width*/ 1, /*shift*/ 2); + + if0_reg6_t(): uhd::soft_reg32_wo_t(cpld::addr(/*cpld*/ 1, /*reg*/ 6), OPTIMIZED_FLUSH) { + set(REGISTER, 0); + } + } if0_reg6; + + class if0_reg7_t : public uhd::soft_reg32_wo_t { + public: + UHD_DEFINE_SOFT_REG_FIELD(SW24_CTRL_CH2, /*width*/ 1, /*shift*/ 0); + + if0_reg7_t(): uhd::soft_reg32_wo_t(cpld::addr(/*cpld*/ 1, /*reg*/ 7), OPTIMIZED_FLUSH) { + set(REGISTER, 0); + } + } if0_reg7; + + //---------------------------------------------- + // RF CCA: CPLD 2 + //---------------------------------------------- + class rf0_reg0_t : public uhd::soft_reg32_wo_t { + public: + UHD_DEFINE_SOFT_REG_FIELD(ATTEN_IN_CH1, /*width*/ 5, /*shift*/ 0); + + rf0_reg0_t(): uhd::soft_reg32_wo_t(cpld::addr(/*cpld*/ 2, /*reg*/ 0), OPTIMIZED_FLUSH) { + set(REGISTER, 0); + } + } rf0_reg0; + + class rf0_reg1_t : public uhd::soft_reg32_wo_t { + public: + UHD_DEFINE_SOFT_REG_FIELD(SWPA1_CTL_CH1, /*width*/ 1, /*shift*/ 1); + UHD_DEFINE_SOFT_REG_FIELD(HB_PREAMP_EN_CH1, /*width*/ 1, /*shift*/ 2); + UHD_DEFINE_SOFT_REG_FIELD(LB_PREAMP_EN_CH1, /*width*/ 1, /*shift*/ 3); + UHD_DEFINE_SOFT_REG_FIELD(SWPA3_CTRL_CH2, /*width*/ 1, /*shift*/ 4); + + rf0_reg1_t(): uhd::soft_reg32_wo_t(cpld::addr(/*cpld*/ 2, /*reg*/ 1), OPTIMIZED_FLUSH) { + set(REGISTER, 0); + } + } rf0_reg1; + + class rf0_reg2_t : public uhd::soft_reg32_wo_t { + public: + UHD_DEFINE_SOFT_REG_FIELD(SW6_CTRL_CH1, /*width*/ 1, /*shift*/ 0); + UHD_DEFINE_SOFT_REG_FIELD(SW5_CTRL_CH1, /*width*/ 1, /*shift*/ 1); + UHD_DEFINE_SOFT_REG_FIELD(SW4_CTRL_CH1, /*width*/ 1, /*shift*/ 2); + UHD_DEFINE_SOFT_REG_FIELD(LO1_LE_CH1, /*width*/ 1, /*shift*/ 3); + UHD_DEFINE_SOFT_REG_FIELD(LO1_LE_CH2, /*width*/ 1, /*shift*/ 4); + + rf0_reg2_t(): uhd::soft_reg32_wo_t(cpld::addr(/*cpld*/ 2, /*reg*/ 2), OPTIMIZED_FLUSH) { + set(REGISTER, 0); + } + } rf0_reg2; + + class rf0_reg3_t : public uhd::soft_reg32_wo_t { + public: + UHD_DEFINE_SOFT_REG_FIELD(SW9_CTRL_CH2, /*width*/ 2, /*shift*/ 0); + UHD_DEFINE_SOFT_REG_FIELD(SW7_CTRL_CH1, /*width*/ 2, /*shift*/ 2); + + rf0_reg3_t(): uhd::soft_reg32_wo_t(cpld::addr(/*cpld*/ 2, /*reg*/ 3), OPTIMIZED_FLUSH) { + set(REGISTER, 0); + } + } rf0_reg3; + + class rf0_reg4_t : public uhd::soft_reg32_wo_t { + public: + UHD_DEFINE_SOFT_REG_FIELD(ATTEN_IN_CH2, /*width*/ 5, /*shift*/ 0); + + rf0_reg4_t(): uhd::soft_reg32_wo_t(cpld::addr(/*cpld*/ 2, /*reg*/ 4), OPTIMIZED_FLUSH) { + set(REGISTER, 0); + } + } rf0_reg4; + + class rf0_reg5_t : public uhd::soft_reg32_wo_t { + public: + UHD_DEFINE_SOFT_REG_FIELD(SW9_CTRL_CH1, /*width*/ 2, /*shift*/ 0); + UHD_DEFINE_SOFT_REG_FIELD(HB_PREAMP_EN_CH2, /*width*/ 1, /*shift*/ 2); + UHD_DEFINE_SOFT_REG_FIELD(SW3_CTRL_CH1, /*width*/ 1, /*shift*/ 4); + + rf0_reg5_t(): uhd::soft_reg32_wo_t(cpld::addr(/*cpld*/ 2, /*reg*/ 5), OPTIMIZED_FLUSH) { + set(REGISTER, 0); + } + } rf0_reg5; + + class rf0_reg6_t : public uhd::soft_reg32_wo_t { + public: + UHD_DEFINE_SOFT_REG_FIELD(SW6_CTRL_CH2, /*width*/ 1, /*shift*/ 0); + UHD_DEFINE_SOFT_REG_FIELD(SW5_CTRL_CH2, /*width*/ 1, /*shift*/ 1); + UHD_DEFINE_SOFT_REG_FIELD(SW4_CTRL_CH2, /*width*/ 1, /*shift*/ 2); + UHD_DEFINE_SOFT_REG_FIELD(SWPA4_CTRL_CH2, /*width*/ 1, /*shift*/ 4); + + rf0_reg6_t(): uhd::soft_reg32_wo_t(cpld::addr(/*cpld*/ 2, /*reg*/ 6), OPTIMIZED_FLUSH) { + set(REGISTER, 0); + } + } rf0_reg6; + + class rf0_reg7_t : public uhd::soft_reg32_wo_t { + public: + UHD_DEFINE_SOFT_REG_FIELD(SWPA1_CTRL_CH2, /*width*/ 1, /*shift*/ 0); + UHD_DEFINE_SOFT_REG_FIELD(SWPA3_CTRL_CH1, /*width*/ 1, /*shift*/ 1); + UHD_DEFINE_SOFT_REG_FIELD(SW3_CTRL_CH2, /*width*/ 1, /*shift*/ 2); + UHD_DEFINE_SOFT_REG_FIELD(SW7_CTRL_CH2, /*width*/ 2, /*shift*/ 3); + + rf0_reg7_t(): uhd::soft_reg32_wo_t(cpld::addr(/*cpld*/ 2, /*reg*/ 7), OPTIMIZED_FLUSH) { + set(REGISTER, 0); + } + } rf0_reg7; + + //---------------------------------------------- + // RF CCA: CPLD 3 + //---------------------------------------------- + class rf1_reg0_t : public uhd::soft_reg32_wo_t { + public: + UHD_DEFINE_SOFT_REG_FIELD(ATTEN_HB_CH1, /*width*/ 5, /*shift*/ 0); + + rf1_reg0_t(): uhd::soft_reg32_wo_t(cpld::addr(/*cpld*/ 3, /*reg*/ 0), OPTIMIZED_FLUSH) { + set(REGISTER, 0); + } + } rf1_reg0; + + class rf1_reg1_t : public uhd::soft_reg32_wo_t { + public: + UHD_DEFINE_SOFT_REG_FIELD(SW17_CTRL_CH1, /*width*/ 1, /*shift*/ 0); + UHD_DEFINE_SOFT_REG_FIELD(AMP_LO1_EN_CH1, /*width*/ 1, /*shift*/ 1); + UHD_DEFINE_SOFT_REG_FIELD(SW16_CTRL_CH1, /*width*/ 1, /*shift*/ 2); + UHD_DEFINE_SOFT_REG_FIELD(SW15_CTRL_CH1, /*width*/ 1, /*shift*/ 3); + UHD_DEFINE_SOFT_REG_FIELD(SW14_CTRL_CH1, /*width*/ 1, /*shift*/ 4); + + rf1_reg1_t(): uhd::soft_reg32_wo_t(cpld::addr(/*cpld*/ 3, /*reg*/ 1), OPTIMIZED_FLUSH) { + set(REGISTER, 0); + } + } rf1_reg1; + + class rf1_reg2_t : public uhd::soft_reg32_wo_t { + public: + UHD_DEFINE_SOFT_REG_FIELD(SW12_CTRL_CH1, /*width*/ 1, /*shift*/ 0); + UHD_DEFINE_SOFT_REG_FIELD(AMP_HB_EN_CH1, /*width*/ 1, /*shift*/ 1); + UHD_DEFINE_SOFT_REG_FIELD(HB_PRESEL_PGA_EN_CH2, /*width*/ 1, /*shift*/ 2); + + rf1_reg2_t(): uhd::soft_reg32_wo_t(cpld::addr(/*cpld*/ 3, /*reg*/ 2), OPTIMIZED_FLUSH) { + set(REGISTER, 0); + } + } rf1_reg2; + + class rf1_reg3_t : public uhd::soft_reg32_wo_t { + public: + UHD_DEFINE_SOFT_REG_FIELD(SW23_CTRL, /*width*/ 1, /*shift*/ 0); + UHD_DEFINE_SOFT_REG_FIELD(SW22_CTRL_CH1, /*width*/ 1, /*shift*/ 1); + UHD_DEFINE_SOFT_REG_FIELD(SW10_CTRL_CH1, /*width*/ 2, /*shift*/ 2); + + rf1_reg3_t(): uhd::soft_reg32_wo_t(cpld::addr(/*cpld*/ 3, /*reg*/ 3), OPTIMIZED_FLUSH) { + set(REGISTER, 0); + } + } rf1_reg3; + + class rf1_reg4_t : public uhd::soft_reg32_wo_t { + public: + UHD_DEFINE_SOFT_REG_FIELD(ATTEN_HB_CH2, /*width*/ 5, /*shift*/ 0); + + rf1_reg4_t(): uhd::soft_reg32_wo_t(cpld::addr(/*cpld*/ 3, /*reg*/ 4), OPTIMIZED_FLUSH) { + set(REGISTER, 0); + } + } rf1_reg4; + + class rf1_reg5_t : public uhd::soft_reg32_wo_t { + public: + UHD_DEFINE_SOFT_REG_FIELD(AMP_LO1_EN_CH2, /*width*/ 1, /*shift*/ 0); + UHD_DEFINE_SOFT_REG_FIELD(SW15_CTRL_CH2, /*width*/ 1, /*shift*/ 1); + UHD_DEFINE_SOFT_REG_FIELD(SW14_CTRL_CH2, /*width*/ 1, /*shift*/ 2); + UHD_DEFINE_SOFT_REG_FIELD(SW18_CTRL_CH1, /*width*/ 1, /*shift*/ 4); + + rf1_reg5_t(): uhd::soft_reg32_wo_t(cpld::addr(/*cpld*/ 3, /*reg*/ 5), OPTIMIZED_FLUSH) { + set(REGISTER, 0); + } + } rf1_reg5; + + class rf1_reg6_t : public uhd::soft_reg32_wo_t { + public: + UHD_DEFINE_SOFT_REG_FIELD(HB_PRESEL_PGA_EN_CH1, /*width*/ 1, /*shift*/ 0); + UHD_DEFINE_SOFT_REG_FIELD(SW17_CTRL_CH2, /*width*/ 1, /*shift*/ 2); + UHD_DEFINE_SOFT_REG_FIELD(SW16_CTRL_CH2, /*width*/ 1, /*shift*/ 3); + UHD_DEFINE_SOFT_REG_FIELD(PREAMP2_EN_CH2, /*width*/ 1, /*shift*/ 4); + + rf1_reg6_t(): uhd::soft_reg32_wo_t(cpld::addr(/*cpld*/ 3, /*reg*/ 6), OPTIMIZED_FLUSH) { + set(REGISTER, 0); + } + } rf1_reg6; + + class rf1_reg7_t : public uhd::soft_reg32_wo_t { + public: + UHD_DEFINE_SOFT_REG_FIELD(SW22_CTRL_CH2, /*width*/ 1, /*shift*/ 0); + UHD_DEFINE_SOFT_REG_FIELD(SW10_CTRL_CH2, /*width*/ 2, /*shift*/ 1); + UHD_DEFINE_SOFT_REG_FIELD(SW12_CTRL_CH2, /*width*/ 1, /*shift*/ 3); + UHD_DEFINE_SOFT_REG_FIELD(AMP_HB_EN_CH2, /*width*/ 1, /*shift*/ 4); + + rf1_reg7_t(): uhd::soft_reg32_wo_t(cpld::addr(/*cpld*/ 3, /*reg*/ 7), OPTIMIZED_FLUSH) { + set(REGISTER, 0); + } + } rf1_reg7; + + //---------------------------------------------- + // RF CCA: CPLD 4 + //---------------------------------------------- + class rf2_reg0_t : public uhd::soft_reg32_wo_t { + public: + UHD_DEFINE_SOFT_REG_FIELD(ATTEN_LB_CH1, /*width*/ 5, /*shift*/ 0); + + rf2_reg0_t(): uhd::soft_reg32_wo_t(cpld::addr(/*cpld*/ 4, /*reg*/ 0), OPTIMIZED_FLUSH) { + set(REGISTER, 0); + } + } rf2_reg0; + + class rf2_reg2_t : public uhd::soft_reg32_wo_t { + public: + UHD_DEFINE_SOFT_REG_FIELD(SW11_CTRL_CH1, /*width*/ 1, /*shift*/ 0); + UHD_DEFINE_SOFT_REG_FIELD(AMP_LB_EN_CH1, /*width*/ 1, /*shift*/ 1); + UHD_DEFINE_SOFT_REG_FIELD(SWPA2_CTRL_CH1, /*width*/ 1, /*shift*/ 2); + + rf2_reg2_t(): uhd::soft_reg32_wo_t(cpld::addr(/*cpld*/ 4, /*reg*/ 2), OPTIMIZED_FLUSH) { + set(REGISTER, 0); + } + } rf2_reg2; + + class rf2_reg3_t : public uhd::soft_reg32_wo_t { + public: + UHD_DEFINE_SOFT_REG_FIELD(PREAMP2_EN_CH1, /*width*/ 1, /*shift*/ 0); + UHD_DEFINE_SOFT_REG_FIELD(SW18_CTRL_CH2, /*width*/ 1, /*shift*/ 1); + UHD_DEFINE_SOFT_REG_FIELD(SW8_CTRL_CH1, /*width*/ 2, /*shift*/ 2); + + rf2_reg3_t(): uhd::soft_reg32_wo_t(cpld::addr(/*cpld*/ 4, /*reg*/ 3), OPTIMIZED_FLUSH) { + set(REGISTER, 0); + } + } rf2_reg3; + + class rf2_reg4_t : public uhd::soft_reg32_wo_t { + public: + UHD_DEFINE_SOFT_REG_FIELD(ATTEN_LB_CH2, /*width*/ 5, /*shift*/ 0); + + rf2_reg4_t(): uhd::soft_reg32_wo_t(cpld::addr(/*cpld*/ 4, /*reg*/ 4), OPTIMIZED_FLUSH) { + set(REGISTER, 0); + } + } rf2_reg4; + + class rf2_reg5_t : public uhd::soft_reg32_wo_t { + public: + UHD_DEFINE_SOFT_REG_FIELD(SWPA2_CTRL_CH2, /*width*/ 1, /*shift*/ 0); + + rf2_reg5_t(): uhd::soft_reg32_wo_t(cpld::addr(/*cpld*/ 4, /*reg*/ 5), OPTIMIZED_FLUSH) { + set(REGISTER, 0); + } + } rf2_reg5; + + class rf2_reg6_t : public uhd::soft_reg32_wo_t { + public: + UHD_DEFINE_SOFT_REG_FIELD(LB_PREAMP_EN_CH2, /*width*/ 1, /*shift*/ 0); + + rf2_reg6_t(): uhd::soft_reg32_wo_t(cpld::addr(/*cpld*/ 4, /*reg*/ 6), OPTIMIZED_FLUSH) { + set(REGISTER, 0); + } + } rf2_reg6; + + class rf2_reg7_t : public uhd::soft_reg32_wo_t { + public: + UHD_DEFINE_SOFT_REG_FIELD(SWPA4_CTRL_CH1, /*width*/ 1, /*shift*/ 0); + UHD_DEFINE_SOFT_REG_FIELD(SW8_CTRL_CH2, /*width*/ 2, /*shift*/ 1); + UHD_DEFINE_SOFT_REG_FIELD(SW11_CTRL_CH2, /*width*/ 1, /*shift*/ 3); + UHD_DEFINE_SOFT_REG_FIELD(AMP_LB_EN_CH2, /*width*/ 1, /*shift*/ 4); + + rf2_reg7_t(): uhd::soft_reg32_wo_t(cpld::addr(/*cpld*/ 4, /*reg*/ 7), OPTIMIZED_FLUSH) { + set(REGISTER, 0); + } + } rf2_reg7; + + twinrx_cpld_regmap() : soft_regmap_t("twinrx_cpld") { + // IF CCA: CPLD 1 + add_to_map(if0_reg0, "if0_reg0"); + add_to_map(if0_reg1, "if0_reg1"); + add_to_map(if0_reg2, "if0_reg2"); + add_to_map(if0_reg3, "if0_reg3"); + add_to_map(if0_reg4, "if0_reg4"); + add_to_map(if0_reg6, "if0_reg6"); + add_to_map(if0_reg7, "if0_reg7"); + // RF CCA: CPLD 2 + add_to_map(rf0_reg0, "rf0_reg0"); + add_to_map(rf0_reg1, "rf0_reg1"); + add_to_map(rf0_reg2, "rf0_reg2"); + add_to_map(rf0_reg3, "rf0_reg3"); + add_to_map(rf0_reg4, "rf0_reg4"); + add_to_map(rf0_reg5, "rf0_reg5"); + add_to_map(rf0_reg6, "rf0_reg6"); + add_to_map(rf0_reg7, "rf0_reg7"); + // RF CCA: CPLD 3 + add_to_map(rf1_reg0, "rf1_reg0"); + add_to_map(rf1_reg1, "rf1_reg1"); + add_to_map(rf1_reg2, "rf1_reg2"); + add_to_map(rf1_reg3, "rf1_reg3"); + add_to_map(rf1_reg4, "rf1_reg4"); + add_to_map(rf1_reg5, "rf1_reg5"); + add_to_map(rf1_reg6, "rf1_reg6"); + add_to_map(rf1_reg7, "rf1_reg7"); + // RF CCA: CPLD 4 + add_to_map(rf2_reg0, "rf2_reg0"); + add_to_map(rf2_reg2, "rf2_reg2"); + add_to_map(rf2_reg3, "rf2_reg3"); + add_to_map(rf2_reg4, "rf2_reg4"); + add_to_map(rf2_reg5, "rf2_reg5"); + add_to_map(rf2_reg6, "rf2_reg6"); + add_to_map(rf2_reg7, "rf2_reg7"); + } +}; + +}}}} //namespaces + +#endif /* INCLUDED_DBOARD_TWINRX_IO_HPP */ diff --git a/host/lib/usrp/dboard_base.cpp b/host/lib/usrp/dboard_base.cpp index fe14c02b9..465b9e489 100644 --- a/host/lib/usrp/dboard_base.cpp +++ b/host/lib/usrp/dboard_base.cpp @@ -32,7 +32,7 @@ struct dboard_base::impl{ dboard_base::dboard_base(ctor_args_t args){ _impl = UHD_PIMPL_MAKE(impl, ()); - _impl->args = *static_cast<dboard_ctor_args_t *>(args); + _impl->args = dboard_ctor_args_t::cast(args); } std::string dboard_base::get_subdev_name(void){ diff --git a/host/lib/usrp/dboard_ctor_args.hpp b/host/lib/usrp/dboard_ctor_args.hpp index 99c071ff8..c8e4006d1 100644 --- a/host/lib/usrp/dboard_ctor_args.hpp +++ b/host/lib/usrp/dboard_ctor_args.hpp @@ -26,11 +26,17 @@ namespace uhd{ namespace usrp{ - struct dboard_ctor_args_t{ + class dboard_ctor_args_t { + public: std::string sd_name; dboard_iface::sptr db_iface; dboard_id_t rx_id, tx_id; property_tree::sptr rx_subtree, tx_subtree; + dboard_base::sptr rx_container, tx_container; + + static const dboard_ctor_args_t& cast(dboard_base::ctor_args_t args) { + return *static_cast<dboard_ctor_args_t*>(args); + } }; }} //namespace diff --git a/host/lib/usrp/dboard_iface.cpp b/host/lib/usrp/dboard_iface.cpp index 092e005f0..2a04095e8 100644 --- a/host/lib/usrp/dboard_iface.cpp +++ b/host/lib/usrp/dboard_iface.cpp @@ -1,5 +1,5 @@ // -// Copyright 2010-2013,2015 Ettus Research LLC +// Copyright 2016 Ettus Research // // 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 @@ -16,78 +16,16 @@ // #include <uhd/usrp/dboard_iface.hpp> -#include <uhd/types/dict.hpp> using namespace uhd::usrp; -struct dboard_iface::impl{ - uhd::dict<unit_t, boost::uint16_t> pin_ctrl_shadow; - uhd::dict<unit_t, uhd::dict<atr_reg_t, boost::uint16_t> > atr_reg_shadow; - uhd::dict<unit_t, boost::uint16_t> gpio_ddr_shadow; - uhd::dict<unit_t, boost::uint16_t> gpio_out_shadow; -}; - -dboard_iface::dboard_iface(void){ - _impl = UHD_PIMPL_MAKE(impl, ()); -} - -dboard_iface::~dboard_iface(void) -{ - //empty -} - -template <typename T> -static T shadow_it(T &shadow, const T &value, const T &mask){ - shadow = (shadow & ~mask) | (value & mask); - return shadow; -} - -void dboard_iface::set_pin_ctrl( - unit_t unit, boost::uint16_t value, boost::uint16_t mask -){ - _set_pin_ctrl(unit, shadow_it(_impl->pin_ctrl_shadow[unit], value, mask)); -} - -boost::uint16_t dboard_iface::get_pin_ctrl(unit_t unit){ - return _impl->pin_ctrl_shadow[unit]; -} - -void dboard_iface::set_atr_reg( - unit_t unit, atr_reg_t reg, boost::uint16_t value, boost::uint16_t mask -){ - _set_atr_reg(unit, reg, shadow_it(_impl->atr_reg_shadow[unit][reg], value, mask)); -} - -boost::uint16_t dboard_iface::get_atr_reg(unit_t unit, atr_reg_t reg){ - return _impl->atr_reg_shadow[unit][reg]; -} - -void dboard_iface::set_gpio_ddr( - unit_t unit, boost::uint16_t value, boost::uint16_t mask -){ - _set_gpio_ddr(unit, shadow_it(_impl->gpio_ddr_shadow[unit], value, mask)); -} - -boost::uint16_t dboard_iface::get_gpio_ddr(unit_t unit){ - return _impl->gpio_ddr_shadow[unit]; -} - -void dboard_iface::set_gpio_out( - unit_t unit, boost::uint16_t value, boost::uint16_t mask -){ - _set_gpio_out(unit, shadow_it(_impl->gpio_out_shadow[unit], value, mask)); -} - -boost::uint16_t dboard_iface::get_gpio_out(unit_t unit){ - return _impl->gpio_out_shadow[unit]; -} - -void dboard_iface::set_command_time(const uhd::time_spec_t&) -{ - throw uhd::not_implemented_error("timed command feature not implemented on this hardware"); -} - -uhd::time_spec_t dboard_iface::get_command_time() +void dboard_iface::sleep(const boost::chrono::nanoseconds& time) { - return uhd::time_spec_t(0.0); + //nanosleep is not really accurate in userland and it is also not very + //cross-platform. So just sleep for the minimum amount of time in us. + if (time < boost::chrono::microseconds(1)) { + boost::this_thread::sleep_for(boost::chrono::microseconds(1)); + } else { + boost::this_thread::sleep_for(time); + } } diff --git a/host/lib/usrp/dboard_manager.cpp b/host/lib/usrp/dboard_manager.cpp index 340c1d3f9..56cd08fd7 100644 --- a/host/lib/usrp/dboard_manager.cpp +++ b/host/lib/usrp/dboard_manager.cpp @@ -37,11 +37,11 @@ using namespace uhd::usrp; **********************************************************************/ class dboard_key_t{ public: - dboard_key_t(const dboard_id_t &id = dboard_id_t::none()): - _rx_id(id), _tx_id(id), _xcvr(false){} + dboard_key_t(const dboard_id_t &id = dboard_id_t::none(), bool restricted = false): + _rx_id(id), _tx_id(id), _xcvr(false), _restricted(restricted) {} - dboard_key_t(const dboard_id_t &rx_id, const dboard_id_t &tx_id): - _rx_id(rx_id), _tx_id(tx_id), _xcvr(true){} + dboard_key_t(const dboard_id_t &rx_id, const dboard_id_t &tx_id, bool restricted = false): + _rx_id(rx_id), _tx_id(tx_id), _xcvr(true), _restricted(restricted) {} dboard_id_t xx_id(void) const{ UHD_ASSERT_THROW(not this->is_xcvr()); @@ -62,9 +62,14 @@ public: return this->_xcvr; } + bool is_restricted(void) const{ + return this->_restricted; + } + private: dboard_id_t _rx_id, _tx_id; bool _xcvr; + bool _restricted; }; bool operator==(const dboard_key_t &lhs, const dboard_key_t &rhs){ @@ -78,8 +83,8 @@ bool operator==(const dboard_key_t &lhs, const dboard_key_t &rhs){ /*********************************************************************** * storage and registering for dboards **********************************************************************/ -//dboard registry tuple: dboard constructor, canonical name, subdev names -typedef boost::tuple<dboard_manager::dboard_ctor_t, std::string, std::vector<std::string> > args_t; +//dboard registry tuple: dboard constructor, canonical name, subdev names, container constructor +typedef boost::tuple<dboard_manager::dboard_ctor_t, std::string, std::vector<std::string>, dboard_manager::dboard_ctor_t> args_t; //map a dboard id to a dboard constructor typedef uhd::dict<dboard_key_t, args_t> id_to_args_map_t; @@ -87,9 +92,10 @@ UHD_SINGLETON_FCN(id_to_args_map_t, get_id_to_args_map) static void register_dboard_key( const dboard_key_t &dboard_key, - dboard_manager::dboard_ctor_t dboard_ctor, + dboard_manager::dboard_ctor_t db_subdev_ctor, const std::string &name, - const std::vector<std::string> &subdev_names + const std::vector<std::string> &subdev_names, + dboard_manager::dboard_ctor_t db_container_ctor ){ UHD_LOGV(always) << "registering: " << name << std::endl; if (get_id_to_args_map().has_key(dboard_key)){ @@ -103,26 +109,49 @@ static void register_dboard_key( ) % dboard_key.xx_id().to_string() % get_id_to_args_map()[dboard_key].get<1>())); } - get_id_to_args_map()[dboard_key] = args_t(dboard_ctor, name, subdev_names); + get_id_to_args_map()[dboard_key] = args_t(db_subdev_ctor, name, subdev_names, db_container_ctor); } void dboard_manager::register_dboard( const dboard_id_t &dboard_id, - dboard_ctor_t dboard_ctor, + dboard_ctor_t db_subdev_ctor, const std::string &name, - const std::vector<std::string> &subdev_names + const std::vector<std::string> &subdev_names, + dboard_ctor_t db_container_ctor ){ - register_dboard_key(dboard_key_t(dboard_id), dboard_ctor, name, subdev_names); + register_dboard_key(dboard_key_t(dboard_id), db_subdev_ctor, name, subdev_names, db_container_ctor); } void dboard_manager::register_dboard( const dboard_id_t &rx_dboard_id, const dboard_id_t &tx_dboard_id, - dboard_ctor_t dboard_ctor, + dboard_ctor_t db_subdev_ctor, + const std::string &name, + const std::vector<std::string> &subdev_names, + dboard_ctor_t db_container_ctor +){ + register_dboard_key(dboard_key_t(rx_dboard_id, tx_dboard_id), db_subdev_ctor, name, subdev_names, db_container_ctor); +} + +void dboard_manager::register_dboard_restricted( + const dboard_id_t &dboard_id, + dboard_ctor_t db_subdev_ctor, const std::string &name, - const std::vector<std::string> &subdev_names + const std::vector<std::string> &subdev_names, + dboard_ctor_t db_container_ctor ){ - register_dboard_key(dboard_key_t(rx_dboard_id, tx_dboard_id), dboard_ctor, name, subdev_names); + register_dboard_key(dboard_key_t(dboard_id, true), db_subdev_ctor, name, subdev_names, db_container_ctor); +} + +void dboard_manager::register_dboard_restricted( + const dboard_id_t &rx_dboard_id, + const dboard_id_t &tx_dboard_id, + dboard_ctor_t db_subdev_ctor, + const std::string &name, + const std::vector<std::string> &subdev_names, + dboard_ctor_t db_container_ctor +){ + register_dboard_key(dboard_key_t(rx_dboard_id, tx_dboard_id, true), db_subdev_ctor, name, subdev_names, db_container_ctor); } std::string dboard_id_t::to_cname(void) const{ @@ -153,17 +182,32 @@ public: dboard_id_t rx_dboard_id, dboard_id_t tx_dboard_id, dboard_iface::sptr iface, - property_tree::sptr subtree + property_tree::sptr subtree, + bool defer_db_init ); - ~dboard_manager_impl(void); + virtual ~dboard_manager_impl(void); + + inline const std::vector<std::string>& get_rx_frontends() const { + return _rx_frontends; + } + + inline const std::vector<std::string>& get_tx_frontends() const { + return _tx_frontends; + } + + void initialize_dboards(); private: - void init(dboard_id_t, dboard_id_t, property_tree::sptr); + void init(dboard_id_t, dboard_id_t, property_tree::sptr, bool); //list of rx and tx dboards in this dboard_manager //each dboard here is actually a subdevice proxy //the subdevice proxy is internal to the cpp file uhd::dict<std::string, dboard_base::sptr> _rx_dboards; uhd::dict<std::string, dboard_base::sptr> _tx_dboards; + std::vector<dboard_base::sptr> _rx_containers; + std::vector<dboard_base::sptr> _tx_containers; + std::vector<std::string> _rx_frontends; + std::vector<std::string> _tx_frontends; dboard_iface::sptr _iface; void set_nice_dboard_if(void); }; @@ -176,13 +220,14 @@ dboard_manager::sptr dboard_manager::make( dboard_id_t tx_dboard_id, dboard_id_t gdboard_id, dboard_iface::sptr iface, - property_tree::sptr subtree + property_tree::sptr subtree, + bool defer_db_init ){ return dboard_manager::sptr( new dboard_manager_impl( rx_dboard_id, (gdboard_id == dboard_id_t::none())? tx_dboard_id : gdboard_id, - iface, subtree + iface, subtree, defer_db_init ) ); } @@ -194,12 +239,13 @@ dboard_manager_impl::dboard_manager_impl( dboard_id_t rx_dboard_id, dboard_id_t tx_dboard_id, dboard_iface::sptr iface, - property_tree::sptr subtree + property_tree::sptr subtree, + bool defer_db_init ): _iface(iface) { try{ - this->init(rx_dboard_id, tx_dboard_id, subtree); + this->init(rx_dboard_id, tx_dboard_id, subtree, defer_db_init); } catch(const std::exception &e){ UHD_MSG(error) << boost::format( @@ -210,12 +256,13 @@ dboard_manager_impl::dboard_manager_impl( //clean up the stuff added by the call above if (subtree->exists("rx_frontends")) subtree->remove("rx_frontends"); if (subtree->exists("tx_frontends")) subtree->remove("tx_frontends"); - this->init(dboard_id_t::none(), dboard_id_t::none(), subtree); + if (subtree->exists("iface")) subtree->remove("iface"); + this->init(dboard_id_t::none(), dboard_id_t::none(), subtree, false); } } void dboard_manager_impl::init( - dboard_id_t rx_dboard_id, dboard_id_t tx_dboard_id, property_tree::sptr subtree + dboard_id_t rx_dboard_id, dboard_id_t tx_dboard_id, property_tree::sptr subtree, bool defer_db_init ){ //find the dboard key matches for the dboard ids dboard_key_t rx_dboard_key, tx_dboard_key, xcvr_dboard_key; @@ -244,6 +291,11 @@ void dboard_manager_impl::init( //initialize the gpio pins before creating subdevs set_nice_dboard_if(); + //conditionally register the dboard iface in the tree + if (not (rx_dboard_key.is_restricted() or tx_dboard_key.is_restricted() or xcvr_dboard_key.is_restricted())) { + subtree->create<dboard_iface::sptr>("iface").set(_iface); + } + //dboard constructor args dboard_ctor_args_t db_ctor_args; db_ctor_args.db_iface = _iface; @@ -252,42 +304,89 @@ void dboard_manager_impl::init( if (xcvr_dboard_key.is_xcvr()){ //extract data for the xcvr dboard key - dboard_ctor_t dboard_ctor; std::string name; std::vector<std::string> subdevs; - boost::tie(dboard_ctor, name, subdevs) = get_id_to_args_map()[xcvr_dboard_key]; + dboard_ctor_t subdev_ctor; std::string name; std::vector<std::string> subdevs; dboard_ctor_t container_ctor; + boost::tie(subdev_ctor, name, subdevs, container_ctor) = get_id_to_args_map()[xcvr_dboard_key]; + + //create the container class. + //a container class exists per N subdevs registered in a register_dboard* call + db_ctor_args.sd_name = "common"; + db_ctor_args.rx_id = rx_dboard_id; + db_ctor_args.tx_id = tx_dboard_id; + db_ctor_args.rx_subtree = subtree->subtree("rx_frontends/" + db_ctor_args.sd_name); + db_ctor_args.tx_subtree = subtree->subtree("tx_frontends/" + db_ctor_args.sd_name); + if (container_ctor) { + db_ctor_args.rx_container = container_ctor(&db_ctor_args); + } else { + db_ctor_args.rx_container = dboard_base::sptr(); + } + db_ctor_args.tx_container = db_ctor_args.rx_container; //Same TX and RX container //create the xcvr object for each subdevice BOOST_FOREACH(const std::string &subdev, subdevs){ db_ctor_args.sd_name = subdev; - db_ctor_args.rx_id = rx_dboard_id; - db_ctor_args.tx_id = tx_dboard_id; - db_ctor_args.rx_subtree = subtree->subtree("rx_frontends/" + subdev); - db_ctor_args.tx_subtree = subtree->subtree("tx_frontends/" + subdev); - dboard_base::sptr xcvr_dboard = dboard_ctor(&db_ctor_args); + db_ctor_args.rx_subtree = subtree->subtree("rx_frontends/" + db_ctor_args.sd_name); + db_ctor_args.tx_subtree = subtree->subtree("tx_frontends/" + db_ctor_args.sd_name); + dboard_base::sptr xcvr_dboard = subdev_ctor(&db_ctor_args); _rx_dboards[subdev] = xcvr_dboard; _tx_dboards[subdev] = xcvr_dboard; + xcvr_dboard->initialize(); } + + //initialize the container after all subdevs have been created + if (container_ctor) { + if (defer_db_init) { + _rx_containers.push_back(db_ctor_args.rx_container); + } else { + db_ctor_args.rx_container->initialize(); + } + } + + //Populate frontend names in-order. + //We cannot use _xx_dboards.keys() here because of the ordering requirement + _rx_frontends = subdevs; + _tx_frontends = subdevs; } //make tx and rx subdevs (separate subdevs for rx and tx dboards) - else{ - + else + { //force the rx key to the unknown board for bad combinations if (rx_dboard_key.is_xcvr() or rx_dboard_key.xx_id() == dboard_id_t::none()){ rx_dboard_key = dboard_key_t(0xfff1); } //extract data for the rx dboard key - dboard_ctor_t rx_dboard_ctor; std::string rx_name; std::vector<std::string> rx_subdevs; - boost::tie(rx_dboard_ctor, rx_name, rx_subdevs) = get_id_to_args_map()[rx_dboard_key]; + dboard_ctor_t rx_dboard_ctor; std::string rx_name; std::vector<std::string> rx_subdevs; dboard_ctor_t rx_cont_ctor; + boost::tie(rx_dboard_ctor, rx_name, rx_subdevs, rx_cont_ctor) = get_id_to_args_map()[rx_dboard_key]; + + //create the container class. + //a container class exists per N subdevs registered in a register_dboard* call + db_ctor_args.sd_name = "common"; + db_ctor_args.rx_id = rx_dboard_id; + db_ctor_args.tx_id = dboard_id_t::none(); + db_ctor_args.rx_subtree = subtree->subtree("rx_frontends/" + db_ctor_args.sd_name); + db_ctor_args.tx_subtree = property_tree::sptr(); + if (rx_cont_ctor) { + db_ctor_args.rx_container = rx_cont_ctor(&db_ctor_args); + } else { + db_ctor_args.rx_container = dboard_base::sptr(); + } //make the rx subdevs BOOST_FOREACH(const std::string &subdev, rx_subdevs){ db_ctor_args.sd_name = subdev; - db_ctor_args.rx_id = rx_dboard_id; - db_ctor_args.tx_id = dboard_id_t::none(); - db_ctor_args.rx_subtree = subtree->subtree("rx_frontends/" + subdev); - db_ctor_args.tx_subtree = property_tree::sptr(); //null + db_ctor_args.rx_subtree = subtree->subtree("rx_frontends/" + db_ctor_args.sd_name); _rx_dboards[subdev] = rx_dboard_ctor(&db_ctor_args); + _rx_dboards[subdev]->initialize(); + } + + //initialize the container after all subdevs have been created + if (rx_cont_ctor) { + if (defer_db_init) { + _rx_containers.push_back(db_ctor_args.rx_container); + } else { + db_ctor_args.rx_container->initialize(); + } } //force the tx key to the unknown board for bad combinations @@ -296,18 +395,53 @@ void dboard_manager_impl::init( } //extract data for the tx dboard key - dboard_ctor_t tx_dboard_ctor; std::string tx_name; std::vector<std::string> tx_subdevs; - boost::tie(tx_dboard_ctor, tx_name, tx_subdevs) = get_id_to_args_map()[tx_dboard_key]; + dboard_ctor_t tx_dboard_ctor; std::string tx_name; std::vector<std::string> tx_subdevs; dboard_ctor_t tx_cont_ctor; + boost::tie(tx_dboard_ctor, tx_name, tx_subdevs, tx_cont_ctor) = get_id_to_args_map()[tx_dboard_key]; + + //create the container class. + //a container class exists per N subdevs registered in a register_dboard* call + db_ctor_args.sd_name = "common"; + db_ctor_args.rx_id = dboard_id_t::none(); + db_ctor_args.tx_id = tx_dboard_id; + db_ctor_args.rx_subtree = property_tree::sptr(); + db_ctor_args.tx_subtree = subtree->subtree("tx_frontends/" + db_ctor_args.sd_name); + if (tx_cont_ctor) { + db_ctor_args.tx_container = tx_cont_ctor(&db_ctor_args); + } else { + db_ctor_args.tx_container = dboard_base::sptr(); + } //make the tx subdevs BOOST_FOREACH(const std::string &subdev, tx_subdevs){ db_ctor_args.sd_name = subdev; - db_ctor_args.rx_id = dboard_id_t::none(); - db_ctor_args.tx_id = tx_dboard_id; - db_ctor_args.rx_subtree = property_tree::sptr(); //null - db_ctor_args.tx_subtree = subtree->subtree("tx_frontends/" + subdev); + db_ctor_args.tx_subtree = subtree->subtree("tx_frontends/" + db_ctor_args.sd_name); _tx_dboards[subdev] = tx_dboard_ctor(&db_ctor_args); + _tx_dboards[subdev]->initialize(); + } + + //initialize the container after all subdevs have been created + if (tx_cont_ctor) { + if (defer_db_init) { + _tx_containers.push_back(db_ctor_args.tx_container); + } else { + db_ctor_args.tx_container->initialize(); + } } + + //Populate frontend names in-order. + //We cannot use _xx_dboards.keys() here because of the ordering requirement + _rx_frontends = rx_subdevs; + _tx_frontends = tx_subdevs; + } +} + +void dboard_manager_impl::initialize_dboards(void) { + BOOST_FOREACH(dboard_base::sptr& _rx_container, _rx_containers) { + _rx_container->initialize(); + } + + BOOST_FOREACH(dboard_base::sptr& _tx_container, _tx_containers) { + _tx_container->initialize(); } } diff --git a/host/lib/usrp/device3/CMakeLists.txt b/host/lib/usrp/device3/CMakeLists.txt new file mode 100644 index 000000000..83f01a2e7 --- /dev/null +++ b/host/lib/usrp/device3/CMakeLists.txt @@ -0,0 +1,25 @@ +# +# Copyright 2014 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 +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. +# + +######################################################################## +# This file included, use CMake directory variables +######################################################################## + +LIBUHD_APPEND_SOURCES( + ${CMAKE_CURRENT_SOURCE_DIR}/device3_impl.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/device3_io_impl.cpp +) diff --git a/host/lib/usrp/device3/device3_impl.cpp b/host/lib/usrp/device3/device3_impl.cpp new file mode 100644 index 000000000..7fcbc01b2 --- /dev/null +++ b/host/lib/usrp/device3/device3_impl.cpp @@ -0,0 +1,188 @@ +// +// Copyright 2014 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 +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +#include "device3_impl.hpp" +#include "graph_impl.hpp" +#include <uhd/utils/msg.hpp> +#include <uhd/rfnoc/block_ctrl_base.hpp> +#include <boost/make_shared.hpp> +#include <algorithm> + +#define UHD_DEVICE3_LOG() UHD_LOGV(never) + +using namespace uhd::usrp; + +device3_impl::device3_impl() + : _sid_framer(0) +{ + _type = uhd::device::USRP; + _async_md.reset(new async_md_type(1000/*messages deep*/)); + _tree = uhd::property_tree::make(); +}; + +//! Returns true if the integer value stored in lhs is smaller than that in rhs +bool _compare_string_indexes(const std::string &lhs, const std::string &rhs) +{ + return boost::lexical_cast<size_t>(lhs) < boost::lexical_cast<size_t>(rhs); +} + +void device3_impl::merge_channel_defs( + const std::vector<uhd::rfnoc::block_id_t> &chan_ids, + const std::vector<uhd::device_addr_t> &chan_args, + const uhd::direction_t dir +) { + UHD_ASSERT_THROW(chan_ids.size() == chan_args.size()); + if (dir == uhd::DX_DIRECTION) { + merge_channel_defs(chan_ids, chan_args, RX_DIRECTION); + merge_channel_defs(chan_ids, chan_args, TX_DIRECTION); + return; + } + + uhd::fs_path chans_root = uhd::fs_path("/channels/") / (dir == RX_DIRECTION ? "rx" : "tx"); + // Store the new positions of the channels: + std::vector<size_t> chan_idxs; + + // 1. Get sorted list of currently defined channels + std::vector<std::string> curr_channels; + if (_tree->exists(chans_root)) { + curr_channels = _tree->list(chans_root); + std::sort(curr_channels.begin(), curr_channels.end(), _compare_string_indexes); + } + + // 2. Cycle through existing channels to find out where to merge + // the new channels. Rules are: + // - The order of chan_ids must be preserved + // - All block indices that are in chan_ids may be overwritten in the channel definition + // - If the channels in chan_ids are not yet in the property tree channel list, + // they are appended. + BOOST_FOREACH(const std::string &chan_idx, curr_channels) { + if (_tree->exists(chans_root / chan_idx)) { + rfnoc::block_id_t chan_block_id = _tree->access<rfnoc::block_id_t>(chans_root / chan_idx).get(); + if (std::find(chan_ids.begin(), chan_ids.end(), chan_block_id) != chan_ids.end()) { + chan_idxs.push_back(boost::lexical_cast<size_t>(chan_idx)); + } + } + } + size_t last_chan_idx = curr_channels.empty() ? 0 : (boost::lexical_cast<size_t>(curr_channels.back()) + 1); + while (chan_idxs.size() < chan_ids.size()) { + chan_idxs.push_back(last_chan_idx); + last_chan_idx++; + } + + // 3. Write the new channels + for (size_t i = 0; i < chan_ids.size(); i++) { + if (not _tree->exists(chans_root / chan_idxs[i])) { + _tree->create<rfnoc::block_id_t>(chans_root / chan_idxs[i]); + } + _tree->access<rfnoc::block_id_t>(chans_root / chan_idxs[i]).set(chan_ids[i]); + if (not _tree->exists(chans_root / chan_idxs[i] / "args")) { + _tree->create<uhd::device_addr_t>(chans_root / chan_idxs[i] / "args"); + } + _tree->access<uhd::device_addr_t>(chans_root / chan_idxs[i] / "args").set(chan_args[i]); + } +} + +/*********************************************************************** + * RFNoC-Specific + **********************************************************************/ +void device3_impl::enumerate_rfnoc_blocks( + size_t device_index, + size_t n_blocks, + size_t base_port, + const uhd::sid_t &base_sid, + uhd::device_addr_t transport_args, + uhd::endianness_t endianness +) { + // entries that are already connected to this block + uhd::sid_t ctrl_sid = base_sid; + uhd::property_tree::sptr subtree = _tree->subtree(uhd::fs_path("/mboards") / device_index); + // 1) Clean property tree entries + // TODO put this back once radios are actual rfnoc blocks!!!!!! + //if (subtree->exists("xbar")) { + //subtree->remove("xbar"); + //} + // 2) Destroy existing block controllers + // TODO: Clear out all the old block control classes + // 3) Create new block controllers + for (size_t i = 0; i < n_blocks; i++) { + UHD_DEVICE3_LOG() << "[RFNOC] ------- Block Setup -----------" << std::endl; + // First, make a transport for port number zero, because we always need that: + ctrl_sid.set_dst_xbarport(base_port + i); + ctrl_sid.set_dst_blockport(0); + both_xports_t xport = this->make_transport( + ctrl_sid, + CTRL, + transport_args + ); + UHD_DEVICE3_LOG() << str(boost::format("Setting up NoC-Shell Control for port #0 (SID: %s)...") % xport.send_sid.to_pp_string_hex()); + uhd::rfnoc::ctrl_iface::sptr ctrl = uhd::rfnoc::ctrl_iface::make( + endianness == ENDIANNESS_BIG, + xport.send, + xport.recv, + xport.send_sid, + str(boost::format("CE_%02d_Port_%02X") % i % ctrl_sid.get_dst_endpoint()) + ); + UHD_DEVICE3_LOG() << "OK" << std::endl; + uint64_t noc_id = ctrl->peek64(uhd::rfnoc::SR_READBACK_REG_ID); + UHD_DEVICE3_LOG() << str(boost::format("Port %d: Found NoC-Block with ID %016X.") % int(ctrl_sid.get_dst_endpoint()) % noc_id) << std::endl; + uhd::rfnoc::make_args_t make_args; + uhd::rfnoc::blockdef::sptr block_def = uhd::rfnoc::blockdef::make_from_noc_id(noc_id); + if (not block_def) { + UHD_DEVICE3_LOG() << "Using default block configuration." << std::endl; + block_def = uhd::rfnoc::blockdef::make_from_noc_id(uhd::rfnoc::DEFAULT_NOC_ID); + } + UHD_ASSERT_THROW(block_def); + make_args.ctrl_ifaces[0] = ctrl; + BOOST_FOREACH(const size_t port_number, block_def->get_all_port_numbers()) { + if (port_number == 0) { // We've already set this up + continue; + } + ctrl_sid.set_dst_blockport(port_number); + both_xports_t xport1 = this->make_transport( + ctrl_sid, + CTRL, + transport_args + ); + UHD_DEVICE3_LOG() << str(boost::format("Setting up NoC-Shell Control for port #%d (SID: %s)...") % port_number % xport1.send_sid.to_pp_string_hex()); + uhd::rfnoc::ctrl_iface::sptr ctrl1 = uhd::rfnoc::ctrl_iface::make( + endianness == ENDIANNESS_BIG, + xport1.send, + xport1.recv, + xport1.send_sid, + str(boost::format("CE_%02d_Port_%02d") % i % ctrl_sid.get_dst_endpoint()) + ); + UHD_DEVICE3_LOG() << "OK" << std::endl; + make_args.ctrl_ifaces[port_number] = ctrl1; + } + + make_args.base_address = xport.send_sid.get_dst(); + make_args.device_index = device_index; + make_args.tree = subtree; + make_args.is_big_endian = (endianness == ENDIANNESS_BIG); + _rfnoc_block_ctrl.push_back(uhd::rfnoc::block_ctrl_base::make(make_args, noc_id)); + } +} + + +uhd::rfnoc::graph::sptr device3_impl::create_graph(const std::string &name) +{ + return boost::make_shared<uhd::rfnoc::graph_impl>( + name, + shared_from_this() + ); +} + diff --git a/host/lib/usrp/device3/device3_impl.hpp b/host/lib/usrp/device3/device3_impl.hpp new file mode 100644 index 000000000..0d94ae21c --- /dev/null +++ b/host/lib/usrp/device3/device3_impl.hpp @@ -0,0 +1,210 @@ +// +// Copyright 2014-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 +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +// Declares the device3_impl class which is a layer between device3 and +// the different 3-rd gen device impls (e.g. x300_impl) + +#ifndef INCLUDED_DEVICE3_IMPL_HPP +#define INCLUDED_DEVICE3_IMPL_HPP + +#include <uhd/transport/bounded_buffer.hpp> +#include <uhd/transport/vrt_if_packet.hpp> +#include <uhd/transport/chdr.hpp> +#include <uhd/transport/zero_copy.hpp> +#include <uhd/types/sid.hpp> +#include <uhd/types/metadata.hpp> +#include <uhd/types/endianness.hpp> +#include <uhd/types/direction.hpp> +#include <uhd/utils/tasks.hpp> +#include <uhd/device3.hpp> +#include "xports.hpp" +// Common FPGA cores: +#include "ctrl_iface.hpp" +#include "rx_dsp_core_3000.hpp" +#include "tx_dsp_core_3000.hpp" +#include "rx_vita_core_3000.hpp" +#include "tx_vita_core_3000.hpp" +#include "rx_frontend_core_200.hpp" +#include "tx_frontend_core_200.hpp" +#include "time_core_3000.hpp" +#include "gpio_atr_3000.hpp" +// RFNoC-specific includes: +#include "radio_ctrl_impl.hpp" + +namespace uhd { namespace usrp { + +/*********************************************************************** + * Default settings (any device3 may override these) + **********************************************************************/ +static const size_t DEVICE3_RX_FC_REQUEST_FREQ = 32; //per flow-control window +static const size_t DEVICE3_TX_FC_RESPONSE_FREQ = 8; +static const size_t DEVICE3_TX_FC_RESPONSE_CYCLES = 0; // Cycles: Off. + +static const size_t DEVICE3_TX_MAX_HDR_LEN = uhd::transport::vrt::chdr::max_if_hdr_words64 * sizeof(boost::uint64_t); // Bytes +static const size_t DEVICE3_RX_MAX_HDR_LEN = uhd::transport::vrt::chdr::max_if_hdr_words64 * sizeof(boost::uint64_t); // Bytes + +class device3_impl : public uhd::device3, public boost::enable_shared_from_this<device3_impl> +{ +public: + /*********************************************************************** + * device3-specific Types + **********************************************************************/ + typedef uhd::transport::bounded_buffer<uhd::async_metadata_t> async_md_type; + + //! The purpose of a transport + enum xport_type_t { + CTRL = 0, + TX_DATA, + RX_DATA + }; + + enum xport_t {AXI, ETH, PCIE}; + + //! Stores all streaming-related options + struct stream_options_t + { + //! Max size of the header in bytes for TX + size_t tx_max_len_hdr; + //! Max size of the header in bytes for RX + size_t rx_max_len_hdr; + //! How often we send ACKs to the upstream block per one full FC window + size_t rx_fc_request_freq; + //! How often the downstream block should send ACKs per one full FC window + size_t tx_fc_response_freq; + //! How often the downstream block should send ACKs in cycles + size_t tx_fc_response_cycles; + stream_options_t(void) + : tx_max_len_hdr(DEVICE3_TX_MAX_HDR_LEN) + , rx_max_len_hdr(DEVICE3_RX_MAX_HDR_LEN) + , rx_fc_request_freq(DEVICE3_RX_FC_REQUEST_FREQ) + , tx_fc_response_freq(DEVICE3_TX_FC_RESPONSE_FREQ) + , tx_fc_response_cycles(DEVICE3_TX_FC_RESPONSE_CYCLES) + {}; + }; + + /*********************************************************************** + * I/O Interface + **********************************************************************/ + uhd::tx_streamer::sptr get_tx_stream(const uhd::stream_args_t &); + uhd::rx_streamer::sptr get_rx_stream(const uhd::stream_args_t &); + bool recv_async_msg(uhd::async_metadata_t &async_metadata, double timeout); + + /*********************************************************************** + * Other public APIs + **********************************************************************/ + rfnoc::graph::sptr create_graph(const std::string &name=""); + +protected: + /*********************************************************************** + * Structors + **********************************************************************/ + device3_impl(); + virtual ~device3_impl() {}; + + /*********************************************************************** + * Streaming-related + **********************************************************************/ + // The 'rate' argument is so we can use these as subscribers to rate changes +public: // TODO make these protected again + void update_rx_streamers(double rate=-1.0); + void update_tx_streamers(double rate=-1.0); +protected: + + /*********************************************************************** + * Transport-related + **********************************************************************/ + stream_options_t stream_options; + + /*! \brief Create a transport to a given endpoint. + * + * \param address The endpoint address of the block we're creating a transport to. + * The source address in this value is not considered, only the + * destination address. + * \param xport_type Specify which kind of transport this is. + * \param args Additional arguments for the transport generation. See \ref page_transport + * for valid arguments. + */ + virtual uhd::both_xports_t make_transport( + const uhd::sid_t &address, + const xport_type_t xport_type, + const uhd::device_addr_t& args + ) = 0; + + virtual uhd::device_addr_t get_tx_hints(size_t) { return uhd::device_addr_t(); }; + virtual uhd::device_addr_t get_rx_hints(size_t) { return uhd::device_addr_t(); }; + virtual uhd::endianness_t get_transport_endianness(size_t mb_index) = 0; + + //! Is called after a streamer is generated + virtual void post_streamer_hooks(uhd::direction_t) {}; + + /*********************************************************************** + * Channel-related + **********************************************************************/ + /*! Merge a list of channels into the existing channel definition. + * + * Intelligently merge the channels described in \p chan_ids + * into the current channel definition. If none of the channels in + * \p chan_ids is in the current definition, they simply get appended. + * Otherwise, they get overwritten in the order of \p chan_ids. + * + * \param chan_ids List of block IDs for the channels. + * \param chan_args New channel args. Must have same length as chan_ids. + * + */ + void merge_channel_defs( + const std::vector<rfnoc::block_id_t> &chan_ids, + const std::vector<uhd::device_addr_t> &chan_args, + const uhd::direction_t dir + ); + + /*********************************************************************** + * RFNoC-Specific + **********************************************************************/ + void enumerate_rfnoc_blocks( + size_t device_index, + size_t n_blocks, + size_t base_port, + const uhd::sid_t &base_sid, + uhd::device_addr_t transport_args, + uhd::endianness_t endianness + ); + + /*********************************************************************** + * Members + **********************************************************************/ + //! A counter, designed to create unique SIDs + size_t _sid_framer; + + // TODO: Maybe move these to private + uhd::dict<std::string, boost::weak_ptr<uhd::rx_streamer> > _rx_streamers; + uhd::dict<std::string, boost::weak_ptr<uhd::tx_streamer> > _tx_streamers; + +private: + /*********************************************************************** + * Private Members + **********************************************************************/ + //! Buffer for async metadata + boost::shared_ptr<async_md_type> _async_md; + + //! This mutex locks the get_xx_stream() functions. + boost::mutex _transport_setup_mutex; +}; + +}} /* namespace uhd::usrp */ + +#endif /* INCLUDED_DEVICE3_IMPL_HPP */ +// vim: sw=4 expandtab: diff --git a/host/lib/usrp/device3/device3_io_impl.cpp b/host/lib/usrp/device3/device3_io_impl.cpp new file mode 100644 index 000000000..8c61f8f15 --- /dev/null +++ b/host/lib/usrp/device3/device3_io_impl.cpp @@ -0,0 +1,851 @@ +// +// Copyright 2014-2016 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 +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +// Provides streaming-related functions which are used by device3 objects. + +#define DEVICE3_STREAMER // For the super_*_packet_handlers + +#include "device3_impl.hpp" +#include <uhd/rfnoc/constants.hpp> +#include <uhd/rfnoc/source_block_ctrl_base.hpp> +#include <uhd/rfnoc/sink_block_ctrl_base.hpp> +#include <uhd/utils/byteswap.hpp> +#include <uhd/utils/log.hpp> +#include <uhd/utils/msg.hpp> +#include "../common/async_packet_handler.hpp" +#include "../../transport/super_recv_packet_handler.hpp" +#include "../../transport/super_send_packet_handler.hpp" +#include "../../rfnoc/rx_stream_terminator.hpp" +#include "../../rfnoc/tx_stream_terminator.hpp" +#include <uhd/rfnoc/rate_node_ctrl.hpp> +#include <uhd/rfnoc/radio_ctrl.hpp> + +#define UHD_STREAMER_LOG() UHD_LOGV(never) + +using namespace uhd; +using namespace uhd::usrp; +using namespace uhd::transport; + +//! CVITA uses 12-Bit sequence numbers +static const boost::uint32_t HW_SEQ_NUM_MASK = 0xfff; + + +/*********************************************************************** + * Helper functions for get_?x_stream() + **********************************************************************/ +static uhd::stream_args_t sanitize_stream_args(const uhd::stream_args_t &args_) +{ + uhd::stream_args_t args = args_; + if (args.channels.empty()) { + args.channels = std::vector<size_t>(1, 0); + } + + return args; +} + +static void check_stream_sig_compatible(const rfnoc::stream_sig_t &stream_sig, stream_args_t &args, const std::string &tx_rx) +{ + if (args.otw_format.empty()) { + if (stream_sig.item_type.empty()) { + throw uhd::runtime_error(str( + boost::format("[%s Streamer] No otw_format defined!") % tx_rx + )); + } else { + args.otw_format = stream_sig.item_type; + } + } else if (not stream_sig.item_type.empty() and stream_sig.item_type != args.otw_format) { + throw uhd::runtime_error(str( + boost::format("[%s Streamer] Conflicting OTW types defined: args.otw_format = '%s' <=> stream_sig.item_type = '%s'") + % tx_rx % args.otw_format % stream_sig.item_type + )); + } + const size_t bpi = convert::get_bytes_per_item(args.otw_format); // bytes per item + if (stream_sig.packet_size) { + if (args.args.has_key("spp")) { + size_t args_spp = args.args.cast<size_t>("spp", 0); + if (args_spp * bpi != stream_sig.packet_size) { + throw uhd::runtime_error(str( + boost::format("[%s Streamer] Conflicting packet sizes defined: args yields %d bytes but stream_sig.packet_size is %d bytes") + % tx_rx % (args_spp * bpi) % stream_sig.packet_size + )); + } + } else { + args.args["spp"] = str(boost::format("%d") % (stream_sig.packet_size / bpi)); + } + } +} + +/*! \brief Returns a list of rx or tx channels for a streamer. + * + * If the given stream args contain instructions to set up channels, + * those are used. Otherwise, the current device's channel definition + * is consulted. + * + * \param args_ Stream args. + * \param[out] chan_list The list of channels in the correct order. + * \param[out] chan_args Channel args for every channel. `chan_args.size() == chan_list.size()` + */ +void generate_channel_list( + const uhd::stream_args_t &args_, + std::vector<uhd::rfnoc::block_id_t> &chan_list, + std::vector<device_addr_t> &chan_args +) { + uhd::stream_args_t args = args_; + BOOST_FOREACH(const size_t chan_idx, args.channels) { + //// Find block ID for this channel: + if (args.args.has_key(str(boost::format("block_id%d") % chan_idx))) { + chan_list.push_back( + uhd::rfnoc::block_id_t( + args.args.pop(str(boost::format("block_id%d") % chan_idx)) + ) + ); + chan_args.push_back(args.args); + } else if (args.args.has_key("block_id")) { + chan_list.push_back(args.args.get("block_id")); + chan_args.push_back(args.args); + chan_args.back().pop("block_id"); + } else { + throw uhd::runtime_error(str( + boost::format("Cannot create streamers: No block_id specified for channel %d.") + % chan_idx + )); + } + //// Find block port for this channel + if (args.args.has_key(str(boost::format("block_port%d") % chan_idx))) { + chan_args.back()["block_port"] = args.args.pop(str(boost::format("block_port%d") % chan_idx)); + } else if (args.args.has_key("block_port")) { + // We have to write it again, because the chan args from the + // property tree might have overwritten this + chan_args.back()["block_port"] = args.args.get("block_port"); + } + } +} + + +/*********************************************************************** + * RX Flow Control Functions + **********************************************************************/ +//! Stores the state of RX flow control +struct rx_fc_cache_t +{ + rx_fc_cache_t(): + last_seq_in(0){} + size_t last_seq_in; +}; + +/*! Determine the size of the flow control window in number of packets. + * + * This value depends on three things: + * - The packet size (in bytes), P + * - The size of the software buffer (in bytes), B + * - The desired buffer fullness, F + * + * The FC window size is thus X = floor(B*F/P). + * + * \param pkt_size The maximum packet size in bytes + * \param sw_buff_size Software buffer size in bytes + * \param rx_args If this has a key 'recv_buff_fullness', this value will + * be used for said fullness. Must be between 0.01 and 1. + * + * \returns The size of the flow control window in number of packets + */ +static size_t get_rx_flow_control_window( + size_t pkt_size, + size_t sw_buff_size, + const device_addr_t& rx_args +) { + double fullness_factor = rx_args.cast<double>( + "recv_buff_fullness", + uhd::rfnoc::DEFAULT_FC_RX_SW_BUFF_FULL_FACTOR + ); + + if (fullness_factor < 0.01 || fullness_factor > 1) { + throw uhd::value_error("recv_buff_fullness must be in [0.01, 1] inclusive (1% to 100%)"); + } + + size_t window_in_pkts = (static_cast<size_t>(sw_buff_size * fullness_factor) / pkt_size); + if (rx_args.has_key("max_recv_window")) { + window_in_pkts = std::min( + window_in_pkts, + rx_args.cast<size_t>("max_recv_window", window_in_pkts) + ); + } + if (window_in_pkts == 0) { + throw uhd::value_error("recv_buff_size must be larger than the recv_frame_size."); + } + UHD_ASSERT_THROW(size_t(sw_buff_size * fullness_factor) >= pkt_size * window_in_pkts); + return window_in_pkts; +} + + +/*! Send out RX flow control packets. + * + * For an rx stream, this function takes care of sending back + * a flow control packet to the source telling it which + * packets have been consumed. + * + * This function should only be called by the function handling + * the rx stream, usually recv() in super_recv_packet_handler. + * + * \param sid The SID that goes into this packet. This is the reversed() + * version of the data stream's SID. + * \param xport A transport object over which to send the data + * \param big_endian Endianness of the transport + * \param seq32_state Pointer to a variable that saves the 32-Bit state + * of the sequence numbers, since we only have 12 Bit + * sequence numbers in CHDR. + * \param last_seq The value to send: The last consumed packet's sequence number. + */ +static void handle_rx_flowctrl( + const sid_t &sid, + zero_copy_if::sptr xport, + endianness_t endianness, + boost::shared_ptr<rx_fc_cache_t> fc_cache, + const size_t last_seq +) { + static const size_t RXFC_PACKET_LEN_IN_WORDS = 2; + static const size_t RXFC_CMD_CODE_OFFSET = 0; + static const size_t RXFC_SEQ_NUM_OFFSET = 1; + + managed_send_buffer::sptr buff = xport->get_send_buff(0.0); + if (not buff) { + throw uhd::runtime_error("handle_rx_flowctrl timed out getting a send buffer"); + } + boost::uint32_t *pkt = buff->cast<boost::uint32_t *>(); + + // Recover sequence number. The sequence numbers handled by the streamers + // are 12 Bits, but we want to know the 32-Bit sequence number. + size_t &seq32 = fc_cache->last_seq_in; + const size_t seq12 = seq32 & HW_SEQ_NUM_MASK; + if (last_seq < seq12) + seq32 += (HW_SEQ_NUM_MASK + 1); + seq32 &= ~HW_SEQ_NUM_MASK; + seq32 |= last_seq; + + // Super-verbose mode: + //static size_t fc_pkt_count = 0; + //UHD_MSG(status) << "sending flow ctrl packet " << fc_pkt_count++ << ", acking " << str(boost::format("%04d\tseq_sw==0x%08x") % last_seq % seq32) << std::endl; + + //load packet info + vrt::if_packet_info_t packet_info; + packet_info.packet_type = vrt::if_packet_info_t::PACKET_TYPE_FC; + packet_info.num_payload_words32 = RXFC_PACKET_LEN_IN_WORDS; + packet_info.num_payload_bytes = packet_info.num_payload_words32*sizeof(boost::uint32_t); + packet_info.packet_count = seq32; + packet_info.sob = false; + packet_info.eob = false; + packet_info.sid = sid.get(); + packet_info.has_sid = true; + packet_info.has_cid = false; + packet_info.has_tsi = false; + packet_info.has_tsf = false; + packet_info.has_tlr = false; + + if (endianness == ENDIANNESS_BIG) { + // Load Header: + vrt::chdr::if_hdr_pack_be(pkt, packet_info); + // Load Payload: (the sequence number) + pkt[packet_info.num_header_words32+RXFC_CMD_CODE_OFFSET] = uhd::htonx<boost::uint32_t>(0); + pkt[packet_info.num_header_words32+RXFC_SEQ_NUM_OFFSET] = uhd::htonx<boost::uint32_t>(seq32); + } else { + // Load Header: + vrt::chdr::if_hdr_pack_le(pkt, packet_info); + // Load Payload: (the sequence number) + pkt[packet_info.num_header_words32+RXFC_CMD_CODE_OFFSET] = uhd::htowx<boost::uint32_t>(0); + pkt[packet_info.num_header_words32+RXFC_SEQ_NUM_OFFSET] = uhd::htowx<boost::uint32_t>(seq32); + } + + //std::cout << " SID=" << std::hex << sid << " hdr bits=" << packet_info.packet_type << " seq32=" << seq32 << std::endl; + //std::cout << "num_packet_words32: " << packet_info.num_packet_words32 << std::endl; + //for (size_t i = 0; i < packet_info.num_packet_words32; i++) { + //std::cout << str(boost::format("0x%08x") % pkt[i]) << " "; + //if (i % 2) { + //std::cout << std::endl; + //} + //} + + //send the buffer over the interface + buff->commit(sizeof(boost::uint32_t)*(packet_info.num_packet_words32)); +} + +/*********************************************************************** + * TX Flow Control Functions + **********************************************************************/ +//! Stores the state of TX flow control +struct tx_fc_cache_t +{ + tx_fc_cache_t(void): + stream_channel(0), + device_channel(0), + last_seq_out(0), + last_seq_ack(0), + seq_queue(1){} + size_t stream_channel; + size_t device_channel; + size_t last_seq_out; + size_t last_seq_ack; + uhd::transport::bounded_buffer<size_t> seq_queue; + boost::shared_ptr<device3_impl::async_md_type> async_queue; + boost::shared_ptr<device3_impl::async_md_type> old_async_queue; +}; + +/*! Return the size of the flow control window in packets. + * + * If the return value of this function is F, the last tx'd packet + * has index N and the last ack'd packet has index M, the amount of + * FC credit we have is C = F + M - N (i.e. we can send C more packets + * before getting another ack). + * + * Note: If `send_buff_size` is set in \p tx_hints, this will + * override hw_buff_size_. + */ +static size_t get_tx_flow_control_window( + size_t pkt_size, + const double hw_buff_size_, + const device_addr_t& tx_hints +) { + double hw_buff_size = tx_hints.cast<double>("send_buff_size", hw_buff_size_); + size_t window_in_pkts = (static_cast<size_t>(hw_buff_size) / pkt_size); + if (window_in_pkts == 0) { + throw uhd::value_error("send_buff_size must be larger than the send_frame_size."); + } + return window_in_pkts; +} + +static managed_send_buffer::sptr get_tx_buff_with_flowctrl( + task::sptr /*holds ref*/, + boost::shared_ptr<tx_fc_cache_t> fc_cache, + zero_copy_if::sptr xport, + size_t fc_window, + const double timeout +){ + while (true) + { + // delta is the amount of FC credit we've used up + const size_t delta = (fc_cache->last_seq_out & HW_SEQ_NUM_MASK) - (fc_cache->last_seq_ack & HW_SEQ_NUM_MASK); + // If we want to send another packet, we must have FC credit left + if ((delta & HW_SEQ_NUM_MASK) < fc_window) + break; + + // If credit is all used up, we check seq_queue for more. + const bool ok = fc_cache->seq_queue.pop_with_timed_wait(fc_cache->last_seq_ack, timeout); + if (not ok) { + return managed_send_buffer::sptr(); //timeout waiting for flow control + } + } + + managed_send_buffer::sptr buff = xport->get_send_buff(timeout); + if (buff) { + fc_cache->last_seq_out++; //update seq, this will actually be a send + } + return buff; +} + +#define DEVICE3_ASYNC_EVENT_CODE_FLOW_CTRL 0 +/*! Handle incoming messages. If they're flow control, update the TX FC cache. + * Otherwise, send them to the async message queue for the user to poll. + * + * This is run inside a uhd::task as long as this streamer lives. + */ +static void handle_tx_async_msgs( + boost::shared_ptr<tx_fc_cache_t> fc_cache, + zero_copy_if::sptr xport, + endianness_t endianness, + boost::function<double(void)> get_tick_rate +) { + managed_recv_buffer::sptr buff = xport->get_recv_buff(); + if (not buff) + return; + + //extract packet info + vrt::if_packet_info_t if_packet_info; + if_packet_info.num_packet_words32 = buff->size()/sizeof(boost::uint32_t); + const boost::uint32_t *packet_buff = buff->cast<const boost::uint32_t *>(); + + //unpacking can fail + boost::uint32_t (*endian_conv)(boost::uint32_t) = uhd::ntohx; + try + { + if (endianness == ENDIANNESS_BIG) + { + vrt::chdr::if_hdr_unpack_be(packet_buff, if_packet_info); + endian_conv = uhd::ntohx; + } + else + { + vrt::chdr::if_hdr_unpack_le(packet_buff, if_packet_info); + endian_conv = uhd::wtohx; + } + } + catch(const std::exception &ex) + { + UHD_MSG(error) << "Error parsing async message packet: " << ex.what() << std::endl; + return; + } + + double tick_rate = get_tick_rate(); + if (tick_rate == rfnoc::tick_node_ctrl::RATE_UNDEFINED) { + tick_rate = 1; + } + + //fill in the async metadata + async_metadata_t metadata; + load_metadata_from_buff( + endian_conv, + metadata, + if_packet_info, + packet_buff, + tick_rate, + fc_cache->stream_channel + ); + + // TODO: Shouldn't we be polling if_packet_info.packet_type == PACKET_TYPE_FC? + // Thing is, on X300, packet_type == 0, so that wouldn't work. But it seems it should. + //The FC response and the burst ack are two indicators that the radio + //consumed packets. Use them to update the FC metadata + if (metadata.event_code == DEVICE3_ASYNC_EVENT_CODE_FLOW_CTRL) { + const size_t seq = metadata.user_payload[0]; + fc_cache->seq_queue.push_with_pop_on_full(seq); + } + + //FC responses don't propagate up to the user so filter them here + if (metadata.event_code != DEVICE3_ASYNC_EVENT_CODE_FLOW_CTRL) { + fc_cache->async_queue->push_with_pop_on_full(metadata); + metadata.channel = fc_cache->device_channel; + fc_cache->old_async_queue->push_with_pop_on_full(metadata); + standard_async_msg_prints(metadata); + } +} + + + +/*********************************************************************** + * Async Data + **********************************************************************/ +bool device3_impl::recv_async_msg( + async_metadata_t &async_metadata, double timeout +) +{ + return _async_md->pop_with_timed_wait(async_metadata, timeout); +} + +/*********************************************************************** + * Receive streamer + **********************************************************************/ +void device3_impl::update_rx_streamers(double /* rate */) +{ + BOOST_FOREACH(const std::string &block_id, _rx_streamers.keys()) { + UHD_STREAMER_LOG() << "[Device3] updating RX streamer to " << block_id << std::endl; + boost::shared_ptr<sph::recv_packet_streamer> my_streamer = + boost::dynamic_pointer_cast<sph::recv_packet_streamer>(_rx_streamers[block_id].lock()); + if (my_streamer) { + double tick_rate = my_streamer->get_terminator()->get_tick_rate(); + if (tick_rate == rfnoc::tick_node_ctrl::RATE_UNDEFINED) { + tick_rate = 1.0; + } + my_streamer->set_tick_rate(tick_rate); + double samp_rate = my_streamer->get_terminator()->get_output_samp_rate(); + if (samp_rate == rfnoc::rate_node_ctrl::RATE_UNDEFINED) { + samp_rate = 1.0; + } + // This formula is not derived by any scientific means -- we just need to + // increase the failure threshold as we increase rates. For 1 Msps, we use + // the default. + const size_t alignment_failure_factor = std::max(size_t(1), size_t(samp_rate * 1000 / tick_rate)); + double scaling = my_streamer->get_terminator()->get_output_scale_factor(); + if (scaling == rfnoc::scalar_node_ctrl::SCALE_UNDEFINED) { + scaling = 1/32767.; + } + UHD_STREAMER_LOG() << " New tick_rate == " << tick_rate << " New samp_rate == " << samp_rate << " New scaling == " << scaling << std::endl; + + my_streamer->set_tick_rate(tick_rate); + my_streamer->set_samp_rate(samp_rate); + // 1000 packets is the default alignment failure threshold + my_streamer->set_alignment_failure_threshold(1000 * alignment_failure_factor); + my_streamer->set_scale_factor(scaling); + } + } +} + +rx_streamer::sptr device3_impl::get_rx_stream(const stream_args_t &args_) +{ + boost::mutex::scoped_lock lock(_transport_setup_mutex); + stream_args_t args = sanitize_stream_args(args_); + + // I. Generate the channel list + std::vector<uhd::rfnoc::block_id_t> chan_list; + std::vector<device_addr_t> chan_args; + generate_channel_list(args, chan_list, chan_args); + // Note: All 'args.args' are merged into chan_args now. + + // II. Iterate over all channels + boost::shared_ptr<sph::recv_packet_streamer> my_streamer; + // The terminator's lifetime is coupled to the streamer. + // There is only one terminator. If the streamer has multiple channels, + // it will be connected to each upstream block. + rfnoc::rx_stream_terminator::sptr recv_terminator = rfnoc::rx_stream_terminator::make(); + for (size_t stream_i = 0; stream_i < chan_list.size(); stream_i++) { + // Get block ID and mb index + uhd::rfnoc::block_id_t block_id = chan_list[stream_i]; + UHD_STREAMER_LOG() << "[RX Streamer] chan " << stream_i << " connecting to " << block_id << std::endl; + // Update args so args.args is always valid for this particular channel: + args.args = chan_args[stream_i]; + size_t mb_index = block_id.get_device_no(); + size_t suggested_block_port = args.args.cast<size_t>("block_port", rfnoc::ANY_PORT); + + // Access to this channel's block control + uhd::rfnoc::source_block_ctrl_base::sptr blk_ctrl = + boost::dynamic_pointer_cast<uhd::rfnoc::source_block_ctrl_base>(get_block_ctrl(block_id)); + + // Connect the terminator with this channel's block. + size_t block_port = blk_ctrl->connect_downstream( + recv_terminator, + suggested_block_port, + args.args + ); + const size_t terminator_port = recv_terminator->connect_upstream(blk_ctrl); + blk_ctrl->set_downstream_port(block_port, terminator_port); + recv_terminator->set_upstream_port(terminator_port, block_port); + + // Check if the block connection is compatible (spp and item type) + check_stream_sig_compatible(blk_ctrl->get_output_signature(block_port), args, "RX"); + + // Setup the DSP transport hints + device_addr_t rx_hints = get_rx_hints(mb_index); + + //allocate sid and create transport + uhd::sid_t stream_address = blk_ctrl->get_address(block_port); + UHD_STREAMER_LOG() << "[RX Streamer] creating rx stream " << rx_hints.to_string() << std::endl; + both_xports_t xport = make_transport(stream_address, RX_DATA, rx_hints); + UHD_STREAMER_LOG() << std::hex << "[RX Streamer] data_sid = " << xport.send_sid << std::dec << " actual recv_buff_size = " << xport.recv_buff_size << std::endl; + + // Configure the block + blk_ctrl->set_destination(xport.send_sid.get_src(), block_port); + + blk_ctrl->sr_write(uhd::rfnoc::SR_RESP_OUT_DST_SID, xport.send_sid.get_src(), block_port); + UHD_STREAMER_LOG() << "[RX Streamer] resp_out_dst_sid == " << xport.send_sid.get_src() << std::endl; + + // Find all upstream radio nodes and set their response in SID to the host + std::vector<boost::shared_ptr<uhd::rfnoc::radio_ctrl> > upstream_radio_nodes = blk_ctrl->find_upstream_node<uhd::rfnoc::radio_ctrl>(); + UHD_STREAMER_LOG() << "[RX Streamer] Number of upstream radio nodes: " << upstream_radio_nodes.size() << std::endl; + BOOST_FOREACH(const boost::shared_ptr<uhd::rfnoc::radio_ctrl> &node, upstream_radio_nodes) { + node->sr_write(uhd::rfnoc::SR_RESP_OUT_DST_SID, xport.send_sid.get_src(), block_port); + } + + // To calculate the max number of samples per packet, we assume the maximum header length + // to avoid fragmentation should the entire header be used. + const size_t bpp = xport.recv->get_recv_frame_size() - stream_options.rx_max_len_hdr; // bytes per packet + const size_t bpi = convert::get_bytes_per_item(args.otw_format); // bytes per item + const size_t spp = std::min(args.args.cast<size_t>("spp", bpp/bpi), bpp/bpi); // samples per packet + UHD_STREAMER_LOG() << "[RX Streamer] spp == " << spp << std::endl; + + //make the new streamer given the samples per packet + if (not my_streamer) + my_streamer = boost::make_shared<sph::recv_packet_streamer>(spp); + my_streamer->resize(chan_list.size()); + + //init some streamer stuff + std::string conv_endianness; + if (get_transport_endianness(mb_index) == ENDIANNESS_BIG) { + my_streamer->set_vrt_unpacker(&vrt::chdr::if_hdr_unpack_be); + conv_endianness = "be"; + } else { + my_streamer->set_vrt_unpacker(&vrt::chdr::if_hdr_unpack_le); + conv_endianness = "le"; + } + + //set the converter + uhd::convert::id_type id; + id.input_format = args.otw_format + "_item32_" + conv_endianness; + id.num_inputs = 1; + id.output_format = args.cpu_format; + id.num_outputs = 1; + my_streamer->set_converter(id); + + //flow control setup + const size_t pkt_size = spp * bpi + stream_options.rx_max_len_hdr; + const size_t fc_window = get_rx_flow_control_window(pkt_size, xport.recv_buff_size, rx_hints); + const size_t fc_handle_window = std::max<size_t>(1, fc_window / stream_options.rx_fc_request_freq); + UHD_STREAMER_LOG()<< "[RX Streamer] Flow Control Window (minus one) = " << fc_window-1 << ", Flow Control Handler Window = " << fc_handle_window << std::endl; + blk_ctrl->configure_flow_control_out( + fc_window-1, // Leave one space for overrun packets TODO make this obsolete + block_port + ); + + //Give the streamer a functor to get the recv_buffer + //bind requires a zero_copy_if::sptr to add a streamer->xport lifetime dependency + my_streamer->set_xport_chan_get_buff( + stream_i, + boost::bind(&zero_copy_if::get_recv_buff, xport.recv, _1), + true /*flush*/ + ); + + //Give the streamer a functor to handle overruns + //bind requires a weak_ptr to break the a streamer->streamer circular dependency + //Using "this" is OK because we know that this device3_impl will outlive the streamer + my_streamer->set_overflow_handler( + stream_i, + boost::bind( + &uhd::rfnoc::rx_stream_terminator::handle_overrun, recv_terminator, + boost::weak_ptr<uhd::rx_streamer>(my_streamer), stream_i + ) + ); + + //Give the streamer a functor to send flow control messages + //handle_rx_flowctrl is static and has no lifetime issues + boost::shared_ptr<rx_fc_cache_t> fc_cache(new rx_fc_cache_t()); + my_streamer->set_xport_handle_flowctrl( + stream_i, boost::bind( + &handle_rx_flowctrl, + xport.send_sid, + xport.send, + get_transport_endianness(mb_index), + fc_cache, + _1 + ), + fc_handle_window, + true/*init*/ + ); + + //Give the streamer a functor issue stream cmd + //bind requires a shared pointer to add a streamer->framer lifetime dependency + my_streamer->set_issue_stream_cmd( + stream_i, + boost::bind(&uhd::rfnoc::source_block_ctrl_base::issue_stream_cmd, blk_ctrl, _1, block_port) + ); + + // Tell the streamer which SID is valid for this channel + my_streamer->set_xport_chan_sid(stream_i, true, xport.send_sid); + } + + // Connect the terminator to the streamer + my_streamer->set_terminator(recv_terminator); + + // Notify all blocks in this chain that they are connected to an active streamer + recv_terminator->set_rx_streamer(true, 0); + + // Store a weak pointer to prevent a streamer->device3_impl->streamer circular dependency. + // Note that we store the streamer only once, and use its terminator's + // ID to do so. + _rx_streamers[recv_terminator->unique_id()] = boost::weak_ptr<sph::recv_packet_streamer>(my_streamer); + + // Sets tick rate, samp rate and scaling on this streamer. + // A registered terminator is required to do this. + update_rx_streamers(); + + post_streamer_hooks(RX_DIRECTION); + return my_streamer; +} + +/*********************************************************************** + * Transmit streamer + **********************************************************************/ +void device3_impl::update_tx_streamers(double /* rate */) +{ + BOOST_FOREACH(const std::string &block_id, _tx_streamers.keys()) { + UHD_STREAMER_LOG() << "[Device3] updating TX streamer: " << block_id << std::endl; + boost::shared_ptr<sph::send_packet_streamer> my_streamer = + boost::dynamic_pointer_cast<sph::send_packet_streamer>(_tx_streamers[block_id].lock()); + if (my_streamer) { + double tick_rate = my_streamer->get_terminator()->get_tick_rate(); + if (tick_rate == rfnoc::tick_node_ctrl::RATE_UNDEFINED) { + tick_rate = 1.0; + } + double samp_rate = my_streamer->get_terminator()->get_input_samp_rate(); + if (samp_rate == rfnoc::rate_node_ctrl::RATE_UNDEFINED) { + samp_rate = 1.0; + } + double scaling = my_streamer->get_terminator()->get_input_scale_factor(); + if (scaling == rfnoc::scalar_node_ctrl::SCALE_UNDEFINED) { + scaling = 32767.; + } + UHD_STREAMER_LOG() << " New tick_rate == " << tick_rate << " New samp_rate == " << samp_rate << " New scaling == " << scaling << std::endl; + my_streamer->set_tick_rate(tick_rate); + my_streamer->set_samp_rate(samp_rate); + my_streamer->set_scale_factor(scaling); + } + } +} + +tx_streamer::sptr device3_impl::get_tx_stream(const uhd::stream_args_t &args_) +{ + boost::mutex::scoped_lock lock(_transport_setup_mutex); + stream_args_t args = sanitize_stream_args(args_); + + // I. Generate the channel list + std::vector<uhd::rfnoc::block_id_t> chan_list; + std::vector<device_addr_t> chan_args; + generate_channel_list(args, chan_list, chan_args); + // Note: All 'args.args' are merged into chan_args now. + + //shared async queue for all channels in streamer + boost::shared_ptr<async_md_type> async_md(new async_md_type(1000/*messages deep*/)); + + // II. Iterate over all channels + boost::shared_ptr<sph::send_packet_streamer> my_streamer; + // The terminator's lifetime is coupled to the streamer. + // There is only one terminator. If the streamer has multiple channels, + // it will be connected to each downstream block. + rfnoc::tx_stream_terminator::sptr send_terminator = rfnoc::tx_stream_terminator::make(); + for (size_t stream_i = 0; stream_i < chan_list.size(); stream_i++) { + // Get block ID and mb index + uhd::rfnoc::block_id_t block_id = chan_list[stream_i]; + // Update args so args.args is always valid for this particular channel: + args.args = chan_args[stream_i]; + size_t mb_index = block_id.get_device_no(); + size_t suggested_block_port = args.args.cast<size_t>("block_port", rfnoc::ANY_PORT); + + // Access to this channel's block control + uhd::rfnoc::sink_block_ctrl_base::sptr blk_ctrl = + boost::dynamic_pointer_cast<uhd::rfnoc::sink_block_ctrl_base>(get_block_ctrl(block_id)); + + // Connect the terminator with this channel's block. + // This will throw if the connection is not possible. + size_t block_port = blk_ctrl->connect_upstream( + send_terminator, + suggested_block_port, + args.args + ); + const size_t terminator_port = send_terminator->connect_downstream(blk_ctrl); + blk_ctrl->set_upstream_port(block_port, terminator_port); + send_terminator->set_downstream_port(terminator_port, block_port); + + // Check if the block connection is compatible (spp and item type) + check_stream_sig_compatible(blk_ctrl->get_input_signature(block_port), args, "TX"); + + // Setup the dsp transport hints + device_addr_t tx_hints = get_tx_hints(mb_index); + + //allocate sid and create transport + uhd::sid_t stream_address = blk_ctrl->get_address(block_port); + UHD_STREAMER_LOG() << "[TX Streamer] creating tx stream " << tx_hints.to_string() << std::endl; + both_xports_t xport = make_transport(stream_address, TX_DATA, tx_hints); + UHD_STREAMER_LOG() << std::hex << "[TX Streamer] data_sid = " << xport.send_sid << std::dec << std::endl; + + // To calculate the max number of samples per packet, we assume the maximum header length + // to avoid fragmentation should the entire header be used. + const size_t bpp = tx_hints.cast<size_t>("bpp", xport.send->get_send_frame_size()) - stream_options.tx_max_len_hdr; + const size_t bpi = convert::get_bytes_per_item(args.otw_format); // bytes per item + const size_t spp = std::min(args.args.cast<size_t>("spp", bpp/bpi), bpp/bpi); // samples per packet + UHD_STREAMER_LOG() << "[TX Streamer] spp == " << spp << std::endl; + + //make the new streamer given the samples per packet + if (not my_streamer) + my_streamer = boost::make_shared<sph::send_packet_streamer>(spp); + my_streamer->resize(chan_list.size()); + + //init some streamer stuff + std::string conv_endianness; + if (get_transport_endianness(mb_index) == ENDIANNESS_BIG) { + my_streamer->set_vrt_packer(&vrt::chdr::if_hdr_pack_be); + conv_endianness = "be"; + } else { + my_streamer->set_vrt_packer(&vrt::chdr::if_hdr_pack_le); + conv_endianness = "le"; + } + + //set the converter + uhd::convert::id_type id; + id.input_format = args.cpu_format; + id.num_inputs = 1; + id.output_format = args.otw_format + "_item32_" + conv_endianness; + id.num_outputs = 1; + my_streamer->set_converter(id); + + //flow control setup + const size_t pkt_size = spp * bpi + stream_options.tx_max_len_hdr; + // For flow control, this value is used to determine the window size in *packets* + size_t fc_window = get_tx_flow_control_window( + pkt_size, // This is the maximum packet size + blk_ctrl->get_fifo_size(block_port), + tx_hints // This can override the value reported by the block! + ); + const size_t fc_handle_window = std::max<size_t>(1, fc_window / stream_options.tx_fc_response_freq); + UHD_STREAMER_LOG() << "[TX Streamer] Flow Control Window = " << fc_window << ", Flow Control Handler Window = " << fc_handle_window << std::endl; + blk_ctrl->configure_flow_control_in( + stream_options.tx_fc_response_cycles, + fc_handle_window, /*pkts*/ + block_port + ); + + boost::shared_ptr<tx_fc_cache_t> fc_cache(new tx_fc_cache_t()); + fc_cache->stream_channel = stream_i; + fc_cache->device_channel = mb_index; + fc_cache->async_queue = async_md; + fc_cache->old_async_queue = _async_md; + + boost::function<double(void)> tick_rate_retriever = boost::bind( + &rfnoc::tick_node_ctrl::get_tick_rate, + send_terminator, + std::set< rfnoc::node_ctrl_base::sptr >() // Need to specify default args with bind + ); + task::sptr task = task::make( + boost::bind( + &handle_tx_async_msgs, + fc_cache, + xport.recv, + get_transport_endianness(mb_index), + tick_rate_retriever + ) + ); + + blk_ctrl->sr_write(uhd::rfnoc::SR_RESP_IN_DST_SID, xport.recv_sid.get_dst(), block_port); + UHD_STREAMER_LOG() << "[TX Streamer] resp_in_dst_sid == " << boost::format("0x%04X") % xport.recv_sid.get_dst() << std::endl; + // Find all downstream radio nodes and set their response in SID to the host + std::vector<boost::shared_ptr<uhd::rfnoc::radio_ctrl> > downstream_radio_nodes = blk_ctrl->find_downstream_node<uhd::rfnoc::radio_ctrl>(); + UHD_STREAMER_LOG() << "[TX Streamer] Number of downstream radio nodes: " << downstream_radio_nodes.size() << std::endl; + BOOST_FOREACH(const boost::shared_ptr<uhd::rfnoc::radio_ctrl> &node, downstream_radio_nodes) { + node->sr_write(uhd::rfnoc::SR_RESP_IN_DST_SID, xport.send_sid.get_src(), block_port); + } + + //Give the streamer a functor to get the send buffer + //get_tx_buff_with_flowctrl is static so bind has no lifetime issues + //xport.send (sptr) is required to add streamer->data-transport lifetime dependency + //task (sptr) is required to add a streamer->async-handler lifetime dependency + my_streamer->set_xport_chan_get_buff( + stream_i, + boost::bind(&get_tx_buff_with_flowctrl, task, fc_cache, xport.send, fc_window, _1) + ); + //Give the streamer a functor handled received async messages + my_streamer->set_async_receiver( + boost::bind(&async_md_type::pop_with_timed_wait, async_md, _1, _2) + ); + my_streamer->set_xport_chan_sid(stream_i, true, xport.send_sid); + // CHDR does not support trailers + my_streamer->set_enable_trailer(false); + } + + // Connect the terminator to the streamer + my_streamer->set_terminator(send_terminator); + + // Notify all blocks in this chain that they are connected to an active streamer + send_terminator->set_tx_streamer(true, 0); + + // Store a weak pointer to prevent a streamer->device3_impl->streamer circular dependency. + // Note that we store the streamer only once, and use its terminator's + // ID to do so. + _tx_streamers[send_terminator->unique_id()] = boost::weak_ptr<sph::send_packet_streamer>(my_streamer); + + // Sets tick rate, samp rate and scaling on this streamer + // A registered terminator is required to do this. + update_tx_streamers(); + + post_streamer_hooks(TX_DIRECTION); + return my_streamer; +} + + diff --git a/host/lib/usrp/e100/CMakeLists.txt b/host/lib/usrp/e100/CMakeLists.txt index 2a1e14eab..da77b85dc 100644 --- a/host/lib/usrp/e100/CMakeLists.txt +++ b/host/lib/usrp/e100/CMakeLists.txt @@ -22,8 +22,6 @@ ######################################################################## # Conditionally configure the USRP-E100 support ######################################################################## -LIBUHD_REGISTER_COMPONENT("E100" ENABLE_E100 OFF "ENABLE_LIBUHD;LINUX" OFF OFF) - IF(ENABLE_E100) INCLUDE_DIRECTORIES(${CMAKE_CURRENT_SOURCE_DIR}/include) diff --git a/host/lib/usrp/e100/dboard_iface.cpp b/host/lib/usrp/e100/dboard_iface.cpp index b5baf6c56..ce0ac026b 100644 --- a/host/lib/usrp/e100/dboard_iface.cpp +++ b/host/lib/usrp/e100/dboard_iface.cpp @@ -1,5 +1,5 @@ // -// Copyright 2010-2011,2015 Ettus Research LLC +// Copyright 2010-2011,2015,2016 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 @@ -66,12 +66,16 @@ public: void write_aux_dac(unit_t, aux_dac_t, double); double read_aux_adc(unit_t, aux_adc_t); - void _set_pin_ctrl(unit_t, boost::uint16_t); - void _set_atr_reg(unit_t, atr_reg_t, boost::uint16_t); - void _set_gpio_ddr(unit_t, boost::uint16_t); - void _set_gpio_out(unit_t, boost::uint16_t); - void set_gpio_debug(unit_t, int); - boost::uint16_t read_gpio(unit_t); + void set_pin_ctrl(unit_t unit, boost::uint32_t value, boost::uint32_t mask = 0xffffffff); + boost::uint32_t get_pin_ctrl(unit_t unit); + void set_atr_reg(unit_t unit, atr_reg_t reg, boost::uint32_t value, boost::uint32_t mask = 0xffffffff); + boost::uint32_t get_atr_reg(unit_t unit, atr_reg_t reg); + void set_gpio_ddr(unit_t unit, boost::uint32_t value, boost::uint32_t mask = 0xffffffff); + boost::uint32_t get_gpio_ddr(unit_t unit); + void set_gpio_out(unit_t unit, boost::uint32_t value, boost::uint32_t mask = 0xffffffff); + boost::uint32_t get_gpio_out(unit_t unit); + boost::uint32_t read_gpio(unit_t unit); + void set_command_time(const uhd::time_spec_t& t); uhd::time_spec_t get_command_time(void); @@ -97,6 +101,7 @@ public: double get_clock_rate(unit_t); void set_clock_enabled(unit_t, bool); double get_codec_rate(unit_t); + void set_fe_connection(unit_t unit, const std::string&, const fe_connection_t& fe_conn); private: timed_wb_iface::sptr _wb_iface; @@ -127,6 +132,7 @@ void e100_dboard_iface::set_clock_rate(unit_t unit, double rate){ switch(unit){ case UNIT_RX: return _clock->set_rx_dboard_clock_rate(rate); case UNIT_TX: return _clock->set_tx_dboard_clock_rate(rate); + case UNIT_BOTH: set_clock_rate(UNIT_RX, rate); set_clock_rate(UNIT_TX, rate); return; } } @@ -142,14 +148,15 @@ double e100_dboard_iface::get_clock_rate(unit_t unit){ switch(unit){ case UNIT_RX: return _clock->get_rx_clock_rate(); case UNIT_TX: return _clock->get_tx_clock_rate(); + default: UHD_THROW_INVALID_CODE_PATH(); } - UHD_THROW_INVALID_CODE_PATH(); } void e100_dboard_iface::set_clock_enabled(unit_t unit, bool enb){ switch(unit){ case UNIT_RX: return _clock->enable_rx_dboard_clock(enb); case UNIT_TX: return _clock->enable_tx_dboard_clock(enb); + case UNIT_BOTH: set_clock_enabled(UNIT_RX, enb); set_clock_enabled(UNIT_TX, enb); return; } } @@ -160,28 +167,40 @@ double e100_dboard_iface::get_codec_rate(unit_t){ /*********************************************************************** * GPIO **********************************************************************/ -void e100_dboard_iface::_set_pin_ctrl(unit_t unit, boost::uint16_t value){ - return _gpio->set_pin_ctrl(unit, value); +void e100_dboard_iface::set_pin_ctrl(unit_t unit, boost::uint32_t value, boost::uint32_t mask){ + _gpio->set_pin_ctrl(unit, static_cast<boost::uint16_t>(value), static_cast<boost::uint16_t>(mask)); } -void e100_dboard_iface::_set_gpio_ddr(unit_t unit, boost::uint16_t value){ - return _gpio->set_gpio_ddr(unit, value); +boost::uint32_t e100_dboard_iface::get_pin_ctrl(unit_t unit){ + return static_cast<boost::uint32_t>(_gpio->get_pin_ctrl(unit)); } -void e100_dboard_iface::_set_gpio_out(unit_t unit, boost::uint16_t value){ - return _gpio->set_gpio_out(unit, value); +void e100_dboard_iface::set_atr_reg(unit_t unit, atr_reg_t reg, boost::uint32_t value, boost::uint32_t mask){ + _gpio->set_atr_reg(unit, reg, static_cast<boost::uint16_t>(value), static_cast<boost::uint16_t>(mask)); } -boost::uint16_t e100_dboard_iface::read_gpio(unit_t unit){ - return _gpio->read_gpio(unit); +boost::uint32_t e100_dboard_iface::get_atr_reg(unit_t unit, atr_reg_t reg){ + return static_cast<boost::uint32_t>(_gpio->get_atr_reg(unit, reg)); +} + +void e100_dboard_iface::set_gpio_ddr(unit_t unit, boost::uint32_t value, boost::uint32_t mask){ + _gpio->set_gpio_ddr(unit, static_cast<boost::uint16_t>(value), static_cast<boost::uint16_t>(mask)); +} + +boost::uint32_t e100_dboard_iface::get_gpio_ddr(unit_t unit){ + return static_cast<boost::uint32_t>(_gpio->get_gpio_ddr(unit)); } -void e100_dboard_iface::_set_atr_reg(unit_t unit, atr_reg_t atr, boost::uint16_t value){ - return _gpio->set_atr_reg(unit, atr, value); +void e100_dboard_iface::set_gpio_out(unit_t unit, boost::uint32_t value, boost::uint32_t mask){ + _gpio->set_gpio_out(unit, static_cast<boost::uint16_t>(value), static_cast<boost::uint16_t>(mask)); } -void e100_dboard_iface::set_gpio_debug(unit_t, int){ - throw uhd::not_implemented_error("no set_gpio_debug implemented"); +boost::uint32_t e100_dboard_iface::get_gpio_out(unit_t unit){ + return static_cast<boost::uint32_t>(_gpio->get_gpio_out(unit)); +} + +boost::uint32_t e100_dboard_iface::read_gpio(unit_t unit){ + return _gpio->read_gpio(unit); } /*********************************************************************** @@ -196,8 +215,8 @@ static boost::uint32_t unit_to_otw_spi_dev(dboard_iface::unit_t unit){ switch(unit){ case dboard_iface::UNIT_TX: return UE_SPI_SS_TX_DB; case dboard_iface::UNIT_RX: return UE_SPI_SS_RX_DB; + default: UHD_THROW_INVALID_CODE_PATH(); } - UHD_THROW_INVALID_CODE_PATH(); } void e100_dboard_iface::write_spi( @@ -268,3 +287,8 @@ void e100_dboard_iface::set_command_time(const uhd::time_spec_t& t) { _wb_iface->set_time(t); } + +void e100_dboard_iface::set_fe_connection(unit_t, const std::string&, const fe_connection_t&) +{ + throw uhd::not_implemented_error("fe connection configuration support not implemented"); +} diff --git a/host/lib/usrp/e100/e100_impl.cpp b/host/lib/usrp/e100/e100_impl.cpp index 6d3c08534..1f8fe84cb 100644 --- a/host/lib/usrp/e100/e100_impl.cpp +++ b/host/lib/usrp/e100/e100_impl.cpp @@ -217,20 +217,20 @@ e100_impl::e100_impl(const uhd::device_addr_t &device_addr){ //////////////////////////////////////////////////////////////////// _tree->create<mboard_eeprom_t>(mb_path / "eeprom") .set(mb_eeprom) - .subscribe(boost::bind(&e100_impl::set_mb_eeprom, this, _1)); + .add_coerced_subscriber(boost::bind(&e100_impl::set_mb_eeprom, this, _1)); //////////////////////////////////////////////////////////////////// // create clock control objects //////////////////////////////////////////////////////////////////// //^^^ clock created up top, just reg props here... ^^^ _tree->create<double>(mb_path / "tick_rate") - .publish(boost::bind(&e100_clock_ctrl::get_fpga_clock_rate, _clock_ctrl)) - .subscribe(boost::bind(&fifo_ctrl_excelsior::set_tick_rate, _fifo_ctrl, _1)) - .subscribe(boost::bind(&e100_impl::update_tick_rate, this, _1)); + .set_publisher(boost::bind(&e100_clock_ctrl::get_fpga_clock_rate, _clock_ctrl)) + .add_coerced_subscriber(boost::bind(&fifo_ctrl_excelsior::set_tick_rate, _fifo_ctrl, _1)) + .add_coerced_subscriber(boost::bind(&e100_impl::update_tick_rate, this, _1)); - //subscribe the command time while we are at it + //add_coerced_subscriber the command time while we are at it _tree->create<time_spec_t>(mb_path / "time/cmd") - .subscribe(boost::bind(&fifo_ctrl_excelsior::set_time, _fifo_ctrl, _1)); + .add_coerced_subscriber(boost::bind(&fifo_ctrl_excelsior::set_time, _fifo_ctrl, _1)); //////////////////////////////////////////////////////////////////// // create codec control objects @@ -241,18 +241,18 @@ e100_impl::e100_impl(const uhd::device_addr_t &device_addr){ _tree->create<std::string>(rx_codec_path / "name").set("ad9522"); _tree->create<meta_range_t>(rx_codec_path / "gains/pga/range").set(e100_codec_ctrl::rx_pga_gain_range); _tree->create<double>(rx_codec_path / "gains/pga/value") - .coerce(boost::bind(&e100_impl::update_rx_codec_gain, this, _1)); + .set_coercer(boost::bind(&e100_impl::update_rx_codec_gain, this, _1)); _tree->create<std::string>(tx_codec_path / "name").set("ad9522"); _tree->create<meta_range_t>(tx_codec_path / "gains/pga/range").set(e100_codec_ctrl::tx_pga_gain_range); _tree->create<double>(tx_codec_path / "gains/pga/value") - .subscribe(boost::bind(&e100_codec_ctrl::set_tx_pga_gain, _codec_ctrl, _1)) - .publish(boost::bind(&e100_codec_ctrl::get_tx_pga_gain, _codec_ctrl)); + .add_coerced_subscriber(boost::bind(&e100_codec_ctrl::set_tx_pga_gain, _codec_ctrl, _1)) + .set_publisher(boost::bind(&e100_codec_ctrl::get_tx_pga_gain, _codec_ctrl)); //////////////////////////////////////////////////////////////////// // and do the misc mboard sensors //////////////////////////////////////////////////////////////////// _tree->create<sensor_value_t>(mb_path / "sensors/ref_locked") - .publish(boost::bind(&e100_impl::get_ref_locked, this)); + .set_publisher(boost::bind(&e100_impl::get_ref_locked, this)); //////////////////////////////////////////////////////////////////// // Create the GPSDO control @@ -272,7 +272,7 @@ e100_impl::e100_impl(const uhd::device_addr_t &device_addr){ 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)); + .set_publisher(boost::bind(&gps_ctrl::get_sensor, _gps, name)); } } else @@ -288,27 +288,27 @@ e100_impl::e100_impl(const uhd::device_addr_t &device_addr){ _tx_fe = tx_frontend_core_200::make(_fifo_ctrl, TOREG(SR_TX_FE)); _tree->create<subdev_spec_t>(mb_path / "rx_subdev_spec") - .subscribe(boost::bind(&e100_impl::update_rx_subdev_spec, this, _1)); + .add_coerced_subscriber(boost::bind(&e100_impl::update_rx_subdev_spec, this, _1)); _tree->create<subdev_spec_t>(mb_path / "tx_subdev_spec") - .subscribe(boost::bind(&e100_impl::update_tx_subdev_spec, this, _1)); + .add_coerced_subscriber(boost::bind(&e100_impl::update_tx_subdev_spec, this, _1)); const fs_path rx_fe_path = mb_path / "rx_frontends" / "A"; const fs_path tx_fe_path = mb_path / "tx_frontends" / "A"; _tree->create<std::complex<double> >(rx_fe_path / "dc_offset" / "value") - .coerce(boost::bind(&rx_frontend_core_200::set_dc_offset, _rx_fe, _1)) + .set_coercer(boost::bind(&rx_frontend_core_200::set_dc_offset, _rx_fe, _1)) .set(std::complex<double>(0.0, 0.0)); _tree->create<bool>(rx_fe_path / "dc_offset" / "enable") - .subscribe(boost::bind(&rx_frontend_core_200::set_dc_offset_auto, _rx_fe, _1)) + .add_coerced_subscriber(boost::bind(&rx_frontend_core_200::set_dc_offset_auto, _rx_fe, _1)) .set(true); _tree->create<std::complex<double> >(rx_fe_path / "iq_balance" / "value") - .subscribe(boost::bind(&rx_frontend_core_200::set_iq_balance, _rx_fe, _1)) + .add_coerced_subscriber(boost::bind(&rx_frontend_core_200::set_iq_balance, _rx_fe, _1)) .set(std::complex<double>(0.0, 0.0)); _tree->create<std::complex<double> >(tx_fe_path / "dc_offset" / "value") - .coerce(boost::bind(&tx_frontend_core_200::set_dc_offset, _tx_fe, _1)) + .set_coercer(boost::bind(&tx_frontend_core_200::set_dc_offset, _tx_fe, _1)) .set(std::complex<double>(0.0, 0.0)); _tree->create<std::complex<double> >(tx_fe_path / "iq_balance" / "value") - .subscribe(boost::bind(&tx_frontend_core_200::set_iq_balance, _tx_fe, _1)) + .add_coerced_subscriber(boost::bind(&tx_frontend_core_200::set_iq_balance, _tx_fe, _1)) .set(std::complex<double>(0.0, 0.0)); //////////////////////////////////////////////////////////////////// @@ -327,20 +327,20 @@ e100_impl::e100_impl(const uhd::device_addr_t &device_addr){ _rx_dsps[dspno]->set_link_rate(E100_RX_LINK_RATE_BPS); _tree->access<double>(mb_path / "tick_rate") - .subscribe(boost::bind(&rx_dsp_core_200::set_tick_rate, _rx_dsps[dspno], _1)); + .add_coerced_subscriber(boost::bind(&rx_dsp_core_200::set_tick_rate, _rx_dsps[dspno], _1)); fs_path rx_dsp_path = mb_path / str(boost::format("rx_dsps/%u") % dspno); _tree->create<meta_range_t>(rx_dsp_path / "rate/range") - .publish(boost::bind(&rx_dsp_core_200::get_host_rates, _rx_dsps[dspno])); + .set_publisher(boost::bind(&rx_dsp_core_200::get_host_rates, _rx_dsps[dspno])); _tree->create<double>(rx_dsp_path / "rate/value") .set(1e6) //some default - .coerce(boost::bind(&rx_dsp_core_200::set_host_rate, _rx_dsps[dspno], _1)) - .subscribe(boost::bind(&e100_impl::update_rx_samp_rate, this, dspno, _1)); + .set_coercer(boost::bind(&rx_dsp_core_200::set_host_rate, _rx_dsps[dspno], _1)) + .add_coerced_subscriber(boost::bind(&e100_impl::update_rx_samp_rate, this, dspno, _1)); _tree->create<double>(rx_dsp_path / "freq/value") - .coerce(boost::bind(&rx_dsp_core_200::set_freq, _rx_dsps[dspno], _1)); + .set_coercer(boost::bind(&rx_dsp_core_200::set_freq, _rx_dsps[dspno], _1)); _tree->create<meta_range_t>(rx_dsp_path / "freq/range") - .publish(boost::bind(&rx_dsp_core_200::get_freq_range, _rx_dsps[dspno])); + .set_publisher(boost::bind(&rx_dsp_core_200::get_freq_range, _rx_dsps[dspno])); _tree->create<stream_cmd_t>(rx_dsp_path / "stream_cmd") - .subscribe(boost::bind(&rx_dsp_core_200::issue_stream_command, _rx_dsps[dspno], _1)); + .add_coerced_subscriber(boost::bind(&rx_dsp_core_200::issue_stream_command, _rx_dsps[dspno], _1)); } //////////////////////////////////////////////////////////////////// @@ -351,17 +351,17 @@ e100_impl::e100_impl(const uhd::device_addr_t &device_addr){ ); _tx_dsp->set_link_rate(E100_TX_LINK_RATE_BPS); _tree->access<double>(mb_path / "tick_rate") - .subscribe(boost::bind(&tx_dsp_core_200::set_tick_rate, _tx_dsp, _1)); + .add_coerced_subscriber(boost::bind(&tx_dsp_core_200::set_tick_rate, _tx_dsp, _1)); _tree->create<meta_range_t>(mb_path / "tx_dsps/0/rate/range") - .publish(boost::bind(&tx_dsp_core_200::get_host_rates, _tx_dsp)); + .set_publisher(boost::bind(&tx_dsp_core_200::get_host_rates, _tx_dsp)); _tree->create<double>(mb_path / "tx_dsps/0/rate/value") .set(1e6) //some default - .coerce(boost::bind(&tx_dsp_core_200::set_host_rate, _tx_dsp, _1)) - .subscribe(boost::bind(&e100_impl::update_tx_samp_rate, this, 0, _1)); + .set_coercer(boost::bind(&tx_dsp_core_200::set_host_rate, _tx_dsp, _1)) + .add_coerced_subscriber(boost::bind(&e100_impl::update_tx_samp_rate, this, 0, _1)); _tree->create<double>(mb_path / "tx_dsps/0/freq/value") - .coerce(boost::bind(&tx_dsp_core_200::set_freq, _tx_dsp, _1)); + .set_coercer(boost::bind(&tx_dsp_core_200::set_freq, _tx_dsp, _1)); _tree->create<meta_range_t>(mb_path / "tx_dsps/0/freq/range") - .publish(boost::bind(&tx_dsp_core_200::get_freq_range, _tx_dsp)); + .set_publisher(boost::bind(&tx_dsp_core_200::get_freq_range, _tx_dsp)); //////////////////////////////////////////////////////////////////// // create time control objects @@ -375,21 +375,21 @@ e100_impl::e100_impl(const uhd::device_addr_t &device_addr){ _fifo_ctrl, TOREG(SR_TIME64), time64_rb_bases ); _tree->access<double>(mb_path / "tick_rate") - .subscribe(boost::bind(&time64_core_200::set_tick_rate, _time64, _1)); + .add_coerced_subscriber(boost::bind(&time64_core_200::set_tick_rate, _time64, _1)); _tree->create<time_spec_t>(mb_path / "time/now") - .publish(boost::bind(&time64_core_200::get_time_now, _time64)) - .subscribe(boost::bind(&time64_core_200::set_time_now, _time64, _1)); + .set_publisher(boost::bind(&time64_core_200::get_time_now, _time64)) + .add_coerced_subscriber(boost::bind(&time64_core_200::set_time_now, _time64, _1)); _tree->create<time_spec_t>(mb_path / "time/pps") - .publish(boost::bind(&time64_core_200::get_time_last_pps, _time64)) - .subscribe(boost::bind(&time64_core_200::set_time_next_pps, _time64, _1)); + .set_publisher(boost::bind(&time64_core_200::get_time_last_pps, _time64)) + .add_coerced_subscriber(boost::bind(&time64_core_200::set_time_next_pps, _time64, _1)); //setup time source props _tree->create<std::string>(mb_path / "time_source/value") - .subscribe(boost::bind(&time64_core_200::set_time_source, _time64, _1)); + .add_coerced_subscriber(boost::bind(&time64_core_200::set_time_source, _time64, _1)); _tree->create<std::vector<std::string> >(mb_path / "time_source/options") - .publish(boost::bind(&time64_core_200::get_time_sources, _time64)); + .set_publisher(boost::bind(&time64_core_200::get_time_sources, _time64)); //setup reference source props _tree->create<std::string>(mb_path / "clock_source/value") - .subscribe(boost::bind(&e100_impl::update_clock_source, this, _1)); + .add_coerced_subscriber(boost::bind(&e100_impl::update_clock_source, this, _1)); std::vector<std::string> clock_sources = boost::assign::list_of("internal")("external")("auto"); if (_gps and _gps->gps_detected()) clock_sources.push_back("gpsdo"); _tree->create<std::vector<std::string> >(mb_path / "clock_source/options").set(clock_sources); @@ -399,7 +399,7 @@ e100_impl::e100_impl(const uhd::device_addr_t &device_addr){ //////////////////////////////////////////////////////////////////// _user = user_settings_core_200::make(_fifo_ctrl, TOREG(SR_USER_REGS)); _tree->create<user_settings_core_200::user_reg_t>(mb_path / "user/regs") - .subscribe(boost::bind(&user_settings_core_200::set_reg, _user, _1)); + .add_coerced_subscriber(boost::bind(&user_settings_core_200::set_reg, _user, _1)); //////////////////////////////////////////////////////////////////// // create dboard control objects @@ -417,32 +417,31 @@ e100_impl::e100_impl(const uhd::device_addr_t &device_addr){ //create the properties and register subscribers _tree->create<dboard_eeprom_t>(mb_path / "dboards/A/rx_eeprom") .set(rx_db_eeprom) - .subscribe(boost::bind(&e100_impl::set_db_eeprom, this, "rx", _1)); + .add_coerced_subscriber(boost::bind(&e100_impl::set_db_eeprom, this, "rx", _1)); _tree->create<dboard_eeprom_t>(mb_path / "dboards/A/tx_eeprom") .set(tx_db_eeprom) - .subscribe(boost::bind(&e100_impl::set_db_eeprom, this, "tx", _1)); + .add_coerced_subscriber(boost::bind(&e100_impl::set_db_eeprom, this, "tx", _1)); _tree->create<dboard_eeprom_t>(mb_path / "dboards/A/gdb_eeprom") .set(gdb_eeprom) - .subscribe(boost::bind(&e100_impl::set_db_eeprom, this, "gdb", _1)); + .add_coerced_subscriber(boost::bind(&e100_impl::set_db_eeprom, this, "gdb", _1)); //create a new dboard interface and manager - _dboard_iface = make_e100_dboard_iface(_fifo_ctrl, _fpga_i2c_ctrl, _fifo_ctrl/*spi*/, _clock_ctrl, _codec_ctrl); - _tree->create<dboard_iface::sptr>(mb_path / "dboards/A/iface").set(_dboard_iface); _dboard_manager = dboard_manager::make( rx_db_eeprom.id, tx_db_eeprom.id, gdb_eeprom.id, - _dboard_iface, _tree->subtree(mb_path / "dboards/A") + make_e100_dboard_iface(_fifo_ctrl, _fpga_i2c_ctrl, _fifo_ctrl/*spi*/, _clock_ctrl, _codec_ctrl), + _tree->subtree(mb_path / "dboards/A") ); //bind frontend corrections to the dboard freq props const fs_path db_tx_fe_path = mb_path / "dboards" / "A" / "tx_frontends"; BOOST_FOREACH(const std::string &name, _tree->list(db_tx_fe_path)){ _tree->access<double>(db_tx_fe_path / name / "freq" / "value") - .subscribe(boost::bind(&e100_impl::set_tx_fe_corrections, this, _1)); + .add_coerced_subscriber(boost::bind(&e100_impl::set_tx_fe_corrections, this, _1)); } const fs_path db_rx_fe_path = mb_path / "dboards" / "A" / "rx_frontends"; BOOST_FOREACH(const std::string &name, _tree->list(db_rx_fe_path)){ _tree->access<double>(db_rx_fe_path / name / "freq" / "value") - .subscribe(boost::bind(&e100_impl::set_rx_fe_corrections, this, _1)); + .add_coerced_subscriber(boost::bind(&e100_impl::set_rx_fe_corrections, this, _1)); } //initialize io handling @@ -457,8 +456,8 @@ e100_impl::e100_impl(const uhd::device_addr_t &device_addr){ //////////////////////////////////////////////////////////////////// this->update_rates(); - _tree->access<double>(mb_path / "tick_rate") //now subscribe the clock rate setter - .subscribe(boost::bind(&e100_clock_ctrl::set_fpga_clock_rate, _clock_ctrl, _1)); + _tree->access<double>(mb_path / "tick_rate") //now add_coerced_subscriber the clock rate setter + .add_coerced_subscriber(boost::bind(&e100_clock_ctrl::set_fpga_clock_rate, _clock_ctrl, _1)); //reset cordic rates and their properties to zero BOOST_FOREACH(const std::string &name, _tree->list(mb_path / "rx_dsps")){ diff --git a/host/lib/usrp/e100/e100_impl.hpp b/host/lib/usrp/e100/e100_impl.hpp index d00668224..b05053f84 100644 --- a/host/lib/usrp/e100/e100_impl.hpp +++ b/host/lib/usrp/e100/e100_impl.hpp @@ -111,7 +111,6 @@ private: //dboard stuff uhd::usrp::dboard_manager::sptr _dboard_manager; - uhd::usrp::dboard_iface::sptr _dboard_iface; bool _ignore_cal_file; std::vector<boost::weak_ptr<uhd::rx_streamer> > _rx_streamers; diff --git a/host/lib/usrp/e300/CMakeLists.txt b/host/lib/usrp/e300/CMakeLists.txt index 9c8aa29b9..68c3520e4 100644 --- a/host/lib/usrp/e300/CMakeLists.txt +++ b/host/lib/usrp/e300/CMakeLists.txt @@ -24,8 +24,6 @@ ######################################################################## find_package(UDev) -LIBUHD_REGISTER_COMPONENT("E300" ENABLE_E300 OFF "ENABLE_LIBUHD" OFF OFF) - IF(ENABLE_E300) LIST(APPEND E300_SOURCES ${CMAKE_CURRENT_SOURCE_DIR}/e300_impl.cpp diff --git a/host/lib/usrp/e300/e300_fpga_defs.hpp b/host/lib/usrp/e300/e300_fpga_defs.hpp index 594461518..36dd47383 100644 --- a/host/lib/usrp/e300/e300_fpga_defs.hpp +++ b/host/lib/usrp/e300/e300_fpga_defs.hpp @@ -21,7 +21,7 @@ namespace uhd { namespace usrp { namespace e300 { namespace fpga { static const size_t NUM_RADIOS = 2; -static const boost::uint32_t COMPAT_MAJOR = 14; +static const boost::uint32_t COMPAT_MAJOR = 16; static const boost::uint32_t COMPAT_MINOR = 0; }}}} // namespace diff --git a/host/lib/usrp/e300/e300_impl.cpp b/host/lib/usrp/e300/e300_impl.cpp index a57c86c1d..114686b4f 100644 --- a/host/lib/usrp/e300/e300_impl.cpp +++ b/host/lib/usrp/e300/e300_impl.cpp @@ -48,6 +48,7 @@ using namespace uhd; using namespace uhd::usrp; +using namespace uhd::usrp::gpio_atr; using namespace uhd::transport; namespace fs = boost::filesystem; namespace asio = boost::asio; @@ -470,14 +471,14 @@ e300_impl::e300_impl(const uhd::device_addr_t &device_addr) BOOST_FOREACH(const std::string &name, _sensor_manager->get_sensors()) { _tree->create<sensor_value_t>(mb_path / "sensors" / name) - .publish(boost::bind(&e300_sensor_manager::get_sensor, _sensor_manager, name)); + .set_publisher(boost::bind(&e300_sensor_manager::get_sensor, _sensor_manager, name)); } #ifdef E300_GPSD if (_gps) { BOOST_FOREACH(const std::string &name, _gps->get_sensors()) { _tree->create<sensor_value_t>(mb_path / "sensors" / name) - .publish(boost::bind(&gpsd_iface::get_sensor, _gps, name)); + .set_publisher(boost::bind(&gpsd_iface::get_sensor, _gps, name)); } } #endif @@ -487,7 +488,7 @@ e300_impl::e300_impl(const uhd::device_addr_t &device_addr) //////////////////////////////////////////////////////////////////// _tree->create<mboard_eeprom_t>(mb_path / "eeprom") .set(_eeprom_manager->get_mb_eeprom()) // set first... - .subscribe(boost::bind( + .add_coerced_subscriber(boost::bind( &e300_eeprom_manager::write_mb_eeprom, _eeprom_manager, _1)); @@ -495,9 +496,9 @@ e300_impl::e300_impl(const uhd::device_addr_t &device_addr) // clocking //////////////////////////////////////////////////////////////////// _tree->create<double>(mb_path / "tick_rate") - .coerce(boost::bind(&e300_impl::_set_tick_rate, this, _1)) - .publish(boost::bind(&e300_impl::_get_tick_rate, this)) - .subscribe(boost::bind(&e300_impl::_update_tick_rate, this, _1)); + .set_coercer(boost::bind(&e300_impl::_set_tick_rate, this, _1)) + .set_publisher(boost::bind(&e300_impl::_get_tick_rate, this)) + .add_coerced_subscriber(boost::bind(&e300_impl::_update_tick_rate, this, _1)); //default some chains on -- needed for setup purposes _codec_ctrl->set_active_chains(true, false, true, false); @@ -509,42 +510,47 @@ e300_impl::e300_impl(const uhd::device_addr_t &device_addr) for(size_t instance = 0; instance < fpga::NUM_RADIOS; instance++) this->_setup_radio(instance); - // Radio 0 loopback through AD9361 - _codec_mgr->loopback_self_test(_radio_perifs[0].ctrl, radio::sr_addr(radio::CODEC_IDLE), radio::RB64_CODEC_READBACK); - // Radio 1 loopback through AD9361 - _codec_mgr->loopback_self_test(_radio_perifs[1].ctrl, radio::sr_addr(radio::CODEC_IDLE), radio::RB64_CODEC_READBACK); - + //now test each radio module's connection to the codec interface + BOOST_FOREACH(radio_perifs_t &perif, _radio_perifs) + { + _codec_mgr->loopback_self_test( + boost::bind( + &radio_ctrl_core_3000::poke32, perif.ctrl, radio::sr_addr(radio::CODEC_IDLE), _1 + ), + boost::bind(&radio_ctrl_core_3000::peek64, perif.ctrl, radio::RB64_CODEC_READBACK) + ); + } //////////////////////////////////////////////////////////////////// // internal gpios //////////////////////////////////////////////////////////////////// - gpio_core_200::sptr fp_gpio = gpio_core_200::make(_radio_perifs[0].ctrl, radio::sr_addr(radio::FP_GPIO), radio::RB32_FP_GPIO); + gpio_atr_3000::sptr fp_gpio = gpio_atr_3000::make(_radio_perifs[0].ctrl, radio::sr_addr(radio::FP_GPIO), radio::RB32_FP_GPIO); BOOST_FOREACH(const gpio_attr_map_t::value_type attr, gpio_attr_map) { _tree->create<boost::uint32_t>(mb_path / "gpio" / "INT0" / attr.second) - .subscribe(boost::bind(&e300_impl::_set_internal_gpio, this, fp_gpio, attr.first, _1)) + .add_coerced_subscriber(boost::bind(&gpio_atr_3000::set_gpio_attr, fp_gpio, attr.first, _1)) .set(0); } _tree->create<boost::uint8_t>(mb_path / "gpio" / "INT0" / "READBACK") - .publish(boost::bind(&e300_impl::_get_internal_gpio, this, fp_gpio)); + .set_publisher(boost::bind(&gpio_atr_3000::read_gpio, fp_gpio)); //////////////////////////////////////////////////////////////////// // register the time keepers - only one can be the highlander //////////////////////////////////////////////////////////////////// _tree->create<time_spec_t>(mb_path / "time" / "now") - .publish(boost::bind(&time_core_3000::get_time_now, _radio_perifs[0].time64)) - .subscribe(boost::bind(&e300_impl::_set_time, this, _1)) + .set_publisher(boost::bind(&time_core_3000::get_time_now, _radio_perifs[0].time64)) + .add_coerced_subscriber(boost::bind(&e300_impl::_set_time, this, _1)) .set(0.0); //re-sync the times when the tick rate changes _tree->access<double>(mb_path / "tick_rate") - .subscribe(boost::bind(&e300_impl::_sync_times, this)); + .add_coerced_subscriber(boost::bind(&e300_impl::_sync_times, this)); _tree->create<time_spec_t>(mb_path / "time" / "pps") - .publish(boost::bind(&time_core_3000::get_time_last_pps, _radio_perifs[0].time64)) - .subscribe(boost::bind(&time_core_3000::set_time_next_pps, _radio_perifs[0].time64, _1)) - .subscribe(boost::bind(&time_core_3000::set_time_next_pps, _radio_perifs[1].time64, _1)); + .set_publisher(boost::bind(&time_core_3000::get_time_last_pps, _radio_perifs[0].time64)) + .add_coerced_subscriber(boost::bind(&time_core_3000::set_time_next_pps, _radio_perifs[0].time64, _1)) + .add_coerced_subscriber(boost::bind(&time_core_3000::set_time_next_pps, _radio_perifs[1].time64, _1)); //setup time source props _tree->create<std::string>(mb_path / "time_source" / "value") - .subscribe(boost::bind(&e300_impl::_update_time_source, this, _1)) + .add_coerced_subscriber(boost::bind(&e300_impl::_update_time_source, this, _1)) .set(e300::DEFAULT_TIME_SRC); #ifdef E300_GPSD static const std::vector<std::string> time_sources = boost::assign::list_of("none")("internal")("external")("gpsdo"); @@ -554,7 +560,7 @@ e300_impl::e300_impl(const uhd::device_addr_t &device_addr) _tree->create<std::vector<std::string> >(mb_path / "time_source" / "options").set(time_sources); //setup reference source props _tree->create<std::string>(mb_path / "clock_source" / "value") - .subscribe(boost::bind(&e300_impl::_update_clock_source, this, _1)) + .add_coerced_subscriber(boost::bind(&e300_impl::_update_clock_source, this, _1)) .set(e300::DEFAULT_CLOCK_SRC); static const std::vector<std::string> clock_sources = boost::assign::list_of("internal"); //external,gpsdo not supported _tree->create<std::vector<std::string> >(mb_path / "clock_source" / "options").set(clock_sources); @@ -565,13 +571,13 @@ e300_impl::e300_impl(const uhd::device_addr_t &device_addr) dboard_eeprom_t db_eeprom; _tree->create<dboard_eeprom_t>(mb_path / "dboards" / "A" / "rx_eeprom") .set(_eeprom_manager->get_db_eeprom()) - .subscribe(boost::bind( + .add_coerced_subscriber(boost::bind( &e300_eeprom_manager::write_db_eeprom, _eeprom_manager, _1)); _tree->create<dboard_eeprom_t>(mb_path / "dboards" / "A" / "tx_eeprom") .set(_eeprom_manager->get_db_eeprom()) - .subscribe(boost::bind( + .add_coerced_subscriber(boost::bind( &e300_eeprom_manager::write_db_eeprom, _eeprom_manager, _1)); @@ -604,10 +610,10 @@ e300_impl::e300_impl(const uhd::device_addr_t &device_addr) _tree->create<subdev_spec_t>(mb_path / "rx_subdev_spec") .set(subdev_spec_t()) - .subscribe(boost::bind(&e300_impl::_update_subdev_spec, this, "rx", _1)); + .add_coerced_subscriber(boost::bind(&e300_impl::_update_subdev_spec, this, "rx", _1)); _tree->create<subdev_spec_t>(mb_path / "tx_subdev_spec") .set(subdev_spec_t()) - .subscribe(boost::bind(&e300_impl::_update_subdev_spec, this, "tx", _1)); + .add_coerced_subscriber(boost::bind(&e300_impl::_update_subdev_spec, this, "tx", _1)); //////////////////////////////////////////////////////////////////// // do some post-init tasks @@ -631,37 +637,6 @@ e300_impl::e300_impl(const uhd::device_addr_t &device_addr) _tree->access<subdev_spec_t>(mb_path / "tx_subdev_spec").set(tx_spec); } -boost::uint8_t e300_impl::_get_internal_gpio(gpio_core_200::sptr gpio) -{ - return boost::uint32_t(gpio->read_gpio(dboard_iface::UNIT_RX)); -} - -void e300_impl::_set_internal_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(); - } -} - uhd::sensor_value_t e300_impl::_get_fe_pll_lock(const bool is_tx) { const boost::uint32_t st = @@ -1001,7 +976,8 @@ void e300_impl::_setup_radio(const size_t dspno) //////////////////////////////////////////////////////////////////// // Set up peripherals //////////////////////////////////////////////////////////////////// - perif.atr = gpio_core_200_32wo::make(perif.ctrl, radio::sr_addr(radio::GPIO)); + perif.atr = gpio_atr_3000::make_write_only(perif.ctrl, radio::sr_addr(radio::GPIO)); + perif.atr->set_atr_mode(MODE_ATR, 0xFFFFFFFF); perif.rx_fe = rx_frontend_core_200::make(perif.ctrl, radio::sr_addr(radio::RX_FRONT)); perif.rx_fe->set_dc_offset(rx_frontend_core_200::DEFAULT_DC_OFFSET_VALUE); perif.rx_fe->set_dc_offset_auto(rx_frontend_core_200::DEFAULT_DC_OFFSET_ENABLE); @@ -1036,26 +1012,25 @@ void e300_impl::_setup_radio(const size_t dspno) // 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)); + .add_coerced_subscriber(boost::bind(&rx_vita_core_3000::set_tick_rate, perif.framer, _1)) + .add_coerced_subscriber(boost::bind(&rx_dsp_core_3000::set_tick_rate, perif.ddc, _1)); const fs_path rx_dsp_path = mb_path / "rx_dsps" / str(boost::format("%u") % dspno); perif.ddc->populate_subtree(_tree->subtree(rx_dsp_path)); _tree->access<double>(rx_dsp_path / "rate" / "value") - .subscribe(boost::bind(&e300_impl::_update_rx_samp_rate, this, dspno, _1)) + .add_coerced_subscriber(boost::bind(&e300_impl::_update_rx_samp_rate, this, dspno, _1)) ; _tree->create<stream_cmd_t>(rx_dsp_path / "stream_cmd") - .subscribe(boost::bind(&rx_vita_core_3000::issue_stream_command, perif.framer, _1)); + .add_coerced_subscriber(boost::bind(&rx_vita_core_3000::issue_stream_command, perif.framer, _1)); //////////////////////////////////////////////////////////////////// // create tx dsp control objects //////////////////////////////////////////////////////////////////// _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)); + .add_coerced_subscriber(boost::bind(&tx_dsp_core_3000::set_tick_rate, perif.duc, _1)); const fs_path tx_dsp_path = mb_path / "tx_dsps" / str(boost::format("%u") % dspno); perif.duc->populate_subtree(_tree->subtree(tx_dsp_path)); _tree->access<double>(tx_dsp_path / "rate" / "value") - .subscribe(boost::bind(&e300_impl::_update_tx_samp_rate, this, dspno, _1)) + .add_coerced_subscriber(boost::bind(&e300_impl::_update_tx_samp_rate, this, dspno, _1)) ; //////////////////////////////////////////////////////////////////// @@ -1075,10 +1050,10 @@ void e300_impl::_setup_radio(const size_t dspno) // This will connect all the e300_impl-specific items _tree->create<sensor_value_t>(rf_fe_path / "sensors" / "lo_locked") - .publish(boost::bind(&e300_impl::_get_fe_pll_lock, this, dir == TX_DIRECTION)) + .set_publisher(boost::bind(&e300_impl::_get_fe_pll_lock, this, dir == TX_DIRECTION)) ; _tree->access<double>(rf_fe_path / "freq" / "value") - .subscribe(boost::bind(&e300_impl::_update_fe_lo_freq, this, key, _1)) + .add_coerced_subscriber(boost::bind(&e300_impl::_update_fe_lo_freq, this, key, _1)) ; // Antenna Setup @@ -1086,7 +1061,7 @@ void e300_impl::_setup_radio(const size_t dspno) 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(&e300_impl::_update_antenna_sel, this, dspno, _1)) + .add_coerced_subscriber(boost::bind(&e300_impl::_update_antenna_sel, this, dspno, _1)) .set("RX2"); } else if (dir == TX_DIRECTION) { @@ -1315,11 +1290,11 @@ void e300_impl::_update_atrs(void) if (enb_tx) fd_reg |= tx_enables | xx_leds; - gpio_core_200_32wo::sptr atr = _radio_perifs[instance].atr; - atr->set_atr_reg(dboard_iface::ATR_REG_IDLE, oo_reg); - atr->set_atr_reg(dboard_iface::ATR_REG_RX_ONLY, rx_reg); - atr->set_atr_reg(dboard_iface::ATR_REG_TX_ONLY, tx_reg); - atr->set_atr_reg(dboard_iface::ATR_REG_FULL_DUPLEX, fd_reg); + gpio_atr_3000::sptr atr = _radio_perifs[instance].atr; + atr->set_atr_reg(ATR_REG_IDLE, oo_reg); + atr->set_atr_reg(ATR_REG_RX_ONLY, rx_reg); + atr->set_atr_reg(ATR_REG_TX_ONLY, tx_reg); + atr->set_atr_reg(ATR_REG_FULL_DUPLEX, fd_reg); } } diff --git a/host/lib/usrp/e300/e300_impl.hpp b/host/lib/usrp/e300/e300_impl.hpp index 595b42679..e9a0b4b9a 100644 --- a/host/lib/usrp/e300/e300_impl.hpp +++ b/host/lib/usrp/e300/e300_impl.hpp @@ -41,7 +41,7 @@ #include "tx_dsp_core_3000.hpp" #include "ad9361_ctrl.hpp" #include "ad936x_manager.hpp" -#include "gpio_core_200.hpp" +#include "gpio_atr_3000.hpp" #include "e300_global_regs.hpp" #include "e300_i2c.hpp" @@ -147,7 +147,7 @@ private: // types struct radio_perifs_t { radio_ctrl_core_3000::sptr ctrl; - gpio_core_200_32wo::sptr atr; + gpio_atr::gpio_atr_3000::sptr atr; time_core_3000::sptr time64; rx_vita_core_3000::sptr framer; rx_dsp_core_3000::sptr ddc; @@ -283,14 +283,6 @@ private: // methods // get frontend lock sensor uhd::sensor_value_t _get_fe_pll_lock(const bool is_tx); - // internal gpios - boost::uint8_t _get_internal_gpio(gpio_core_200::sptr); - - void _set_internal_gpio( - gpio_core_200::sptr gpio, - const gpio_attr_t attr, - const boost::uint32_t value); - private: // members uhd::device_addr_t _device_addr; xport_t _xport_path; diff --git a/host/lib/usrp/e300/e300_io_impl.cpp b/host/lib/usrp/e300/e300_io_impl.cpp index 29d250c8f..c84042e98 100644 --- a/host/lib/usrp/e300/e300_io_impl.cpp +++ b/host/lib/usrp/e300/e300_io_impl.cpp @@ -87,7 +87,6 @@ void e300_impl::_update_tick_rate(const double rate) boost::dynamic_pointer_cast<sph::send_packet_streamer>(perif.tx_streamer.lock()); if (my_streamer) my_streamer->set_tick_rate(rate); - perif.deframer->set_tick_rate(_tick_rate); } } @@ -158,10 +157,8 @@ void e300_impl::_update_subdev_spec( const std::string conn = _tree->access<std::string>( mb_path / "dboards" / spec[i].db_name / ("rx_frontends") / spec[i].sd_name / "connection").get(); - - const bool fe_swapped = (conn == "QI" or conn == "Q"); - _radio_perifs[i].ddc->set_mux(conn, fe_swapped); - _radio_perifs[i].rx_fe->set_mux(fe_swapped); + _radio_perifs[i].ddc->set_mux(usrp::fe_connection_t(conn)); + _radio_perifs[i].rx_fe->set_mux(false); } } diff --git a/host/lib/usrp/e300/e300_regs.hpp b/host/lib/usrp/e300/e300_regs.hpp index 846c759a4..74e45df00 100644 --- a/host/lib/usrp/e300/e300_regs.hpp +++ b/host/lib/usrp/e300/e300_regs.hpp @@ -41,7 +41,7 @@ static const uint32_t TIME = 128; static const uint32_t RX_DSP = 144; static const uint32_t TX_DSP = 184; static const uint32_t LEDS = 195; -static const uint32_t FP_GPIO = 200; +static const uint32_t FP_GPIO = 201; static const uint32_t RX_FRONT = 208; static const uint32_t TX_FRONT = 216; static const uint32_t CODEC_IDLE = 250; diff --git a/host/lib/usrp/e300/e300_remote_codec_ctrl.cpp b/host/lib/usrp/e300/e300_remote_codec_ctrl.cpp index 17a564f21..cb2583b1b 100644 --- a/host/lib/usrp/e300/e300_remote_codec_ctrl.cpp +++ b/host/lib/usrp/e300/e300_remote_codec_ctrl.cpp @@ -36,6 +36,9 @@ public: { } + void set_timed_spi(uhd::spi_iface::sptr spi_iface, boost::uint32_t slave_num) {}; + void set_safe_spi(uhd::spi_iface::sptr spi_iface, boost::uint32_t slave_num) {}; + double set_gain(const std::string &which, const double value) { _clear(); diff --git a/host/lib/usrp/fe_connection.cpp b/host/lib/usrp/fe_connection.cpp new file mode 100644 index 000000000..071f5ecf2 --- /dev/null +++ b/host/lib/usrp/fe_connection.cpp @@ -0,0 +1,67 @@ +// +// Copyright 2016 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 +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +#include <uhd/usrp/fe_connection.hpp> +#include <uhd/exception.hpp> +#include <boost/regex.hpp> +#include <uhd/utils/math.hpp> + +using namespace uhd::usrp; + +fe_connection_t::fe_connection_t( + sampling_t sampling_mode, bool iq_swapped, + bool i_inverted, bool q_inverted, double if_freq +) : _sampling_mode(sampling_mode), _iq_swapped(iq_swapped), + _i_inverted(i_inverted), _q_inverted(q_inverted), _if_freq(if_freq) +{ +} + +fe_connection_t::fe_connection_t(const std::string& conn_str, double if_freq) { + static const boost::regex conn_regex("([IQ])(b?)(([IQ])(b?))?"); + boost::cmatch matches; + if (boost::regex_match(conn_str.c_str(), matches, conn_regex)) { + if (matches[3].length() == 0) { + //Connection in {I, Q, Ib, Qb} + _sampling_mode = REAL; + _iq_swapped = (matches[1].str() == "Q"); + _i_inverted = (matches[2].length() != 0); + _q_inverted = false; //IQ is swapped after inversion + } else { + //Connection in {I(b?)Q(b?), Q(b?)I(b?), I(b?)I(b?), Q(b?)Q(b?)} + _sampling_mode = (matches[1].str() == matches[4].str()) ? HETERODYNE : QUADRATURE; + _iq_swapped = (matches[1].str() == "Q"); + size_t i_idx = _iq_swapped ? 5 : 2, q_idx = _iq_swapped ? 2 : 5; + _i_inverted = (matches[i_idx].length() != 0); + _q_inverted = (matches[q_idx].length() != 0); + + if (_sampling_mode == HETERODYNE and _i_inverted != _q_inverted) { + throw uhd::value_error("Invalid connection string: " + conn_str); + } + } + _if_freq = if_freq; + } else { + throw uhd::value_error("Invalid connection string: " + conn_str); + } +} + +bool uhd::usrp::operator==(const fe_connection_t &lhs, const fe_connection_t &rhs){ + return ((lhs.get_sampling_mode() == rhs.get_sampling_mode()) and + (lhs.is_iq_swapped() == rhs.is_iq_swapped()) and + (lhs.is_i_inverted() == rhs.is_i_inverted()) and + (lhs.is_q_inverted() == rhs.is_q_inverted()) and + uhd::math::frequencies_are_equal(lhs.get_if_freq(), rhs.get_if_freq())); +} diff --git a/host/lib/usrp/multi_usrp.cpp b/host/lib/usrp/multi_usrp.cpp index 396237e24..7905a6d32 100644 --- a/host/lib/usrp/multi_usrp.cpp +++ b/host/lib/usrp/multi_usrp.cpp @@ -1,5 +1,5 @@ // -// Copyright 2010-2013 Ettus Research LLC +// Copyright 2010-2016 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 @@ -27,6 +27,7 @@ #include <uhd/usrp/dboard_eeprom.hpp> #include <uhd/convert.hpp> #include <uhd/utils/soft_register.hpp> +#include "legacy_compat.hpp" #include <boost/assign/list_of.hpp> #include <boost/thread.hpp> #include <boost/foreach.hpp> @@ -39,6 +40,7 @@ using namespace uhd; using namespace uhd::usrp; const std::string multi_usrp::ALL_GAINS = ""; +const std::string multi_usrp::ALL_LOS = "all"; UHD_INLINE std::string string_vector_to_string(std::vector<std::string> values, std::string delimiter = std::string(" ")) { @@ -389,12 +391,28 @@ public: multi_usrp_impl(const device_addr_t &addr){ _dev = device::make(addr, device::USRP); _tree = _dev->get_tree(); + _is_device3 = bool(boost::dynamic_pointer_cast<uhd::device3>(_dev)); + + if (is_device3()) { + _legacy_compat = rfnoc::legacy_compat::make(get_device3(), addr); + } } device::sptr get_device(void){ return _dev; } + bool is_device3(void) { + return _is_device3; + } + + device3::sptr get_device3(void) { + if (not is_device3()) { + throw uhd::type_error("Cannot call get_device3() on a non-generation 3 device."); + } + return boost::dynamic_pointer_cast<uhd::device3>(_dev); + } + dict<std::string, std::string> get_usrp_rx_info(size_t chan){ mboard_chan_pair mcp = rx_chan_to_mcp(chan); dict<std::string, std::string> usrp_info; @@ -438,8 +456,10 @@ public: ******************************************************************/ void set_master_clock_rate(double rate, size_t mboard){ if (mboard != ALL_MBOARDS){ - if (_tree->exists(mb_root(mboard) / "auto_tick_rate")) { + if (_tree->exists(mb_root(mboard) / "auto_tick_rate") + and _tree->access<bool>(mb_root(mboard) / "auto_tick_rate").get()) { _tree->access<bool>(mb_root(mboard) / "auto_tick_rate").set(false); + UHD_MSG(status) << "Setting master clock rate selection to 'manual'." << std::endl; } _tree->access<double>(mb_root(mboard) / "tick_rate").set(rate); return; @@ -605,7 +625,12 @@ public: void issue_stream_cmd(const stream_cmd_t &stream_cmd, size_t chan){ if (chan != ALL_CHANS){ - _tree->access<stream_cmd_t>(rx_dsp_root(chan) / "stream_cmd").set(stream_cmd); + if (is_device3()) { + mboard_chan_pair mcp = rx_chan_to_mcp(chan); + _legacy_compat->issue_stream_cmd(stream_cmd, mcp.mboard, mcp.chan); + } else { + _tree->access<stream_cmd_t>(rx_dsp_root(chan) / "stream_cmd").set(stream_cmd); + } return; } for (size_t c = 0; c < get_rx_num_channels(); c++){ @@ -740,6 +765,9 @@ public: ******************************************************************/ rx_streamer::sptr get_rx_stream(const stream_args_t &args) { _check_link_rate(args, false); + if (is_device3()) { + return _legacy_compat->get_rx_stream(args); + } return this->get_device()->get_rx_stream(args); } @@ -830,6 +858,171 @@ public: return _tree->access<meta_range_t>(rx_rf_fe_root(chan) / "freq" / "range").get(); } + std::vector<std::string> get_rx_lo_names(size_t chan = 0){ + std::vector<std::string> lo_names; + if (_tree->exists(rx_rf_fe_root(chan) / "los")) { + BOOST_FOREACH(const std::string &name, _tree->list(rx_rf_fe_root(chan) / "los")) { + lo_names.push_back(name); + } + } + return lo_names; + } + + void set_rx_lo_source(const std::string &src, const std::string &name = ALL_LOS, size_t chan = 0){ + if (_tree->exists(rx_rf_fe_root(chan) / "los")) { + if (name == ALL_LOS) { + if (_tree->exists(rx_rf_fe_root(chan) / "los" / ALL_LOS)) { + //Special value ALL_LOS support atomically sets the source for all LOs + _tree->access<std::string>(rx_rf_fe_root(chan) / "los" / ALL_LOS / "source" / "value").set(src); + } else { + BOOST_FOREACH(const std::string &n, _tree->list(rx_rf_fe_root(chan) / "los")) { + this->set_rx_lo_source(src, n, chan); + } + } + } else { + if (_tree->exists(rx_rf_fe_root(chan) / "los")) { + _tree->access<std::string>(rx_rf_fe_root(chan) / "los" / name / "source" / "value").set(src); + } else { + throw uhd::runtime_error("Could not find LO stage " + name); + } + } + } else { + throw uhd::runtime_error("This device does not support manual configuration of LOs"); + } + } + + const std::string get_rx_lo_source(const std::string &name = ALL_LOS, size_t chan = 0){ + if (_tree->exists(rx_rf_fe_root(chan) / "los")) { + if (name == ALL_LOS) { + //Special value ALL_LOS support atomically sets the source for all LOs + return _tree->access<std::string>(rx_rf_fe_root(chan) / "los" / ALL_LOS / "source" / "value").get(); + } else { + if (_tree->exists(rx_rf_fe_root(chan) / "los")) { + return _tree->access<std::string>(rx_rf_fe_root(chan) / "los" / name / "source" / "value").get(); + } else { + throw uhd::runtime_error("Could not find LO stage " + name); + } + } + } else { + // If the daughterboard doesn't expose it's LO(s) then it can only be internal + return "internal"; + } + } + + std::vector<std::string> get_rx_lo_sources(const std::string &name = ALL_LOS, size_t chan = 0) { + if (_tree->exists(rx_rf_fe_root(chan) / "los")) { + if (name == ALL_LOS) { + if (_tree->exists(rx_rf_fe_root(chan) / "los" / ALL_LOS)) { + //Special value ALL_LOS support atomically sets the source for all LOs + return _tree->access< std::vector<std::string> >(rx_rf_fe_root(chan) / "los" / ALL_LOS / "source" / "options").get(); + } else { + return std::vector<std::string>(); + } + } else { + if (_tree->exists(rx_rf_fe_root(chan) / "los")) { + return _tree->access< std::vector<std::string> >(rx_rf_fe_root(chan) / "los" / name / "source" / "options").get(); + } else { + throw uhd::runtime_error("Could not find LO stage " + name); + } + } + } else { + // If the daughterboard doesn't expose it's LO(s) then it can only be internal + return std::vector<std::string>(1, "internal"); + } + } + + void set_rx_lo_export_enabled(bool enabled, const std::string &name = ALL_LOS, size_t chan = 0){ + if (_tree->exists(rx_rf_fe_root(chan) / "los")) { + if (name == ALL_LOS) { + if (_tree->exists(rx_rf_fe_root(chan) / "los" / ALL_LOS)) { + //Special value ALL_LOS support atomically sets the source for all LOs + _tree->access<bool>(rx_rf_fe_root(chan) / "los" / ALL_LOS / "export").set(enabled); + } else { + BOOST_FOREACH(const std::string &n, _tree->list(rx_rf_fe_root(chan) / "los")) { + this->set_rx_lo_export_enabled(enabled, n, chan); + } + } + } else { + if (_tree->exists(rx_rf_fe_root(chan) / "los")) { + _tree->access<bool>(rx_rf_fe_root(chan) / "los" / name / "export").set(enabled); + } else { + throw uhd::runtime_error("Could not find LO stage " + name); + } + } + } else { + throw uhd::runtime_error("This device does not support manual configuration of LOs"); + } + } + + bool get_rx_lo_export_enabled(const std::string &name = ALL_LOS, size_t chan = 0){ + if (_tree->exists(rx_rf_fe_root(chan) / "los")) { + if (name == ALL_LOS) { + //Special value ALL_LOS support atomically sets the source for all LOs + return _tree->access<bool>(rx_rf_fe_root(chan) / "los" / ALL_LOS / "export").get(); + } else { + if (_tree->exists(rx_rf_fe_root(chan) / "los")) { + return _tree->access<bool>(rx_rf_fe_root(chan) / "los" / name / "export").get(); + } else { + throw uhd::runtime_error("Could not find LO stage " + name); + } + } + } else { + // If the daughterboard doesn't expose it's LO(s), assume it cannot export + return false; + } + } + + double set_rx_lo_freq(double freq, const std::string &name = ALL_LOS, size_t chan = 0){ + if (_tree->exists(rx_rf_fe_root(chan) / "los")) { + if (name == ALL_LOS) { + throw uhd::runtime_error("LO frequency must be set for each stage individually"); + } else { + if (_tree->exists(rx_rf_fe_root(chan) / "los")) { + _tree->access<double>(rx_rf_fe_root(chan) / "los" / name / "freq" / "value").set(freq); + return _tree->access<double>(rx_rf_fe_root(chan) / "los" / name / "freq" / "value").get(); + } else { + throw uhd::runtime_error("Could not find LO stage " + name); + } + } + } else { + throw uhd::runtime_error("This device does not support manual configuration of LOs"); + } + } + + double get_rx_lo_freq(const std::string &name = ALL_LOS, size_t chan = 0){ + if (_tree->exists(rx_rf_fe_root(chan) / "los")) { + if (name == ALL_LOS) { + throw uhd::runtime_error("LO frequency must be retrieved for each stage individually"); + } else { + if (_tree->exists(rx_rf_fe_root(chan) / "los")) { + return _tree->access<double>(rx_rf_fe_root(chan) / "los" / name / "freq" / "value").get(); + } else { + throw uhd::runtime_error("Could not find LO stage " + name); + } + } + } else { + // Return actual RF frequency if the daughterboard doesn't expose it's LO(s) + return _tree->access<double>(rx_rf_fe_root(chan) / "freq" /" value").get(); + } + } + + freq_range_t get_rx_lo_freq_range(const std::string &name = ALL_LOS, size_t chan = 0){ + if (_tree->exists(rx_rf_fe_root(chan) / "los")) { + if (name == ALL_LOS) { + throw uhd::runtime_error("LO frequency range must be retrieved for each stage individually"); + } else { + if (_tree->exists(rx_rf_fe_root(chan) / "los")) { + return _tree->access<freq_range_t>(rx_rf_fe_root(chan) / "los" / name / "freq" / "range").get(); + } else { + throw uhd::runtime_error("Could not find LO stage " + name); + } + } + } else { + // Return the actual RF range if the daughterboard doesn't expose it's LO(s) + return _tree->access<meta_range_t>(rx_rf_fe_root(chan) / "freq" / "range").get(); + } + } + void set_rx_gain(double gain, const std::string &name, size_t chan){ /* Check if any AGC mode is enable and if so warn the user */ if (chan != ALL_CHANS) { @@ -1100,6 +1293,9 @@ public: ******************************************************************/ tx_streamer::sptr get_tx_stream(const stream_args_t &args) { _check_link_rate(args, true); + if (is_device3()) { + return _legacy_compat->get_tx_stream(args); + } return this->get_device()->get_tx_stream(args); } @@ -1346,10 +1542,10 @@ public: if (attr == "CTRL") iface->set_pin_ctrl(unit, boost::uint16_t(value), boost::uint16_t(mask)); if (attr == "DDR") iface->set_gpio_ddr(unit, boost::uint16_t(value), boost::uint16_t(mask)); if (attr == "OUT") iface->set_gpio_out(unit, boost::uint16_t(value), boost::uint16_t(mask)); - if (attr == "ATR_0X") iface->set_atr_reg(unit, dboard_iface::ATR_REG_IDLE, boost::uint16_t(value), boost::uint16_t(mask)); - if (attr == "ATR_RX") iface->set_atr_reg(unit, dboard_iface::ATR_REG_RX_ONLY, boost::uint16_t(value), boost::uint16_t(mask)); - if (attr == "ATR_TX") iface->set_atr_reg(unit, dboard_iface::ATR_REG_TX_ONLY, boost::uint16_t(value), boost::uint16_t(mask)); - if (attr == "ATR_XX") iface->set_atr_reg(unit, dboard_iface::ATR_REG_FULL_DUPLEX, boost::uint16_t(value), boost::uint16_t(mask)); + if (attr == "ATR_0X") iface->set_atr_reg(unit, gpio_atr::ATR_REG_IDLE, boost::uint16_t(value), boost::uint16_t(mask)); + if (attr == "ATR_RX") iface->set_atr_reg(unit, gpio_atr::ATR_REG_RX_ONLY, boost::uint16_t(value), boost::uint16_t(mask)); + if (attr == "ATR_TX") iface->set_atr_reg(unit, gpio_atr::ATR_REG_TX_ONLY, boost::uint16_t(value), boost::uint16_t(mask)); + if (attr == "ATR_XX") iface->set_atr_reg(unit, gpio_atr::ATR_REG_FULL_DUPLEX, boost::uint16_t(value), boost::uint16_t(mask)); } } @@ -1367,10 +1563,10 @@ public: if (attr == "CTRL") return iface->get_pin_ctrl(unit); if (attr == "DDR") return iface->get_gpio_ddr(unit); if (attr == "OUT") return iface->get_gpio_out(unit); - if (attr == "ATR_0X") return iface->get_atr_reg(unit, dboard_iface::ATR_REG_IDLE); - if (attr == "ATR_RX") return iface->get_atr_reg(unit, dboard_iface::ATR_REG_RX_ONLY); - if (attr == "ATR_TX") return iface->get_atr_reg(unit, dboard_iface::ATR_REG_TX_ONLY); - if (attr == "ATR_XX") return iface->get_atr_reg(unit, dboard_iface::ATR_REG_FULL_DUPLEX); + if (attr == "ATR_0X") return iface->get_atr_reg(unit, gpio_atr::ATR_REG_IDLE); + if (attr == "ATR_RX") return iface->get_atr_reg(unit, gpio_atr::ATR_REG_RX_ONLY); + if (attr == "ATR_TX") return iface->get_atr_reg(unit, gpio_atr::ATR_REG_TX_ONLY); + if (attr == "ATR_XX") return iface->get_atr_reg(unit, gpio_atr::ATR_REG_FULL_DUPLEX); if (attr == "READBACK") return iface->read_gpio(unit); } return 0; @@ -1456,9 +1652,8 @@ public: default: throw uhd::assertion_error("multi_usrp::read_register - register has invalid bitwidth: " + path); } - } else { - throw uhd::not_implemented_error("multi_usrp::read_register - register IO not supported for this device"); } + throw uhd::not_implemented_error("multi_usrp::read_register - register IO not supported for this device"); } std::vector<std::string> enumerate_registers(const size_t mboard) @@ -1494,6 +1689,8 @@ public: private: device::sptr _dev; property_tree::sptr _tree; + bool _is_device3; + uhd::rfnoc::legacy_compat::sptr _legacy_compat; struct mboard_chan_pair{ size_t mboard, chan; @@ -1546,6 +1743,10 @@ private: fs_path rx_dsp_root(const size_t chan) { mboard_chan_pair mcp = rx_chan_to_mcp(chan); + if (is_device3()) { + return _legacy_compat->rx_dsp_root(mcp.mboard, mcp.chan); + } + if (_tree->exists(mb_root(mcp.mboard) / "rx_chan_dsp_mapping")) { std::vector<size_t> map = _tree->access<std::vector<size_t> >(mb_root(mcp.mboard) / "rx_chan_dsp_mapping").get(); UHD_ASSERT_THROW(map.size() > mcp.chan); @@ -1566,6 +1767,10 @@ private: fs_path tx_dsp_root(const size_t chan) { mboard_chan_pair mcp = tx_chan_to_mcp(chan); + if (is_device3()) { + return _legacy_compat->tx_dsp_root(mcp.mboard, mcp.chan); + } + if (_tree->exists(mb_root(mcp.mboard) / "tx_chan_dsp_mapping")) { std::vector<size_t> map = _tree->access<std::vector<size_t> >(mb_root(mcp.mboard) / "tx_chan_dsp_mapping").get(); UHD_ASSERT_THROW(map.size() > mcp.chan); @@ -1585,6 +1790,9 @@ private: fs_path rx_fe_root(const size_t chan) { mboard_chan_pair mcp = rx_chan_to_mcp(chan); + if (is_device3()) { + return _legacy_compat->rx_fe_root(mcp.mboard, mcp.chan); + } try { const subdev_spec_pair_t spec = get_rx_subdev_spec(mcp.mboard).at(mcp.chan); @@ -1599,6 +1807,9 @@ private: fs_path tx_fe_root(const size_t chan) { mboard_chan_pair mcp = tx_chan_to_mcp(chan); + if (is_device3()) { + return _legacy_compat->tx_fe_root(mcp.mboard, mcp.chan); + } try { const subdev_spec_pair_t spec = get_tx_subdev_spec(mcp.mboard).at(mcp.chan); diff --git a/host/lib/usrp/n230/CMakeLists.txt b/host/lib/usrp/n230/CMakeLists.txt new file mode 100644 index 000000000..9eaccffba --- /dev/null +++ b/host/lib/usrp/n230/CMakeLists.txt @@ -0,0 +1,37 @@ +# +# Copyright 2013 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 +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. +# + +######################################################################## +# This file included, use CMake directory variables +######################################################################## + +######################################################################## +# Conditionally configure the N230 support +######################################################################## +IF(ENABLE_N230) + LIBUHD_APPEND_SOURCES( + ${CMAKE_CURRENT_SOURCE_DIR}/n230_cores.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/n230_impl.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/n230_resource_manager.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/n230_eeprom_manager.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/n230_stream_manager.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/n230_clk_pps_ctrl.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/n230_frontend_ctrl.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/n230_uart.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/n230_image_loader.cpp + ) +ENDIF(ENABLE_N230) diff --git a/host/lib/usrp/n230/n230_clk_pps_ctrl.cpp b/host/lib/usrp/n230/n230_clk_pps_ctrl.cpp new file mode 100644 index 000000000..9d704b702 --- /dev/null +++ b/host/lib/usrp/n230/n230_clk_pps_ctrl.cpp @@ -0,0 +1,158 @@ +// +// Copyright 2013-2014 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 +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +#include "n230_clk_pps_ctrl.hpp" + +#include <uhd/utils/msg.hpp> +#include <uhd/utils/safe_call.hpp> +#include <boost/cstdint.hpp> +#include <boost/format.hpp> +#include <boost/foreach.hpp> +#include <stdexcept> +#include <cmath> +#include <cstdlib> + +namespace uhd { namespace usrp { namespace n230 { + +class n230_clk_pps_ctrl_impl : public n230_clk_pps_ctrl +{ +public: + n230_clk_pps_ctrl_impl( + ad9361_ctrl::sptr codec_ctrl, + n230_ref_pll_ctrl::sptr ref_pll_ctrl, + fpga::core_misc_reg_t& core_misc_reg, + fpga::core_pps_sel_reg_t& core_pps_sel, + fpga::core_status_reg_t& core_status_reg, + const std::vector<time_core_3000::sptr>& time_cores + ): _codec_ctrl(codec_ctrl), + _ref_pll_ctrl(ref_pll_ctrl), + _core_misc_reg(core_misc_reg), + _core_pps_sel_reg(core_pps_sel), + _core_status_reg(core_status_reg), + _time_cores(time_cores), + _tick_rate(0.0), + _clock_source("<undefined>"), + _time_source("<undefined>") + { + } + + virtual ~n230_clk_pps_ctrl_impl() + { + } + + double set_tick_rate(const double rate) + { + UHD_MSG(status) << "Configuring a tick rate of " << rate/1e6 << " MHz... "; + _tick_rate = _codec_ctrl->set_clock_rate(rate); + UHD_MSG(status) << "got " << _tick_rate/1e6 << " MHz\n"; + + BOOST_FOREACH(time_core_3000::sptr& time_core, _time_cores) { + time_core->set_tick_rate(_tick_rate); + time_core->self_test(); + } + + return _tick_rate; + } + + double get_tick_rate() + { + return _tick_rate; + } + + void set_clock_source(const std::string &source) + { + if (_clock_source == source) return; + + if (source == "internal") { + _ref_pll_ctrl->set_lock_to_ext_ref(false); + } else if (source == "external" || source == "gpsdo") { + _ref_pll_ctrl->set_lock_to_ext_ref(true); + } else { + throw uhd::key_error("set_clock_source: unknown source: " + source); + } + _core_misc_reg.write(fpga::core_misc_reg_t::REF_SEL, (source == "gpsdo") ? 1 : 0); + + _clock_source = source; + } + + const std::string& get_clock_source() + { + return _clock_source; + } + + uhd::sensor_value_t get_ref_locked() + { + bool locked = false; + if (_clock_source == "external" || _clock_source == "gpsdo") { + locked = (_core_status_reg.read(fpga::core_status_reg_t::REF_LOCKED) == 1); + } else { + //If the source is internal, the charge pump on the ADF4001 is tristated which + //means that the 40MHz VCTXXO is free running i.e. always "locked" + locked = true; + } + return sensor_value_t("Ref", locked, "locked", "unlocked"); + } + + void set_pps_source(const std::string &source) + { + if (_time_source == source) return; + + if (source == "none" or source == "gpsdo") { + _core_pps_sel_reg.write(fpga::core_pps_sel_reg_t::EXT_PPS_EN, 0); + } else if (source == "external") { + _core_pps_sel_reg.write(fpga::core_pps_sel_reg_t::EXT_PPS_EN, 1); + } else { + throw uhd::key_error("update_time_source: unknown source: " + source); + } + + _time_source = source; + } + + const std::string& get_pps_source() + { + return _time_source; + } + +private: + ad9361_ctrl::sptr _codec_ctrl; + n230_ref_pll_ctrl::sptr _ref_pll_ctrl; + fpga::core_misc_reg_t& _core_misc_reg; + fpga::core_pps_sel_reg_t& _core_pps_sel_reg; + fpga::core_status_reg_t& _core_status_reg; + std::vector<time_core_3000::sptr> _time_cores; + double _tick_rate; + std::string _clock_source; + std::string _time_source; +}; + +}}} //namespace + +using namespace uhd::usrp::n230; +using namespace uhd::usrp; + +n230_clk_pps_ctrl::sptr n230_clk_pps_ctrl::make( + ad9361_ctrl::sptr codec_ctrl, + n230_ref_pll_ctrl::sptr ref_pll_ctrl, + fpga::core_misc_reg_t& core_misc_reg, + fpga::core_pps_sel_reg_t& core_pps_sel_reg, + fpga::core_status_reg_t& core_status_reg, + const std::vector<time_core_3000::sptr>& time_cores) +{ + return sptr(new n230_clk_pps_ctrl_impl( + codec_ctrl, ref_pll_ctrl, core_misc_reg, core_pps_sel_reg, core_status_reg, time_cores)); +} + diff --git a/host/lib/usrp/n230/n230_clk_pps_ctrl.hpp b/host/lib/usrp/n230/n230_clk_pps_ctrl.hpp new file mode 100644 index 000000000..3e0a21e04 --- /dev/null +++ b/host/lib/usrp/n230/n230_clk_pps_ctrl.hpp @@ -0,0 +1,89 @@ +// +// Copyright 2013-2014 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 +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +#ifndef INCLUDED_N230_CLK_PPS_CTRL_HPP +#define INCLUDED_N230_CLK_PPS_CTRL_HPP + +#include "time_core_3000.hpp" +#include "ad9361_ctrl.hpp" +#include <uhd/types/sensors.hpp> +#include <boost/shared_ptr.hpp> +#include <boost/utility.hpp> +#include <vector> +#include "n230_cores.hpp" +#include "n230_fpga_defs.h" + +namespace uhd { namespace usrp { namespace n230 { + +class n230_clk_pps_ctrl : boost::noncopyable +{ +public: + typedef boost::shared_ptr<n230_clk_pps_ctrl> sptr; + + static sptr make( + ad9361_ctrl::sptr codec_ctrl, + n230_ref_pll_ctrl::sptr ref_pll_ctrl, + fpga::core_misc_reg_t& core_misc_reg, + fpga::core_pps_sel_reg_t& core_pps_sel_reg, + fpga::core_status_reg_t& core_status_reg, + const std::vector<time_core_3000::sptr>& time_cores); + + virtual ~n230_clk_pps_ctrl() {} + + /*********************************************************************** + * Tick Rate + **********************************************************************/ + /*! Set the master clock rate of the device. + * \return the clock frequency in Hz + */ + virtual double set_tick_rate(const double rate) = 0; + + /*! Get the master clock rate of the device. + * \return the clock frequency in Hz + */ + virtual double get_tick_rate() = 0; + + /*********************************************************************** + * Reference clock + **********************************************************************/ + /*! Set the reference clock source of the device. + */ + virtual void set_clock_source(const std::string &source) = 0; + + /*! Get the reference clock source of the device. + */ + virtual const std::string& get_clock_source() = 0; + + /*! Get the reference clock lock status. + */ + virtual uhd::sensor_value_t get_ref_locked() = 0; + + /*********************************************************************** + * Time source + **********************************************************************/ + /*! Set the time source of the device. + */ + virtual void set_pps_source(const std::string &source) = 0; + + /*! Get the reference clock source of the device. + */ + virtual const std::string& get_pps_source() = 0; +}; + +}}} //namespace + +#endif /* INCLUDED_N230_CLK_PPS_CTRL_HPP */ diff --git a/host/lib/usrp/n230/n230_cores.cpp b/host/lib/usrp/n230/n230_cores.cpp new file mode 100644 index 000000000..58c702ec1 --- /dev/null +++ b/host/lib/usrp/n230/n230_cores.cpp @@ -0,0 +1,91 @@ +// +// Copyright 2013-2014 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 +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +#include "n230_cores.hpp" +#include "n230_fpga_defs.h" +#include "n230_fw_defs.h" + +namespace uhd { namespace usrp { namespace n230 { + +n230_core_spi_core::n230_core_spi_core( + uhd::wb_iface::sptr iface, + perif_t default_perif) : + _spi_core(spi_core_3000::make(iface, + fpga::sr_addr(fpga::SR_CORE_SPI), + fpga::rb_addr(fpga::RB_CORE_SPI))), + _current_perif(default_perif), + _last_perif(default_perif) +{ + change_perif(default_perif); +} + +boost::uint32_t n230_core_spi_core::transact_spi( + int which_slave, + const spi_config_t &config, + boost::uint32_t data, + size_t num_bits, + bool readback) +{ + boost::mutex::scoped_lock lock(_mutex); + return _spi_core->transact_spi(which_slave, config, data, num_bits, readback); +} + +void n230_core_spi_core::change_perif(perif_t perif) +{ + boost::mutex::scoped_lock lock(_mutex); + _last_perif = _current_perif; + _current_perif = perif; + + switch (_current_perif) { + case CODEC: + _spi_core->set_divider(fw::CPU_CLOCK_FREQ/fw::CODEC_SPI_CLOCK_FREQ); + break; + case PLL: + _spi_core->set_divider(fw::CPU_CLOCK_FREQ/fw::ADF4001_SPI_CLOCK_FREQ); + break; + } +} + +void n230_core_spi_core::restore_perif() +{ + change_perif(_last_perif); +} + +n230_ref_pll_ctrl::n230_ref_pll_ctrl(n230_core_spi_core::sptr spi) : + adf4001_ctrl(spi, fpga::ADF4001_SPI_SLAVE_NUM), + _spi(spi) +{ +} + +void n230_ref_pll_ctrl::set_lock_to_ext_ref(bool external) +{ + _spi->change_perif(n230_core_spi_core::PLL); + adf4001_ctrl::set_lock_to_ext_ref(external); + _spi->restore_perif(); +} + +}}} //namespace + +using namespace uhd::usrp::n230; +using namespace uhd::usrp; + +n230_core_spi_core::sptr n230_core_spi_core::make( + uhd::wb_iface::sptr iface, n230_core_spi_core::perif_t default_perif) +{ + return sptr(new n230_core_spi_core(iface, default_perif)); +} + diff --git a/host/lib/usrp/n230/n230_cores.hpp b/host/lib/usrp/n230/n230_cores.hpp new file mode 100644 index 000000000..3f56c1889 --- /dev/null +++ b/host/lib/usrp/n230/n230_cores.hpp @@ -0,0 +1,71 @@ +// +// Copyright 2013-2014 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 +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +#ifndef INCLUDED_N230_CORES_HPP +#define INCLUDED_N230_CORES_HPP + +#include "spi_core_3000.hpp" +#include "adf4001_ctrl.hpp" +#include <boost/thread/mutex.hpp> + +namespace uhd { namespace usrp { namespace n230 { + +class n230_core_spi_core : boost::noncopyable, public uhd::spi_iface { + +public: + typedef boost::shared_ptr<n230_core_spi_core> sptr; + + enum perif_t { + CODEC, PLL + }; + + n230_core_spi_core(uhd::wb_iface::sptr iface, perif_t default_perif); + + virtual boost::uint32_t transact_spi( + int which_slave, + const spi_config_t &config, + boost::uint32_t data, + size_t num_bits, + bool readback); + + void change_perif(perif_t perif); + void restore_perif(); + + static sptr make(uhd::wb_iface::sptr iface, perif_t default_perif = CODEC); + +private: + spi_core_3000::sptr _spi_core; + perif_t _current_perif; + perif_t _last_perif; + boost::mutex _mutex; +}; + +class n230_ref_pll_ctrl : public adf4001_ctrl { +public: + typedef boost::shared_ptr<n230_ref_pll_ctrl> sptr; + + n230_ref_pll_ctrl(n230_core_spi_core::sptr spi); + void set_lock_to_ext_ref(bool external); + +private: + n230_core_spi_core::sptr _spi; +}; + + +}}} //namespace + +#endif /* INCLUDED_N230_CORES_HPP */ diff --git a/host/lib/usrp/n230/n230_defaults.h b/host/lib/usrp/n230/n230_defaults.h new file mode 100644 index 000000000..a25978585 --- /dev/null +++ b/host/lib/usrp/n230/n230_defaults.h @@ -0,0 +1,65 @@ +// +// Copyright 2014 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 +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +#ifndef INCLUDED_N230_DEFAULTS_H +#define INCLUDED_N230_DEFAULTS_H + +#include <stdint.h> +#ifndef __cplusplus +#include <stdbool.h> +#endif +#include <uhd/transport/udp_constants.hpp> + +namespace uhd { +namespace usrp { +namespace n230 { + +static const double DEFAULT_TICK_RATE = 46.08e6; +static const double MAX_TICK_RATE = 50e6; +static const double MIN_TICK_RATE = 1e6; + +static const double DEFAULT_TX_SAMP_RATE = 1.0e6; +static const double DEFAULT_RX_SAMP_RATE = 1.0e6; +static const double DEFAULT_DDC_FREQ = 0.0; +static const double DEFAULT_DUC_FREQ = 0.0; + +static const double DEFAULT_FE_GAIN = 0.0; +static const double DEFAULT_FE_FREQ = 1.0e9; +static const double DEFAULT_FE_BW = 56e6; + +static const std::string DEFAULT_TIME_SRC = "none"; +static const std::string DEFAULT_CLOCK_SRC = "internal"; + +static const size_t DEFAULT_FRAME_SIZE = 1500 - 20 - 8; //default ipv4 mtu - ipv4 header - udp header +static const size_t MAX_FRAME_SIZE = 8000; +static const size_t MIN_FRAME_SIZE = IP_PROTOCOL_MIN_MTU_SIZE; + +static const size_t DEFAULT_NUM_FRAMES = 32; + +//A 1MiB SRAM is shared between two radios so we allocate each +//radio 0.5MiB minus 8 packets worth of buffering to ensure +//that the FIFO does not overflow +static const size_t DEFAULT_SEND_BUFF_SIZE = 500*1024; +#if defined(UHD_PLATFORM_MACOS) || defined(UHD_PLATFORM_BSD) +static const size_t DEFAULT_RECV_BUFF_SIZE = 0x100000; //1Mib +#elif defined(UHD_PLATFORM_LINUX) || defined(UHD_PLATFORM_WIN32) +static const size_t DEFAULT_RECV_BUFF_SIZE = 0x2000000;//32MiB +#endif + +}}} //namespace + +#endif /* INCLUDED_N230_DEFAULTS_H */ diff --git a/host/lib/usrp/n230/n230_device_args.hpp b/host/lib/usrp/n230/n230_device_args.hpp new file mode 100644 index 000000000..014a6cd14 --- /dev/null +++ b/host/lib/usrp/n230/n230_device_args.hpp @@ -0,0 +1,128 @@ +// +// Copyright 2014 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 +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +#ifndef INCLUDED_N230_DEV_ARGS_HPP +#define INCLUDED_N230_DEV_ARGS_HPP + +#include <uhd/types/wb_iface.hpp> +#include <uhd/transport/udp_simple.hpp> +#include <boost/thread/mutex.hpp> +#include "../common/constrained_device_args.hpp" +#include "n230_defaults.h" + +namespace uhd { namespace usrp { namespace n230 { + +class n230_device_args_t : public constrained_device_args_t +{ +public: + enum loopback_mode_t { LOOPBACK_OFF=0, LOOPBACK_RADIO=1, LOOPBACK_CODEC=2 }; + + n230_device_args_t(): + _master_clock_rate("master_clock_rate", n230::DEFAULT_TICK_RATE), + _send_frame_size("send_frame_size", n230::DEFAULT_FRAME_SIZE), + _recv_frame_size("recv_frame_size", n230::DEFAULT_FRAME_SIZE), + _num_send_frames("num_send_frames", n230::DEFAULT_NUM_FRAMES), + _num_recv_frames("num_recv_frames", n230::DEFAULT_NUM_FRAMES), + _send_buff_size("send_buff_size", n230::DEFAULT_SEND_BUFF_SIZE), + _recv_buff_size("recv_buff_size", n230::DEFAULT_RECV_BUFF_SIZE), + _safe_mode("safe_mode", false), + _loopback_mode("loopback_mode", LOOPBACK_OFF, boost::assign::list_of("off")("radio")("codec")) + {} + + double get_master_clock_rate() const { + return _master_clock_rate.get(); + } + size_t get_send_frame_size() const { + return _send_frame_size.get(); + } + size_t get_recv_frame_size() const { + return _recv_frame_size.get(); + } + size_t get_num_send_frames() const { + return _num_send_frames.get(); + } + size_t get_num_recv_frames() const { + return _num_recv_frames.get(); + } + size_t get_send_buff_size() const { + return _send_buff_size.get(); + } + size_t get_recv_buff_size() const { + return _recv_buff_size.get(); + } + bool get_safe_mode() const { + return _safe_mode.get(); + } + loopback_mode_t get_loopback_mode() const { + return _loopback_mode.get(); + } + + inline virtual std::string to_string() const { + return _master_clock_rate.to_string() + ", " + + _send_frame_size.to_string() + ", " + + _recv_frame_size.to_string() + ", " + + _num_send_frames.to_string() + ", " + + _num_recv_frames.to_string() + ", " + + _send_buff_size.to_string() + ", " + + _recv_buff_size.to_string() + ", " + + _safe_mode.to_string() + ", " + + _loopback_mode.to_string(); + } +private: + virtual void _parse(const device_addr_t& dev_args) { + //Extract parameters from dev_args + if (dev_args.has_key(_master_clock_rate.key())) + _master_clock_rate.parse(dev_args[_master_clock_rate.key()]); + if (dev_args.has_key(_send_frame_size.key())) + _send_frame_size.parse(dev_args[_send_frame_size.key()]); + if (dev_args.has_key(_recv_frame_size.key())) + _recv_frame_size.parse(dev_args[_recv_frame_size.key()]); + if (dev_args.has_key(_num_send_frames.key())) + _num_send_frames.parse(dev_args[_num_send_frames.key()]); + if (dev_args.has_key(_num_recv_frames.key())) + _num_recv_frames.parse(dev_args[_num_recv_frames.key()]); + if (dev_args.has_key(_send_buff_size.key())) + _send_buff_size.parse(dev_args[_send_buff_size.key()]); + if (dev_args.has_key(_recv_buff_size.key())) + _recv_buff_size.parse(dev_args[_recv_buff_size.key()]); + if (dev_args.has_key(_safe_mode.key())) + _safe_mode.parse(dev_args[_safe_mode.key()]); + if (dev_args.has_key(_loopback_mode.key())) + _loopback_mode.parse(dev_args[_loopback_mode.key()], false /* assert invalid */); + + //Sanity check params + _enforce_range(_master_clock_rate, MIN_TICK_RATE, MAX_TICK_RATE); + _enforce_range(_send_frame_size, MIN_FRAME_SIZE, MAX_FRAME_SIZE); + _enforce_range(_recv_frame_size, MIN_FRAME_SIZE, MAX_FRAME_SIZE); + _enforce_range(_num_send_frames, (size_t)2, (size_t)UINT_MAX); + _enforce_range(_num_recv_frames, (size_t)2, (size_t)UINT_MAX); + } + + constrained_device_args_t::num_arg<double> _master_clock_rate; + constrained_device_args_t::num_arg<size_t> _send_frame_size; + constrained_device_args_t::num_arg<size_t> _recv_frame_size; + constrained_device_args_t::num_arg<size_t> _num_send_frames; + constrained_device_args_t::num_arg<size_t> _num_recv_frames; + constrained_device_args_t::num_arg<size_t> _send_buff_size; + constrained_device_args_t::num_arg<size_t> _recv_buff_size; + constrained_device_args_t::bool_arg _safe_mode; + constrained_device_args_t::enum_arg<loopback_mode_t> _loopback_mode; +}; + +}}} //namespace + +#endif //INCLUDED_N230_DEV_ARGS_HPP diff --git a/host/lib/usrp/n230/n230_eeprom.h b/host/lib/usrp/n230/n230_eeprom.h new file mode 100644 index 000000000..b6c2a0c76 --- /dev/null +++ b/host/lib/usrp/n230/n230_eeprom.h @@ -0,0 +1,124 @@ +// +// Copyright 2014 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 +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +#ifndef INCLUDED_N230_EEPROM_H +#define INCLUDED_N230_EEPROM_H + +#include <stdint.h> +#ifndef __cplusplus +#include <stdbool.h> +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +#define N230_NUM_ETH_PORTS 2 +#define N230_MAX_NUM_ETH_PORTS 2 + +#if (N230_NUM_ETH_PORTS > N230_MAX_NUM_ETH_PORTS) +#error +#endif + +#define N230_EEPROM_VER_MAJOR 1 +#define N230_EEPROM_VER_MINOR 1 +#define N230_EEPROM_SERIAL_LEN 9 +#define N230_EEPROM_NAME_LEN 32 + +typedef struct +{ + uint8_t mac_addr[6]; + uint8_t _pad[2]; + uint32_t subnet; + uint32_t ip_addr; +} n230_eth_eeprom_map_t; + +typedef struct +{ + //Data format version + uint16_t data_version_major; + uint16_t data_version_minor; + + //HW identification info + uint16_t hw_revision; + uint16_t hw_product; + uint8_t serial[N230_EEPROM_SERIAL_LEN]; + uint8_t _pad_serial; + uint16_t hw_revision_compat; + uint8_t _pad0[18 - (N230_EEPROM_SERIAL_LEN + 1)]; + + //Ethernet specific + uint32_t gateway; + n230_eth_eeprom_map_t eth_info[N230_MAX_NUM_ETH_PORTS]; + + //User specific + uint8_t user_name[N230_EEPROM_NAME_LEN]; +} n230_eeprom_map_t; + +#ifdef __cplusplus +} //extern "C" +#endif + +// The following definitions are only useful in firmware. Exclude in host code. +#ifndef __cplusplus + +/*! + * Read the eeprom and update caches. + * Returns true if read was successful. + * If the read was not successful then the cache is initialized with + * default values and marked as dirty. + */ +bool read_n230_eeprom(); + +/*! + * Write the contents of the cache to the eeprom. + * Returns true if write was successful. + */ +bool write_n230_eeprom(); + +/*! + * Returns the dirty state of the cache. + */ +bool is_n230_eeprom_cache_dirty(); + +/*! + * Returns a const pointer to the EEPROM map. + */ +const n230_eeprom_map_t* get_n230_const_eeprom_map(); + +/*! + * Returns the settings for the the 'iface'th ethernet interface + */ +const n230_eth_eeprom_map_t* get_n230_ethernet_info(uint32_t iface); + +/*! + * Returns a non-const pointer to the EEPROM map. Will mark the cache as dirty. + */ +n230_eeprom_map_t* get_n230_eeprom_map(); + +/*! + * FPGA Image operations + */ +inline void read_n230_fpga_image_page(uint32_t offset, void *buf, uint32_t num_bytes); + +inline bool write_n230_fpga_image_page(uint32_t offset, const void *buf, uint32_t num_bytes); + +inline bool erase_n230_fpga_image_sector(uint32_t offset); + +#endif //ifdef __cplusplus + +#endif /* INCLUDED_N230_EEPROM_H */ diff --git a/host/lib/usrp/n230/n230_eeprom_manager.cpp b/host/lib/usrp/n230/n230_eeprom_manager.cpp new file mode 100644 index 000000000..b19deb23a --- /dev/null +++ b/host/lib/usrp/n230/n230_eeprom_manager.cpp @@ -0,0 +1,207 @@ +// +// Copyright 2013-2014,2016 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 +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +#include "n230_eeprom.h" +#include <uhd/utils/byteswap.hpp> +#include <uhd/utils/msg.hpp> +#include <uhd/exception.hpp> +#include <uhd/types/mac_addr.hpp> +#include <boost/format.hpp> +#include <boost/asio.hpp> //used for htonl and ntohl +#include "n230_eeprom_manager.hpp" + +namespace uhd { namespace usrp { namespace n230 { + +const double n230_eeprom_manager::UDP_TIMEOUT_IN_SEC = 2.0; + +n230_eeprom_manager::n230_eeprom_manager(const std::string& addr): + _seq_num(0) +{ + _udp_xport = transport::udp_simple::make_connected( + addr, BOOST_STRINGIZE(N230_FW_COMMS_FLASH_PROG_PORT)); + read_mb_eeprom(); +} + +static const std::string _bytes_to_string(const uint8_t* bytes, size_t max_len) +{ + std::string out; + for (size_t i = 0; i < max_len; i++) { + if (bytes[i] < 32 or bytes[i] > 127) return out; + out += bytes[i]; + } + return out; +} + +static void _string_to_bytes(const std::string &string, size_t max_len, uint8_t* buffer) +{ + byte_vector_t bytes; + const size_t len = std::min(string.size(), max_len); + for (size_t i = 0; i < len; i++){ + buffer[i] = string[i]; + } + if (len < max_len - 1) buffer[len] = '\0'; +} + +const mboard_eeprom_t& n230_eeprom_manager::read_mb_eeprom() +{ + boost::mutex::scoped_lock lock(_mutex); + + //Read EEPROM from device + _transact(N230_FLASH_COMM_CMD_READ_NV_DATA); + const n230_eeprom_map_t* map_ptr = reinterpret_cast<const n230_eeprom_map_t*>(_response.data); + const n230_eeprom_map_t& map = *map_ptr; + + uint16_t ver_major = uhd::htonx<boost::uint16_t>(map.data_version_major); + uint16_t ver_minor = uhd::htonx<boost::uint16_t>(map.data_version_minor); + + _mb_eeprom["product"] = boost::lexical_cast<std::string>( + uhd::htonx<boost::uint16_t>(map.hw_product)); + _mb_eeprom["revision"] = boost::lexical_cast<std::string>( + uhd::htonx<boost::uint16_t>(map.hw_revision)); + //The revision_compat field does not exist in version 1.0 + //EEPROM version 1.0 will only exist on HW revision 1 so it is safe to set + //revision_compat = revision + if (ver_major == 1 and ver_minor == 0) { + _mb_eeprom["revision_compat"] = _mb_eeprom["revision"]; + } else { + _mb_eeprom["revision_compat"] = boost::lexical_cast<std::string>( + uhd::htonx<boost::uint16_t>(map.hw_revision_compat)); + } + _mb_eeprom["serial"] = _bytes_to_string( + map.serial, N230_EEPROM_SERIAL_LEN); + + //Extract ethernet info + _mb_eeprom["gateway"] = boost::asio::ip::address_v4( + uhd::htonx<boost::uint32_t>(map.gateway)).to_string(); + for (size_t i = 0; i < N230_MAX_NUM_ETH_PORTS; i++) { + const std::string n(1, i+'0'); + _mb_eeprom["ip-addr"+n] = boost::asio::ip::address_v4( + uhd::htonx<boost::uint32_t>(map.eth_info[i].ip_addr)).to_string(); + _mb_eeprom["subnet"+n] = boost::asio::ip::address_v4( + uhd::htonx<boost::uint32_t>(map.eth_info[i].subnet)).to_string(); + byte_vector_t mac_addr(map.eth_info[i].mac_addr, map.eth_info[i].mac_addr + 6); + _mb_eeprom["mac-addr"+n] = mac_addr_t::from_bytes(mac_addr).to_string(); + } + + _mb_eeprom["name"] = _bytes_to_string( + map.user_name, N230_EEPROM_NAME_LEN); + + return _mb_eeprom; +} + +void n230_eeprom_manager::write_mb_eeprom(const mboard_eeprom_t& eeprom) +{ + boost::mutex::scoped_lock lock(_mutex); + + _mb_eeprom = eeprom; + + n230_eeprom_map_t* map_ptr = reinterpret_cast<n230_eeprom_map_t*>(_request.data); + memset(map_ptr, 0xff, sizeof(n230_eeprom_map_t)); //Initialize to erased state + //Read EEPROM from device + _transact(N230_FLASH_COMM_CMD_READ_NV_DATA); + memcpy(map_ptr, _response.data, sizeof(n230_eeprom_map_t)); + n230_eeprom_map_t& map = *map_ptr; + + // Automatic version upgrade handling + uint16_t old_ver_major = uhd::htonx<boost::uint16_t>(map.data_version_major); + uint16_t old_ver_minor = uhd::htonx<boost::uint16_t>(map.data_version_minor); + + //The revision_compat field does not exist for version 1.0 so force write it + //EEPROM version 1.0 will only exist on HW revision 1 so it is safe to set + //revision_compat = revision for the upgrade + bool force_write_version_compat = (old_ver_major == 1 and old_ver_minor == 0); + + map.data_version_major = uhd::htonx<boost::uint16_t>(N230_EEPROM_VER_MAJOR); + map.data_version_minor = uhd::htonx<boost::uint16_t>(N230_EEPROM_VER_MINOR); + + if (_mb_eeprom.has_key("product")) { + map.hw_product = uhd::htonx<boost::uint16_t>( + boost::lexical_cast<boost::uint16_t>(_mb_eeprom["product"])); + } + if (_mb_eeprom.has_key("revision")) { + map.hw_revision = uhd::htonx<boost::uint16_t>( + boost::lexical_cast<boost::uint16_t>(_mb_eeprom["revision"])); + } + if (_mb_eeprom.has_key("revision_compat")) { + map.hw_revision_compat = uhd::htonx<boost::uint16_t>( + boost::lexical_cast<boost::uint16_t>(_mb_eeprom["revision_compat"])); + } else if (force_write_version_compat) { + map.hw_revision_compat = map.hw_revision; + } + if (_mb_eeprom.has_key("serial")) { + _string_to_bytes(_mb_eeprom["serial"], N230_EEPROM_SERIAL_LEN, map.serial); + } + + //Push ethernet info + if (_mb_eeprom.has_key("gateway")){ + map.gateway = uhd::htonx<boost::uint32_t>( + boost::asio::ip::address_v4::from_string(_mb_eeprom["gateway"]).to_ulong()); + } + for (size_t i = 0; i < N230_MAX_NUM_ETH_PORTS; i++) { + const std::string n(1, i+'0'); + if (_mb_eeprom.has_key("ip-addr"+n)){ + map.eth_info[i].ip_addr = uhd::htonx<boost::uint32_t>( + boost::asio::ip::address_v4::from_string(_mb_eeprom["ip-addr"+n]).to_ulong()); + } + if (_mb_eeprom.has_key("subnet"+n)){ + map.eth_info[i].subnet = uhd::htonx<boost::uint32_t>( + boost::asio::ip::address_v4::from_string(_mb_eeprom["subnet"+n]).to_ulong()); + } + if (_mb_eeprom.has_key("mac-addr"+n)) { + byte_vector_t mac_addr = mac_addr_t::from_string(_mb_eeprom["mac-addr"+n]).to_bytes(); + std::copy(mac_addr.begin(), mac_addr.end(), map.eth_info[i].mac_addr); + } + } + //store the name + if (_mb_eeprom.has_key("name")) { + _string_to_bytes(_mb_eeprom["name"], N230_EEPROM_NAME_LEN, map.user_name); + } + + //Write EEPROM to device + _transact(N230_FLASH_COMM_CMD_WRITE_NV_DATA); +} + +void n230_eeprom_manager::_transact(const boost::uint32_t command) +{ + //Load request struct + _request.flags = uhd::htonx<boost::uint32_t>(N230_FLASH_COMM_FLAGS_ACK | command); + _request.seq = uhd::htonx<boost::uint32_t>(_seq_num++); + + //Send request + _flush_xport(); + _udp_xport->send(boost::asio::buffer(&_request, sizeof(_request))); + + //Recv reply + const size_t nbytes = _udp_xport->recv(boost::asio::buffer(&_response, sizeof(_response)), UDP_TIMEOUT_IN_SEC); + if (nbytes == 0) throw uhd::io_error("n230_eeprom_manager::_transact failure"); + + //Sanity checks + const size_t flags = uhd::ntohx<boost::uint32_t>(_response.flags); + UHD_ASSERT_THROW(nbytes == sizeof(_response)); + UHD_ASSERT_THROW(_response.seq == _request.seq); + UHD_ASSERT_THROW(flags & command); +} + +void n230_eeprom_manager::_flush_xport() +{ + char buff[sizeof(n230_flash_prog_t)] = {}; + while (_udp_xport->recv(boost::asio::buffer(buff), 0.0)) { + /*NOP*/ + } +} + +}}}; //namespace diff --git a/host/lib/usrp/n230/n230_eeprom_manager.hpp b/host/lib/usrp/n230/n230_eeprom_manager.hpp new file mode 100644 index 000000000..cc5aee9f3 --- /dev/null +++ b/host/lib/usrp/n230/n230_eeprom_manager.hpp @@ -0,0 +1,58 @@ +// +// Copyright 2013-2014 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 +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +#ifndef INCLUDED_N230_EEPROM_MANAGER_HPP +#define INCLUDED_N230_EEPROM_MANAGER_HPP + +#include <boost/thread/mutex.hpp> +#include <uhd/transport/udp_simple.hpp> +#include <uhd/types/dict.hpp> +#include <uhd/usrp/mboard_eeprom.hpp> +#include "n230_fw_host_iface.h" + +namespace uhd { namespace usrp { namespace n230 { + +class n230_eeprom_manager : boost::noncopyable +{ +public: + n230_eeprom_manager(const std::string& addr); + + const mboard_eeprom_t& read_mb_eeprom(); + void write_mb_eeprom(const mboard_eeprom_t& eeprom); + + inline const mboard_eeprom_t& get_mb_eeprom() { + return _mb_eeprom; + } + +private: //Functions + void _transact(const boost::uint32_t command); + void _flush_xport(); + +private: //Members + mboard_eeprom_t _mb_eeprom; + transport::udp_simple::sptr _udp_xport; + n230_flash_prog_t _request; + n230_flash_prog_t _response; + boost::uint32_t _seq_num; + boost::mutex _mutex; + + static const double UDP_TIMEOUT_IN_SEC; +}; + +}}} //namespace + +#endif /* INCLUDED_N230_EEPROM_MANAGER_HPP */ diff --git a/host/lib/usrp/n230/n230_fpga_defs.h b/host/lib/usrp/n230/n230_fpga_defs.h new file mode 100644 index 000000000..3aa96643f --- /dev/null +++ b/host/lib/usrp/n230/n230_fpga_defs.h @@ -0,0 +1,207 @@ +// +// Copyright 2014 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 +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +#ifndef INCLUDED_N230_FPGA_DEFS_H +#define INCLUDED_N230_FPGA_DEFS_H + +#include <stdint.h> +#ifndef __cplusplus +#include <stdbool.h> +#endif +#include <uhd/utils/soft_register.hpp> + +namespace uhd { +namespace usrp { +namespace n230 { +namespace fpga { + +static inline uint32_t sr_addr(uint32_t offset) { + return (offset*4); +} + +static inline uint32_t rb_addr(uint32_t offset) { + return (offset*8); +} + +static const size_t NUM_RADIOS = 2; +static const double BUS_CLK_RATE = 80e6; + +/******************************************************************* + * CVITA Routing + *******************************************************************/ +static const uint32_t CVITA_UDP_PORT = 49153; +static const bool CVITA_BIG_ENDIAN = true; + +enum xb_endpoint_t { + N230_XB_DST_E0 = 0, + N230_XB_DST_E1 = 1, + N230_XB_DST_R0 = 2, + N230_XB_DST_R1 = 3, + N230_XB_DST_GCTRL = 4, + N230_XB_DST_UART = 5 +}; + +static const boost::uint8_t RADIO_CTRL_SUFFIX = 0x00; +static const boost::uint8_t RADIO_FC_SUFFIX = 0x01; +static const boost::uint8_t RADIO_DATA_SUFFIX = 0x02; + +/******************************************************************* + * Seting Register Base addresses + *******************************************************************/ +static const uint32_t SR_CORE_RADIO_CONTROL = 3; +static const uint32_t SR_CORE_LOOPBACK = 4; +static const uint32_t SR_CORE_BIST1 = 5; +static const uint32_t SR_CORE_BIST2 = 6; +static const uint32_t SR_CORE_SPI = 8; +static const uint32_t SR_CORE_MISC = 16; +static const uint32_t SR_CORE_DATA_DELAY = 17; +static const uint32_t SR_CORE_CLK_DELAY = 18; +static const uint32_t SR_CORE_COMPAT = 24; +static const uint32_t SR_CORE_READBACK = 32; +static const uint32_t SR_CORE_GPSDO_ST = 40; +static const uint32_t SR_CORE_PPS_SEL = 48; +static const uint32_t SR_CORE_MS0_GPIO = 50; +static const uint32_t SR_CORE_MS1_GPIO = 58; + +static const uint32_t RB_CORE_SIGNATUE = 0; +static const uint32_t RB_CORE_SPI = 1; +static const uint32_t RB_CORE_STATUS = 2; +static const uint32_t RB_CORE_BIST = 3; +static const uint32_t RB_CORE_VERSION_HASH = 4; +static const uint32_t RB_CORE_MS0_GPIO = 5; +static const uint32_t RB_CORE_MS1_GPIO = 6; + +/******************************************************************* + * Seting Register Base addresses + *******************************************************************/ +static const uint32_t SR_RADIO_SPI = 8; +static const uint32_t SR_RADIO_ATR = 12; +static const uint32_t SR_RADIO_SW_RST = 20; +static const uint32_t SR_RADIO_TEST = 21; +static const uint32_t SR_RADIO_CODEC_IDLE = 22; +static const uint32_t SR_RADIO_READBACK = 32; +static const uint32_t SR_RADIO_TX_CTRL = 64; +static const uint32_t SR_RADIO_RX_CTRL = 96; +static const uint32_t SR_RADIO_RX_DSP = 144; +static const uint32_t SR_RADIO_TX_DSP = 184; +static const uint32_t SR_RADIO_TIME = 128; +static const uint32_t SR_RADIO_RX_FMT = 136; +static const uint32_t SR_RADIO_TX_FMT = 138; +static const uint32_t SR_RADIO_USER_SR = 253; + +static const uint32_t RB_RADIO_TEST = 0; +static const uint32_t RB_RADIO_TIME_NOW = 1; +static const uint32_t RB_RADIO_TIME_PPS = 2; +static const uint32_t RB_RADIO_CODEC_DATA = 3; +static const uint32_t RB_RADIO_DEBUG = 4; +static const uint32_t RB_RADIO_FRAMER = 5; +static const uint32_t SR_RADIO_USER_RB = 7; + +static const uint32_t AD9361_SPI_SLAVE_NUM = 0x1; +static const uint32_t ADF4001_SPI_SLAVE_NUM = 0x2; + +static const uint32_t RB_N230_PRODUCT_ID = 1; +static const uint32_t RB_N230_COMPAT_MAJOR = 0x20; +static const uint32_t RB_N230_COMPAT_SAFE = 0xC0; + +/******************************************************************* + * Codec Interface Specific + *******************************************************************/ + +// Matches delay setting of 0x00 in AD9361 register 0x006 +static const uint32_t CODEC_DATA_DELAY = 0; +static const uint32_t CODEC_CLK_DELAY = 16; + +//This number must be < 46.08MHz to make sure we don't +//violate timing for radio_clk. It is only used during +//initialization so the exact value does not matter. +static const double CODEC_DEFAULT_CLK_RATE = 40e6; + +/******************************************************************* + * Link Specific + *******************************************************************/ +static const double N230_LINK_RATE_BPS = 1e9/8; + +/******************************************************************* + * GPSDO + *******************************************************************/ +static const uint32_t GPSDO_UART_BAUDRATE = 115200; +static const uint32_t GPSDO_ST_ABSENT = 0x83; +/******************************************************************* + * Register Objects + *******************************************************************/ +class core_radio_ctrl_reg_t : public soft_reg32_wo_t { +public: + UHD_DEFINE_SOFT_REG_FIELD(MIMO, /*width*/ 1, /*shift*/ 0); //[0] + UHD_DEFINE_SOFT_REG_FIELD(CODEC_ARST, /*width*/ 1, /*shift*/ 1); //[1] + + core_radio_ctrl_reg_t(): + soft_reg32_wo_t(fpga::sr_addr(fpga::SR_CORE_RADIO_CONTROL)) + { + //Initial values + set(CODEC_ARST, 0); + set(MIMO, 1); //MIMO always ON for now + } +}; + +class core_misc_reg_t : public soft_reg32_wo_t { +public: + UHD_DEFINE_SOFT_REG_FIELD(REF_SEL, /*width*/ 1, /*shift*/ 0); //[0] + UHD_DEFINE_SOFT_REG_FIELD(RX_BANDSEL_C, /*width*/ 1, /*shift*/ 1); //[1] + UHD_DEFINE_SOFT_REG_FIELD(RX_BANDSEL_B, /*width*/ 1, /*shift*/ 2); //[2] + UHD_DEFINE_SOFT_REG_FIELD(RX_BANDSEL_A, /*width*/ 1, /*shift*/ 3); //[3] + UHD_DEFINE_SOFT_REG_FIELD(TX_BANDSEL_B, /*width*/ 1, /*shift*/ 4); //[4] + UHD_DEFINE_SOFT_REG_FIELD(TX_BANDSEL_A, /*width*/ 1, /*shift*/ 5); //[5] + + core_misc_reg_t(): + soft_reg32_wo_t(fpga::sr_addr(fpga::SR_CORE_MISC)) + { + //Initial values + set(REF_SEL, 0); + set(RX_BANDSEL_C, 0); + set(RX_BANDSEL_B, 0); + set(RX_BANDSEL_A, 0); + set(TX_BANDSEL_B, 0); + set(TX_BANDSEL_A, 0); + } +}; + +class core_pps_sel_reg_t : public soft_reg32_wo_t { +public: + UHD_DEFINE_SOFT_REG_FIELD(EXT_PPS_EN, /*width*/ 1, /*shift*/ 0); //[0] + + core_pps_sel_reg_t(): + soft_reg32_wo_t(fpga::sr_addr(fpga::SR_CORE_PPS_SEL)) + { + //Initial values + set(EXT_PPS_EN, 0); + } +}; + +class core_status_reg_t : public soft_reg64_ro_t { +public: + UHD_DEFINE_SOFT_REG_FIELD(REF_LOCKED, /*width*/ 1, /*shift*/ 0); //[0] + UHD_DEFINE_SOFT_REG_FIELD(GPSDO_STATUS, /*width*/ 8, /*shift*/ 32); //[32:39] + + core_status_reg_t(): + soft_reg64_ro_t(fpga::rb_addr(fpga::RB_CORE_STATUS)) + { } +}; + +}}}} //namespace + +#endif /* INCLUDED_N230_FPGA_DEFS_H */ diff --git a/host/lib/usrp/n230/n230_frontend_ctrl.cpp b/host/lib/usrp/n230/n230_frontend_ctrl.cpp new file mode 100644 index 000000000..e0820d9b2 --- /dev/null +++ b/host/lib/usrp/n230/n230_frontend_ctrl.cpp @@ -0,0 +1,243 @@ +// +// Copyright 2013-2014,2016 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 +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +#include "n230_frontend_ctrl.hpp" + +#include <uhd/utils/msg.hpp> +#include <uhd/exception.hpp> +#include <uhd/types/dict.hpp> +#include <boost/format.hpp> +#include <boost/thread.hpp> +#include "n230_fpga_defs.h" + +namespace uhd { namespace usrp { namespace n230 { + +/* ATR Control Bits */ +static const boost::uint32_t TX_ENABLE = (1 << 7); +static const boost::uint32_t SFDX_RX = (1 << 6); +static const boost::uint32_t SFDX_TX = (1 << 5); +static const boost::uint32_t SRX_RX = (1 << 4); +static const boost::uint32_t SRX_TX = (1 << 3); +static const boost::uint32_t LED_RX = (1 << 2); +static const boost::uint32_t LED_TXRX_RX = (1 << 1); +static const boost::uint32_t LED_TXRX_TX = (1 << 0); + +/* ATR State Definitions. */ +static const boost::uint32_t STATE_OFF = 0x00; +static const boost::uint32_t STATE_RX_RX2 = (SFDX_RX + | SFDX_TX + | LED_RX); +static const boost::uint32_t STATE_RX_TXRX = (SRX_RX + | SRX_TX + | LED_TXRX_RX); +static const boost::uint32_t STATE_FDX_TXRX = (TX_ENABLE + | SFDX_RX + | SFDX_TX + | LED_TXRX_TX + | LED_RX); +static const boost::uint32_t STATE_TX_TXRX = (TX_ENABLE + | SFDX_RX + | SFDX_TX + | LED_TXRX_TX); + +using namespace uhd::usrp; + +class n230_frontend_ctrl_impl : public n230_frontend_ctrl +{ +public: + n230_frontend_ctrl_impl( + radio_ctrl_core_3000::sptr core_ctrl, + fpga::core_misc_reg_t& core_misc_reg, + ad9361_ctrl::sptr codec_ctrl, + const std::vector<gpio_atr::gpio_atr_3000::sptr>& gpio_cores + ): _core_ctrl(core_ctrl), + _codec_ctrl(codec_ctrl), + _gpio_cores(gpio_cores), + _core_misc_reg(core_misc_reg) + { + } + + virtual ~n230_frontend_ctrl_impl() + { + } + + void set_antenna_sel(const size_t which, const std::string &ant) + { + if (ant != "TX/RX" and ant != "RX2") + throw uhd::value_error("n230: unknown RX antenna option: " + ant); + + _fe_states[which].rx_ant = ant; + _flush_atr_state(); + } + + void set_stream_state(const fe_state_t fe0_state_, const fe_state_t fe1_state_) + { + //Update soft-state + _fe_states[0].state = fe0_state_; + _fe_states[1].state = fe1_state_; + + const fe_state_t fe0_state = _fe_states[0].state; + const fe_state_t fe1_state = (_gpio_cores.size() > 1) ? _fe_states[1].state : NONE_STREAMING; + + const size_t num_tx = (_is_tx(fe0_state) ? 1 : 0) + (_is_tx(fe1_state) ? 1 : 0); + const size_t num_rx = (_is_rx(fe0_state) ? 1 : 0) + (_is_rx(fe1_state) ? 1 : 0); + + //setup the active chains in the codec + if ((num_rx + num_tx) == 0) { + _codec_ctrl->set_active_chains( + true, false, + true, false); //enable something + } else { + _codec_ctrl->set_active_chains( + _is_tx(fe0_state), _is_tx(fe1_state), + _is_rx(fe0_state), _is_rx(fe1_state)); + } + + _core_misc_reg.flush(); + //atrs change based on enables + _flush_atr_state(); + } + + + void set_stream_state(const size_t which, const fe_state_t state) + { + if (which == 0) { + set_stream_state(state, _fe_states[1].state); + } else if (which == 1) { + set_stream_state(_fe_states[0].state, state); + } else { + throw uhd::value_error( + str(boost::format("n230: unknown stream index option: %d") % which) + ); + } + } + + void set_bandsel(const std::string& which, double freq) + { + using namespace n230::fpga; + + if(which[0] == 'R') { + if(freq < 2.2e9) { + _core_misc_reg.set(core_misc_reg_t::RX_BANDSEL_A, 0); + _core_misc_reg.set(core_misc_reg_t::RX_BANDSEL_B, 0); + _core_misc_reg.set(core_misc_reg_t::RX_BANDSEL_C, 1); + } else if((freq >= 2.2e9) && (freq < 4e9)) { + _core_misc_reg.set(core_misc_reg_t::RX_BANDSEL_A, 0); + _core_misc_reg.set(core_misc_reg_t::RX_BANDSEL_B, 1); + _core_misc_reg.set(core_misc_reg_t::RX_BANDSEL_C, 0); + } else if((freq >= 4e9) && (freq <= 6e9)) { + _core_misc_reg.set(core_misc_reg_t::RX_BANDSEL_A, 1); + _core_misc_reg.set(core_misc_reg_t::RX_BANDSEL_B, 0); + _core_misc_reg.set(core_misc_reg_t::RX_BANDSEL_C, 0); + } else { + UHD_THROW_INVALID_CODE_PATH(); + } + } else if(which[0] == 'T') { + if(freq < 2.5e9) { + _core_misc_reg.set(core_misc_reg_t::TX_BANDSEL_A, 0); + _core_misc_reg.set(core_misc_reg_t::TX_BANDSEL_B, 1); + } else if((freq >= 2.5e9) && (freq <= 6e9)) { + _core_misc_reg.set(core_misc_reg_t::TX_BANDSEL_A, 1); + _core_misc_reg.set(core_misc_reg_t::TX_BANDSEL_B, 0); + } else { + UHD_THROW_INVALID_CODE_PATH(); + } + } else { + UHD_THROW_INVALID_CODE_PATH(); + } + + _core_misc_reg.flush(); + } + + void set_self_test_mode(self_test_mode_t mode) + { + switch (mode) { + case LOOPBACK_RADIO: { + _core_ctrl->poke32(fpga::sr_addr(fpga::SR_CORE_LOOPBACK), 0x1); + } break; + case LOOPBACK_CODEC: { + _core_ctrl->poke32(fpga::sr_addr(fpga::SR_CORE_LOOPBACK), 0x0); + _codec_ctrl->data_port_loopback(true); + } break; + //Default = disable + default: + case LOOPBACK_DISABLED: { + _core_ctrl->poke32(fpga::sr_addr(fpga::SR_CORE_LOOPBACK), 0x0); + _codec_ctrl->data_port_loopback(false); + } break; + } + } + +private: + void _flush_atr_state() + { + for (size_t i = 0; i < _gpio_cores.size(); i++) { + const fe_state_cache_t& fe_state_cache = _fe_states[i]; + const bool enb_rx = _is_rx(fe_state_cache.state); + const bool enb_tx = _is_tx(fe_state_cache.state); + const bool is_rx2 = (fe_state_cache.rx_ant == "RX2"); + const size_t rxonly = (enb_rx)? ((is_rx2)? STATE_RX_RX2 : STATE_RX_TXRX) : STATE_OFF; + const size_t txonly = (enb_tx)? (STATE_TX_TXRX) : STATE_OFF; + size_t fd = STATE_OFF; + if (enb_rx and enb_tx) fd = STATE_FDX_TXRX; + if (enb_rx and not enb_tx) fd = rxonly; + if (not enb_rx and enb_tx) fd = txonly; + _gpio_cores[i]->set_atr_reg(gpio_atr::ATR_REG_IDLE, STATE_OFF); + _gpio_cores[i]->set_atr_reg(gpio_atr::ATR_REG_RX_ONLY, rxonly); + _gpio_cores[i]->set_atr_reg(gpio_atr::ATR_REG_TX_ONLY, txonly); + _gpio_cores[i]->set_atr_reg(gpio_atr::ATR_REG_FULL_DUPLEX, fd); + } + } + + inline static bool _is_tx(const fe_state_t state) + { + return state == TX_STREAMING || state == TXRX_STREAMING; + } + + inline static bool _is_rx(const fe_state_t state) + { + return state == RX_STREAMING || state == TXRX_STREAMING; + } + +private: + struct fe_state_cache_t { + fe_state_cache_t() : state(NONE_STREAMING), rx_ant("RX2") + {} + fe_state_t state; + std::string rx_ant; + }; + + radio_ctrl_core_3000::sptr _core_ctrl; + ad9361_ctrl::sptr _codec_ctrl; + std::vector<gpio_atr::gpio_atr_3000::sptr> _gpio_cores; + fpga::core_misc_reg_t& _core_misc_reg; + uhd::dict<size_t, fe_state_cache_t> _fe_states; +}; + +}}} //namespace + +using namespace uhd::usrp::n230; + +n230_frontend_ctrl::sptr n230_frontend_ctrl::make( + radio_ctrl_core_3000::sptr core_ctrl, + fpga::core_misc_reg_t& core_misc_reg, + ad9361_ctrl::sptr codec_ctrl, + const std::vector<gpio_atr::gpio_atr_3000::sptr>& gpio_cores) +{ + return sptr(new n230_frontend_ctrl_impl(core_ctrl, core_misc_reg, codec_ctrl, gpio_cores)); +} + diff --git a/host/lib/usrp/n230/n230_frontend_ctrl.hpp b/host/lib/usrp/n230/n230_frontend_ctrl.hpp new file mode 100644 index 000000000..377d23ba8 --- /dev/null +++ b/host/lib/usrp/n230/n230_frontend_ctrl.hpp @@ -0,0 +1,76 @@ +// +// Copyright 2013-2014 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 +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +#ifndef INCLUDED_N230_FRONTEND_CTRL_HPP +#define INCLUDED_N230_FRONTEND_CTRL_HPP + +#include "radio_ctrl_core_3000.hpp" +#include "ad9361_ctrl.hpp" +#include "gpio_atr_3000.hpp" +#include <uhd/types/sensors.hpp> +#include <boost/shared_ptr.hpp> +#include <boost/utility.hpp> +#include <vector> +#include "n230_fpga_defs.h" + +namespace uhd { namespace usrp { namespace n230 { + +enum fe_state_t { + NONE_STREAMING, TX_STREAMING, RX_STREAMING, TXRX_STREAMING +}; + +enum self_test_mode_t { + LOOPBACK_DISABLED, LOOPBACK_RADIO, LOOPBACK_CODEC +}; + + +class n230_frontend_ctrl : boost::noncopyable +{ +public: + typedef boost::shared_ptr<n230_frontend_ctrl> sptr; + + static sptr make( + radio_ctrl_core_3000::sptr core_ctrl, + fpga::core_misc_reg_t& core_misc_reg, + ad9361_ctrl::sptr codec_ctrl, + const std::vector<gpio_atr::gpio_atr_3000::sptr>& gpio_cores); + + virtual ~n230_frontend_ctrl() {} + + virtual void set_antenna_sel( + const size_t which, + const std::string &ant) = 0; + + virtual void set_stream_state( + const size_t which, + const fe_state_t state) = 0; + + virtual void set_stream_state( + const fe_state_t fe0_state, + const fe_state_t fe1_state) = 0; + + virtual void set_bandsel( + const std::string& which, + double freq) = 0; + + virtual void set_self_test_mode( + self_test_mode_t mode) = 0; +}; + +}}} //namespace + +#endif /* INCLUDED_N230_FRONTEND_CTRL_HPP */ diff --git a/host/lib/usrp/n230/n230_fw_defs.h b/host/lib/usrp/n230/n230_fw_defs.h new file mode 100644 index 000000000..fbdc67ebb --- /dev/null +++ b/host/lib/usrp/n230/n230_fw_defs.h @@ -0,0 +1,137 @@ +// +// Copyright 2014 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 +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +#ifndef INCLUDED_N230_FW_DEFS_H +#define INCLUDED_N230_FW_DEFS_H + +#include <stdint.h> + +/*! + * Constants specific to N230 firmware. + * This header is shared by the firmware and host code. + * Therefore, this header may only contain valid C code. + * However, if it is included from within the host code, + * it will be namespaced appropriately + */ +#ifdef __cplusplus +namespace uhd { +namespace usrp { +namespace n230 { +namespace fw { +#endif + +static inline uint32_t reg_addr(uint32_t base, uint32_t offset) { + return ((base) + (offset)*4); +} + +/******************************************************************* + * Global + *******************************************************************/ +static const uint32_t CPU_CLOCK_FREQ = 80000000; +static const uint32_t PER_MILLISEC_CRON_JOBID = 0; +static const uint32_t PER_SECOND_CRON_JOBID = 1; + +/******************************************************************* + * Wishbone slave addresses + *******************************************************************/ +static const uint32_t WB_MAIN_RAM_BASE = 0x0000; +static const uint32_t WB_PKT_RAM_BASE = 0x8000; +static const uint32_t WB_SBRB_BASE = 0xa000; +static const uint32_t WB_SPI_FLASH_BASE = 0xb000; +static const uint32_t WB_ETH0_MAC_BASE = 0xc000; +static const uint32_t WB_ETH1_MAC_BASE = 0xd000; +static const uint32_t WB_XB_SBRB_BASE = 0xe000; +static const uint32_t WB_ETH0_I2C_BASE = 0xf600; +static const uint32_t WB_ETH1_I2C_BASE = 0xf700; +static const uint32_t WB_DBG_UART_BASE = 0xf900; + +/******************************************************************* + * Seting Register Base addresses + *******************************************************************/ +static const uint32_t SR_ZPU_SW_RST = 0; +static const uint32_t SR_ZPU_BOOT_DONE = 1; +static const uint32_t SR_ZPU_LEDS = 2; +static const uint32_t SR_ZPU_XB_LOCAL = 4; +static const uint32_t SR_ZPU_SFP_CTRL0 = 16; +static const uint32_t SR_ZPU_SFP_CTRL1 = 17; +static const uint32_t SR_ZPU_ETHINT0 = 64; +static const uint32_t SR_ZPU_ETHINT1 = 80; + +static const uint32_t SR_ZPU_SW_RST_NONE = 0x0; +static const uint32_t SR_ZPU_SW_RST_PHY = 0x1; +static const uint32_t SR_ZPU_SW_RST_RADIO = 0x2; + +/******************************************************************* + * Readback addresses + *******************************************************************/ +static const uint32_t RB_ZPU_COMPAT = 0; +static const uint32_t RB_ZPU_COUNTER = 1; +static const uint32_t RB_ZPU_SFP_STATUS0 = 2; +static const uint32_t RB_ZPU_SFP_STATUS1 = 3; +static const uint32_t RB_ZPU_ETH0_PKT_CNT = 6; +static const uint32_t RB_ZPU_ETH1_PKT_CNT = 7; + +/******************************************************************* + * Ethernet + *******************************************************************/ +static const uint32_t WB_PKT_RAM_CTRL_OFFSET = 0x1FFC; + +static const uint32_t SR_ZPU_ETHINT_FRAMER_BASE = 0; +static const uint32_t SR_ZPU_ETHINT_DISPATCHER_BASE = 8; + +//Eth framer constants +static const uint32_t ETH_FRAMER_SRC_MAC_HI = 0; +static const uint32_t ETH_FRAMER_SRC_MAC_LO = 1; +static const uint32_t ETH_FRAMER_SRC_IP_ADDR = 2; +static const uint32_t ETH_FRAMER_SRC_UDP_PORT = 3; +static const uint32_t ETH_FRAMER_DST_RAM_ADDR = 4; +static const uint32_t ETH_FRAMER_DST_IP_ADDR = 5; +static const uint32_t ETH_FRAMER_DST_UDP_MAC = 6; +static const uint32_t ETH_FRAMER_DST_MAC_LO = 7; + +/******************************************************************* + * CODEC + *******************************************************************/ +static const uint32_t CODEC_SPI_CLOCK_FREQ = 4000000; //4MHz +static const uint32_t ADF4001_SPI_CLOCK_FREQ = 200000; //200kHz + +/******************************************************************* + * UART + *******************************************************************/ +static const uint32_t DBG_UART_BAUD = 115200; + +/******************************************************************* + * Build Compatability Numbers + *******************************************************************/ +static const uint8_t PRODUCT_NUM = 0x01; +static const uint8_t COMPAT_MAJOR = 0x00; +static const uint16_t COMPAT_MINOR = 0x0000; + +static inline uint8_t get_prod_num(uint32_t compat_reg) { + return (compat_reg >> 24) & 0xFF; +} +static inline uint8_t get_compat_major(uint32_t compat_reg) { + return (compat_reg >> 16) & 0xFF; +} +static inline uint8_t get_compat_minor(uint32_t compat_reg) { + return compat_reg & 0xFFFF; +} + +#ifdef __cplusplus +}}}} //namespace +#endif +#endif /* INCLUDED_N230_FW_DEFS_H */ diff --git a/host/lib/usrp/n230/n230_fw_host_iface.h b/host/lib/usrp/n230/n230_fw_host_iface.h new file mode 100644 index 000000000..0391af0d9 --- /dev/null +++ b/host/lib/usrp/n230/n230_fw_host_iface.h @@ -0,0 +1,128 @@ +// +// Copyright 2014 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 +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +#ifndef INCLUDED_N230_FW_HOST_IFACE_H +#define INCLUDED_N230_FW_HOST_IFACE_H + +#include <stdint.h> + +/*! + * Structs and constants for N230 communication between firmware and host. + * This header is shared by the firmware and host code. + * Therefore, this header may only contain valid C code. + */ +#ifdef __cplusplus +extern "C" { +#endif + +//-------------------------------------------------- +// Ethernet related +// +#define N230_DEFAULT_ETH0_MAC {0x00, 0x50, 0xC2, 0x85, 0x3f, 0xff} +#define N230_DEFAULT_ETH1_MAC {0x00, 0x50, 0xC2, 0x85, 0x3f, 0x33} +#define N230_DEFAULT_ETH0_IP (192 << 24 | 168 << 16 | 10 << 8 | 2 << 0) +#define N230_DEFAULT_ETH1_IP (192 << 24 | 168 << 16 | 20 << 8 | 2 << 0) +#define N230_DEFAULT_ETH0_MASK (255 << 24 | 255 << 16 | 255 << 8 | 0 << 0) +#define N230_DEFAULT_ETH1_MASK (255 << 24 | 255 << 16 | 255 << 8 | 0 << 0) +#define N230_DEFAULT_GATEWAY (192 << 24 | 168 << 16 | 10 << 8 | 1 << 0) + +#define N230_FW_COMMS_UDP_PORT 49152 +#define N230_FW_COMMS_CVITA_PORT 49153 +#define N230_FW_COMMS_FLASH_PROG_PORT 49154 +// +//-------------------------------------------------- + +//-------------------------------------------------- +// Memory shared with host +// +#define N230_FW_HOST_SHMEM_BASE_ADDR 0x10000 +#define N230_FW_HOST_SHMEM_RW_BASE_ADDR 0x1000C +#define N230_FW_HOST_SHMEM_NUM_WORDS (sizeof(n230_host_shared_mem_data_t)/sizeof(uint32_t)) + +#define N230_FW_HOST_SHMEM_MAX_ADDR \ + (N230_FW_HOST_SHMEM_BASE_ADDR + ((N230_FW_HOST_SHMEM_NUM_WORDS - 1) * sizeof(uint32_t))) + +#define N230_FW_HOST_SHMEM_OFFSET(member) \ + (N230_FW_HOST_SHMEM_BASE_ADDR + ((uint32_t)offsetof(n230_host_shared_mem_data_t, member))) + +//The shared memory block can only be accessed on 32-bit boundaries +typedef struct { //All fields must be 32-bit wide to avoid packing directives + //Read-Only fields (N230_FW_HOST_SHMEM_BASE_ADDR) + uint32_t fw_compat_num; //Compat number must be at offset 0 + uint32_t fw_version_hash; + uint32_t claim_status; + + //Read-Write fields (N230_FW_HOST_SHMEM_RW_BASE_ADDR) + uint32_t scratch; + uint32_t claim_time; + uint32_t claim_src; +} n230_host_shared_mem_data_t; + +typedef union +{ + uint32_t buff[N230_FW_HOST_SHMEM_NUM_WORDS]; + n230_host_shared_mem_data_t data; +} n230_host_shared_mem_t; + +#define N230_FW_PRODUCT_ID 1 +#define N230_FW_COMPAT_NUM_MAJOR 32 +#define N230_FW_COMPAT_NUM_MINOR 0 +#define N230_FW_COMPAT_NUM (((N230_FW_COMPAT_NUM_MAJOR & 0xFF) << 16) | (N230_FW_COMPAT_NUM_MINOR & 0xFFFF)) +// +//-------------------------------------------------- + +//-------------------------------------------------- +// Flash read-write interface for host +// +#define N230_FLASH_COMM_FLAGS_ACK 0x00000001 +#define N230_FLASH_COMM_FLAGS_CMD_MASK 0x00000FF0 +#define N230_FLASH_COMM_FLAGS_ERROR_MASK 0xFF000000 + +#define N230_FLASH_COMM_CMD_READ_NV_DATA 0x00000010 +#define N230_FLASH_COMM_CMD_WRITE_NV_DATA 0x00000020 +#define N230_FLASH_COMM_CMD_READ_FPGA 0x00000030 +#define N230_FLASH_COMM_CMD_WRITE_FPGA 0x00000040 +#define N230_FLASH_COMM_CMD_ERASE_FPGA 0x00000050 + +#define N230_FLASH_COMM_ERR_PKT_ERROR 0x80000000 +#define N230_FLASH_COMM_ERR_CMD_ERROR 0x40000000 +#define N230_FLASH_COMM_ERR_SIZE_ERROR 0x20000000 + +#define N230_FLASH_COMM_MAX_PAYLOAD_SIZE 128 + +typedef struct +{ + uint32_t flags; + uint32_t seq; + uint32_t offset; + uint32_t size; + uint8_t data[N230_FLASH_COMM_MAX_PAYLOAD_SIZE]; +} n230_flash_prog_t; +// +//-------------------------------------------------- + +#define N230_HW_REVISION_COMPAT 1 +#define N230_HW_REVISION_MIN 1 + + +#define N230_CLAIMER_TIMEOUT_IN_MS 2000 + +#ifdef __cplusplus +} +#endif + +#endif /* INCLUDED_N230_FW_HOST_IFACE_H */ diff --git a/host/lib/usrp/n230/n230_image_loader.cpp b/host/lib/usrp/n230/n230_image_loader.cpp new file mode 100644 index 000000000..9dd4a252d --- /dev/null +++ b/host/lib/usrp/n230/n230_image_loader.cpp @@ -0,0 +1,209 @@ +// +// Copyright 2016 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 +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +#include <fstream> +#include <algorithm> +#include <uhd/image_loader.hpp> +#include <uhd/exception.hpp> +#include <uhd/utils/static.hpp> +#include <uhd/utils/byteswap.hpp> +#include <uhd/utils/paths.hpp> +#include <uhd/transport/udp_simple.hpp> +#include <boost/algorithm/string.hpp> +#include <boost/filesystem.hpp> +#include <boost/format.hpp> +#include "n230_fw_host_iface.h" +#include "n230_impl.hpp" + +using namespace uhd; +using namespace uhd::usrp; +using namespace uhd::transport; + +struct xil_bitfile_hdr_t { + xil_bitfile_hdr_t(): + valid(false), userid(0), product(""), + fpga(""), timestamp(""), filesize(0) + {} + + bool valid; + boost::uint32_t userid; + std::string product; + std::string fpga; + std::string timestamp; + boost::uint32_t filesize; +}; + +static inline boost::uint16_t _to_uint16(boost::uint8_t* buf) { + return (static_cast<boost::uint16_t>(buf[0]) << 8) | + (static_cast<boost::uint16_t>(buf[1]) << 0); +} + +static inline boost::uint32_t _to_uint32(boost::uint8_t* buf) { + return (static_cast<boost::uint32_t>(buf[0]) << 24) | + (static_cast<boost::uint32_t>(buf[1]) << 16) | + (static_cast<boost::uint32_t>(buf[2]) << 8) | + (static_cast<boost::uint32_t>(buf[3]) << 0); +} + +static void _parse_bitfile_header(const std::string& filepath, xil_bitfile_hdr_t& hdr) { + // Read header into memory + std::ifstream img_file(filepath.c_str(), std::ios::binary); + static const size_t MAX_HDR_SIZE = 1024; + boost::scoped_array<char> hdr_buf(new char[MAX_HDR_SIZE]); + img_file.seekg(0, std::ios::beg); + img_file.read(hdr_buf.get(), MAX_HDR_SIZE); + img_file.close(); + + //Parse header + size_t ptr = 0; + boost::uint8_t* buf = reinterpret_cast<boost::uint8_t*>(hdr_buf.get()); //Shortcut + + boost::uint8_t signature[10] = {0x00, 0x09, 0x0f, 0xf0, 0x0f, 0xf0, 0x0f, 0xf0, 0x0f, 0xf0}; + if (memcmp(buf, signature, 10) == 0) { //Validate signature + ptr += _to_uint16(buf + ptr) + 2; + ptr += _to_uint16(buf + ptr) + 1; + + std::string fields[4]; + for (size_t i = 0; i < 4; i++) { + size_t key = buf[ptr++] - 'a'; + boost::uint16_t len = _to_uint16(buf + ptr); ptr += 2; + fields[key] = std::string(reinterpret_cast<char*>(buf + ptr), size_t(len)); ptr += len; + } + + hdr.filesize = _to_uint32(buf + ++ptr); ptr += 4; + hdr.fpga = fields[1]; + hdr.timestamp = fields[2] + std::string(" ") + fields[3]; + + std::vector<std::string> tokens; + boost::split(tokens, fields[0], boost::is_any_of(";")); + if (tokens.size() == 3) { + hdr.product = tokens[0]; + std::vector<std::string> uidtokens; + boost::split(uidtokens, tokens[1], boost::is_any_of("=")); + if (uidtokens.size() == 2 and uidtokens[0] == "UserID") { + std::stringstream stream; + stream << uidtokens[1]; + stream >> std::hex >> hdr.userid; + hdr.valid = true; + } + } + } +} + +static size_t _send_and_recv( + udp_simple::sptr xport, + n230_flash_prog_t& out, n230_flash_prog_t& in) +{ + static boost::uint32_t seqno = 0; + out.seq = htonx<boost::uint32_t>(++seqno); + xport->send(boost::asio::buffer(&out, sizeof(n230_flash_prog_t))); + size_t len = xport->recv(boost::asio::buffer(&in, udp_simple::mtu), 0.5); + if (len != sizeof(n230_flash_prog_t) or ntohx<boost::uint32_t>(in.seq) != seqno) { + throw uhd::io_error("Error communicating with the device."); + } + return len; +} + + +static bool n230_image_loader(const image_loader::image_loader_args_t &loader_args){ + // Run discovery routine and ensure that exactly one N230 is specified + device_addrs_t devs = usrp::n230::n230_impl::n230_find(loader_args.args); + if (devs.size() == 0 or !loader_args.load_fpga) return false; + if (devs.size() > 1) { + throw uhd::runtime_error("Multiple devices match the specified args. To avoid accidentally updating the " + "wrong device, please narrow the search by specifying a unique \"addr\" argument."); + } + device_addr_t dev = devs[0]; + + // Sanity check the specified bitfile + std::string fpga_img_path = loader_args.fpga_path; + bool fpga_path_specified = !loader_args.fpga_path.empty(); + if (not fpga_path_specified) { + fpga_img_path = ( + fs::path(uhd::get_pkg_path()) / "share" / "uhd" / "images" / "usrp_n230_fpga.bit" + ).string(); + } + + if (not boost::filesystem::exists(fpga_img_path)) { + if (fpga_path_specified) { + throw uhd::runtime_error(str(boost::format("The file \"%s\" does not exist.") % fpga_img_path)); + } else { + throw uhd::runtime_error(str(boost::format( + "Could not find the default FPGA image: %s.\n" + "Either specify the --fpga-path argument or download the latest prebuilt images:\n" + "%s\n") + % fpga_img_path % print_utility_error("uhd_images_downloader.py"))); + } + } + xil_bitfile_hdr_t hdr; + _parse_bitfile_header(fpga_img_path, hdr); + + // Create a UDP communication link + udp_simple::sptr udp_xport = + udp_simple::make_connected(dev["addr"],BOOST_STRINGIZE(N230_FW_COMMS_FLASH_PROG_PORT)); + + if (hdr.valid and hdr.product == "n230") { + if (hdr.userid != 0x5AFE0000) { + std::cout << boost::format("Unit: USRP N230 (%s, %s)\n-- FPGA Image: %s\n") + % dev["addr"] % dev["serial"] % fpga_img_path; + + // Write image + std::ifstream image(fpga_img_path.c_str(), std::ios::binary); + size_t image_size = boost::filesystem::file_size(fpga_img_path); + + static const size_t SECTOR_SIZE = 65536; + static const size_t IMAGE_BASE = 0x400000; + + n230_flash_prog_t out, in; + size_t bytes_written = 0; + while (bytes_written < image_size) { + size_t payload_size = std::min<size_t>(image_size - bytes_written, N230_FLASH_COMM_MAX_PAYLOAD_SIZE); + if (bytes_written % SECTOR_SIZE == 0) { + out.flags = htonx<boost::uint32_t>(N230_FLASH_COMM_FLAGS_ACK|N230_FLASH_COMM_CMD_ERASE_FPGA); + out.offset = htonx<boost::uint32_t>(bytes_written + IMAGE_BASE); + out.size = htonx<boost::uint32_t>(payload_size); + _send_and_recv(udp_xport, out, in); + } + out.flags = htonx<boost::uint32_t>(N230_FLASH_COMM_FLAGS_ACK|N230_FLASH_COMM_CMD_WRITE_FPGA); + out.offset = htonx<boost::uint32_t>(bytes_written + IMAGE_BASE); + out.size = htonx<boost::uint32_t>(payload_size); + image.read((char*)out.data, payload_size); + _send_and_recv(udp_xport, out, in); + bytes_written += ntohx<boost::uint32_t>(in.size); + std::cout << boost::format("\r-- Loading FPGA image: %d%%") + % (int(double(bytes_written) / double(image_size) * 100.0)) + << std::flush; + } + std::cout << std::endl << "FPGA image loaded successfully." << std::endl; + std::cout << std::endl << "Power-cycle the device to run the image." << std::endl; + return true; + } else { + throw uhd::runtime_error("This utility cannot burn a failsafe image!"); + } + } else { + throw uhd::runtime_error(str(boost::format("The file at path \"%s\" is not a valid USRP N230 FPGA image.") + % fpga_img_path)); + } +} + +UHD_STATIC_BLOCK(register_n230_image_loader){ + std::string recovery_instructions = "Aborting. Your USRP N230 device will likely boot in safe mode.\n" + "Please re-run this command with the additional \"safe_mode\" device argument\n" + "to recover your device."; + + image_loader::register_image_loader("n230", n230_image_loader, recovery_instructions); +} diff --git a/host/lib/usrp/n230/n230_impl.cpp b/host/lib/usrp/n230/n230_impl.cpp new file mode 100644 index 000000000..5e8aa37b7 --- /dev/null +++ b/host/lib/usrp/n230/n230_impl.cpp @@ -0,0 +1,591 @@ +// +// Copyright 2014 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 +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +#include "n230_impl.hpp" + +#include "usrp3_fw_ctrl_iface.hpp" +#include "validate_subdev_spec.hpp" +#include <uhd/utils/static.hpp> +#include <uhd/transport/if_addrs.hpp> +#include <uhd/transport/udp_zero_copy.hpp> +#include <uhd/usrp/subdev_spec.hpp> +#include <uhd/utils/byteswap.hpp> +#include <uhd/utils/log.hpp> +#include <uhd/utils/msg.hpp> +#include <uhd/types/sensors.hpp> +#include <uhd/types/ranges.hpp> +#include <uhd/types/direction.hpp> +#include <uhd/usrp/mboard_eeprom.hpp> +#include <uhd/usrp/dboard_eeprom.hpp> +#include <uhd/usrp/gps_ctrl.hpp> +#include <boost/format.hpp> +#include <boost/foreach.hpp> +#include <boost/lexical_cast.hpp> +#include <boost/bind.hpp> +#include <boost/algorithm/string.hpp> +#include <boost/assign/list_of.hpp> +#include <boost/asio/ip/address_v4.hpp> +#include <boost/asio.hpp> //used for htonl and ntohl +#include <boost/make_shared.hpp> + +#include "../common/fw_comm_protocol.h" +#include "n230_defaults.h" +#include "n230_fpga_defs.h" +#include "n230_fw_defs.h" +#include "n230_fw_host_iface.h" + +namespace uhd { namespace usrp { namespace n230 { + +using namespace uhd::transport; +namespace asio = boost::asio; + +//---------------------------------------------------------- +// Static device registration with framework +//---------------------------------------------------------- +UHD_STATIC_BLOCK(register_n230_device) +{ + device::register_device(&n230_impl::n230_find, &n230_impl::n230_make, device::USRP); +} + +//---------------------------------------------------------- +// Device discovery +//---------------------------------------------------------- +uhd::device_addrs_t n230_impl::n230_find(const uhd::device_addr_t &multi_dev_hint) +{ + //handle the multi-device discovery + device_addrs_t hints = separate_device_addr(multi_dev_hint); + if (hints.size() > 1){ + device_addrs_t found_devices; + std::string error_msg; + BOOST_FOREACH(const device_addr_t &hint_i, hints){ + device_addrs_t found_devices_i = n230_find(hint_i); + if (found_devices_i.size() != 1) error_msg += str(boost::format( + "Could not resolve device hint \"%s\" to a single device." + ) % hint_i.to_string()); + else found_devices.push_back(found_devices_i[0]); + } + if (found_devices.empty()) return device_addrs_t(); + if (not error_msg.empty()) throw uhd::value_error(error_msg); + return device_addrs_t(1, combine_device_addrs(found_devices)); + } + + //initialize the hint for a single device case + UHD_ASSERT_THROW(hints.size() <= 1); + hints.resize(1); //in case it was empty + device_addr_t hint = hints[0]; + device_addrs_t n230_addrs; + + //return an empty list of addresses when type is set to non-n230 + if (hint.has_key("type") and hint["type"] != "n230") return n230_addrs; + + //Return an empty list of addresses when a resource is specified, + //since a resource is intended for a different, non-networked, device. + if (hint.has_key("resource")) return n230_addrs; + + //if no address was specified, send a broadcast on each interface + if (not hint.has_key("addr")) { + BOOST_FOREACH(const if_addrs_t &if_addrs, get_if_addrs()) { + //avoid the loopback device + if (if_addrs.inet == asio::ip::address_v4::loopback().to_string()) continue; + + //create a new hint with this broadcast address + device_addr_t new_hint = hint; + new_hint["addr"] = if_addrs.bcast; + + //call discover with the new hint and append results + device_addrs_t new_n230_addrs = n230_find(new_hint); + n230_addrs.insert(n230_addrs.begin(), + new_n230_addrs.begin(), new_n230_addrs.end() + ); + } + return n230_addrs; + } + + std::vector<std::string> discovered_addrs = + usrp3::usrp3_fw_ctrl_iface::discover_devices( + hint["addr"], BOOST_STRINGIZE(N230_FW_COMMS_UDP_PORT), N230_FW_PRODUCT_ID); + + BOOST_FOREACH(const std::string& addr, discovered_addrs) + { + device_addr_t new_addr; + new_addr["type"] = "n230"; + new_addr["addr"] = addr; + + //Attempt a simple 2-way communication with a connected socket. + //Reason: Although the USRP will respond the broadcast above, + //we may not be able to communicate directly (non-broadcast). + udp_simple::sptr ctrl_xport = udp_simple::make_connected(new_addr["addr"], BOOST_STRINGIZE(N230_FW_COMMS_UDP_PORT)); + + //Corner case: If two devices have the same IP but different MAC + //addresses and are used back-to-back it takes a while for ARP tables + //on the host to update in which period brodcasts will respond but + //connected communication can fail. Retry the following call to allow + //the stack to update + size_t first_conn_retries = 10; + usrp3::usrp3_fw_ctrl_iface::sptr fw_ctrl; + while (first_conn_retries > 0) { + try { + fw_ctrl = usrp3::usrp3_fw_ctrl_iface::make(ctrl_xport, N230_FW_PRODUCT_ID, false /*verbose*/); + break; + } catch (uhd::io_error& ex) { + boost::this_thread::sleep(boost::posix_time::milliseconds(500)); + first_conn_retries--; + } + } + if (first_conn_retries > 0) { + uint32_t compat_reg = fw_ctrl->peek32(fw::reg_addr(fw::WB_SBRB_BASE, fw::RB_ZPU_COMPAT)); + if (fw::get_prod_num(compat_reg) == fw::PRODUCT_NUM) { + if (!n230_resource_manager::is_device_claimed(fw_ctrl)) { + //Not claimed by another process or host + try { + //Try to read the EEPROM to get the name and serial + n230_eeprom_manager eeprom_mgr(new_addr["addr"]); + const mboard_eeprom_t& eeprom = eeprom_mgr.get_mb_eeprom(); + new_addr["name"] = eeprom["name"]; + new_addr["serial"] = eeprom["serial"]; + } + catch(const std::exception &) + { + //set these values as empty string so the device may still be found + //and the filter's below can still operate on the discovered device + new_addr["name"] = ""; + new_addr["serial"] = ""; + } + //filter the discovered device below by matching optional keys + if ((not hint.has_key("name") or hint["name"] == new_addr["name"]) and + (not hint.has_key("serial") or hint["serial"] == new_addr["serial"])) + { + n230_addrs.push_back(new_addr); + } + } + } + } + } + + return n230_addrs; +} + +/*********************************************************************** + * Make + **********************************************************************/ +device::sptr n230_impl::n230_make(const device_addr_t &device_addr) +{ + return device::sptr(new n230_impl(device_addr)); +} + +/*********************************************************************** + * n230_impl::ctor + **********************************************************************/ +n230_impl::n230_impl(const uhd::device_addr_t& dev_addr) +{ + UHD_MSG(status) << "N230 initialization sequence..." << std::endl; + _dev_args.parse(dev_addr); + _tree = uhd::property_tree::make(); + + //TODO: Only supports one motherboard per device class. + const fs_path mb_path = "/mboards/0"; + + //Initialize addresses + std::vector<std::string> ip_addrs(1, dev_addr["addr"]); + if (dev_addr.has_key("secondary-addr")) { + ip_addrs.push_back(dev_addr["secondary-addr"]); + } + + //Read EEPROM and perform version checks before talking to HW + _eeprom_mgr = boost::make_shared<n230_eeprom_manager>(ip_addrs[0]); + const mboard_eeprom_t& mb_eeprom = _eeprom_mgr->get_mb_eeprom(); + bool recover_mb_eeprom = dev_addr.has_key("recover_mb_eeprom"); + if (recover_mb_eeprom) { + UHD_MSG(warning) << "UHD is operating in EEPROM Recovery Mode which disables hardware version " + "checks.\nOperating in this mode may cause hardware damage and unstable " + "radio performance!"<< std::endl; + } + boost::uint16_t hw_rev = boost::lexical_cast<boost::uint16_t>(mb_eeprom["revision"]); + boost::uint16_t hw_rev_compat = boost::lexical_cast<boost::uint16_t>(mb_eeprom["revision_compat"]); + if (not recover_mb_eeprom) { + if (hw_rev_compat > N230_HW_REVISION_COMPAT) { + throw uhd::runtime_error(str(boost::format( + "Hardware is too new for this software. Please upgrade to a driver that supports hardware revision %d.") + % hw_rev)); + } + } + + //Initialize all subsystems + _resource_mgr = boost::make_shared<n230_resource_manager>(ip_addrs, _dev_args.get_safe_mode()); + _stream_mgr = boost::make_shared<n230_stream_manager>(_dev_args, _resource_mgr, _tree); + + //Build property tree + _initialize_property_tree(mb_path); + + //Debug loopback mode + switch(_dev_args.get_loopback_mode()) { + case n230_device_args_t::LOOPBACK_RADIO: + UHD_MSG(status) << "DEBUG: Running in TX->RX Radio loopback mode.\n"; + _resource_mgr->get_frontend_ctrl().set_self_test_mode(LOOPBACK_RADIO); + break; + case n230_device_args_t::LOOPBACK_CODEC: + UHD_MSG(status) << "DEBUG: Running in TX->RX CODEC loopback mode.\n"; + _resource_mgr->get_frontend_ctrl().set_self_test_mode(LOOPBACK_CODEC); + break; + default: + _resource_mgr->get_frontend_ctrl().set_self_test_mode(LOOPBACK_DISABLED); + break; + } +} + +/*********************************************************************** + * n230_impl::dtor + **********************************************************************/ +n230_impl::~n230_impl() +{ + _stream_mgr.reset(); + _eeprom_mgr.reset(); + _resource_mgr.reset(); + _tree.reset(); +} + +/*********************************************************************** + * n230_impl::get_rx_stream + **********************************************************************/ +rx_streamer::sptr n230_impl::get_rx_stream(const uhd::stream_args_t &args) +{ + return _stream_mgr->get_rx_stream(args); +} + +/*********************************************************************** + * n230_impl::get_tx_stream + **********************************************************************/ +tx_streamer::sptr n230_impl::get_tx_stream(const uhd::stream_args_t &args) +{ + return _stream_mgr->get_tx_stream(args); +} + +/*********************************************************************** + * n230_impl::recv_async_msg + **********************************************************************/ +bool n230_impl::recv_async_msg(uhd::async_metadata_t &async_metadata, double timeout) +{ + return _stream_mgr->recv_async_msg(async_metadata, timeout); +} + +/*********************************************************************** + * _initialize_property_tree + **********************************************************************/ +void n230_impl::_initialize_property_tree(const fs_path& mb_path) +{ + //------------------------------------------------------------------ + // General info + //------------------------------------------------------------------ + _tree->create<std::string>("/name").set("N230 Device"); + + _tree->create<std::string>(mb_path / "name").set("N230"); + _tree->create<std::string>(mb_path / "codename").set("N230"); + _tree->create<std::string>(mb_path / "dboards").set("none"); //No dboards. + + _tree->create<std::string>(mb_path / "fw_version").set(str(boost::format("%u.%u") + % _resource_mgr->get_version(FIRMWARE, COMPAT_MAJOR) + % _resource_mgr->get_version(FIRMWARE, COMPAT_MINOR))); + _tree->create<std::string>(mb_path / "fw_version_hash").set(str(boost::format("%s") + % _resource_mgr->get_version_hash(FIRMWARE))); + _tree->create<std::string>(mb_path / "fpga_version").set(str(boost::format("%u.%u") + % _resource_mgr->get_version(FPGA, COMPAT_MAJOR) + % _resource_mgr->get_version(FPGA, COMPAT_MINOR))); + _tree->create<std::string>(mb_path / "fpga_version_hash").set(str(boost::format("%s") + % _resource_mgr->get_version_hash(FPGA))); + + _tree->create<double>(mb_path / "link_max_rate").set(_resource_mgr->get_max_link_rate()); + + //------------------------------------------------------------------ + // EEPROM + //------------------------------------------------------------------ + _tree->create<mboard_eeprom_t>(mb_path / "eeprom") + .set(_eeprom_mgr->get_mb_eeprom()) //Set first... + .add_coerced_subscriber(boost::bind(&n230_eeprom_manager::write_mb_eeprom, _eeprom_mgr, _1)); //..then enable writer + + //------------------------------------------------------------------ + // Create codec nodes + //------------------------------------------------------------------ + const fs_path rx_codec_path = mb_path / ("rx_codecs") / "A"; + _tree->create<std::string>(rx_codec_path / "name") + .set("N230 RX dual ADC"); + _tree->create<int>(rx_codec_path / "gains"); //Empty because gains are in frontend + + const fs_path tx_codec_path = mb_path / ("tx_codecs") / "A"; + _tree->create<std::string>(tx_codec_path / "name") + .set("N230 TX dual DAC"); + _tree->create<int>(tx_codec_path / "gains"); //Empty because gains are in frontend + + //------------------------------------------------------------------ + // Create clock and time control nodes + //------------------------------------------------------------------ + _tree->create<double>(mb_path / "tick_rate") + .set_coercer(boost::bind(&n230_clk_pps_ctrl::set_tick_rate, _resource_mgr->get_clk_pps_ctrl_sptr(), _1)) + .set_publisher(boost::bind(&n230_clk_pps_ctrl::get_tick_rate, _resource_mgr->get_clk_pps_ctrl_sptr())) + .add_coerced_subscriber(boost::bind(&n230_stream_manager::update_tick_rate, _stream_mgr, _1)); + + //Register time now and pps onto available radio cores + //radio0 is the master + _tree->create<time_spec_t>(mb_path / "time" / "cmd"); + _tree->create<time_spec_t>(mb_path / "time" / "now") + .set_publisher(boost::bind(&time_core_3000::get_time_now, _resource_mgr->get_radio(0).time)); + _tree->create<time_spec_t>(mb_path / "time" / "pps") + .set_publisher(boost::bind(&time_core_3000::get_time_last_pps, _resource_mgr->get_radio(0).time)); + + //Setup time source props + _tree->create<std::string>(mb_path / "time_source" / "value") + .add_coerced_subscriber(boost::bind(&n230_impl::_check_time_source, this, _1)) + .add_coerced_subscriber(boost::bind(&n230_clk_pps_ctrl::set_pps_source, _resource_mgr->get_clk_pps_ctrl_sptr(), _1)) + .set(n230::DEFAULT_TIME_SRC); + static const std::vector<std::string> time_sources = boost::assign::list_of("none")("external")("gpsdo"); + _tree->create<std::vector<std::string> >(mb_path / "time_source" / "options") + .set(time_sources); + + //Setup reference source props + _tree->create<std::string>(mb_path / "clock_source" / "value") + .add_coerced_subscriber(boost::bind(&n230_impl::_check_clock_source, this, _1)) + .add_coerced_subscriber(boost::bind(&n230_clk_pps_ctrl::set_clock_source, _resource_mgr->get_clk_pps_ctrl_sptr(), _1)) + .set(n230::DEFAULT_CLOCK_SRC); + 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); + _tree->create<sensor_value_t>(mb_path / "sensors" / "ref_locked") + .set_publisher(boost::bind(&n230_clk_pps_ctrl::get_ref_locked, _resource_mgr->get_clk_pps_ctrl_sptr())); + + //------------------------------------------------------------------ + // Create frontend mapping + //------------------------------------------------------------------ + _tree->create<subdev_spec_t>(mb_path / "rx_subdev_spec") + .set(subdev_spec_t()) + .add_coerced_subscriber(boost::bind(&n230_impl::_update_rx_subdev_spec, this, _1)); + _tree->create<subdev_spec_t>(mb_path / "tx_subdev_spec") + .set(subdev_spec_t()) + .add_coerced_subscriber(boost::bind(&n230_impl::_update_tx_subdev_spec, this, _1)); + + //------------------------------------------------------------------ + // Create a fake dboard to put frontends in + //------------------------------------------------------------------ + //For completeness we give it a fake EEPROM as well + dboard_eeprom_t db_eeprom; //Default state: ID is 0xffff, Version and serial empty + _tree->create<dboard_eeprom_t>(mb_path / "dboards" / "A" / "rx_eeprom").set(db_eeprom); + _tree->create<dboard_eeprom_t>(mb_path / "dboards" / "A" / "tx_eeprom").set(db_eeprom); + _tree->create<dboard_eeprom_t>(mb_path / "dboards" / "A" / "gdb_eeprom").set(db_eeprom); + + //------------------------------------------------------------------ + // Create radio specific nodes + //------------------------------------------------------------------ + for (size_t radio_instance = 0; radio_instance < fpga::NUM_RADIOS; radio_instance++) { + _initialize_radio_properties(mb_path, radio_instance); + } + //Update tick rate on newly created radio objects + _tree->access<double>(mb_path / "tick_rate").set(_dev_args.get_master_clock_rate()); + + //------------------------------------------------------------------ + // Initialize subdev specs + //------------------------------------------------------------------ + subdev_spec_t rx_spec, tx_spec; + BOOST_FOREACH(const std::string &fe, _tree->list(mb_path / "dboards" / "A" / "rx_frontends")) + { + rx_spec.push_back(subdev_spec_pair_t("A", fe)); + } + BOOST_FOREACH(const std::string &fe, _tree->list(mb_path / "dboards" / "A" / "tx_frontends")) + { + tx_spec.push_back(subdev_spec_pair_t("A", fe)); + } + _tree->access<subdev_spec_t>(mb_path / "rx_subdev_spec").set(rx_spec); + _tree->access<subdev_spec_t>(mb_path / "tx_subdev_spec").set(tx_spec); + + //------------------------------------------------------------------ + // MiniSAS GPIO + //------------------------------------------------------------------ + _tree->create<boost::uint32_t>(mb_path / "gpio" / "FP0" / "DDR") + .set(0) + .add_coerced_subscriber(boost::bind(&gpio_atr::gpio_atr_3000::set_gpio_attr, + _resource_mgr->get_minisas_gpio_ctrl_sptr(0), gpio_atr::GPIO_DDR, _1)); + _tree->create<boost::uint32_t>(mb_path / "gpio" / "FP1" / "DDR") + .set(0) + .add_coerced_subscriber(boost::bind(&gpio_atr::gpio_atr_3000::set_gpio_attr, + _resource_mgr->get_minisas_gpio_ctrl_sptr(1), gpio_atr::GPIO_DDR, _1)); + _tree->create<boost::uint32_t>(mb_path / "gpio" / "FP0" / "OUT") + .set(0) + .add_coerced_subscriber(boost::bind(&gpio_atr::gpio_atr_3000::set_gpio_attr, + _resource_mgr->get_minisas_gpio_ctrl_sptr(0), gpio_atr::GPIO_OUT, _1)); + _tree->create<boost::uint32_t>(mb_path / "gpio" / "FP1" / "OUT") + .set(0) + .add_coerced_subscriber(boost::bind(&gpio_atr::gpio_atr_3000::set_gpio_attr, + _resource_mgr->get_minisas_gpio_ctrl_sptr(1), gpio_atr::GPIO_OUT, _1)); + _tree->create<boost::uint32_t>(mb_path / "gpio" / "FP0" / "READBACK") + .set_publisher(boost::bind(&gpio_atr::gpio_atr_3000::read_gpio, _resource_mgr->get_minisas_gpio_ctrl_sptr(0))); + _tree->create<boost::uint32_t>(mb_path / "gpio" / "FP1" / "READBACK") + .set_publisher(boost::bind(&gpio_atr::gpio_atr_3000::read_gpio, _resource_mgr->get_minisas_gpio_ctrl_sptr(1))); + + //------------------------------------------------------------------ + // GPSDO sensors + //------------------------------------------------------------------ + if (_resource_mgr->is_gpsdo_present()) { + uhd::gps_ctrl::sptr gps_ctrl = _resource_mgr->get_gps_ctrl(); + BOOST_FOREACH(const std::string &name, gps_ctrl->get_sensors()) + { + _tree->create<sensor_value_t>(mb_path / "sensors" / name) + .set_publisher(boost::bind(&gps_ctrl::get_sensor, gps_ctrl, name)); + } + } +} + +/*********************************************************************** + * _initialize_radio_properties + **********************************************************************/ +void n230_impl::_initialize_radio_properties(const fs_path& mb_path, size_t instance) +{ + radio_resource_t& perif = _resource_mgr->get_radio(instance); + + //Time + _tree->access<time_spec_t>(mb_path / "time" / "cmd") + .add_coerced_subscriber(boost::bind(&radio_ctrl_core_3000::set_time, perif.ctrl, _1)); + _tree->access<double>(mb_path / "tick_rate") + .add_coerced_subscriber(boost::bind(&radio_ctrl_core_3000::set_tick_rate, perif.ctrl, _1)); + _tree->access<time_spec_t>(mb_path / "time" / "now") + .add_coerced_subscriber(boost::bind(&time_core_3000::set_time_now, perif.time, _1)); + _tree->access<time_spec_t>(mb_path / "time" / "pps") + .add_coerced_subscriber(boost::bind(&time_core_3000::set_time_next_pps, perif.time, _1)); + + //RX DSP + _tree->access<double>(mb_path / "tick_rate") + .add_coerced_subscriber(boost::bind(&rx_vita_core_3000::set_tick_rate, perif.framer, _1)) + .add_coerced_subscriber(boost::bind(&rx_dsp_core_3000::set_tick_rate, perif.ddc, _1)); + const fs_path rx_dsp_path = mb_path / "rx_dsps" / str(boost::format("%u") % instance); + _tree->create<meta_range_t>(rx_dsp_path / "rate" / "range") + .set_publisher(boost::bind(&rx_dsp_core_3000::get_host_rates, perif.ddc)); + _tree->create<double>(rx_dsp_path / "rate" / "value") + .set_coercer(boost::bind(&rx_dsp_core_3000::set_host_rate, perif.ddc, _1)) + .add_coerced_subscriber(boost::bind(&n230_stream_manager::update_rx_samp_rate, _stream_mgr, instance, _1)) + .set(n230::DEFAULT_RX_SAMP_RATE); + _tree->create<double>(rx_dsp_path / "freq" / "value") + .set_coercer(boost::bind(&rx_dsp_core_3000::set_freq, perif.ddc, _1)) + .set(n230::DEFAULT_DDC_FREQ); + _tree->create<meta_range_t>(rx_dsp_path / "freq" / "range") + .set_publisher(boost::bind(&rx_dsp_core_3000::get_freq_range, perif.ddc)); + _tree->create<stream_cmd_t>(rx_dsp_path / "stream_cmd") + .add_coerced_subscriber(boost::bind(&rx_vita_core_3000::issue_stream_command, perif.framer, _1)); + + //TX DSP + _tree->access<double>(mb_path / "tick_rate") + .add_coerced_subscriber(boost::bind(&tx_dsp_core_3000::set_tick_rate, perif.duc, _1)); + const fs_path tx_dsp_path = mb_path / "tx_dsps" / str(boost::format("%u") % instance); + _tree->create<meta_range_t>(tx_dsp_path / "rate" / "range") + .set_publisher(boost::bind(&tx_dsp_core_3000::get_host_rates, perif.duc)); + _tree->create<double>(tx_dsp_path / "rate" / "value") + .set_coercer(boost::bind(&tx_dsp_core_3000::set_host_rate, perif.duc, _1)) + .add_coerced_subscriber(boost::bind(&n230_stream_manager::update_tx_samp_rate, _stream_mgr, instance, _1)) + .set(n230::DEFAULT_TX_SAMP_RATE); + _tree->create<double>(tx_dsp_path / "freq" / "value") + .set_coercer(boost::bind(&tx_dsp_core_3000::set_freq, perif.duc, _1)) + .set(n230::DEFAULT_DUC_FREQ); + _tree->create<meta_range_t>(tx_dsp_path / "freq" / "range") + .set_publisher(boost::bind(&tx_dsp_core_3000::get_freq_range, perif.duc)); + + //RF Frontend Interfacing + static const std::vector<direction_t> data_directions = boost::assign::list_of(RX_DIRECTION)(TX_DIRECTION); + BOOST_FOREACH(direction_t direction, data_directions) { + const std::string dir_str = (direction == RX_DIRECTION) ? "rx" : "tx"; + const std::string key = boost::to_upper_copy(dir_str) + str(boost::format("%u") % (instance + 1)); + const fs_path rf_fe_path = mb_path / "dboards" / "A" / (dir_str + "_frontends") / ((instance==0)?"A":"B"); + + //CODEC subtree + _resource_mgr->get_codec_mgr().populate_frontend_subtree(_tree->subtree(rf_fe_path), key, direction); + + //User settings + _tree->create<uhd::wb_iface::sptr>(rf_fe_path / "user_settings" / "iface") + .set(perif.user_settings); + + //Setup antenna stuff + if (key[0] == 'R') { + 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") + .add_coerced_subscriber(boost::bind(&n230_frontend_ctrl::set_antenna_sel, _resource_mgr->get_frontend_ctrl_sptr(), instance, _1)) + .set("RX2"); + } + if (key[0] == 'T') { + 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"); + } + } +} + +void n230_impl::_update_rx_subdev_spec(const uhd::usrp::subdev_spec_t &spec) +{ + //sanity checking + if (spec.size()) validate_subdev_spec(_tree, spec, "rx"); + UHD_ASSERT_THROW(spec.size() <= fpga::NUM_RADIOS); + + if (spec.size() > 0) { + UHD_ASSERT_THROW(spec[0].db_name == "A"); + UHD_ASSERT_THROW(spec[0].sd_name == "A"); + } + if (spec.size() > 1) { + //TODO we can support swapping at a later date, only this combo is supported + UHD_ASSERT_THROW(spec[1].db_name == "A"); + UHD_ASSERT_THROW(spec[1].sd_name == "B"); + } + + _stream_mgr->update_stream_states(); +} + +void n230_impl::_update_tx_subdev_spec(const uhd::usrp::subdev_spec_t &spec) +{ + //sanity checking + if (spec.size()) validate_subdev_spec(_tree, spec, "tx"); + UHD_ASSERT_THROW(spec.size() <= fpga::NUM_RADIOS); + + if (spec.size() > 0) { + UHD_ASSERT_THROW(spec[0].db_name == "A"); + UHD_ASSERT_THROW(spec[0].sd_name == "A"); + } + if (spec.size() > 1) { + //TODO we can support swapping at a later date, only this combo is supported + UHD_ASSERT_THROW(spec[1].db_name == "A"); + UHD_ASSERT_THROW(spec[1].sd_name == "B"); + } + + _stream_mgr->update_stream_states(); +} + +void n230_impl::_check_time_source(std::string source) +{ + if (source == "gpsdo") + { + uhd::gps_ctrl::sptr gps_ctrl = _resource_mgr->get_gps_ctrl(); + if (not (gps_ctrl and gps_ctrl->gps_detected())) + throw uhd::runtime_error("GPSDO time source not available"); + } +} + +void n230_impl::_check_clock_source(std::string source) +{ + if (source == "gpsdo") + { + uhd::gps_ctrl::sptr gps_ctrl = _resource_mgr->get_gps_ctrl(); + if (not (gps_ctrl and gps_ctrl->gps_detected())) + throw uhd::runtime_error("GPSDO clock source not available"); + } +} + +}}} //namespace diff --git a/host/lib/usrp/n230/n230_impl.hpp b/host/lib/usrp/n230/n230_impl.hpp new file mode 100644 index 000000000..b644dd8a3 --- /dev/null +++ b/host/lib/usrp/n230/n230_impl.hpp @@ -0,0 +1,81 @@ +// +// Copyright 2014 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 +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +#ifndef INCLUDED_N230_IMPL_HPP +#define INCLUDED_N230_IMPL_HPP + +#include <uhd/property_tree.hpp> +#include <uhd/device.hpp> +#include <uhd/usrp/subdev_spec.hpp> + +#include "n230_device_args.hpp" +#include "n230_eeprom_manager.hpp" +#include "n230_resource_manager.hpp" +#include "n230_stream_manager.hpp" +#include "recv_packet_demuxer_3000.hpp" + +namespace uhd { namespace usrp { namespace n230 { + +class n230_impl : public uhd::device +{ +public: //Functions + // ctor and dtor + n230_impl(const uhd::device_addr_t& device_addr); + virtual ~n230_impl(void); + + //--------------------------------------------------------------------- + // uhd::device interface + // + static sptr make(const uhd::device_addr_t &hint, size_t which = 0); + + //! Make a new receive streamer from the streamer arguments + virtual uhd::rx_streamer::sptr get_rx_stream(const uhd::stream_args_t &args); + + //! Make a new transmit streamer from the streamer arguments + virtual uhd::tx_streamer::sptr get_tx_stream(const uhd::stream_args_t &args); + + //!Receive and asynchronous message from the device. + virtual bool recv_async_msg(uhd::async_metadata_t &async_metadata, double timeout = 0.1); + + //!Registration methods the discovery and factory system. + //[static void register_device(const find_t &find, const make_t &make)] + static uhd::device_addrs_t n230_find(const uhd::device_addr_t &hint); + static uhd::device::sptr n230_make(const uhd::device_addr_t &device_addr); + // + //--------------------------------------------------------------------- + + typedef uhd::transport::bounded_buffer<uhd::async_metadata_t> async_md_type; + +private: //Functions + void _initialize_property_tree(const fs_path& mb_path); + void _initialize_radio_properties(const fs_path& mb_path, size_t instance); + + void _update_rx_subdev_spec(const uhd::usrp::subdev_spec_t &); + void _update_tx_subdev_spec(const uhd::usrp::subdev_spec_t &); + void _check_time_source(std::string); + void _check_clock_source(std::string); + +private: //Classes and Members + n230_device_args_t _dev_args; + boost::shared_ptr<n230_resource_manager> _resource_mgr; + boost::shared_ptr<n230_eeprom_manager> _eeprom_mgr; + boost::shared_ptr<n230_stream_manager> _stream_mgr; +}; + +}}} //namespace + +#endif /* INCLUDED_N230_IMPL_HPP */ diff --git a/host/lib/usrp/n230/n230_resource_manager.cpp b/host/lib/usrp/n230/n230_resource_manager.cpp new file mode 100644 index 000000000..f13dd0b33 --- /dev/null +++ b/host/lib/usrp/n230/n230_resource_manager.cpp @@ -0,0 +1,569 @@ +// +// Copyright 2013 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 +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +#include "n230_resource_manager.hpp" + +#include "usrp3_fw_ctrl_iface.hpp" +#include <uhd/transport/if_addrs.hpp> +#include <uhd/transport/udp_zero_copy.hpp> +#include <uhd/utils/byteswap.hpp> +#include <uhd/utils/msg.hpp> +#include <uhd/utils/platform.hpp> +#include <uhd/utils/paths.hpp> +#include <boost/format.hpp> +#include <boost/thread.hpp> +#include <boost/lexical_cast.hpp> +#include <boost/functional/hash.hpp> +#include <boost/lexical_cast.hpp> +#include <boost/make_shared.hpp> +#include "n230_fw_defs.h" +#include "n230_fw_host_iface.h" + +#define IF_DATA_I_MASK 0xFFF00000 +#define IF_DATA_Q_MASK 0x0000FFF0 + +namespace uhd { namespace usrp { namespace n230 { + +//Constants +static const uint8_t N230_HOST_SRC_ADDR_ETH0 = 0; +static const uint8_t N230_HOST_SRC_ADDR_ETH1 = 1; +static const uint8_t N230_HOST_DEST_ADDR = 2; + +static const uint8_t N230_ETH0_IFACE_ID = 0; +static const uint8_t N230_ETH1_IFACE_ID = 1; + +class n230_ad9361_client_t : public ad9361_params { +public: + ~n230_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; + default: return 0; + } + } + clocking_mode_t get_clocking_mode() { + return AD9361_XTAL_N_CLK_PATH; + } + digital_interface_mode_t get_digital_interface_mode() { + return AD9361_DDR_FDD_LVDS; + } + digital_interface_delays_t get_digital_interface_timing() { + digital_interface_delays_t delays; + delays.rx_clk_delay = 0; + delays.rx_data_delay = 0; + delays.tx_clk_delay = 0; + delays.tx_data_delay = 2; + return delays; + } +}; + +n230_resource_manager::n230_resource_manager( + const std::vector<std::string> ip_addrs, + const bool safe_mode +) : + _safe_mode(safe_mode), + _last_host_enpoint(0) +{ + if (_safe_mode) UHD_MSG(warning) << "Initializing device in safe mode\n"; + UHD_MSG(status) << "Setup basic communication...\n"; + + //Discover ethernet interfaces + bool dual_eth_expected = (ip_addrs.size() > 1); + BOOST_FOREACH(const std::string& addr, ip_addrs) { + n230_eth_conn_t conn_iface; + conn_iface.ip_addr = addr; + + boost::uint32_t iface_id = 0xFFFFFFFF; + try { + iface_id = usrp3::usrp3_fw_ctrl_iface::get_iface_id( + conn_iface.ip_addr, BOOST_STRINGIZE(N230_FW_COMMS_UDP_PORT), N230_FW_PRODUCT_ID); + } catch (uhd::io_error&) { + throw uhd::io_error(str(boost::format( + "Could not communicate with the device over address %s") % + conn_iface.ip_addr)); + } + switch (iface_id) { + case N230_ETH0_IFACE_ID: conn_iface.type = ETH0; break; + case N230_ETH1_IFACE_ID: conn_iface.type = ETH1; break; + default: { + if (dual_eth_expected) { + throw uhd::runtime_error("N230 Initialization Error: Could not detect ethernet port number."); + } else { + //For backwards compatibility, if only one port is specified, assume that a detection + //failure means that the device does not support dual-ethernet behavior. + conn_iface.type = ETH0; break; + } + } + } + _eth_conns.push_back(conn_iface); + } + if (_eth_conns.size() < 1) { + throw uhd::runtime_error("N230 Initialization Error: No eth interfaces specified.)"); + } + + //Create firmware communication interface + _fw_ctrl = usrp3::usrp3_fw_ctrl_iface::make( + transport::udp_simple::make_connected( + _get_conn(PRI_ETH).ip_addr, BOOST_STRINGIZE(N230_FW_COMMS_UDP_PORT)), N230_FW_PRODUCT_ID); + if (_fw_ctrl.get() == NULL) { + throw uhd::runtime_error("N230 Initialization Error: Could not create n230_ctrl_iface.)"); + } + _check_fw_compat(); + + //Start the device claimer + _claimer_task = uhd::task::make(boost::bind(&n230_resource_manager::_claimer_loop, this)); + + //Create common settings interface + const sid_t core_sid = _generate_sid(CORE, _get_conn(PRI_ETH).type); + + transport::udp_zero_copy::buff_params dummy_out_params; + transport::zero_copy_if::sptr core_xport = + _create_transport(_get_conn(PRI_ETH), core_sid, device_addr_t(), dummy_out_params); + if (core_xport.get() == NULL) { + throw uhd::runtime_error("N230 Initialization Error: Could not create settings transport.)"); + } + _core_ctrl = radio_ctrl_core_3000::make( + fpga::CVITA_BIG_ENDIAN, core_xport, core_xport, core_sid.get()); + if (_core_ctrl.get() == NULL) { + throw uhd::runtime_error("N230 Initialization Error: Could not create settings ctrl.)"); + } + _check_fpga_compat(); + + UHD_MSG(status) << boost::format("Version signatures... Firmware:%s FPGA:%s...\n") + % _fw_version.get_hash_str() % _fpga_version.get_hash_str(); + + _core_radio_ctrl_reg.initialize(*_core_ctrl, true /*flush*/); + _core_misc_reg.initialize(*_core_ctrl, true /*flush*/); + _core_pps_sel_reg.initialize(*_core_ctrl, true /*flush*/); + _core_status_reg.initialize(*_core_ctrl); + + //Create common SPI interface + _core_spi_ctrl = n230_core_spi_core::make(_core_ctrl); + if (_core_spi_ctrl.get() == NULL) { + throw uhd::runtime_error("N230 Initialization Error: Could not create SPI ctrl.)"); + } + + //Create AD9361 interface + UHD_MSG(status) << "Initializing CODEC...\n"; + _codec_ctrl = ad9361_ctrl::make_spi( + boost::make_shared<n230_ad9361_client_t>(), _core_spi_ctrl, fpga::AD9361_SPI_SLAVE_NUM); + if (_codec_ctrl.get() == NULL) { + throw uhd::runtime_error("N230 Initialization Error: Could not create Catalina ctrl.)"); + } + _codec_ctrl->set_clock_rate(fpga::CODEC_DEFAULT_CLK_RATE); + _codec_mgr = ad936x_manager::make(_codec_ctrl, fpga::NUM_RADIOS); + _codec_mgr->init_codec(); + + //Create AD4001 interface + _ref_pll_ctrl = boost::make_shared<n230_ref_pll_ctrl>(_core_spi_ctrl); + if (_ref_pll_ctrl.get() == NULL) { + throw uhd::runtime_error("N230 Initialization Error: Could not create ADF4001 ctrl.)"); + } + + //Reset SERDES interface and synchronize to frame sync from AD9361 + _reset_codec_digital_interface(); + + std::vector<time_core_3000::sptr> time_cores; + std::vector<gpio_atr::gpio_atr_3000::sptr> gpio_cores; + for (size_t i = 0; i < fpga::NUM_RADIOS; i++) { + _initialize_radio(i); + time_cores.push_back(_radios[i].time); + gpio_cores.push_back(_radios[i].gpio_atr); + } + + //Create clock and PPS control interface + _clk_pps_ctrl = n230_clk_pps_ctrl::make( + _codec_ctrl, _ref_pll_ctrl, _core_misc_reg, _core_pps_sel_reg, _core_status_reg, time_cores); + if (_clk_pps_ctrl.get() == NULL) { + throw uhd::runtime_error("N230 Initialization Error: Could not create clock and PPS ctrl.)"); + } + + //Create front-end control interface + _frontend_ctrl = n230_frontend_ctrl::make(_core_ctrl, _core_misc_reg, _codec_ctrl, gpio_cores); + if (_frontend_ctrl.get() == NULL) { + throw uhd::runtime_error("N230 Initialization Error: Could not create front-end ctrl.)"); + } + + //Create miniSAS GPIO interfaces + _ms0_gpio = gpio_atr::gpio_atr_3000::make( + _core_ctrl, fpga::sr_addr(fpga::SR_CORE_MS0_GPIO), fpga::rb_addr(fpga::RB_CORE_MS0_GPIO)); + _ms0_gpio->set_atr_mode(gpio_atr::MODE_GPIO,gpio_atr::gpio_atr_3000::MASK_SET_ALL); + _ms1_gpio = gpio_atr::gpio_atr_3000::make( + _core_ctrl, fpga::sr_addr(fpga::SR_CORE_MS1_GPIO), fpga::rb_addr(fpga::RB_CORE_MS1_GPIO)); + _ms1_gpio->set_atr_mode(gpio_atr::MODE_GPIO,gpio_atr::gpio_atr_3000::MASK_SET_ALL); + + //Create GPSDO interface + if (_core_status_reg.read(fpga::core_status_reg_t::GPSDO_STATUS) != fpga::GPSDO_ST_ABSENT) { + UHD_MSG(status) << "Detecting GPSDO.... " << std::flush; + try { + const sid_t gps_uart_sid = _generate_sid(GPS_UART, _get_conn(PRI_ETH).type); + transport::zero_copy_if::sptr gps_uart_xport = + _create_transport(_get_conn(PRI_ETH), gps_uart_sid, device_addr_t(), dummy_out_params); + _gps_uart = n230_uart::make(gps_uart_xport, uhd::htonx(gps_uart_sid.get())); + _gps_uart->set_baud_divider(fpga::BUS_CLK_RATE/fpga::GPSDO_UART_BAUDRATE); + _gps_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 + _gps_ctrl = gps_ctrl::make(_gps_uart); + } catch(std::exception &e) { + UHD_MSG(error) << "An error occurred making GPSDO control: " << e.what() << std::endl; + } + if (not is_gpsdo_present()) { + _core_ctrl->poke32(fpga::sr_addr(fpga::SR_CORE_GPSDO_ST), fpga::GPSDO_ST_ABSENT); + } + } + + //Perform data self-tests + _frontend_ctrl->set_stream_state(TXRX_STREAMING, TXRX_STREAMING); + for (size_t i = 0; i < fpga::NUM_RADIOS; i++) { + _frontend_ctrl->set_self_test_mode(LOOPBACK_RADIO); + bool radio_selftest_pass = _radio_data_loopback_self_test(_radios[i].ctrl); + if (!radio_selftest_pass) { + throw uhd::runtime_error("N230 Initialization Error: Data loopback test failed.)"); + } + + _frontend_ctrl->set_self_test_mode(LOOPBACK_CODEC); + bool codec_selftest_pass = _radio_data_loopback_self_test(_radios[i].ctrl); + if (!codec_selftest_pass) { + throw uhd::runtime_error("N230 Initialization Error: Codec loopback test failed.)"); + } + } + _frontend_ctrl->set_self_test_mode(LOOPBACK_DISABLED); + _frontend_ctrl->set_stream_state(NONE_STREAMING, NONE_STREAMING); +} + +n230_resource_manager::~n230_resource_manager() +{ + _claimer_task.reset(); + { //Critical section + boost::mutex::scoped_lock(_claimer_mutex); + _fw_ctrl->poke32(N230_FW_HOST_SHMEM_OFFSET(claim_time), 0); + _fw_ctrl->poke32(N230_FW_HOST_SHMEM_OFFSET(claim_src), 0); + } +} + +transport::zero_copy_if::sptr n230_resource_manager::create_transport( + n230_data_dir_t direction, + size_t radio_instance, + const device_addr_t ¶ms, + sid_t& sid_pair, + transport::udp_zero_copy::buff_params& buff_out_params) +{ + const n230_eth_conn_t& conn = _get_conn((radio_instance==1)?SEC_ETH:PRI_ETH); + const sid_t temp_sid_pair = + _generate_sid(direction==RX_DATA?RADIO_RX_DATA:RADIO_TX_DATA, conn.type, radio_instance); + transport::zero_copy_if::sptr xport = _create_transport(conn, temp_sid_pair, params, buff_out_params); + if (xport.get() == NULL) { + throw uhd::runtime_error("N230 Create Data Transport: Could not create data transport.)"); + } else { + sid_pair = temp_sid_pair; + } + return xport; +} + +bool n230_resource_manager::is_device_claimed(uhd::usrp::usrp3::usrp3_fw_ctrl_iface::sptr fw_ctrl) +{ + boost::mutex::scoped_lock(_claimer_mutex); + + //If timed out then device is definitely unclaimed + if (fw_ctrl->peek32(N230_FW_HOST_SHMEM_OFFSET(claim_status)) == 0) + return false; + + //otherwise check claim src to determine if another thread with the same src has claimed the device + return fw_ctrl->peek32(N230_FW_HOST_SHMEM_OFFSET(claim_src)) != get_process_hash(); +} + +void n230_resource_manager::_claimer_loop() +{ + { //Critical section + boost::mutex::scoped_lock(_claimer_mutex); + _fw_ctrl->poke32(N230_FW_HOST_SHMEM_OFFSET(claim_time), time(NULL)); + _fw_ctrl->poke32(N230_FW_HOST_SHMEM_OFFSET(claim_src), get_process_hash()); + } + boost::this_thread::sleep(boost::posix_time::milliseconds(N230_CLAIMER_TIMEOUT_IN_MS / 2)); +} + +void n230_resource_manager::_initialize_radio(size_t instance) +{ + radio_resource_t& radio = _radios[instance]; + + //Create common settings interface + const sid_t ctrl_sid = _generate_sid(RADIO_CONTROL, _get_conn(PRI_ETH).type, instance); + transport::udp_zero_copy::buff_params buff_out_params; + transport::zero_copy_if::sptr ctrl_xport = + _create_transport(_get_conn(PRI_ETH), ctrl_sid, device_addr_t(), buff_out_params); + if (ctrl_xport.get() == NULL) { + throw uhd::runtime_error("N230 Initialization Error: Could not create radio transport.)"); + } + radio.ctrl = radio_ctrl_core_3000::make( + fpga::CVITA_BIG_ENDIAN, ctrl_xport, ctrl_xport, ctrl_sid.get()); + if (radio.ctrl.get() == NULL) { + throw uhd::runtime_error("N230 Initialization Error: Could not create radio ctrl.)"); + } + + //Perform register loopback test to verify the radio clock + bool reg_selftest_pass = _radio_register_loopback_self_test(radio.ctrl); + if (!reg_selftest_pass) { + throw uhd::runtime_error("N230 Initialization Error: Register loopback test failed.)"); + } + + //Write-only ATR interface + radio.gpio_atr = gpio_atr::gpio_atr_3000::make_write_only(radio.ctrl, fpga::sr_addr(fpga::SR_RADIO_ATR)); + radio.gpio_atr->set_atr_mode(gpio_atr::MODE_ATR,gpio_atr::gpio_atr_3000::MASK_SET_ALL); + + //Core VITA time interface + time_core_3000::readback_bases_type time_bases; + time_bases.rb_now = fpga::rb_addr(fpga::RB_RADIO_TIME_NOW); + time_bases.rb_pps = fpga::rb_addr(fpga::RB_RADIO_TIME_PPS); + radio.time = time_core_3000::make(radio.ctrl, fpga::sr_addr(fpga::SR_RADIO_TIME), time_bases); + if (radio.time.get() == NULL) { + throw uhd::runtime_error("N230 Initialization Error: Could not create time core.)"); + } + + //RX DSP + radio.framer = rx_vita_core_3000::make( + radio.ctrl, fpga::sr_addr(fpga::SR_RADIO_RX_CTRL)); + radio.ddc = rx_dsp_core_3000::make(radio.ctrl, fpga::sr_addr(fpga::SR_RADIO_RX_DSP), true /*old DDC?*/); + if (radio.framer.get() == NULL || radio.ddc.get() == NULL) { + throw uhd::runtime_error("N230 Initialization Error: Could not create RX DSP interface.)"); + } + radio.ddc->set_link_rate(fpga::N230_LINK_RATE_BPS); + + //TX DSP + radio.deframer = tx_vita_core_3000::make(radio.ctrl, fpga::sr_addr(fpga::SR_RADIO_TX_CTRL)); + radio.duc = tx_dsp_core_3000::make(radio.ctrl, fpga::sr_addr(fpga::SR_RADIO_TX_DSP)); + if (radio.deframer.get() == NULL || radio.duc.get() == NULL) { + throw uhd::runtime_error("N230 Initialization Error: Could not create RX DSP interface.)"); + } + radio.duc->set_link_rate(fpga::N230_LINK_RATE_BPS); + + //User settings + radio.user_settings = user_settings_core_3000::make(radio.ctrl, + fpga::sr_addr(fpga::SR_RADIO_USER_SR), fpga::rb_addr(fpga::SR_RADIO_USER_RB)); + if (radio.user_settings.get() == NULL) { + throw uhd::runtime_error("N230 Initialization Error: Could not create user settings bus.)"); + } +} + +boost::uint8_t xb_ep_to_sid(fpga::xb_endpoint_t ep) { + return static_cast<boost::uint8_t>(ep) << 4; +} + +const sid_t n230_resource_manager::_generate_sid(const n230_endpoint_t type, const n230_eth_port_t xport, size_t instance) +{ + fpga::xb_endpoint_t xb_dest_ep; + boost::uint8_t sid_dest_ep = 0; + fpga::xb_endpoint_t xb_ret_ep = (xport == ETH1) ? fpga::N230_XB_DST_E1 : fpga::N230_XB_DST_E0; + boost::uint8_t sid_ret_addr = (xport == ETH1) ? N230_HOST_SRC_ADDR_ETH1 : N230_HOST_SRC_ADDR_ETH0; + + if (type == CORE or type == GPS_UART) { + //Non-radio endpoints + xb_dest_ep = (type == CORE) ? fpga::N230_XB_DST_GCTRL : fpga::N230_XB_DST_UART; + sid_dest_ep = xb_ep_to_sid(xb_dest_ep); + } else { + //Radio endpoints + xb_dest_ep = (instance == 1) ? fpga::N230_XB_DST_R1 : fpga::N230_XB_DST_R0; + sid_dest_ep = xb_ep_to_sid(xb_dest_ep); + switch (type) { + case RADIO_TX_DATA: + sid_dest_ep |= fpga::RADIO_DATA_SUFFIX; + break; + case RADIO_RX_DATA: + sid_dest_ep |= fpga::RADIO_FC_SUFFIX; + break; + default: + sid_dest_ep |= fpga::RADIO_CTRL_SUFFIX; + break; + } + } + + //Increment last host logical endpoint + sid_t sid(sid_ret_addr, ++_last_host_enpoint, N230_HOST_DEST_ADDR, sid_dest_ep); + + //Program the crossbar addr + _fw_ctrl->poke32(fw::reg_addr(fw::WB_SBRB_BASE, fw::SR_ZPU_XB_LOCAL), sid.get_dst_addr()); + // Program CAM entry for returning packets to us + // This type of packet does not match the XB_LOCAL address and is looked up in the lower half of the CAM + _fw_ctrl->poke32(fw::reg_addr(fw::WB_XB_SBRB_BASE, sid.get_src_addr()), static_cast<boost::uint32_t>(xb_ret_ep)); + // Program CAM entry for outgoing packets matching a N230 resource (for example a Radio) + // This type of packet does matches the XB_LOCAL address and is looked up in the upper half of the CAM + _fw_ctrl->poke32(fw::reg_addr(fw::WB_XB_SBRB_BASE, 256 + sid.get_dst_endpoint()), static_cast<boost::uint32_t>(xb_dest_ep)); + + return sid; +} + +transport::zero_copy_if::sptr n230_resource_manager::_create_transport( + const n230_eth_conn_t& eth_conn, + const sid_t& sid, const device_addr_t &buff_params, + transport::udp_zero_copy::buff_params& buff_params_out) +{ + transport::zero_copy_xport_params default_buff_args; + default_buff_args.recv_frame_size = transport::udp_simple::mtu; + default_buff_args.send_frame_size = transport::udp_simple::mtu; + default_buff_args.num_recv_frames = 32; + default_buff_args.num_send_frames = 32; + + transport::zero_copy_if::sptr xport = transport::udp_zero_copy::make( + eth_conn.ip_addr, boost::lexical_cast<std::string>(fpga::CVITA_UDP_PORT), + default_buff_args, buff_params_out, buff_params); + + if (xport.get()) { + _program_dispatcher(*xport, eth_conn.type, sid); + } + return xport; +} + +void n230_resource_manager::_program_dispatcher( + transport::zero_copy_if& xport, const n230_eth_port_t port, const sid_t& sid) +{ + //Send a mini packet with SID into the ZPU + //ZPU will reprogram the ethernet framer + transport::managed_send_buffer::sptr buff = xport.get_send_buff(); + buff->cast<boost::uint32_t *>()[0] = 0; //eth dispatch looks for != 0 + buff->cast<boost::uint32_t *>()[1] = uhd::htonx(sid.get()); + buff->commit(8); + buff.reset(); + + //reprogram the ethernet dispatcher's udp port (should be safe to always set) + uint32_t disp_base_offset = + ((port == ETH1) ? fw::SR_ZPU_ETHINT1 : fw::SR_ZPU_ETHINT0) + fw::SR_ZPU_ETHINT_DISPATCHER_BASE; + _fw_ctrl->poke32(fw::reg_addr(fw::WB_SBRB_BASE, disp_base_offset + fw::ETH_FRAMER_SRC_UDP_PORT), fpga::CVITA_UDP_PORT); + + //Do a peek to an arbitrary address to guarantee that the + //ethernet framer has been programmed before we return. + _fw_ctrl->peek32(0); +} + +void n230_resource_manager::_reset_codec_digital_interface() +{ + //Set timing registers + _core_ctrl->poke32(fpga::sr_addr(fpga::SR_CORE_DATA_DELAY), fpga::CODEC_DATA_DELAY); + _core_ctrl->poke32(fpga::sr_addr(fpga::SR_CORE_CLK_DELAY), fpga::CODEC_CLK_DELAY); + + _core_radio_ctrl_reg.write(fpga::core_radio_ctrl_reg_t::CODEC_ARST, 1); + boost::this_thread::sleep(boost::posix_time::milliseconds(10)); + _core_radio_ctrl_reg.write(fpga::core_radio_ctrl_reg_t::CODEC_ARST, 0); +} + +bool n230_resource_manager::_radio_register_loopback_self_test(wb_iface::sptr iface) +{ + bool test_fail = false; + size_t hash = static_cast<size_t>(time(NULL)); + for (size_t i = 0; i < 100; i++) { + boost::hash_combine(hash, i); + iface->poke32(fpga::sr_addr(fpga::SR_RADIO_TEST), boost::uint32_t(hash)); + test_fail = iface->peek32(fpga::rb_addr(fpga::RB_RADIO_TEST)) != boost::uint32_t(hash); + if (test_fail) break; //exit loop on any failure + } + return !test_fail; +} + +bool n230_resource_manager::_radio_data_loopback_self_test(wb_iface::sptr iface) +{ + bool test_fail = false; + 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) & (IF_DATA_I_MASK | IF_DATA_Q_MASK); + iface->poke32(fpga::sr_addr(fpga::SR_RADIO_CODEC_IDLE), word32); + iface->peek64(fpga::rb_addr(fpga::RB_RADIO_CODEC_DATA)); //block until request completes + boost::this_thread::sleep(boost::posix_time::microseconds(100)); //wait for loopback to propagate through codec + const boost::uint64_t rb_word64 = iface->peek64(fpga::rb_addr(fpga::RB_RADIO_CODEC_DATA)); + const boost::uint32_t rb_tx = boost::uint32_t(rb_word64 >> 32); + const boost::uint32_t rb_rx = boost::uint32_t(rb_word64 & 0xffffffff); + test_fail = word32 != rb_tx or word32 != rb_rx; + if (test_fail) + UHD_MSG(fastpath) << boost::format("mismatch (exp:%x, got:%x and %x)... ") % word32 % rb_tx % rb_rx; + break; //exit loop on any failure + } + + /* Zero out the idle data. */ + iface->poke32(fpga::sr_addr(fpga::SR_RADIO_CODEC_IDLE), 0); + return !test_fail; +} + +std::string n230_resource_manager::_get_fpga_upgrade_msg() { + std::string img_loader_path = + (fs::path(uhd::get_pkg_path()) / "bin" / "uhd_image_loader").string(); + + return str(boost::format( + "\nDownload the appropriate FPGA images for this version of UHD.\n" + "%s\n\n" + "Then burn a new image to the on-board flash storage of your\n" + "USRP N230 device using the image loader utility. Use this command:\n" + "\n \"%s\" --args=\"type=n230,addr=%s\"\n") + % print_utility_error("uhd_images_downloader.py") + % img_loader_path % _get_conn(PRI_ETH).ip_addr); + +} + +void n230_resource_manager::_check_fw_compat() +{ + boost::uint32_t compat_num = _fw_ctrl->peek32(N230_FW_HOST_SHMEM_OFFSET(fw_compat_num)); + _fw_version.compat_major = compat_num >> 16; + _fw_version.compat_minor = compat_num; + _fw_version.version_hash = _fw_ctrl->peek32(N230_FW_HOST_SHMEM_OFFSET(fw_version_hash)); + + if (_fw_version.compat_major != N230_FW_COMPAT_NUM_MAJOR){ + throw uhd::runtime_error(str(boost::format( + "Expected firmware compatibility number %d.x, but got %d.%d\n" + "The firmware build is not compatible with the host code build.\n" + "%s" + ) % static_cast<boost::uint32_t>(N230_FW_COMPAT_NUM_MAJOR) + % static_cast<boost::uint32_t>(_fw_version.compat_major) + % static_cast<boost::uint32_t>(_fw_version.compat_minor) + % _get_fpga_upgrade_msg())); + } +} + +void n230_resource_manager::_check_fpga_compat() +{ + const boost::uint64_t compat = _core_ctrl->peek64(fpga::rb_addr(fpga::RB_CORE_SIGNATUE)); + const boost::uint32_t signature = boost::uint32_t(compat >> 32); + const boost::uint16_t product_id = boost::uint8_t(compat >> 24); + _fpga_version.compat_major = static_cast<boost::uint8_t>(compat >> 16); + _fpga_version.compat_minor = static_cast<boost::uint16_t>(compat); + + const boost::uint64_t version_hash = _core_ctrl->peek64(fpga::rb_addr(fpga::RB_CORE_VERSION_HASH)); + _fpga_version.version_hash = boost::uint32_t(version_hash); + + if (signature != 0x0ACE0BA5E || product_id != fpga::RB_N230_PRODUCT_ID) + throw uhd::runtime_error("Signature check failed. Please contact support."); + + bool is_safe_image = (_fpga_version.compat_major > fpga::RB_N230_COMPAT_SAFE); + + if (is_safe_image && !_safe_mode) { + throw uhd::runtime_error( + "The device appears to have the failsafe FPGA image loaded\n" + "This could have happened because the production FPGA image in the flash was either corrupt or non-existent\n" + "To remedy this error, please burn a valid FPGA image to the flash.\n" + "To continue using the failsafe image with UHD, create the UHD device with the \"safe_mode\" device arg.\n" + "Radio functionality/performance not guaranteed when operating in safe mode.\n"); + } else if (_fpga_version.compat_major != fpga::RB_N230_COMPAT_MAJOR && !is_safe_image) { + throw uhd::runtime_error(str(boost::format( + "Expected FPGA compatibility number %d.x, but got %d.%d:\n" + "The FPGA build is not compatible with the host code build.\n" + "%s" + ) % static_cast<boost::uint32_t>(fpga::RB_N230_COMPAT_MAJOR) + % static_cast<boost::uint32_t>(_fpga_version.compat_major) + % static_cast<boost::uint32_t>(_fpga_version.compat_minor) + % _get_fpga_upgrade_msg())); + } +} + +}}} //namespace diff --git a/host/lib/usrp/n230/n230_resource_manager.hpp b/host/lib/usrp/n230/n230_resource_manager.hpp new file mode 100644 index 000000000..0a1178bd2 --- /dev/null +++ b/host/lib/usrp/n230/n230_resource_manager.hpp @@ -0,0 +1,318 @@ +// +// Copyright 2014 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 +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +#ifndef INCLUDED_N230_RESOURCE_MANAGER_HPP +#define INCLUDED_N230_RESOURCE_MANAGER_HPP + +#include "radio_ctrl_core_3000.hpp" +#include "spi_core_3000.hpp" +#include "gpio_atr_3000.hpp" +#include "rx_vita_core_3000.hpp" +#include "tx_vita_core_3000.hpp" +#include "time_core_3000.hpp" +#include "rx_dsp_core_3000.hpp" +#include "tx_dsp_core_3000.hpp" +#include "user_settings_core_3000.hpp" +#include "ad9361_ctrl.hpp" +#include "ad936x_manager.hpp" +#include <uhd/utils/tasks.hpp> +#include <uhd/types/sid.hpp> +#include <uhd/types/device_addr.hpp> +#include <uhd/utils/soft_register.hpp> +#include <uhd/transport/udp_zero_copy.hpp> +#include <uhd/transport/bounded_buffer.hpp> +#include <uhd/usrp/gps_ctrl.hpp> + +#include "usrp3_fw_ctrl_iface.hpp" +#include "n230_clk_pps_ctrl.hpp" +#include "n230_cores.hpp" +#include "n230_fpga_defs.h" +#include "n230_frontend_ctrl.hpp" +#include "n230_uart.hpp" + +namespace uhd { namespace usrp { namespace n230 { + +enum n230_eth_port_t { + ETH0, + ETH1 +}; + +enum n230_eth_pref_t { + PRI_ETH, + SEC_ETH +}; + +enum n230_endpoint_t { + RADIO_TX_DATA, + RADIO_RX_DATA, + RADIO_CONTROL, + CORE, + GPS_UART +}; + +enum n230_ver_src_t { + SOFTWARE, + FIRMWARE, + FPGA +}; + +enum n230_version_t { + COMPAT_MAJOR, + COMPAT_MINOR +}; + +enum n230_data_dir_t { + RX_DATA, TX_DATA +}; + +//Radio resources +class radio_resource_t : public boost::noncopyable { +public: + radio_ctrl_core_3000::sptr ctrl; + gpio_atr::gpio_atr_3000::sptr gpio_atr; + time_core_3000::sptr time; + rx_vita_core_3000::sptr framer; + rx_dsp_core_3000::sptr ddc; + tx_vita_core_3000::sptr deframer; + tx_dsp_core_3000::sptr duc; + user_settings_core_3000::sptr user_settings; +}; + +class n230_resource_manager : public boost::noncopyable +{ +public: //Methods + n230_resource_manager(const std::vector<std::string> ip_addrs, const bool safe_mode); + virtual ~n230_resource_manager(); + + static bool is_device_claimed(uhd::usrp::usrp3::usrp3_fw_ctrl_iface::sptr fw_ctrl); + + inline bool is_device_claimed() { + if (_fw_ctrl.get()) { + return is_device_claimed(_fw_ctrl); + } else { + return false; + } + } + + inline boost::uint32_t get_version(n230_ver_src_t src, n230_version_t type) { + switch (src) { + case FPGA: return _fpga_version.get(type); + case FIRMWARE: return _fw_version.get(type); + default: return 0; + } + } + + inline const std::string get_version_hash(n230_ver_src_t src) { + switch (src) { + case FPGA: return _fpga_version.get_hash_str(); + case FIRMWARE: return _fw_version.get_hash_str(); + default: return ""; + } + } + + //Firmware control interface + inline wb_iface& get_fw_ctrl() const { + return *_fw_ctrl; + } + inline wb_iface::sptr get_fw_ctrl_sptr() { + return _fw_ctrl; + } + + //Core settings control interface + inline radio_ctrl_core_3000& get_core_ctrl() const { + return *_core_ctrl; + } + inline radio_ctrl_core_3000::sptr get_core_ctrl_sptr() { + return _core_ctrl; + } + + //AD931 control interface + inline ad9361_ctrl& get_codec_ctrl() const { + return *_codec_ctrl; + } + inline ad9361_ctrl::sptr get_codec_ctrl_sptr() { + return _codec_ctrl; + } + inline uhd::usrp::ad936x_manager& get_codec_mgr() const { + return *_codec_mgr; + } + + //Clock PPS controls + inline n230_ref_pll_ctrl& get_ref_pll_ctrl() const { + return *_ref_pll_ctrl; + } + inline n230_ref_pll_ctrl::sptr get_ref_pll_ctrl_sptr() { + return _ref_pll_ctrl; + } + + //Clock PPS controls + inline n230_clk_pps_ctrl& get_clk_pps_ctrl() const { + return *_clk_pps_ctrl; + } + inline n230_clk_pps_ctrl::sptr get_clk_pps_ctrl_sptr() { + return _clk_pps_ctrl; + } + + //Front-end control + inline n230_frontend_ctrl& get_frontend_ctrl() const { + return *_frontend_ctrl; + } + inline n230_frontend_ctrl::sptr get_frontend_ctrl_sptr() { + return _frontend_ctrl; + } + + //MiniSAS GPIO control + inline gpio_atr::gpio_atr_3000::sptr get_minisas_gpio_ctrl_sptr(size_t idx) { + return idx == 0 ? _ms0_gpio : _ms1_gpio; + } + + inline gpio_atr::gpio_atr_3000& get_minisas_gpio_ctrl(size_t idx) { + return *get_minisas_gpio_ctrl_sptr(idx); + } + + //GPSDO control + inline bool is_gpsdo_present() { + return _gps_ctrl.get() and _gps_ctrl->gps_detected(); + } + + inline uhd::gps_ctrl::sptr get_gps_ctrl(void) { + return _gps_ctrl; + } + + inline radio_resource_t& get_radio(size_t instance) { + return _radios[instance]; + } + + //Transport to stream data + transport::zero_copy_if::sptr create_transport( + n230_data_dir_t direction, size_t radio_instance, + const device_addr_t ¶ms, sid_t& sid, + transport::udp_zero_copy::buff_params& buff_out_params); + + //Misc + inline double get_max_link_rate() { + return fpga::N230_LINK_RATE_BPS * _eth_conns.size(); + } + +private: + struct ver_info_t { + boost::uint8_t compat_major; + boost::uint16_t compat_minor; + boost::uint32_t version_hash; + + boost::uint32_t get(n230_version_t type) { + switch (type) { + case COMPAT_MAJOR: return compat_major; + case COMPAT_MINOR: return compat_minor; + default: return 0; + } + } + + const std::string get_hash_str() { + return (str(boost::format("%07x%s") + % (version_hash & 0x0FFFFFFF) + % ((version_hash & 0xF0000000) ? "(modified)" : ""))); + } + }; + + struct n230_eth_conn_t { + std::string ip_addr; + n230_eth_port_t type; + }; + + //-- Functions -- + + void _claimer_loop(); + + void _initialize_radio(size_t instance); + + std::string _get_fpga_upgrade_msg(); + void _check_fw_compat(); + void _check_fpga_compat(); + + const sid_t _generate_sid( + const n230_endpoint_t type, const n230_eth_port_t xport, size_t instance = 0); + + transport::zero_copy_if::sptr _create_transport( + const n230_eth_conn_t& eth_conn, + const sid_t& sid, const device_addr_t &buff_params, + transport::udp_zero_copy::buff_params& buff_params_out); + + void _program_dispatcher( + transport::zero_copy_if& xport, const n230_eth_port_t port, const sid_t& sid); + + void _reset_codec_digital_interface(); + + bool _radio_register_loopback_self_test(wb_iface::sptr iface); + + bool _radio_data_loopback_self_test(wb_iface::sptr iface); + + inline const n230_eth_conn_t& _get_conn(const n230_eth_pref_t pref) { + if (_eth_conns.size() == 1) + return _eth_conns[0]; + else + return _eth_conns[(pref==PRI_ETH)?0:1]; + } + + //-- Members -- + + std::vector<n230_eth_conn_t> _eth_conns; + const bool _safe_mode; + ver_info_t _fw_version; + ver_info_t _fpga_version; + + //Firmware register interface + uhd::usrp::usrp3::usrp3_fw_ctrl_iface::sptr _fw_ctrl; + uhd::task::sptr _claimer_task; + static boost::mutex _claimer_mutex; //All claims and checks in this process are serialized + + //Transport + boost::uint8_t _last_host_enpoint; + + //Radio settings interface + radio_ctrl_core_3000::sptr _core_ctrl; + n230_core_spi_core::sptr _core_spi_ctrl; + ad9361_ctrl::sptr _codec_ctrl; + uhd::usrp::ad936x_manager::sptr _codec_mgr; + + //Core Registers + fpga::core_radio_ctrl_reg_t _core_radio_ctrl_reg; + fpga::core_misc_reg_t _core_misc_reg; + fpga::core_pps_sel_reg_t _core_pps_sel_reg; + fpga::core_status_reg_t _core_status_reg; + + //Radio peripherals + radio_resource_t _radios[fpga::NUM_RADIOS]; + + //Misc IO peripherals + n230_ref_pll_ctrl::sptr _ref_pll_ctrl; + n230_clk_pps_ctrl::sptr _clk_pps_ctrl; + n230_frontend_ctrl::sptr _frontend_ctrl; + + //miniSAS GPIO + gpio_atr::gpio_atr_3000::sptr _ms0_gpio; + gpio_atr::gpio_atr_3000::sptr _ms1_gpio; + + //GPSDO + n230_uart::sptr _gps_uart; + uhd::gps_ctrl::sptr _gps_ctrl; + +}; + +}}} //namespace + +#endif //INCLUDED_N230_RESOURCE_MANAGER_HPP diff --git a/host/lib/usrp/n230/n230_stream_manager.cpp b/host/lib/usrp/n230/n230_stream_manager.cpp new file mode 100644 index 000000000..e7624ecd6 --- /dev/null +++ b/host/lib/usrp/n230/n230_stream_manager.cpp @@ -0,0 +1,562 @@ +// +// Copyright 2013 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 +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +#include "n230_stream_manager.hpp" + +#include "../../transport/super_recv_packet_handler.hpp" +#include "../../transport/super_send_packet_handler.hpp" +#include "async_packet_handler.hpp" +#include <uhd/transport/bounded_buffer.hpp> +#include <boost/bind.hpp> +#include <uhd/utils/tasks.hpp> +#include <uhd/utils/log.hpp> +#include <boost/foreach.hpp> +#include <boost/make_shared.hpp> + +static const double N230_RX_SW_BUFF_FULL_FACTOR = 0.90; //Buffer should ideally be 90% full. +static const size_t N230_RX_FC_REQUEST_FREQ = 32; //per flow-control window +static const size_t N230_TX_MAX_ASYNC_MESSAGES = 1000; +static const size_t N230_TX_MAX_SPP = 4092; +static const size_t N230_TX_FC_RESPONSE_FREQ = 10; //per flow-control window + +static const boost::uint32_t N230_EVENT_CODE_FLOW_CTRL = 0; + +namespace uhd { namespace usrp { namespace n230 { + +using namespace uhd::transport; + +n230_stream_manager::~n230_stream_manager() +{ +} + +/*********************************************************************** + * Receive streamer + **********************************************************************/ +n230_stream_manager::n230_stream_manager( + const n230_device_args_t& dev_args, + boost::shared_ptr<n230_resource_manager> resource_mgr, + boost::weak_ptr<property_tree> prop_tree +) : + _dev_args(dev_args), + _resource_mgr(resource_mgr), + _tree(prop_tree) +{ + _async_md_queue.reset(new async_md_queue_t(N230_TX_MAX_ASYNC_MESSAGES)); +} + +/*********************************************************************** + * Receive streamer + **********************************************************************/ +rx_streamer::sptr n230_stream_manager::get_rx_stream(const uhd::stream_args_t &args_) +{ + boost::mutex::scoped_lock lock(_stream_setup_mutex); + + stream_args_t args = args_; + + //setup defaults for unspecified values + if (args.otw_format.empty()) args.otw_format = "sc16"; + args.channels = args.channels.empty()? std::vector<size_t>(1, 0) : args.channels; + + boost::shared_ptr<sph::recv_packet_streamer> my_streamer; + for (size_t stream_i = 0; stream_i < args.channels.size(); stream_i++) + { + const size_t chan = args.channels[stream_i]; + radio_resource_t& perif = _resource_mgr->get_radio(chan); + + //setup transport hints (default to a large recv buff) + //TODO: Propagate the device_args class into streamer in the future + device_addr_t device_addr = args.args; + if (not device_addr.has_key("recv_buff_size")) { + device_addr["recv_buff_size"] = boost::lexical_cast<std::string>(_dev_args.get_recv_buff_size()); + } + if (not device_addr.has_key("recv_frame_size")) { + device_addr["recv_frame_size"] = boost::lexical_cast<std::string>(_dev_args.get_recv_frame_size()); + } + if (not device_addr.has_key("num_recv_frames")) { + device_addr["num_recv_frames"] = boost::lexical_cast<std::string>(_dev_args.get_num_recv_frames()); + } + + transport::udp_zero_copy::buff_params buff_params_out; + sid_t sid; + zero_copy_if::sptr xport = _resource_mgr->create_transport( + RX_DATA, chan, device_addr, sid, buff_params_out); + + //calculate packet size + static const size_t hdr_size = 0 + + vrt::max_if_hdr_words32*sizeof(boost::uint32_t) + //+ sizeof(vrt::if_packet_info_t().tlr) //no longer using trailer + - sizeof(vrt::if_packet_info_t().cid) //no class id ever used + - sizeof(vrt::if_packet_info_t().tsi) //no int time ever used + ; + const size_t bpp = xport->get_recv_frame_size() - hdr_size; + const size_t bpi = convert::get_bytes_per_item(args.otw_format); + size_t spp = unsigned(args.args.cast<double>("spp", bpp/bpi)); + spp = std::min<size_t>(N230_TX_MAX_SPP, spp); //FPGA FIFO maximum for framing at full rate + + //make the new streamer given the samples per packet + if (not my_streamer) my_streamer = boost::make_shared<sph::recv_packet_streamer>(spp); + my_streamer->resize(args.channels.size()); + + //init some streamer stuff + my_streamer->set_vrt_unpacker(&n230_stream_manager::_cvita_hdr_unpack); + + //set the converter + uhd::convert::id_type id; + id.input_format = args.otw_format + "_item32_be"; + id.num_inputs = 1; + id.output_format = args.cpu_format; + id.num_outputs = 1; + my_streamer->set_converter(id); + + perif.framer->clear(); + perif.framer->set_nsamps_per_packet(spp); + perif.framer->set_sid(sid.reversed().get()); + perif.framer->setup(args); + perif.ddc->setup(args); + + //Give the streamer a functor to get the recv_buffer + //bind requires a zero_copy_if::sptr to add a streamer->xport lifetime dependency + my_streamer->set_xport_chan_get_buff( + stream_i, + boost::bind(&zero_copy_if::get_recv_buff, xport, _1), + true /*flush*/ + ); + + my_streamer->set_overflow_handler(stream_i, boost::bind( + &n230_stream_manager::_handle_overflow, this, chan + )); + + my_streamer->set_issue_stream_cmd(stream_i, boost::bind( + &rx_vita_core_3000::issue_stream_command, perif.framer, _1 + )); + + const size_t fc_window = _get_rx_flow_control_window( + xport->get_recv_frame_size(), buff_params_out.recv_buff_size); + const size_t fc_handle_window = std::max<size_t>(1, fc_window / N230_RX_FC_REQUEST_FREQ); + + perif.framer->configure_flow_control(fc_window); + + //Give the streamer a functor to send flow control messages + //handle_rx_flowctrl is static and has no lifetime issues + boost::shared_ptr<rx_fc_cache_t> fc_cache(new rx_fc_cache_t()); + my_streamer->set_xport_handle_flowctrl( + stream_i, boost::bind(&n230_stream_manager::_handle_rx_flowctrl, sid.get(), xport, fc_cache, _1), + fc_handle_window, + true/*init*/ + ); + + //Store a weak pointer to prevent a streamer->manager->streamer circular dependency + _rx_streamers[chan] = my_streamer; //store weak pointer + _rx_stream_cached_args[chan] = args; + + //Sets tick and samp rates on all streamer + update_tick_rate(_get_tick_rate()); + + //TODO: Find a way to remove this dependency + property_tree::sptr prop_tree = _tree.lock(); + if (prop_tree) { + //TODO: Update this to support multiple motherboards + const fs_path mb_path = "/mboards/0"; + prop_tree->access<double>(mb_path / "rx_dsps" / boost::lexical_cast<std::string>(chan) / "rate" / "value").update(); + } + } + update_stream_states(); + + return my_streamer; +} + +/*********************************************************************** + * Transmit streamer + **********************************************************************/ +tx_streamer::sptr n230_stream_manager::get_tx_stream(const uhd::stream_args_t &args_) +{ + boost::mutex::scoped_lock lock(_stream_setup_mutex); + + uhd::stream_args_t args = args_; + + //setup defaults for unspecified values + if (not args.otw_format.empty() and args.otw_format != "sc16") { + throw uhd::value_error("n230_impl::get_tx_stream only supports otw_format sc16"); + } + args.otw_format = "sc16"; + args.channels = args.channels.empty()? std::vector<size_t>(1, 0) : args.channels; + + //shared async queue for all channels in streamer + boost::shared_ptr<async_md_queue_t> async_md(new async_md_queue_t(N230_TX_MAX_ASYNC_MESSAGES)); + + boost::shared_ptr<sph::send_packet_streamer> my_streamer; + for (size_t stream_i = 0; stream_i < args.channels.size(); stream_i++) + { + const size_t chan = args.channels[stream_i]; + radio_resource_t& perif = _resource_mgr->get_radio(chan); + + //setup transport hints (default to a large recv buff) + //TODO: Propagate the device_args class into streamer in the future + device_addr_t device_addr = args.args; + if (not device_addr.has_key("send_buff_size")) { + device_addr["send_buff_size"] = boost::lexical_cast<std::string>(_dev_args.get_send_buff_size()); + } + if (not device_addr.has_key("send_frame_size")) { + device_addr["send_frame_size"] = boost::lexical_cast<std::string>(_dev_args.get_send_frame_size()); + } + if (not device_addr.has_key("num_send_frames")) { + device_addr["num_send_frames"] = boost::lexical_cast<std::string>(_dev_args.get_num_send_frames()); + } + + transport::udp_zero_copy::buff_params buff_params_out; + sid_t sid; + zero_copy_if::sptr xport = _resource_mgr->create_transport( + TX_DATA, chan, device_addr, sid, buff_params_out); + + //calculate packet size + static const size_t hdr_size = 0 + + vrt::num_vrl_words32*sizeof(boost::uint32_t) + + vrt::max_if_hdr_words32*sizeof(boost::uint32_t) + //+ sizeof(vrt::if_packet_info_t().tlr) //forced to have trailer + - sizeof(vrt::if_packet_info_t().cid) //no class id ever used + - sizeof(vrt::if_packet_info_t().tsi) //no int time ever used + ; + const size_t bpp = xport->get_send_frame_size() - hdr_size; + const size_t bpi = convert::get_bytes_per_item(args.otw_format); + const size_t spp = unsigned(args.args.cast<double>("spp", bpp/bpi)); + + //make the new streamer given the samples per packet + if (not my_streamer) my_streamer = boost::make_shared<sph::send_packet_streamer>(spp); + my_streamer->resize(args.channels.size()); + my_streamer->set_vrt_packer(&n230_stream_manager::_cvita_hdr_pack); + + //set the converter + uhd::convert::id_type id; + id.input_format = args.cpu_format; + id.num_inputs = 1; + id.output_format = args.otw_format + "_item32_be"; + id.num_outputs = 1; + my_streamer->set_converter(id); + + perif.deframer->clear(); + perif.deframer->setup(args); + perif.duc->setup(args); + + //flow control setup + size_t fc_window = _get_tx_flow_control_window( + bpp, device_addr.cast<size_t>("send_buff_size", _dev_args.get_send_buff_size())); + //In packets + const size_t fc_handle_window = (fc_window / N230_TX_FC_RESPONSE_FREQ); + + perif.deframer->configure_flow_control(0/*cycs off*/, fc_handle_window); + boost::shared_ptr<tx_fc_cache_t> fc_cache(new tx_fc_cache_t()); + fc_cache->stream_channel = stream_i; + fc_cache->device_channel = chan; + fc_cache->async_queue = async_md; + fc_cache->old_async_queue = _async_md_queue; + + tick_rate_retriever_t get_tick_rate_fn = boost::bind(&n230_stream_manager::_get_tick_rate, this); + task::sptr task = task::make( + boost::bind(&n230_stream_manager::_handle_tx_async_msgs, + fc_cache, xport, get_tick_rate_fn)); + + //Give the streamer a functor to get the send buffer + //get_tx_buff_with_flowctrl is static so bind has no lifetime issues + //xport.send (sptr) is required to add streamer->data-transport lifetime dependency + //task (sptr) is required to add a streamer->async-handler lifetime dependency + my_streamer->set_xport_chan_get_buff( + stream_i, + boost::bind(&n230_stream_manager::_get_tx_buff_with_flowctrl, task, fc_cache, xport, fc_window, _1) + ); + //Give the streamer a functor handled received async messages + my_streamer->set_async_receiver( + boost::bind(&async_md_queue_t::pop_with_timed_wait, async_md, _1, _2) + ); + my_streamer->set_xport_chan_sid(stream_i, true, sid.get()); + my_streamer->set_enable_trailer(false); //TODO not implemented trailer support yet + + //Store a weak pointer to prevent a streamer->manager->streamer circular dependency + _tx_streamers[chan] = boost::weak_ptr<sph::send_packet_streamer>(my_streamer); + _tx_stream_cached_args[chan] = args; + + //Sets tick and samp rates on all streamer + update_tick_rate(_get_tick_rate()); + + //TODO: Find a way to remove this dependency + property_tree::sptr prop_tree = _tree.lock(); + if (prop_tree) { + //TODO: Update this to support multiple motherboards + const fs_path mb_path = "/mboards/0"; + prop_tree->access<double>(mb_path / "tx_dsps" / boost::lexical_cast<std::string>(chan) / "rate" / "value").update(); + } + } + update_stream_states(); + + return my_streamer; +} + +/*********************************************************************** + * Async Message Receiver + **********************************************************************/ +bool n230_stream_manager::recv_async_msg(async_metadata_t &async_metadata, double timeout) +{ + return _async_md_queue->pop_with_timed_wait(async_metadata, timeout); +} + +/*********************************************************************** + * Sample Rate Updaters + **********************************************************************/ +void n230_stream_manager::update_rx_samp_rate(const size_t dspno, const double rate) +{ + boost::shared_ptr<sph::recv_packet_streamer> my_streamer = + boost::dynamic_pointer_cast<sph::recv_packet_streamer>(_rx_streamers[dspno].lock()); + if (not my_streamer) return; + my_streamer->set_samp_rate(rate); + const double adj = _resource_mgr->get_radio(dspno).ddc->get_scaling_adjustment(); + my_streamer->set_scale_factor(adj); +} + +void n230_stream_manager::update_tx_samp_rate(const size_t dspno, const double rate) +{ + boost::shared_ptr<sph::send_packet_streamer> my_streamer = + boost::dynamic_pointer_cast<sph::send_packet_streamer>(_tx_streamers[dspno].lock()); + if (not my_streamer) return; + my_streamer->set_samp_rate(rate); + const double adj = _resource_mgr->get_radio(dspno).duc->get_scaling_adjustment(); + my_streamer->set_scale_factor(adj); +} + +/*********************************************************************** + * Tick Rate Updater + **********************************************************************/ +void n230_stream_manager::update_tick_rate(const double rate) +{ + for (size_t i = 0; i < fpga::NUM_RADIOS; i++) { + radio_resource_t& perif = _resource_mgr->get_radio(i); + + boost::shared_ptr<sph::recv_packet_streamer> my_rx_streamer = + boost::dynamic_pointer_cast<sph::recv_packet_streamer>(_rx_streamers[i].lock()); + if (my_rx_streamer) my_rx_streamer->set_tick_rate(rate); + perif.framer->set_tick_rate(rate); + + boost::shared_ptr<sph::send_packet_streamer> my_tx_streamer = + boost::dynamic_pointer_cast<sph::send_packet_streamer>(_tx_streamers[i].lock()); + if (my_tx_streamer) my_tx_streamer->set_tick_rate(rate); + } +} + +/*********************************************************************** + * Stream State Updater + **********************************************************************/ +void n230_stream_manager::update_stream_states() +{ + //extract settings from state variables + const bool enb_tx0 = bool(_tx_streamers[0].lock()); + const bool enb_rx0 = bool(_rx_streamers[0].lock()); + const bool enb_tx1 = bool(_tx_streamers[1].lock()); + const bool enb_rx1 = bool(_rx_streamers[1].lock()); + + fe_state_t fe0_state = NONE_STREAMING; + if (enb_tx0 && enb_rx0) fe0_state = TXRX_STREAMING; + else if (enb_tx0) fe0_state = TX_STREAMING; + else if (enb_rx0) fe0_state = RX_STREAMING; + + fe_state_t fe1_state = NONE_STREAMING; + if (enb_tx1 && enb_rx1) fe1_state = TXRX_STREAMING; + else if (enb_tx1) fe1_state = TX_STREAMING; + else if (enb_rx1) fe1_state = RX_STREAMING; + + _resource_mgr->get_frontend_ctrl().set_stream_state(fe0_state, fe1_state); +} + +size_t n230_stream_manager::_get_rx_flow_control_window(size_t frame_size, size_t sw_buff_size) +{ + double sw_buff_max = sw_buff_size * N230_RX_SW_BUFF_FULL_FACTOR; + size_t window_in_pkts = (static_cast<size_t>(sw_buff_max) / frame_size); + if (window_in_pkts == 0) { + throw uhd::value_error("recv_buff_size must be larger than the recv_frame_size."); + } + return window_in_pkts; +} + +void n230_stream_manager::_handle_overflow(const size_t i) +{ + boost::shared_ptr<sph::recv_packet_streamer> my_streamer = + boost::dynamic_pointer_cast<sph::recv_packet_streamer>(_rx_streamers[i].lock()); + if (my_streamer->get_num_channels() == 2) { + //MIMO + //find out if we were in continuous mode before stopping + const bool in_continuous_streaming_mode = _resource_mgr->get_radio(i).framer->in_continuous_streaming_mode(); + //stop streaming + my_streamer->issue_stream_cmd(stream_cmd_t::STREAM_MODE_STOP_CONTINUOUS); + //restart streaming + if (in_continuous_streaming_mode) { + stream_cmd_t stream_cmd(stream_cmd_t::STREAM_MODE_START_CONTINUOUS); + stream_cmd.stream_now = false; + stream_cmd.time_spec = _resource_mgr->get_radio(i).time->get_time_now() + time_spec_t(0.01); + my_streamer->issue_stream_cmd(stream_cmd); + } + } else { + _resource_mgr->get_radio(i).framer->handle_overflow(); + } +} + +void n230_stream_manager::_handle_rx_flowctrl( + const sid_t& sid, + zero_copy_if::sptr xport, + boost::shared_ptr<rx_fc_cache_t> fc_cache, + const size_t last_seq) +{ + static const size_t RXFC_PACKET_LEN_IN_WORDS = 2; + static const size_t RXFC_CMD_CODE_OFFSET = 0; + static const size_t RXFC_SEQ_NUM_OFFSET = 1; + + managed_send_buffer::sptr buff = xport->get_send_buff(0.0); + if (not buff) { + throw uhd::runtime_error("handle_rx_flowctrl timed out getting a send buffer"); + } + boost::uint32_t *pkt = buff->cast<boost::uint32_t *>(); + + //recover seq32 + size_t& seq_sw = fc_cache->last_seq_in; + const size_t seq_hw = seq_sw & HW_SEQ_NUM_MASK; + if (last_seq < seq_hw) seq_sw += (HW_SEQ_NUM_MASK + 1); + seq_sw &= ~HW_SEQ_NUM_MASK; + seq_sw |= last_seq; + + //load packet info + vrt::if_packet_info_t packet_info; + packet_info.packet_type = vrt::if_packet_info_t::PACKET_TYPE_CONTEXT; + packet_info.num_payload_words32 = RXFC_PACKET_LEN_IN_WORDS; + packet_info.num_payload_bytes = packet_info.num_payload_words32*sizeof(boost::uint32_t); + packet_info.packet_count = seq_sw; + packet_info.sob = false; + packet_info.eob = false; + packet_info.sid = sid.get(); + packet_info.has_sid = true; + packet_info.has_cid = false; + packet_info.has_tsi = false; + packet_info.has_tsf = false; + packet_info.has_tlr = false; + + //load header + _cvita_hdr_pack(pkt, packet_info); + + //load payload + pkt[packet_info.num_header_words32 + RXFC_CMD_CODE_OFFSET] = uhd::htonx<boost::uint32_t>(N230_EVENT_CODE_FLOW_CTRL); + pkt[packet_info.num_header_words32 + RXFC_SEQ_NUM_OFFSET] = uhd::htonx<boost::uint32_t>(seq_sw); + + //send the buffer over the interface + buff->commit(sizeof(boost::uint32_t)*(packet_info.num_packet_words32)); +} + +void n230_stream_manager::_handle_tx_async_msgs( + boost::shared_ptr<tx_fc_cache_t> fc_cache, + zero_copy_if::sptr xport, + tick_rate_retriever_t get_tick_rate) +{ + managed_recv_buffer::sptr buff = xport->get_recv_buff(); + if (not buff) return; + + //extract packet info + vrt::if_packet_info_t if_packet_info; + if_packet_info.num_packet_words32 = buff->size()/sizeof(boost::uint32_t); + const boost::uint32_t *packet_buff = buff->cast<const boost::uint32_t *>(); + + //unpacking can fail + uint32_t (*endian_conv)(uint32_t) = uhd::ntohx; + try { + _cvita_hdr_unpack(packet_buff, if_packet_info); + endian_conv = uhd::ntohx; + } catch(const std::exception &ex) { + UHD_MSG(error) << "Error parsing async message packet: " << ex.what() << std::endl; + return; + } + + //fill in the async metadata + async_metadata_t metadata; + load_metadata_from_buff( + endian_conv, metadata, if_packet_info, packet_buff, + get_tick_rate(), fc_cache->stream_channel); + + //The FC response and the burst ack are two indicators that the radio + //consumed packets. Use them to update the FC metadata + if (metadata.event_code == N230_EVENT_CODE_FLOW_CTRL or + metadata.event_code == async_metadata_t::EVENT_CODE_BURST_ACK + ) { + const size_t seq = metadata.user_payload[0]; + fc_cache->seq_queue.push_with_pop_on_full(seq); + } + + //FC responses don't propagate up to the user so filter them here + if (metadata.event_code != N230_EVENT_CODE_FLOW_CTRL) { + fc_cache->async_queue->push_with_pop_on_full(metadata); + metadata.channel = fc_cache->device_channel; + fc_cache->old_async_queue->push_with_pop_on_full(metadata); + standard_async_msg_prints(metadata); + } +} + +managed_send_buffer::sptr n230_stream_manager::_get_tx_buff_with_flowctrl( + task::sptr /*holds ref*/, + boost::shared_ptr<tx_fc_cache_t> fc_cache, + zero_copy_if::sptr xport, + size_t fc_pkt_window, + const double timeout) +{ + while (true) + { + const size_t delta = (fc_cache->last_seq_out & HW_SEQ_NUM_MASK) - (fc_cache->last_seq_ack & HW_SEQ_NUM_MASK); + if ((delta & HW_SEQ_NUM_MASK) <= fc_pkt_window) break; + + const bool ok = fc_cache->seq_queue.pop_with_timed_wait(fc_cache->last_seq_ack, timeout); + if (not ok) return managed_send_buffer::sptr(); //timeout waiting for flow control + } + + managed_send_buffer::sptr buff = xport->get_send_buff(timeout); + if (buff) fc_cache->last_seq_out++; //update seq, this will actually be a send + return buff; +} + +size_t n230_stream_manager::_get_tx_flow_control_window( + size_t payload_size, + size_t hw_buff_size) +{ + size_t window_in_pkts = hw_buff_size / payload_size; + if (window_in_pkts == 0) { + throw uhd::value_error("send_buff_size must be larger than the send_frame_size."); + } + return window_in_pkts; +} + +double n230_stream_manager::_get_tick_rate() +{ + return _resource_mgr->get_clk_pps_ctrl().get_tick_rate(); +} + +void n230_stream_manager::_cvita_hdr_unpack( + const boost::uint32_t *packet_buff, + vrt::if_packet_info_t &if_packet_info) +{ + if_packet_info.link_type = vrt::if_packet_info_t::LINK_TYPE_CHDR; + return vrt::if_hdr_unpack_be(packet_buff, if_packet_info); +} + +void n230_stream_manager::_cvita_hdr_pack( + boost::uint32_t *packet_buff, + vrt::if_packet_info_t &if_packet_info) +{ + if_packet_info.link_type = vrt::if_packet_info_t::LINK_TYPE_CHDR; + return vrt::if_hdr_pack_be(packet_buff, if_packet_info); +} + +}}} //namespace diff --git a/host/lib/usrp/n230/n230_stream_manager.hpp b/host/lib/usrp/n230/n230_stream_manager.hpp new file mode 100644 index 000000000..7a496c4e9 --- /dev/null +++ b/host/lib/usrp/n230/n230_stream_manager.hpp @@ -0,0 +1,151 @@ +// +// Copyright 2014 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 +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +#ifndef INCLUDED_N230_STREAM_MANAGER_HPP +#define INCLUDED_N230_STREAM_MANAGER_HPP + +#include "time_core_3000.hpp" +#include "rx_vita_core_3000.hpp" +#include <uhd/types/sid.hpp> +#include <uhd/types/device_addr.hpp> +#include <uhd/types/metadata.hpp> +#include <uhd/transport/zero_copy.hpp> +#include <uhd/transport/bounded_buffer.hpp> +#include <uhd/transport/vrt_if_packet.hpp> +#include <uhd/property_tree.hpp> +#include <uhd/utils/tasks.hpp> +#include <boost/smart_ptr.hpp> +#include "n230_device_args.hpp" +#include "n230_resource_manager.hpp" + +namespace uhd { namespace usrp { namespace n230 { + +class n230_stream_manager : public boost::noncopyable +{ +public: //Methods + n230_stream_manager( + const n230_device_args_t& dev_args, + boost::shared_ptr<n230_resource_manager> resource_mgr, + boost::weak_ptr<property_tree> prop_tree); + virtual ~n230_stream_manager(); + + rx_streamer::sptr get_rx_stream( + const uhd::stream_args_t &args); + + tx_streamer::sptr get_tx_stream( + const uhd::stream_args_t &args_); + + bool recv_async_msg( + async_metadata_t &async_metadata, + double timeout); + + void update_stream_states(); + + void update_rx_samp_rate( + const size_t dspno, const double rate); + + void update_tx_samp_rate( + const size_t dspno, const double rate); + + void update_tick_rate( + const double rate); + +private: + typedef transport::bounded_buffer<async_metadata_t> async_md_queue_t; + + struct rx_fc_cache_t + { + rx_fc_cache_t(): + last_seq_in(0){} + size_t last_seq_in; + }; + + struct tx_fc_cache_t + { + tx_fc_cache_t(): + stream_channel(0), + device_channel(0), + last_seq_out(0), + last_seq_ack(0), + seq_queue(1){} + size_t stream_channel; + size_t device_channel; + size_t last_seq_out; + size_t last_seq_ack; + transport::bounded_buffer<size_t> seq_queue; + boost::shared_ptr<async_md_queue_t> async_queue; + boost::shared_ptr<async_md_queue_t> old_async_queue; + }; + + typedef boost::function<double(void)> tick_rate_retriever_t; + + void _handle_overflow(const size_t i); + + double _get_tick_rate(); + + static size_t _get_rx_flow_control_window( + size_t frame_size, size_t sw_buff_size); + + static void _handle_rx_flowctrl( + const sid_t& sid, + transport::zero_copy_if::sptr xport, + boost::shared_ptr<rx_fc_cache_t> fc_cache, + const size_t last_seq); + + static void _handle_tx_async_msgs( + boost::shared_ptr<tx_fc_cache_t> guts, + transport::zero_copy_if::sptr xport, + tick_rate_retriever_t get_tick_rate); + + static transport::managed_send_buffer::sptr _get_tx_buff_with_flowctrl( + task::sptr /*holds ref*/, + boost::shared_ptr<tx_fc_cache_t> guts, + transport::zero_copy_if::sptr xport, + size_t fc_pkt_window, + const double timeout); + + static size_t _get_tx_flow_control_window( + size_t payload_size, + size_t hw_buff_size); + + static void _cvita_hdr_unpack( + const boost::uint32_t *packet_buff, + transport::vrt::if_packet_info_t &if_packet_info); + + static void _cvita_hdr_pack( + boost::uint32_t *packet_buff, + transport::vrt::if_packet_info_t &if_packet_info); + + const n230_device_args_t _dev_args; + boost::shared_ptr<n230_resource_manager> _resource_mgr; + //TODO: Find a way to remove this dependency + boost::weak_ptr<property_tree> _tree; + + boost::mutex _stream_setup_mutex; + uhd::msg_task::sptr _async_task; + boost::shared_ptr<async_md_queue_t> _async_md_queue; + boost::weak_ptr<uhd::tx_streamer> _tx_streamers[fpga::NUM_RADIOS]; + boost::weak_ptr<uhd::rx_streamer> _rx_streamers[fpga::NUM_RADIOS]; + stream_args_t _tx_stream_cached_args[fpga::NUM_RADIOS]; + stream_args_t _rx_stream_cached_args[fpga::NUM_RADIOS]; + + static const boost::uint32_t HW_SEQ_NUM_MASK = 0xFFF; +}; + +}}} //namespace + +#endif //INCLUDED_N230_STREAM_MANAGER_HPP diff --git a/host/lib/usrp/n230/n230_uart.cpp b/host/lib/usrp/n230/n230_uart.cpp new file mode 100644 index 000000000..20936c303 --- /dev/null +++ b/host/lib/usrp/n230/n230_uart.cpp @@ -0,0 +1,131 @@ +// +// Copyright 2013 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 +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +#include "n230_uart.hpp" + +#include <uhd/transport/bounded_buffer.hpp> +#include <uhd/transport/vrt_if_packet.hpp> +#include <uhd/utils/byteswap.hpp> +#include <uhd/utils/msg.hpp> +#include <uhd/types/time_spec.hpp> +#include <uhd/exception.hpp> + +using namespace uhd; +using namespace uhd::transport; + +namespace uhd { namespace usrp { namespace n230 { + +struct n230_uart_impl : n230_uart +{ + n230_uart_impl(zero_copy_if::sptr xport, const boost::uint32_t sid): + _xport(xport), + _sid(sid), + _count(0), + _char_queue(4096) + { + //this default baud divider is over 9000 + this->set_baud_divider(9001); + + //create a task to handle incoming packets + _recv_task = uhd::task::make(boost::bind(&n230_uart_impl::handle_recv, this)); + } + + void send_char(const char ch) + { + managed_send_buffer::sptr buff = _xport->get_send_buff(); + UHD_ASSERT_THROW(bool(buff)); + + vrt::if_packet_info_t packet_info; + packet_info.link_type = vrt::if_packet_info_t::LINK_TYPE_CHDR; + packet_info.packet_type = vrt::if_packet_info_t::PACKET_TYPE_CONTEXT; + packet_info.num_payload_words32 = 2; + packet_info.num_payload_bytes = packet_info.num_payload_words32*sizeof(boost::uint32_t); + packet_info.packet_count = _count++; + packet_info.sob = false; + packet_info.eob = false; + packet_info.sid = _sid; + packet_info.has_sid = true; + packet_info.has_cid = false; + packet_info.has_tsi = false; + packet_info.has_tsf = false; + packet_info.has_tlr = false; + + boost::uint32_t *packet_buff = buff->cast<boost::uint32_t *>(); + vrt::if_hdr_pack_le(packet_buff, packet_info); + packet_buff[packet_info.num_header_words32+0] = uhd::htonx(boost::uint32_t(_baud_div)); + packet_buff[packet_info.num_header_words32+1] = uhd::htonx(boost::uint32_t(ch)); + buff->commit(packet_info.num_packet_words32*sizeof(boost::uint32_t)); + } + + void write_uart(const std::string &buff) + { + static bool r_sent = false; + for (size_t i = 0; i < buff.size(); i++) + { + if (buff[i] == '\n' and not r_sent) this->send_char('\r'); + this->send_char(buff[i]); + r_sent = (buff[i] == '\r'); + } + } + + std::string read_uart(double timeout) + { + std::string line; + char ch = '\0'; + while (_char_queue.pop_with_timed_wait(ch, timeout)) + { + if (ch == '\r') continue; + line += std::string(&ch, 1); + if (ch == '\n') return line; + } + return line; + } + + void handle_recv(void) + { + managed_recv_buffer::sptr buff = _xport->get_recv_buff(); + if (not buff) + return; + const boost::uint32_t *packet_buff = buff->cast<const boost::uint32_t *>(); + vrt::if_packet_info_t packet_info; + packet_info.link_type = vrt::if_packet_info_t::LINK_TYPE_CHDR; + packet_info.num_packet_words32 = buff->size()/sizeof(boost::uint32_t); + vrt::if_hdr_unpack_be(packet_buff, packet_info); + const char ch = char(uhd::ntohx(packet_buff[packet_info.num_header_words32+1])); + _char_queue.push_with_pop_on_full(ch); + } + + void set_baud_divider(const double baud_div) + { + _baud_div = size_t(baud_div + 0.5); + } + + const zero_copy_if::sptr _xport; + const boost::uint32_t _sid; + size_t _count; + size_t _baud_div; + bounded_buffer<char> _char_queue; + uhd::task::sptr _recv_task; +}; + + +n230_uart::sptr n230_uart::make(zero_copy_if::sptr xport, const boost::uint32_t sid) +{ + return n230_uart::sptr(new n230_uart_impl(xport, sid)); +} + +}}} //namespace diff --git a/host/lib/usrp/n230/n230_uart.hpp b/host/lib/usrp/n230/n230_uart.hpp new file mode 100644 index 000000000..0bde12ab2 --- /dev/null +++ b/host/lib/usrp/n230/n230_uart.hpp @@ -0,0 +1,38 @@ +// +// Copyright 2013 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 +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +#ifndef INCLUDED_N230_UART_HPP +#define INCLUDED_N230_UART_HPP + +#include <uhd/transport/zero_copy.hpp> +#include <uhd/types/serial.hpp> //uart iface +#include <uhd/utils/tasks.hpp> +#include <boost/shared_ptr.hpp> +#include <boost/utility.hpp> +namespace uhd { namespace usrp { namespace n230 { + +class n230_uart: boost::noncopyable, public uhd::uart_iface +{ +public: + typedef boost::shared_ptr<n230_uart> sptr; + static sptr make(uhd::transport::zero_copy_if::sptr, const boost::uint32_t sid); + virtual void set_baud_divider(const double baud_div) = 0; +}; + +}}} //namespace + +#endif /* INCLUDED_N230_UART_HPP */ diff --git a/host/lib/usrp/usrp1/CMakeLists.txt b/host/lib/usrp/usrp1/CMakeLists.txt index 47344e841..6924ba3b0 100644 --- a/host/lib/usrp/usrp1/CMakeLists.txt +++ b/host/lib/usrp/usrp1/CMakeLists.txt @@ -22,8 +22,6 @@ ######################################################################## # Conditionally configure the USRP1 support ######################################################################## -LIBUHD_REGISTER_COMPONENT("USRP1" ENABLE_USRP1 ON "ENABLE_LIBUHD;ENABLE_USB" OFF OFF) - IF(ENABLE_USRP1) LIBUHD_APPEND_SOURCES( ${CMAKE_CURRENT_SOURCE_DIR}/codec_ctrl.cpp diff --git a/host/lib/usrp/usrp1/dboard_iface.cpp b/host/lib/usrp/usrp1/dboard_iface.cpp index 4c3141d9e..5640e8dae 100644 --- a/host/lib/usrp/usrp1/dboard_iface.cpp +++ b/host/lib/usrp/usrp1/dboard_iface.cpp @@ -1,5 +1,5 @@ // -// Copyright 2010-2012 Ettus Research LLC +// Copyright 2010-2012,2015,2016 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 @@ -63,6 +63,7 @@ using namespace uhd; using namespace uhd::usrp; +using namespace uhd::usrp::gpio_atr; using namespace boost::assign; static const dboard_id_t tvrx_id(0x0040); @@ -106,12 +107,23 @@ public: void write_aux_dac(unit_t, aux_dac_t, double); double read_aux_adc(unit_t, aux_adc_t); + void set_pin_ctrl(unit_t unit, boost::uint32_t value, boost::uint32_t mask = 0xffffffff); + boost::uint32_t get_pin_ctrl(unit_t unit); + void set_atr_reg(unit_t unit, atr_reg_t reg, boost::uint32_t value, boost::uint32_t mask = 0xffffffff); + boost::uint32_t get_atr_reg(unit_t unit, atr_reg_t reg); + void set_gpio_ddr(unit_t unit, boost::uint32_t value, boost::uint32_t mask = 0xffffffff); + boost::uint32_t get_gpio_ddr(unit_t unit); + void set_gpio_out(unit_t unit, boost::uint32_t value, boost::uint32_t mask = 0xffffffff); + boost::uint32_t get_gpio_out(unit_t unit); + boost::uint32_t read_gpio(unit_t unit); + void _set_pin_ctrl(unit_t, boost::uint16_t); void _set_atr_reg(unit_t, atr_reg_t, boost::uint16_t); void _set_gpio_ddr(unit_t, boost::uint16_t); void _set_gpio_out(unit_t, boost::uint16_t); - void set_gpio_debug(unit_t, int); - boost::uint16_t read_gpio(unit_t); + + void set_command_time(const uhd::time_spec_t& t); + uhd::time_spec_t get_command_time(void); void write_i2c(boost::uint16_t, const byte_vector_t &); byte_vector_t read_i2c(boost::uint16_t, size_t); @@ -131,6 +143,7 @@ public: double get_clock_rate(unit_t); void set_clock_enabled(unit_t, bool); double get_codec_rate(unit_t); + void set_fe_connection(unit_t unit, const std::string&, const fe_connection_t& fe_conn); private: usrp1_iface::sptr _iface; @@ -139,6 +152,8 @@ private: const usrp1_impl::dboard_slot_t _dboard_slot; const double &_master_clock_rate; const dboard_id_t _rx_dboard_id; + uhd::dict<unit_t, boost::uint16_t> _pin_ctrl, _gpio_out, _gpio_ddr; + uhd::dict<unit_t, uhd::dict<atr_reg_t, boost::uint16_t> > _atr_regs; }; /*********************************************************************** @@ -217,6 +232,65 @@ double usrp1_dboard_iface::get_codec_rate(unit_t){ /*********************************************************************** * GPIO **********************************************************************/ +template <typename T> +static T shadow_it(T &shadow, const T &value, const T &mask){ + shadow = (shadow & ~mask) | (value & mask); + return shadow; +} + +void usrp1_dboard_iface::set_pin_ctrl(unit_t unit, boost::uint32_t value, boost::uint32_t mask){ + _set_pin_ctrl(unit, shadow_it(_pin_ctrl[unit], static_cast<boost::uint16_t>(value), static_cast<boost::uint16_t>(mask))); +} + +boost::uint32_t usrp1_dboard_iface::get_pin_ctrl(unit_t unit){ + return _pin_ctrl[unit]; +} + +void usrp1_dboard_iface::set_atr_reg(unit_t unit, atr_reg_t reg, boost::uint32_t value, boost::uint32_t mask){ + _set_atr_reg(unit, reg, shadow_it(_atr_regs[unit][reg], static_cast<boost::uint16_t>(value), static_cast<boost::uint16_t>(mask))); +} + +boost::uint32_t usrp1_dboard_iface::get_atr_reg(unit_t unit, atr_reg_t reg){ + return _atr_regs[unit][reg]; +} + +void usrp1_dboard_iface::set_gpio_ddr(unit_t unit, boost::uint32_t value, boost::uint32_t mask){ + _set_gpio_ddr(unit, shadow_it(_gpio_ddr[unit], static_cast<boost::uint16_t>(value), static_cast<boost::uint16_t>(mask))); +} + +boost::uint32_t usrp1_dboard_iface::get_gpio_ddr(unit_t unit){ + return _gpio_ddr[unit]; +} + +void usrp1_dboard_iface::set_gpio_out(unit_t unit, boost::uint32_t value, boost::uint32_t mask){ + _set_gpio_out(unit, shadow_it(_gpio_out[unit], static_cast<boost::uint16_t>(value), static_cast<boost::uint16_t>(mask))); +} + +boost::uint32_t usrp1_dboard_iface::get_gpio_out(unit_t unit){ + return _gpio_out[unit]; +} + +boost::uint32_t usrp1_dboard_iface::read_gpio(unit_t unit) +{ + boost::uint32_t out_value; + + if (_dboard_slot == usrp1_impl::DBOARD_SLOT_A) + out_value = _iface->peek32(1); + else if (_dboard_slot == usrp1_impl::DBOARD_SLOT_B) + out_value = _iface->peek32(2); + else + UHD_THROW_INVALID_CODE_PATH(); + + switch(unit) { + case UNIT_RX: + return (boost::uint32_t)((out_value >> 16) & 0x0000ffff); + case UNIT_TX: + return (boost::uint32_t)((out_value >> 0) & 0x0000ffff); + default: UHD_THROW_INVALID_CODE_PATH(); + } + UHD_ASSERT_THROW(false); +} + void usrp1_dboard_iface::_set_pin_ctrl(unit_t unit, boost::uint16_t value) { switch(unit) { @@ -232,6 +306,7 @@ void usrp1_dboard_iface::_set_pin_ctrl(unit_t unit, boost::uint16_t value) else if (_dboard_slot == usrp1_impl::DBOARD_SLOT_B) _iface->poke32(FR_ATR_MASK_2, value); break; + default: UHD_THROW_INVALID_CODE_PATH(); } } @@ -250,6 +325,7 @@ void usrp1_dboard_iface::_set_gpio_ddr(unit_t unit, boost::uint16_t value) else if (_dboard_slot == usrp1_impl::DBOARD_SLOT_B) _iface->poke32(FR_OE_2, 0xffff0000 | value); break; + default: UHD_THROW_INVALID_CODE_PATH(); } } @@ -268,34 +344,10 @@ void usrp1_dboard_iface::_set_gpio_out(unit_t unit, boost::uint16_t value) else if (_dboard_slot == usrp1_impl::DBOARD_SLOT_B) _iface->poke32(FR_IO_2, 0xffff0000 | value); break; + default: UHD_THROW_INVALID_CODE_PATH(); } } -void usrp1_dboard_iface::set_gpio_debug(unit_t, int) -{ - /* NOP */ -} - -boost::uint16_t usrp1_dboard_iface::read_gpio(unit_t unit) -{ - boost::uint32_t out_value; - - if (_dboard_slot == usrp1_impl::DBOARD_SLOT_A) - out_value = _iface->peek32(1); - else if (_dboard_slot == usrp1_impl::DBOARD_SLOT_B) - out_value = _iface->peek32(2); - else - UHD_THROW_INVALID_CODE_PATH(); - - switch(unit) { - case UNIT_RX: - return (boost::uint16_t)((out_value >> 16) & 0x0000ffff); - case UNIT_TX: - return (boost::uint16_t)((out_value >> 0) & 0x0000ffff); - } - UHD_ASSERT_THROW(false); -} - void usrp1_dboard_iface::_set_atr_reg(unit_t unit, atr_reg_t atr, boost::uint16_t value) { @@ -316,6 +368,7 @@ void usrp1_dboard_iface::_set_atr_reg(unit_t unit, else if (_dboard_slot == usrp1_impl::DBOARD_SLOT_B) _iface->poke32(FR_ATR_RXVAL_2, value); break; + default: UHD_THROW_INVALID_CODE_PATH(); } } else if (atr == ATR_REG_FULL_DUPLEX) { switch(unit) { @@ -331,6 +384,7 @@ void usrp1_dboard_iface::_set_atr_reg(unit_t unit, else if (_dboard_slot == usrp1_impl::DBOARD_SLOT_B) _iface->poke32(FR_ATR_TXVAL_2, value); break; + default: UHD_THROW_INVALID_CODE_PATH(); } } } @@ -361,6 +415,8 @@ static boost::uint32_t unit_to_otw_spi_dev(dboard_iface::unit_t unit, return SPI_ENABLE_RX_B; else break; + default: + break; } UHD_THROW_INVALID_CODE_PATH(); } @@ -429,3 +485,23 @@ double usrp1_dboard_iface::read_aux_adc(dboard_iface::unit_t unit, return _codec->read_aux_adc(unit_to_which_to_aux_adc[unit][which]); } + +/*********************************************************************** + * Unsupported + **********************************************************************/ + +void usrp1_dboard_iface::set_command_time(const uhd::time_spec_t&) +{ + throw uhd::not_implemented_error("timed command support not implemented"); +} + +uhd::time_spec_t usrp1_dboard_iface::get_command_time() +{ + throw uhd::not_implemented_error("timed command support not implemented"); +} + +void usrp1_dboard_iface::set_fe_connection(unit_t, const std::string&, const fe_connection_t&) +{ + throw uhd::not_implemented_error("fe connection configuration support not implemented"); +} + diff --git a/host/lib/usrp/usrp1/usrp1_impl.cpp b/host/lib/usrp/usrp1/usrp1_impl.cpp index dbd5408e8..5e1a70a8f 100644 --- a/host/lib/usrp/usrp1/usrp1_impl.cpp +++ b/host/lib/usrp/usrp1/usrp1_impl.cpp @@ -209,13 +209,13 @@ usrp1_impl::usrp1_impl(const device_addr_t &device_addr){ const fs_path mb_path = "/mboards/0"; _tree->create<std::string>(mb_path / "name").set("USRP1"); _tree->create<std::string>(mb_path / "load_eeprom") - .subscribe(boost::bind(&fx2_ctrl::usrp_load_eeprom, _fx2_ctrl, _1)); + .add_coerced_subscriber(boost::bind(&fx2_ctrl::usrp_load_eeprom, _fx2_ctrl, _1)); //////////////////////////////////////////////////////////////////// // create user-defined control objects //////////////////////////////////////////////////////////////////// _tree->create<std::pair<boost::uint8_t, boost::uint32_t> >(mb_path / "user" / "regs") - .subscribe(boost::bind(&usrp1_impl::set_reg, this, _1)); + .add_coerced_subscriber(boost::bind(&usrp1_impl::set_reg, this, _1)); //////////////////////////////////////////////////////////////////// // setup the mboard eeprom @@ -223,7 +223,7 @@ usrp1_impl::usrp1_impl(const device_addr_t &device_addr){ const mboard_eeprom_t mb_eeprom(*_fx2_ctrl, USRP1_EEPROM_MAP_KEY); _tree->create<mboard_eeprom_t>(mb_path / "eeprom") .set(mb_eeprom) - .subscribe(boost::bind(&usrp1_impl::set_mb_eeprom, this, _1)); + .add_coerced_subscriber(boost::bind(&usrp1_impl::set_mb_eeprom, this, _1)); //////////////////////////////////////////////////////////////////// // create clock control objects @@ -247,7 +247,7 @@ usrp1_impl::usrp1_impl(const device_addr_t &device_addr){ } UHD_MSG(status) << boost::format("Using FPGA clock rate of %fMHz...") % (_master_clock_rate/1e6) << std::endl; _tree->create<double>(mb_path / "tick_rate") - .subscribe(boost::bind(&usrp1_impl::update_tick_rate, this, _1)) + .add_coerced_subscriber(boost::bind(&usrp1_impl::update_tick_rate, this, _1)) .set(_master_clock_rate); //////////////////////////////////////////////////////////////////// @@ -260,13 +260,13 @@ usrp1_impl::usrp1_impl(const device_addr_t &device_addr){ _tree->create<std::string>(rx_codec_path / "name").set("ad9522"); _tree->create<meta_range_t>(rx_codec_path / "gains/pga/range").set(usrp1_codec_ctrl::rx_pga_gain_range); _tree->create<double>(rx_codec_path / "gains/pga/value") - .coerce(boost::bind(&usrp1_impl::update_rx_codec_gain, this, db, _1)) + .set_coercer(boost::bind(&usrp1_impl::update_rx_codec_gain, this, db, _1)) .set(0.0); _tree->create<std::string>(tx_codec_path / "name").set("ad9522"); _tree->create<meta_range_t>(tx_codec_path / "gains/pga/range").set(usrp1_codec_ctrl::tx_pga_gain_range); _tree->create<double>(tx_codec_path / "gains/pga/value") - .subscribe(boost::bind(&usrp1_codec_ctrl::set_tx_pga_gain, _dbc[db].codec, _1)) - .publish(boost::bind(&usrp1_codec_ctrl::get_tx_pga_gain, _dbc[db].codec)) + .add_coerced_subscriber(boost::bind(&usrp1_codec_ctrl::set_tx_pga_gain, _dbc[db].codec, _1)) + .set_publisher(boost::bind(&usrp1_codec_ctrl::get_tx_pga_gain, _dbc[db].codec)) .set(0.0); } @@ -281,18 +281,18 @@ usrp1_impl::usrp1_impl(const device_addr_t &device_addr){ //////////////////////////////////////////////////////////////////// _tree->create<subdev_spec_t>(mb_path / "rx_subdev_spec") .set(subdev_spec_t()) - .subscribe(boost::bind(&usrp1_impl::update_rx_subdev_spec, this, _1)); + .add_coerced_subscriber(boost::bind(&usrp1_impl::update_rx_subdev_spec, this, _1)); _tree->create<subdev_spec_t>(mb_path / "tx_subdev_spec") .set(subdev_spec_t()) - .subscribe(boost::bind(&usrp1_impl::update_tx_subdev_spec, this, _1)); + .add_coerced_subscriber(boost::bind(&usrp1_impl::update_tx_subdev_spec, this, _1)); BOOST_FOREACH(const std::string &db, _dbc.keys()){ const fs_path rx_fe_path = mb_path / "rx_frontends" / db; _tree->create<std::complex<double> >(rx_fe_path / "dc_offset" / "value") - .coerce(boost::bind(&usrp1_impl::set_rx_dc_offset, this, db, _1)) + .set_coercer(boost::bind(&usrp1_impl::set_rx_dc_offset, this, db, _1)) .set(std::complex<double>(0.0, 0.0)); _tree->create<bool>(rx_fe_path / "dc_offset" / "enable") - .subscribe(boost::bind(&usrp1_impl::set_enb_rx_dc_offset, this, db, _1)) + .add_coerced_subscriber(boost::bind(&usrp1_impl::set_enb_rx_dc_offset, this, db, _1)) .set(true); } @@ -303,19 +303,19 @@ usrp1_impl::usrp1_impl(const device_addr_t &device_addr){ for (size_t dspno = 0; dspno < get_num_ddcs(); dspno++){ fs_path rx_dsp_path = mb_path / str(boost::format("rx_dsps/%u") % dspno); _tree->create<meta_range_t>(rx_dsp_path / "rate/range") - .publish(boost::bind(&usrp1_impl::get_rx_dsp_host_rates, this)); + .set_publisher(boost::bind(&usrp1_impl::get_rx_dsp_host_rates, this)); _tree->create<double>(rx_dsp_path / "rate/value") .set(1e6) //some default rate - .coerce(boost::bind(&usrp1_impl::update_rx_samp_rate, this, dspno, _1)); + .set_coercer(boost::bind(&usrp1_impl::update_rx_samp_rate, this, dspno, _1)); _tree->create<double>(rx_dsp_path / "freq/value") - .coerce(boost::bind(&usrp1_impl::update_rx_dsp_freq, this, dspno, _1)); + .set_coercer(boost::bind(&usrp1_impl::update_rx_dsp_freq, this, dspno, _1)); _tree->create<meta_range_t>(rx_dsp_path / "freq/range") - .publish(boost::bind(&usrp1_impl::get_rx_dsp_freq_range, this)); + .set_publisher(boost::bind(&usrp1_impl::get_rx_dsp_freq_range, this)); _tree->create<stream_cmd_t>(rx_dsp_path / "stream_cmd"); if (dspno == 0){ - //only subscribe the callback for dspno 0 since it will stream all dsps + //only add_coerced_subscriber the callback for dspno 0 since it will stream all dsps _tree->access<stream_cmd_t>(rx_dsp_path / "stream_cmd") - .subscribe(boost::bind(&soft_time_ctrl::issue_stream_cmd, _soft_time_ctrl, _1)); + .add_coerced_subscriber(boost::bind(&soft_time_ctrl::issue_stream_cmd, _soft_time_ctrl, _1)); } } @@ -326,22 +326,22 @@ usrp1_impl::usrp1_impl(const device_addr_t &device_addr){ for (size_t dspno = 0; dspno < get_num_ducs(); dspno++){ fs_path tx_dsp_path = mb_path / str(boost::format("tx_dsps/%u") % dspno); _tree->create<meta_range_t>(tx_dsp_path / "rate/range") - .publish(boost::bind(&usrp1_impl::get_tx_dsp_host_rates, this)); + .set_publisher(boost::bind(&usrp1_impl::get_tx_dsp_host_rates, this)); _tree->create<double>(tx_dsp_path / "rate/value") .set(1e6) //some default rate - .coerce(boost::bind(&usrp1_impl::update_tx_samp_rate, this, dspno, _1)); + .set_coercer(boost::bind(&usrp1_impl::update_tx_samp_rate, this, dspno, _1)); _tree->create<double>(tx_dsp_path / "freq/value") - .coerce(boost::bind(&usrp1_impl::update_tx_dsp_freq, this, dspno, _1)); + .set_coercer(boost::bind(&usrp1_impl::update_tx_dsp_freq, this, dspno, _1)); _tree->create<meta_range_t>(tx_dsp_path / "freq/range") - .publish(boost::bind(&usrp1_impl::get_tx_dsp_freq_range, this)); + .set_publisher(boost::bind(&usrp1_impl::get_tx_dsp_freq_range, this)); } //////////////////////////////////////////////////////////////////// // create time control objects //////////////////////////////////////////////////////////////////// _tree->create<time_spec_t>(mb_path / "time/now") - .publish(boost::bind(&soft_time_ctrl::get_time, _soft_time_ctrl)) - .subscribe(boost::bind(&soft_time_ctrl::set_time, _soft_time_ctrl, _1)); + .set_publisher(boost::bind(&soft_time_ctrl::get_time, _soft_time_ctrl)) + .add_coerced_subscriber(boost::bind(&soft_time_ctrl::set_time, _soft_time_ctrl, _1)); _tree->create<std::vector<std::string> >(mb_path / "clock_source/options").set(std::vector<std::string>(1, "internal")); _tree->create<std::vector<std::string> >(mb_path / "time_source/options").set(std::vector<std::string>(1, "none")); @@ -365,24 +365,23 @@ usrp1_impl::usrp1_impl(const device_addr_t &device_addr){ //create the properties and register subscribers _tree->create<dboard_eeprom_t>(mb_path / "dboards" / db/ "rx_eeprom") .set(rx_db_eeprom) - .subscribe(boost::bind(&usrp1_impl::set_db_eeprom, this, db, "rx", _1)); + .add_coerced_subscriber(boost::bind(&usrp1_impl::set_db_eeprom, this, db, "rx", _1)); _tree->create<dboard_eeprom_t>(mb_path / "dboards" / db/ "tx_eeprom") .set(tx_db_eeprom) - .subscribe(boost::bind(&usrp1_impl::set_db_eeprom, this, db, "tx", _1)); + .add_coerced_subscriber(boost::bind(&usrp1_impl::set_db_eeprom, this, db, "tx", _1)); _tree->create<dboard_eeprom_t>(mb_path / "dboards" / db/ "gdb_eeprom") .set(gdb_eeprom) - .subscribe(boost::bind(&usrp1_impl::set_db_eeprom, this, db, "gdb", _1)); + .add_coerced_subscriber(boost::bind(&usrp1_impl::set_db_eeprom, this, db, "gdb", _1)); //create a new dboard interface and manager - _dbc[db].dboard_iface = make_dboard_iface( + dboard_iface::sptr dboard_iface = make_dboard_iface( _iface, _dbc[db].codec, (db == "A")? DBOARD_SLOT_A : DBOARD_SLOT_B, _master_clock_rate, rx_db_eeprom.id ); - _tree->create<dboard_iface::sptr>(mb_path / "dboards" / db/ "iface").set(_dbc[db].dboard_iface); _dbc[db].dboard_manager = dboard_manager::make( rx_db_eeprom.id, tx_db_eeprom.id, gdb_eeprom.id, - _dbc[db].dboard_iface, _tree->subtree(mb_path / "dboards" / db) + dboard_iface, _tree->subtree(mb_path / "dboards" / db) ); //init the subdev specs if we have a dboard (wont leave this loop empty) diff --git a/host/lib/usrp/usrp1/usrp1_impl.hpp b/host/lib/usrp/usrp1/usrp1_impl.hpp index 012bc0794..da901bd6c 100644 --- a/host/lib/usrp/usrp1/usrp1_impl.hpp +++ b/host/lib/usrp/usrp1/usrp1_impl.hpp @@ -92,7 +92,6 @@ private: uhd::transport::usb_zero_copy::sptr _data_transport; struct db_container_type{ usrp1_codec_ctrl::sptr codec; - uhd::usrp::dboard_iface::sptr dboard_iface; uhd::usrp::dboard_manager::sptr dboard_manager; }; uhd::dict<std::string, db_container_type> _dbc; diff --git a/host/lib/usrp/usrp2/CMakeLists.txt b/host/lib/usrp/usrp2/CMakeLists.txt index d9894adaf..edf77a654 100644 --- a/host/lib/usrp/usrp2/CMakeLists.txt +++ b/host/lib/usrp/usrp2/CMakeLists.txt @@ -22,8 +22,6 @@ ######################################################################## # Conditionally configure the USRP2 support ######################################################################## -LIBUHD_REGISTER_COMPONENT("USRP2" ENABLE_USRP2 ON "ENABLE_LIBUHD" OFF OFF) - IF(ENABLE_USRP2) LIBUHD_APPEND_SOURCES( ${CMAKE_CURRENT_SOURCE_DIR}/clock_ctrl.cpp diff --git a/host/lib/usrp/usrp2/dboard_iface.cpp b/host/lib/usrp/usrp2/dboard_iface.cpp index 7bb69c7b7..a6ba1e0b7 100644 --- a/host/lib/usrp/usrp2/dboard_iface.cpp +++ b/host/lib/usrp/usrp2/dboard_iface.cpp @@ -1,5 +1,5 @@ // -// Copyright 2010-2012,2015 Ettus Research LLC +// Copyright 2010-2012,2015,2016 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 @@ -54,12 +54,16 @@ public: void write_aux_dac(unit_t, aux_dac_t, double); double read_aux_adc(unit_t, aux_adc_t); - void _set_pin_ctrl(unit_t, boost::uint16_t); - void _set_atr_reg(unit_t, atr_reg_t, boost::uint16_t); - void _set_gpio_ddr(unit_t, boost::uint16_t); - void _set_gpio_out(unit_t, boost::uint16_t); - void set_gpio_debug(unit_t, int); - boost::uint16_t read_gpio(unit_t); + void set_pin_ctrl(unit_t unit, boost::uint32_t value, boost::uint32_t mask = 0xffffffff); + boost::uint32_t get_pin_ctrl(unit_t unit); + void set_atr_reg(unit_t unit, atr_reg_t reg, boost::uint32_t value, boost::uint32_t mask = 0xffffffff); + boost::uint32_t get_atr_reg(unit_t unit, atr_reg_t reg); + void set_gpio_ddr(unit_t unit, boost::uint32_t value, boost::uint32_t mask = 0xffffffff); + boost::uint32_t get_gpio_ddr(unit_t unit); + void set_gpio_out(unit_t unit, boost::uint32_t value, boost::uint32_t mask = 0xffffffff); + boost::uint32_t get_gpio_out(unit_t unit); + boost::uint32_t read_gpio(unit_t unit); + void set_command_time(const uhd::time_spec_t& t); uhd::time_spec_t get_command_time(void); @@ -71,6 +75,7 @@ public: std::vector<double> get_clock_rates(unit_t); void set_clock_enabled(unit_t, bool); double get_codec_rate(unit_t); + void set_fe_connection(unit_t unit, const std::string&, const fe_connection_t& fe_conn); void write_spi( unit_t unit, @@ -149,18 +154,22 @@ usrp2_dboard_iface::~usrp2_dboard_iface(void){ * Clocks **********************************************************************/ void usrp2_dboard_iface::set_clock_rate(unit_t unit, double rate){ + if (unit == UNIT_BOTH) throw uhd::runtime_error("UNIT_BOTH not supported."); _clock_rates[unit] = rate; //set to shadow switch(unit){ case UNIT_RX: _clock_ctrl->set_rate_rx_dboard_clock(rate); return; case UNIT_TX: _clock_ctrl->set_rate_tx_dboard_clock(rate); return; + default: UHD_THROW_INVALID_CODE_PATH(); } } double usrp2_dboard_iface::get_clock_rate(unit_t unit){ + if (unit == UNIT_BOTH) throw uhd::runtime_error("UNIT_BOTH not supported."); return _clock_rates[unit]; //get from shadow } std::vector<double> usrp2_dboard_iface::get_clock_rates(unit_t unit){ + if (unit == UNIT_BOTH) throw uhd::runtime_error("UNIT_BOTH not supported."); switch(unit){ case UNIT_RX: return _clock_ctrl->get_rates_rx_dboard_clock(); case UNIT_TX: return _clock_ctrl->get_rates_tx_dboard_clock(); @@ -169,40 +178,56 @@ std::vector<double> usrp2_dboard_iface::get_clock_rates(unit_t unit){ } void usrp2_dboard_iface::set_clock_enabled(unit_t unit, bool enb){ + if (unit == UNIT_BOTH) throw uhd::runtime_error("UNIT_BOTH not supported."); switch(unit){ - case UNIT_RX: _clock_ctrl->enable_rx_dboard_clock(enb); return; - case UNIT_TX: _clock_ctrl->enable_tx_dboard_clock(enb); return; + case UNIT_RX: _clock_ctrl->enable_rx_dboard_clock(enb); return; + case UNIT_TX: _clock_ctrl->enable_tx_dboard_clock(enb); return; + case UNIT_BOTH: set_clock_enabled(UNIT_RX, enb); set_clock_enabled(UNIT_TX, enb); return; } } -double usrp2_dboard_iface::get_codec_rate(unit_t){ +double usrp2_dboard_iface::get_codec_rate(unit_t unit){ + if (unit == UNIT_BOTH) throw uhd::runtime_error("UNIT_BOTH not supported."); return _clock_ctrl->get_master_clock_rate(); } + /*********************************************************************** * GPIO **********************************************************************/ -void usrp2_dboard_iface::_set_pin_ctrl(unit_t unit, boost::uint16_t value){ - return _gpio->set_pin_ctrl(unit, value); +void usrp2_dboard_iface::set_pin_ctrl(unit_t unit, boost::uint32_t value, boost::uint32_t mask){ + _gpio->set_pin_ctrl(unit, static_cast<boost::uint16_t>(value), static_cast<boost::uint16_t>(mask)); } -void usrp2_dboard_iface::_set_gpio_ddr(unit_t unit, boost::uint16_t value){ - return _gpio->set_gpio_ddr(unit, value); +boost::uint32_t usrp2_dboard_iface::get_pin_ctrl(unit_t unit){ + return static_cast<boost::uint32_t>(_gpio->get_pin_ctrl(unit)); } -void usrp2_dboard_iface::_set_gpio_out(unit_t unit, boost::uint16_t value){ - return _gpio->set_gpio_out(unit, value); +void usrp2_dboard_iface::set_atr_reg(unit_t unit, atr_reg_t reg, boost::uint32_t value, boost::uint32_t mask){ + _gpio->set_atr_reg(unit, reg, static_cast<boost::uint16_t>(value), static_cast<boost::uint16_t>(mask)); } -boost::uint16_t usrp2_dboard_iface::read_gpio(unit_t unit){ - return _gpio->read_gpio(unit); +boost::uint32_t usrp2_dboard_iface::get_atr_reg(unit_t unit, atr_reg_t reg){ + return static_cast<boost::uint32_t>(_gpio->get_atr_reg(unit, reg)); +} + +void usrp2_dboard_iface::set_gpio_ddr(unit_t unit, boost::uint32_t value, boost::uint32_t mask){ + _gpio->set_gpio_ddr(unit, static_cast<boost::uint16_t>(value), static_cast<boost::uint16_t>(mask)); +} + +boost::uint32_t usrp2_dboard_iface::get_gpio_ddr(unit_t unit){ + return static_cast<boost::uint32_t>(_gpio->get_gpio_ddr(unit)); } -void usrp2_dboard_iface::_set_atr_reg(unit_t unit, atr_reg_t atr, boost::uint16_t value){ - return _gpio->set_atr_reg(unit, atr, value); +void usrp2_dboard_iface::set_gpio_out(unit_t unit, boost::uint32_t value, boost::uint32_t mask){ + _gpio->set_gpio_out(unit, static_cast<boost::uint16_t>(value), static_cast<boost::uint16_t>(mask)); } -void usrp2_dboard_iface::set_gpio_debug(unit_t, int){ - throw uhd::not_implemented_error("no set_gpio_debug implemented"); +boost::uint32_t usrp2_dboard_iface::get_gpio_out(unit_t unit){ + return static_cast<boost::uint32_t>(_gpio->get_gpio_out(unit)); +} + +boost::uint32_t usrp2_dboard_iface::read_gpio(unit_t unit){ + return _gpio->read_gpio(unit); } /*********************************************************************** @@ -219,6 +244,7 @@ void usrp2_dboard_iface::write_spi( boost::uint32_t data, size_t num_bits ){ + if (unit == UNIT_BOTH) throw uhd::runtime_error("UNIT_BOTH not supported."); _spi_iface->write_spi(unit_to_spi_dev[unit], config, data, num_bits); } @@ -228,6 +254,7 @@ boost::uint32_t usrp2_dboard_iface::read_write_spi( boost::uint32_t data, size_t num_bits ){ + if (unit == UNIT_BOTH) throw uhd::runtime_error("UNIT_BOTH not supported."); return _spi_iface->read_spi(unit_to_spi_dev[unit], config, data, num_bits); } @@ -250,6 +277,7 @@ void usrp2_dboard_iface::_write_aux_dac(unit_t unit){ (UNIT_RX, SPI_SS_RX_DAC) (UNIT_TX, SPI_SS_TX_DAC) ; + if (unit == UNIT_BOTH) throw uhd::runtime_error("UNIT_BOTH not supported."); _spi_iface->write_spi( unit_to_spi_dac[unit], spi_config_t::EDGE_FALL, _dac_regs[unit].get_reg(), 24 @@ -257,6 +285,8 @@ void usrp2_dboard_iface::_write_aux_dac(unit_t unit){ } void usrp2_dboard_iface::write_aux_dac(unit_t unit, aux_dac_t which, double value){ + if (unit == UNIT_BOTH) throw uhd::runtime_error("UNIT_BOTH not supported."); + _dac_regs[unit].data = boost::math::iround(4095*value/3.3); _dac_regs[unit].cmd = ad5623_regs_t::CMD_WR_UP_DAC_CHAN_N; @@ -285,6 +315,8 @@ double usrp2_dboard_iface::read_aux_adc(unit_t unit, aux_adc_t which){ (UNIT_TX, SPI_SS_TX_ADC) ; + if (unit == UNIT_BOTH) throw uhd::runtime_error("UNIT_BOTH not supported."); + //setup spi config args spi_config_t config; config.mosi_edge = spi_config_t::EDGE_FALL; @@ -320,3 +352,8 @@ void usrp2_dboard_iface::set_command_time(const uhd::time_spec_t& t) { _wb_iface->set_time(t); } + +void usrp2_dboard_iface::set_fe_connection(unit_t, const std::string&, const fe_connection_t&) +{ + throw uhd::not_implemented_error("fe connection configuration support not implemented"); +} diff --git a/host/lib/usrp/usrp2/usrp2_impl.cpp b/host/lib/usrp/usrp2/usrp2_impl.cpp index 7b59dfaf1..b0c29392c 100644 --- a/host/lib/usrp/usrp2/usrp2_impl.cpp +++ b/host/lib/usrp/usrp2/usrp2_impl.cpp @@ -474,15 +474,15 @@ usrp2_impl::usrp2_impl(const device_addr_t &_device_addr) : //////////////////////////////////////////////////////////////// _tree->create<mboard_eeprom_t>(mb_path / "eeprom") .set(_mbc[mb].iface->mb_eeprom) - .subscribe(boost::bind(&usrp2_impl::set_mb_eeprom, this, mb, _1)); + .add_coerced_subscriber(boost::bind(&usrp2_impl::set_mb_eeprom, this, mb, _1)); //////////////////////////////////////////////////////////////// // create clock control objects //////////////////////////////////////////////////////////////// _mbc[mb].clock = usrp2_clock_ctrl::make(_mbc[mb].iface, _mbc[mb].spiface); _tree->create<double>(mb_path / "tick_rate") - .publish(boost::bind(&usrp2_clock_ctrl::get_master_clock_rate, _mbc[mb].clock)) - .subscribe(boost::bind(&usrp2_impl::update_tick_rate, this, _1)); + .set_publisher(boost::bind(&usrp2_clock_ctrl::get_master_clock_rate, _mbc[mb].clock)) + .add_coerced_subscriber(boost::bind(&usrp2_impl::update_tick_rate, this, _1)); //////////////////////////////////////////////////////////////// // create codec control objects @@ -500,10 +500,10 @@ usrp2_impl::usrp2_impl(const device_addr_t &_device_addr) : _tree->create<std::string>(rx_codec_path / "name").set("ads62p44"); _tree->create<meta_range_t>(rx_codec_path / "gains/digital/range").set(meta_range_t(0, 6.0, 0.5)); _tree->create<double>(rx_codec_path / "gains/digital/value") - .subscribe(boost::bind(&usrp2_codec_ctrl::set_rx_digital_gain, _mbc[mb].codec, _1)).set(0); + .add_coerced_subscriber(boost::bind(&usrp2_codec_ctrl::set_rx_digital_gain, _mbc[mb].codec, _1)).set(0); _tree->create<meta_range_t>(rx_codec_path / "gains/fine/range").set(meta_range_t(0, 0.5, 0.05)); _tree->create<double>(rx_codec_path / "gains/fine/value") - .subscribe(boost::bind(&usrp2_codec_ctrl::set_rx_digital_fine_gain, _mbc[mb].codec, _1)).set(0); + .add_coerced_subscriber(boost::bind(&usrp2_codec_ctrl::set_rx_digital_fine_gain, _mbc[mb].codec, _1)).set(0); }break; case usrp2_iface::USRP2_REV3: @@ -550,7 +550,7 @@ usrp2_impl::usrp2_impl(const device_addr_t &_device_addr) : BOOST_FOREACH(const std::string &name, _mbc[mb].gps->get_sensors()) { _tree->create<sensor_value_t>(mb_path / "sensors" / name) - .publish(boost::bind(&gps_ctrl::get_sensor, _mbc[mb].gps, name)); + .set_publisher(boost::bind(&gps_ctrl::get_sensor, _mbc[mb].gps, name)); } } else @@ -563,9 +563,9 @@ usrp2_impl::usrp2_impl(const device_addr_t &_device_addr) : // and do the misc mboard sensors //////////////////////////////////////////////////////////////// _tree->create<sensor_value_t>(mb_path / "sensors/mimo_locked") - .publish(boost::bind(&usrp2_impl::get_mimo_locked, this, mb)); + .set_publisher(boost::bind(&usrp2_impl::get_mimo_locked, this, mb)); _tree->create<sensor_value_t>(mb_path / "sensors/ref_locked") - .publish(boost::bind(&usrp2_impl::get_ref_locked, this, mb)); + .set_publisher(boost::bind(&usrp2_impl::get_ref_locked, this, mb)); //////////////////////////////////////////////////////////////// // create frontend control objects @@ -578,27 +578,27 @@ usrp2_impl::usrp2_impl(const device_addr_t &_device_addr) : ); _tree->create<subdev_spec_t>(mb_path / "rx_subdev_spec") - .subscribe(boost::bind(&usrp2_impl::update_rx_subdev_spec, this, mb, _1)); + .add_coerced_subscriber(boost::bind(&usrp2_impl::update_rx_subdev_spec, this, mb, _1)); _tree->create<subdev_spec_t>(mb_path / "tx_subdev_spec") - .subscribe(boost::bind(&usrp2_impl::update_tx_subdev_spec, this, mb, _1)); + .add_coerced_subscriber(boost::bind(&usrp2_impl::update_tx_subdev_spec, this, mb, _1)); const fs_path rx_fe_path = mb_path / "rx_frontends" / "A"; const fs_path tx_fe_path = mb_path / "tx_frontends" / "A"; _tree->create<std::complex<double> >(rx_fe_path / "dc_offset" / "value") - .coerce(boost::bind(&rx_frontend_core_200::set_dc_offset, _mbc[mb].rx_fe, _1)) + .set_coercer(boost::bind(&rx_frontend_core_200::set_dc_offset, _mbc[mb].rx_fe, _1)) .set(std::complex<double>(0.0, 0.0)); _tree->create<bool>(rx_fe_path / "dc_offset" / "enable") - .subscribe(boost::bind(&rx_frontend_core_200::set_dc_offset_auto, _mbc[mb].rx_fe, _1)) + .add_coerced_subscriber(boost::bind(&rx_frontend_core_200::set_dc_offset_auto, _mbc[mb].rx_fe, _1)) .set(true); _tree->create<std::complex<double> >(rx_fe_path / "iq_balance" / "value") - .subscribe(boost::bind(&rx_frontend_core_200::set_iq_balance, _mbc[mb].rx_fe, _1)) + .add_coerced_subscriber(boost::bind(&rx_frontend_core_200::set_iq_balance, _mbc[mb].rx_fe, _1)) .set(std::complex<double>(0.0, 0.0)); _tree->create<std::complex<double> >(tx_fe_path / "dc_offset" / "value") - .coerce(boost::bind(&tx_frontend_core_200::set_dc_offset, _mbc[mb].tx_fe, _1)) + .set_coercer(boost::bind(&tx_frontend_core_200::set_dc_offset, _mbc[mb].tx_fe, _1)) .set(std::complex<double>(0.0, 0.0)); _tree->create<std::complex<double> >(tx_fe_path / "iq_balance" / "value") - .subscribe(boost::bind(&tx_frontend_core_200::set_iq_balance, _mbc[mb].tx_fe, _1)) + .add_coerced_subscriber(boost::bind(&tx_frontend_core_200::set_iq_balance, _mbc[mb].tx_fe, _1)) .set(std::complex<double>(0.0, 0.0)); //////////////////////////////////////////////////////////////// @@ -613,20 +613,20 @@ usrp2_impl::usrp2_impl(const device_addr_t &_device_addr) : for (size_t dspno = 0; dspno < _mbc[mb].rx_dsps.size(); dspno++){ _mbc[mb].rx_dsps[dspno]->set_link_rate(USRP2_LINK_RATE_BPS); _tree->access<double>(mb_path / "tick_rate") - .subscribe(boost::bind(&rx_dsp_core_200::set_tick_rate, _mbc[mb].rx_dsps[dspno], _1)); + .add_coerced_subscriber(boost::bind(&rx_dsp_core_200::set_tick_rate, _mbc[mb].rx_dsps[dspno], _1)); fs_path rx_dsp_path = mb_path / str(boost::format("rx_dsps/%u") % dspno); _tree->create<meta_range_t>(rx_dsp_path / "rate/range") - .publish(boost::bind(&rx_dsp_core_200::get_host_rates, _mbc[mb].rx_dsps[dspno])); + .set_publisher(boost::bind(&rx_dsp_core_200::get_host_rates, _mbc[mb].rx_dsps[dspno])); _tree->create<double>(rx_dsp_path / "rate/value") .set(1e6) //some default - .coerce(boost::bind(&rx_dsp_core_200::set_host_rate, _mbc[mb].rx_dsps[dspno], _1)) - .subscribe(boost::bind(&usrp2_impl::update_rx_samp_rate, this, mb, dspno, _1)); + .set_coercer(boost::bind(&rx_dsp_core_200::set_host_rate, _mbc[mb].rx_dsps[dspno], _1)) + .add_coerced_subscriber(boost::bind(&usrp2_impl::update_rx_samp_rate, this, mb, dspno, _1)); _tree->create<double>(rx_dsp_path / "freq/value") - .coerce(boost::bind(&rx_dsp_core_200::set_freq, _mbc[mb].rx_dsps[dspno], _1)); + .set_coercer(boost::bind(&rx_dsp_core_200::set_freq, _mbc[mb].rx_dsps[dspno], _1)); _tree->create<meta_range_t>(rx_dsp_path / "freq/range") - .publish(boost::bind(&rx_dsp_core_200::get_freq_range, _mbc[mb].rx_dsps[dspno])); + .set_publisher(boost::bind(&rx_dsp_core_200::get_freq_range, _mbc[mb].rx_dsps[dspno])); _tree->create<stream_cmd_t>(rx_dsp_path / "stream_cmd") - .subscribe(boost::bind(&rx_dsp_core_200::issue_stream_command, _mbc[mb].rx_dsps[dspno], _1)); + .add_coerced_subscriber(boost::bind(&rx_dsp_core_200::issue_stream_command, _mbc[mb].rx_dsps[dspno], _1)); } //////////////////////////////////////////////////////////////// @@ -637,17 +637,17 @@ usrp2_impl::usrp2_impl(const device_addr_t &_device_addr) : ); _mbc[mb].tx_dsp->set_link_rate(USRP2_LINK_RATE_BPS); _tree->access<double>(mb_path / "tick_rate") - .subscribe(boost::bind(&tx_dsp_core_200::set_tick_rate, _mbc[mb].tx_dsp, _1)); + .add_coerced_subscriber(boost::bind(&tx_dsp_core_200::set_tick_rate, _mbc[mb].tx_dsp, _1)); _tree->create<meta_range_t>(mb_path / "tx_dsps/0/rate/range") - .publish(boost::bind(&tx_dsp_core_200::get_host_rates, _mbc[mb].tx_dsp)); + .set_publisher(boost::bind(&tx_dsp_core_200::get_host_rates, _mbc[mb].tx_dsp)); _tree->create<double>(mb_path / "tx_dsps/0/rate/value") .set(1e6) //some default - .coerce(boost::bind(&tx_dsp_core_200::set_host_rate, _mbc[mb].tx_dsp, _1)) - .subscribe(boost::bind(&usrp2_impl::update_tx_samp_rate, this, mb, 0, _1)); + .set_coercer(boost::bind(&tx_dsp_core_200::set_host_rate, _mbc[mb].tx_dsp, _1)) + .add_coerced_subscriber(boost::bind(&usrp2_impl::update_tx_samp_rate, this, mb, 0, _1)); _tree->create<double>(mb_path / "tx_dsps/0/freq/value") - .coerce(boost::bind(&tx_dsp_core_200::set_freq, _mbc[mb].tx_dsp, _1)); + .set_coercer(boost::bind(&tx_dsp_core_200::set_freq, _mbc[mb].tx_dsp, _1)); _tree->create<meta_range_t>(mb_path / "tx_dsps/0/freq/range") - .publish(boost::bind(&tx_dsp_core_200::get_freq_range, _mbc[mb].tx_dsp)); + .set_publisher(boost::bind(&tx_dsp_core_200::get_freq_range, _mbc[mb].tx_dsp)); //setup dsp flow control const double ups_per_sec = device_args_i.cast<double>("ups_per_sec", 20); @@ -670,22 +670,22 @@ usrp2_impl::usrp2_impl(const device_addr_t &_device_addr) : _mbc[mb].wbiface, U2_REG_SR_ADDR(SR_TIME64), time64_rb_bases, mimo_clock_sync_delay_cycles ); _tree->access<double>(mb_path / "tick_rate") - .subscribe(boost::bind(&time64_core_200::set_tick_rate, _mbc[mb].time64, _1)); + .add_coerced_subscriber(boost::bind(&time64_core_200::set_tick_rate, _mbc[mb].time64, _1)); _tree->create<time_spec_t>(mb_path / "time/now") - .publish(boost::bind(&time64_core_200::get_time_now, _mbc[mb].time64)) - .subscribe(boost::bind(&time64_core_200::set_time_now, _mbc[mb].time64, _1)); + .set_publisher(boost::bind(&time64_core_200::get_time_now, _mbc[mb].time64)) + .add_coerced_subscriber(boost::bind(&time64_core_200::set_time_now, _mbc[mb].time64, _1)); _tree->create<time_spec_t>(mb_path / "time/pps") - .publish(boost::bind(&time64_core_200::get_time_last_pps, _mbc[mb].time64)) - .subscribe(boost::bind(&time64_core_200::set_time_next_pps, _mbc[mb].time64, _1)); + .set_publisher(boost::bind(&time64_core_200::get_time_last_pps, _mbc[mb].time64)) + .add_coerced_subscriber(boost::bind(&time64_core_200::set_time_next_pps, _mbc[mb].time64, _1)); //setup time source props _tree->create<std::string>(mb_path / "time_source/value") - .subscribe(boost::bind(&time64_core_200::set_time_source, _mbc[mb].time64, _1)) + .add_coerced_subscriber(boost::bind(&time64_core_200::set_time_source, _mbc[mb].time64, _1)) .set("none"); _tree->create<std::vector<std::string> >(mb_path / "time_source/options") - .publish(boost::bind(&time64_core_200::get_time_sources, _mbc[mb].time64)); + .set_publisher(boost::bind(&time64_core_200::get_time_sources, _mbc[mb].time64)); //setup reference source props _tree->create<std::string>(mb_path / "clock_source/value") - .subscribe(boost::bind(&usrp2_impl::update_clock_source, this, mb, _1)) + .add_coerced_subscriber(boost::bind(&usrp2_impl::update_clock_source, this, mb, _1)) .set("internal"); std::vector<std::string> clock_sources = boost::assign::list_of("internal")("external")("mimo"); if (_mbc[mb].gps and _mbc[mb].gps->gps_detected()) clock_sources.push_back("gpsdo"); @@ -697,18 +697,18 @@ usrp2_impl::usrp2_impl(const device_addr_t &_device_addr) : case usrp2_iface::USRP_N200_R4: case usrp2_iface::USRP_N210_R4: _tree->create<time_spec_t>(mb_path / "time/cmd") - .subscribe(boost::bind(&usrp2_fifo_ctrl::set_time, _mbc[mb].fifo_ctrl, _1)); + .add_coerced_subscriber(boost::bind(&usrp2_fifo_ctrl::set_time, _mbc[mb].fifo_ctrl, _1)); default: break; //otherwise, do not register } _tree->access<double>(mb_path / "tick_rate") - .subscribe(boost::bind(&usrp2_fifo_ctrl::set_tick_rate, _mbc[mb].fifo_ctrl, _1)); + .add_coerced_subscriber(boost::bind(&usrp2_fifo_ctrl::set_tick_rate, _mbc[mb].fifo_ctrl, _1)); //////////////////////////////////////////////////////////////////// // create user-defined control objects //////////////////////////////////////////////////////////////////// _mbc[mb].user = user_settings_core_200::make(_mbc[mb].wbiface, U2_REG_SR_ADDR(SR_USER_REGS)); _tree->create<user_settings_core_200::user_reg_t>(mb_path / "user/regs") - .subscribe(boost::bind(&user_settings_core_200::set_reg, _mbc[mb].user, _1)); + .add_coerced_subscriber(boost::bind(&user_settings_core_200::set_reg, _mbc[mb].user, _1)); //////////////////////////////////////////////////////////////// // create dboard control objects @@ -726,32 +726,31 @@ usrp2_impl::usrp2_impl(const device_addr_t &_device_addr) : //create the properties and register subscribers _tree->create<dboard_eeprom_t>(mb_path / "dboards/A/rx_eeprom") .set(rx_db_eeprom) - .subscribe(boost::bind(&usrp2_impl::set_db_eeprom, this, mb, "rx", _1)); + .add_coerced_subscriber(boost::bind(&usrp2_impl::set_db_eeprom, this, mb, "rx", _1)); _tree->create<dboard_eeprom_t>(mb_path / "dboards/A/tx_eeprom") .set(tx_db_eeprom) - .subscribe(boost::bind(&usrp2_impl::set_db_eeprom, this, mb, "tx", _1)); + .add_coerced_subscriber(boost::bind(&usrp2_impl::set_db_eeprom, this, mb, "tx", _1)); _tree->create<dboard_eeprom_t>(mb_path / "dboards/A/gdb_eeprom") .set(gdb_eeprom) - .subscribe(boost::bind(&usrp2_impl::set_db_eeprom, this, mb, "gdb", _1)); + .add_coerced_subscriber(boost::bind(&usrp2_impl::set_db_eeprom, this, mb, "gdb", _1)); //create a new dboard interface and manager - _mbc[mb].dboard_iface = make_usrp2_dboard_iface(_mbc[mb].wbiface, _mbc[mb].iface/*i2c*/, _mbc[mb].spiface, _mbc[mb].clock); - _tree->create<dboard_iface::sptr>(mb_path / "dboards/A/iface").set(_mbc[mb].dboard_iface); _mbc[mb].dboard_manager = dboard_manager::make( rx_db_eeprom.id, tx_db_eeprom.id, gdb_eeprom.id, - _mbc[mb].dboard_iface, _tree->subtree(mb_path / "dboards/A") + make_usrp2_dboard_iface(_mbc[mb].wbiface, _mbc[mb].iface/*i2c*/, _mbc[mb].spiface, _mbc[mb].clock), + _tree->subtree(mb_path / "dboards/A") ); //bind frontend corrections to the dboard freq props const fs_path db_tx_fe_path = mb_path / "dboards" / "A" / "tx_frontends"; BOOST_FOREACH(const std::string &name, _tree->list(db_tx_fe_path)){ _tree->access<double>(db_tx_fe_path / name / "freq" / "value") - .subscribe(boost::bind(&usrp2_impl::set_tx_fe_corrections, this, mb, _1)); + .add_coerced_subscriber(boost::bind(&usrp2_impl::set_tx_fe_corrections, this, mb, _1)); } const fs_path db_rx_fe_path = mb_path / "dboards" / "A" / "rx_frontends"; BOOST_FOREACH(const std::string &name, _tree->list(db_rx_fe_path)){ _tree->access<double>(db_rx_fe_path / name / "freq" / "value") - .subscribe(boost::bind(&usrp2_impl::set_rx_fe_corrections, this, mb, _1)); + .add_coerced_subscriber(boost::bind(&usrp2_impl::set_rx_fe_corrections, this, mb, _1)); } } diff --git a/host/lib/usrp/usrp2/usrp2_impl.hpp b/host/lib/usrp/usrp2/usrp2_impl.hpp index 07cd98b4c..47fcec657 100644 --- a/host/lib/usrp/usrp2/usrp2_impl.hpp +++ b/host/lib/usrp/usrp2/usrp2_impl.hpp @@ -102,7 +102,6 @@ private: uhd::transport::zero_copy_if::sptr tx_dsp_xport; uhd::transport::zero_copy_if::sptr fifo_ctrl_xport; uhd::usrp::dboard_manager::sptr dboard_manager; - uhd::usrp::dboard_iface::sptr dboard_iface; size_t rx_chan_occ, tx_chan_occ; mb_container_type(void): rx_chan_occ(0), tx_chan_occ(0){} }; diff --git a/host/lib/usrp/usrp_c.cpp b/host/lib/usrp/usrp_c.cpp index 69f2bd5e5..943f96db0 100644 --- a/host/lib/usrp/usrp_c.cpp +++ b/host/lib/usrp/usrp_c.cpp @@ -1,5 +1,5 @@ /* - * Copyright 2015 Ettus Research LLC + * Copyright 2015-2016 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 @@ -837,6 +837,95 @@ uhd_error uhd_usrp_get_fe_rx_freq_range( ) } +UHD_API uhd_error uhd_usrp_get_rx_lo_names( + uhd_usrp_handle h, + size_t chan, + uhd_string_vector_handle rx_lo_names_out +){ + UHD_SAFE_C_SAVE_ERROR(h, + rx_lo_names_out->string_vector_cpp = USRP(h)->get_rx_lo_names(chan); + ) +} + +UHD_API uhd_error uhd_usrp_set_rx_lo_source( + uhd_usrp_handle h, + const char* src, + const char* name, + size_t chan +){ + UHD_SAFE_C_SAVE_ERROR(h, + USRP(h)->set_rx_lo_source(src, name, chan); + ) +} + +UHD_API uhd_error uhd_usrp_get_rx_lo_source( + uhd_usrp_handle h, + const char* name, + size_t chan, + char* rx_lo_source_out, + size_t strbuffer_len +){ + UHD_SAFE_C_SAVE_ERROR(h, + strncpy(rx_lo_source_out, USRP(h)->get_rx_lo_source(name, chan).c_str(), strbuffer_len); + ) +} + +UHD_API uhd_error uhd_usrp_get_rx_lo_sources( + uhd_usrp_handle h, + const char* name, + size_t chan, + uhd_string_vector_handle rx_lo_sources_out +){ + UHD_SAFE_C_SAVE_ERROR(h, + rx_lo_sources_out->string_vector_cpp = USRP(h)->get_rx_lo_sources(name, chan); + ) +} + +UHD_API uhd_error uhd_usrp_set_rx_lo_export_enabled( + uhd_usrp_handle h, + bool enabled, + const char* name, + size_t chan +){ + UHD_SAFE_C_SAVE_ERROR(h, + USRP(h)->set_rx_lo_export_enabled(enabled, name, chan); + ) +} + +UHD_API uhd_error uhd_usrp_get_rx_lo_export_enabled( + uhd_usrp_handle h, + const char* name, + size_t chan, + bool* result_out +) { + UHD_SAFE_C_SAVE_ERROR(h, + *result_out = USRP(h)->get_rx_lo_export_enabled(name, chan); + ) +} + +UHD_API uhd_error uhd_usrp_set_rx_lo_freq( + uhd_usrp_handle h, + double freq, + const char* name, + size_t chan, + double* coerced_freq_out +){ + UHD_SAFE_C_SAVE_ERROR(h, + *coerced_freq_out = USRP(h)->set_rx_lo_freq(freq, name, chan); + ) +} + +UHD_API uhd_error uhd_usrp_get_rx_lo_freq( + uhd_usrp_handle h, + const char* name, + size_t chan, + double* rx_lo_freq_out +){ + UHD_SAFE_C_SAVE_ERROR(h, + *rx_lo_freq_out = USRP(h)->get_rx_lo_freq(name, chan); + ) +} + uhd_error uhd_usrp_set_rx_gain( uhd_usrp_handle h, double gain, diff --git a/host/lib/usrp/x300/CMakeLists.txt b/host/lib/usrp/x300/CMakeLists.txt index 3d6348eec..ea237b008 100644 --- a/host/lib/usrp/x300/CMakeLists.txt +++ b/host/lib/usrp/x300/CMakeLists.txt @@ -22,10 +22,9 @@ ######################################################################## # Conditionally configure the X300 support ######################################################################## -LIBUHD_REGISTER_COMPONENT("X300" ENABLE_X300 ON "ENABLE_LIBUHD" OFF OFF) - IF(ENABLE_X300) LIBUHD_APPEND_SOURCES( + ${CMAKE_CURRENT_SOURCE_DIR}/x300_radio_ctrl_impl.cpp ${CMAKE_CURRENT_SOURCE_DIR}/x300_impl.cpp ${CMAKE_CURRENT_SOURCE_DIR}/x300_fw_ctrl.cpp ${CMAKE_CURRENT_SOURCE_DIR}/x300_fw_uart.cpp @@ -35,7 +34,6 @@ IF(ENABLE_X300) ${CMAKE_CURRENT_SOURCE_DIR}/x300_dboard_iface.cpp ${CMAKE_CURRENT_SOURCE_DIR}/x300_clock_ctrl.cpp ${CMAKE_CURRENT_SOURCE_DIR}/x300_image_loader.cpp - ${CMAKE_CURRENT_SOURCE_DIR}/x300_adc_dac_utils.cpp ${CMAKE_CURRENT_SOURCE_DIR}/cdecode.c ) ENDIF(ENABLE_X300) diff --git a/host/lib/usrp/x300/x300_dac_ctrl.cpp b/host/lib/usrp/x300/x300_dac_ctrl.cpp index d49fba383..6ffd1ede4 100644 --- a/host/lib/usrp/x300/x300_dac_ctrl.cpp +++ b/host/lib/usrp/x300/x300_dac_ctrl.cpp @@ -1,5 +1,5 @@ // -// Copyright 2010-2014 Ettus Research LLC +// Copyright 2010-2016 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 diff --git a/host/lib/usrp/x300/x300_dac_ctrl.hpp b/host/lib/usrp/x300/x300_dac_ctrl.hpp index f2a407971..ca47a90e7 100644 --- a/host/lib/usrp/x300/x300_dac_ctrl.hpp +++ b/host/lib/usrp/x300/x300_dac_ctrl.hpp @@ -1,5 +1,5 @@ // -// Copyright 2010-2014 Ettus Research LLC +// Copyright 2010-2016 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 diff --git a/host/lib/usrp/x300/x300_dboard_iface.cpp b/host/lib/usrp/x300/x300_dboard_iface.cpp index 502630109..b28768f90 100644 --- a/host/lib/usrp/x300/x300_dboard_iface.cpp +++ b/host/lib/usrp/x300/x300_dboard_iface.cpp @@ -1,5 +1,5 @@ // -// Copyright 2013,2015 Ettus Research LLC +// Copyright 2013,2015,2016 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 @@ -15,85 +15,16 @@ // along with this program. If not, see <http://www.gnu.org/licenses/>. // -#include "x300_impl.hpp" +#include "x300_dboard_iface.hpp" #include "x300_regs.hpp" -#include <uhd/usrp/dboard_iface.hpp> #include <uhd/utils/safe_call.hpp> #include <boost/assign/list_of.hpp> #include <boost/math/special_functions/round.hpp> -#include "ad7922_regs.hpp" //aux adc -#include "ad5623_regs.hpp" //aux dac using namespace uhd; using namespace uhd::usrp; using namespace boost::assign; -class x300_dboard_iface : public dboard_iface -{ -public: - x300_dboard_iface(const x300_dboard_iface_config_t &config); - ~x300_dboard_iface(void); - - special_props_t get_special_props(void) - { - special_props_t props; - props.soft_clock_divider = false; - props.mangle_i2c_addrs = (_config.dboard_slot == 1); - return props; - } - - void write_aux_dac(unit_t, aux_dac_t, double); - double read_aux_adc(unit_t, aux_adc_t); - - void _set_pin_ctrl(unit_t, boost::uint16_t); - void _set_atr_reg(unit_t, atr_reg_t, boost::uint16_t); - void _set_gpio_ddr(unit_t, boost::uint16_t); - void _set_gpio_out(unit_t, boost::uint16_t); - - void set_command_time(const uhd::time_spec_t& t); - uhd::time_spec_t get_command_time(void); - - void set_gpio_debug(unit_t, int); - boost::uint16_t read_gpio(unit_t); - - void write_i2c(boost::uint16_t, const byte_vector_t &); - byte_vector_t read_i2c(boost::uint16_t, size_t); - - void set_clock_rate(unit_t, double); - double get_clock_rate(unit_t); - std::vector<double> get_clock_rates(unit_t); - void set_clock_enabled(unit_t, bool); - double get_codec_rate(unit_t); - - void write_spi( - unit_t unit, - const spi_config_t &config, - boost::uint32_t data, - size_t num_bits - ); - - boost::uint32_t read_write_spi( - unit_t unit, - const spi_config_t &config, - boost::uint32_t data, - size_t num_bits - ); - - const x300_dboard_iface_config_t _config; - uhd::dict<unit_t, ad5623_regs_t> _dac_regs; - uhd::dict<unit_t, double> _clock_rates; - void _write_aux_dac(unit_t); - -}; - -/*********************************************************************** - * Make Function - **********************************************************************/ -dboard_iface::sptr x300_make_dboard_iface(const x300_dboard_iface_config_t &config) -{ - return dboard_iface::sptr(new x300_dboard_iface(config)); -} - /*********************************************************************** * Structors **********************************************************************/ @@ -116,27 +47,6 @@ x300_dboard_iface::x300_dboard_iface(const x300_dboard_iface_config_t &config): this->set_clock_enabled(UNIT_RX, false); this->set_clock_enabled(UNIT_TX, false); - - - //some test code - /* - { - - this->write_aux_dac(UNIT_TX, AUX_DAC_A, .1); - this->write_aux_dac(UNIT_TX, AUX_DAC_B, 1); - this->write_aux_dac(UNIT_RX, AUX_DAC_A, 2); - this->write_aux_dac(UNIT_RX, AUX_DAC_B, 3); - while (1) - { - UHD_VAR(this->read_aux_adc(UNIT_TX, AUX_ADC_A)); - UHD_VAR(this->read_aux_adc(UNIT_TX, AUX_ADC_B)); - UHD_VAR(this->read_aux_adc(UNIT_RX, AUX_ADC_A)); - UHD_VAR(this->read_aux_adc(UNIT_RX, AUX_ADC_B)); - sleep(1); - } - } - */ - } x300_dboard_iface::~x300_dboard_iface(void) @@ -153,6 +63,8 @@ x300_dboard_iface::~x300_dboard_iface(void) **********************************************************************/ void x300_dboard_iface::set_clock_rate(unit_t unit, double rate) { + if (unit == UNIT_BOTH) throw uhd::runtime_error("UNIT_BOTH not supported."); + // Just return if the requested rate is already set if (std::fabs(_clock_rates[unit] - rate) < std::numeric_limits<double>::epsilon()) return; @@ -165,17 +77,21 @@ void x300_dboard_iface::set_clock_rate(unit_t unit, double rate) case UNIT_TX: _config.clock->set_dboard_rate(_config.which_tx_clk, rate); break; + default: + UHD_THROW_INVALID_CODE_PATH(); } _clock_rates[unit] = rate; //set to shadow } double x300_dboard_iface::get_clock_rate(unit_t unit) { + if (unit == UNIT_BOTH) throw uhd::runtime_error("UNIT_BOTH not supported."); return _clock_rates[unit]; //get from shadow } std::vector<double> x300_dboard_iface::get_clock_rates(unit_t unit) { + if (unit == UNIT_BOTH) throw uhd::runtime_error("UNIT_BOTH not supported."); switch(unit) { case UNIT_RX: @@ -189,6 +105,7 @@ std::vector<double> x300_dboard_iface::get_clock_rates(unit_t unit) void x300_dboard_iface::set_clock_enabled(unit_t unit, bool enb) { + if (unit == UNIT_BOTH) throw uhd::runtime_error("UNIT_BOTH not supported."); switch(unit) { case UNIT_RX: @@ -200,57 +117,74 @@ void x300_dboard_iface::set_clock_enabled(unit_t unit, bool enb) } } -double x300_dboard_iface::get_codec_rate(unit_t) +double x300_dboard_iface::get_codec_rate(unit_t unit) { + if (unit == UNIT_BOTH) throw uhd::runtime_error("UNIT_BOTH not supported."); return _config.clock->get_master_clock_rate(); } /*********************************************************************** * GPIO **********************************************************************/ -void x300_dboard_iface::_set_pin_ctrl(unit_t unit, boost::uint16_t value) +void x300_dboard_iface::set_pin_ctrl(unit_t unit, boost::uint32_t value, boost::uint32_t mask) { - return _config.gpio->set_pin_ctrl(unit, value); + _config.gpio->set_pin_ctrl(unit, value, mask); } -void x300_dboard_iface::_set_gpio_ddr(unit_t unit, boost::uint16_t value) +boost::uint32_t x300_dboard_iface::get_pin_ctrl(unit_t unit) { - return _config.gpio->set_gpio_ddr(unit, value); + return _config.gpio->get_pin_ctrl(unit); } -void x300_dboard_iface::_set_gpio_out(unit_t unit, boost::uint16_t value) +void x300_dboard_iface::set_atr_reg(unit_t unit, atr_reg_t reg, boost::uint32_t value, boost::uint32_t mask) { - return _config.gpio->set_gpio_out(unit, value); + _config.gpio->set_atr_reg(unit, reg, value, mask); } -boost::uint16_t x300_dboard_iface::read_gpio(unit_t unit) +boost::uint32_t x300_dboard_iface::get_atr_reg(unit_t unit, atr_reg_t reg) { - return _config.gpio->read_gpio(unit); + return _config.gpio->get_atr_reg(unit, reg); +} + +void x300_dboard_iface::set_gpio_ddr(unit_t unit, boost::uint32_t value, boost::uint32_t mask) +{ + _config.gpio->set_gpio_ddr(unit, value, mask); +} + +boost::uint32_t x300_dboard_iface::get_gpio_ddr(unit_t unit) +{ + return _config.gpio->get_gpio_ddr(unit); } -void x300_dboard_iface::_set_atr_reg(unit_t unit, atr_reg_t atr, boost::uint16_t value) +void x300_dboard_iface::set_gpio_out(unit_t unit, boost::uint32_t value, boost::uint32_t mask) { - return _config.gpio->set_atr_reg(unit, atr, value); + _config.gpio->set_gpio_out(unit, value, mask); } -void x300_dboard_iface::set_gpio_debug(unit_t, int) +boost::uint32_t x300_dboard_iface::get_gpio_out(unit_t unit) { - throw uhd::not_implemented_error("no set_gpio_debug implemented"); + return _config.gpio->get_gpio_out(unit); +} + +boost::uint32_t x300_dboard_iface::read_gpio(unit_t unit) +{ + return _config.gpio->read_gpio(unit); } /*********************************************************************** * SPI **********************************************************************/ -#define toslaveno(unit) \ - (((unit) == dboard_iface::UNIT_TX)? _config.tx_spi_slaveno : _config.rx_spi_slaveno) - void x300_dboard_iface::write_spi( unit_t unit, const spi_config_t &config, boost::uint32_t data, size_t num_bits ){ - _config.spi->write_spi(toslaveno(unit), config, data, num_bits); + boost::uint32_t slave = 0; + if (unit == UNIT_TX) slave |= _config.tx_spi_slaveno; + if (unit == UNIT_RX) slave |= _config.rx_spi_slaveno; + + _config.spi->write_spi(int(slave), config, data, num_bits); } boost::uint32_t x300_dboard_iface::read_write_spi( @@ -259,7 +193,10 @@ boost::uint32_t x300_dboard_iface::read_write_spi( boost::uint32_t data, size_t num_bits ){ - return _config.spi->read_spi(toslaveno(unit), config, data, num_bits); + if (unit == UNIT_BOTH) throw uhd::runtime_error("UNIT_BOTH not supported."); + return _config.spi->read_spi( + (unit==dboard_iface::UNIT_TX)?_config.tx_spi_slaveno:_config.rx_spi_slaveno, + config, data, num_bits); } /*********************************************************************** @@ -284,6 +221,7 @@ void x300_dboard_iface::_write_aux_dac(unit_t unit) (UNIT_RX, DB_RX_LSDAC_SEN) (UNIT_TX, DB_TX_LSDAC_SEN) ; + if (unit == UNIT_BOTH) throw uhd::runtime_error("UNIT_BOTH not supported."); _config.spi->write_spi( unit_to_spi_dac[unit], spi_config_t::EDGE_FALL, _dac_regs[unit].get_reg(), 24 @@ -292,6 +230,8 @@ void x300_dboard_iface::_write_aux_dac(unit_t unit) void x300_dboard_iface::write_aux_dac(unit_t unit, aux_dac_t which, double value) { + if (unit == UNIT_BOTH) throw uhd::runtime_error("UNIT_BOTH not supported."); + _dac_regs[unit].data = boost::math::iround(4095*value/3.3); _dac_regs[unit].cmd = ad5623_regs_t::CMD_WR_UP_DAC_CHAN_N; @@ -321,6 +261,8 @@ double x300_dboard_iface::read_aux_adc(unit_t unit, aux_adc_t which) (UNIT_TX, DB_TX_LSADC_SEN) ; + if (unit == UNIT_BOTH) throw uhd::runtime_error("UNIT_BOTH not supported."); + //setup spi config args spi_config_t config; config.mosi_edge = spi_config_t::EDGE_FALL; @@ -356,3 +298,25 @@ void x300_dboard_iface::set_command_time(const uhd::time_spec_t& t) { _config.cmd_time_ctrl->set_time(t); } + +void x300_dboard_iface::add_rx_fe( + const std::string& fe_name, + rx_frontend_core_3000::sptr fe_core) +{ + _rx_fes[fe_name] = fe_core; +} + +void x300_dboard_iface::set_fe_connection( + unit_t unit, const std::string& fe_name, + const fe_connection_t& fe_conn) +{ + if (unit == UNIT_RX) { + if (_rx_fes.has_key(fe_name)) { + _rx_fes[fe_name]->set_fe_connection(fe_conn); + } else { + throw uhd::assertion_error("front-end name was not registered: " + fe_name); + } + } else { + throw uhd::not_implemented_error("frontend connection not configurable for TX"); + } +} diff --git a/host/lib/usrp/x300/x300_dboard_iface.hpp b/host/lib/usrp/x300/x300_dboard_iface.hpp new file mode 100644 index 000000000..124d768e8 --- /dev/null +++ b/host/lib/usrp/x300/x300_dboard_iface.hpp @@ -0,0 +1,115 @@ +// +// Copyright 2010-2014 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 +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +#ifndef INCLUDED_X300_DBOARD_IFACE_HPP +#define INCLUDED_X300_DBOARD_IFACE_HPP + +#include "x300_clock_ctrl.hpp" +#include "spi_core_3000.hpp" +#include "i2c_core_100_wb32.hpp" +#include "gpio_atr_3000.hpp" +#include "rx_frontend_core_3000.hpp" +#include <uhd/usrp/dboard_iface.hpp> +#include "ad7922_regs.hpp" //aux adc +#include "ad5623_regs.hpp" //aux dac +#include <uhd/types/dict.hpp> + +struct x300_dboard_iface_config_t +{ + uhd::usrp::gpio_atr::db_gpio_atr_3000::sptr gpio; + spi_core_3000::sptr spi; + size_t rx_spi_slaveno; + size_t tx_spi_slaveno; + uhd::i2c_iface::sptr i2c; + x300_clock_ctrl::sptr clock; + x300_clock_which_t which_rx_clk; + x300_clock_which_t which_tx_clk; + boost::uint8_t dboard_slot; + uhd::timed_wb_iface::sptr cmd_time_ctrl; +}; + +class x300_dboard_iface : public uhd::usrp::dboard_iface +{ +public: + x300_dboard_iface(const x300_dboard_iface_config_t &config); + ~x300_dboard_iface(void); + + inline special_props_t get_special_props(void) + { + special_props_t props; + props.soft_clock_divider = false; + props.mangle_i2c_addrs = (_config.dboard_slot == 1); + return props; + } + + void write_aux_dac(unit_t, aux_dac_t, double); + double read_aux_adc(unit_t, aux_adc_t); + + void set_pin_ctrl(unit_t unit, boost::uint32_t value, boost::uint32_t mask = 0xffffffff); + boost::uint32_t get_pin_ctrl(unit_t unit); + void set_atr_reg(unit_t unit, atr_reg_t reg, boost::uint32_t value, boost::uint32_t mask = 0xffffffff); + boost::uint32_t get_atr_reg(unit_t unit, atr_reg_t reg); + void set_gpio_ddr(unit_t unit, boost::uint32_t value, boost::uint32_t mask = 0xffffffff); + boost::uint32_t get_gpio_ddr(unit_t unit); + void set_gpio_out(unit_t unit, boost::uint32_t value, boost::uint32_t mask = 0xffffffff); + boost::uint32_t get_gpio_out(unit_t unit); + boost::uint32_t read_gpio(unit_t unit); + + void set_command_time(const uhd::time_spec_t& t); + uhd::time_spec_t get_command_time(void); + + void write_i2c(boost::uint16_t, const uhd::byte_vector_t &); + uhd::byte_vector_t read_i2c(boost::uint16_t, size_t); + + void set_clock_rate(unit_t, double); + double get_clock_rate(unit_t); + std::vector<double> get_clock_rates(unit_t); + void set_clock_enabled(unit_t, bool); + double get_codec_rate(unit_t); + + void write_spi( + unit_t unit, + const uhd::spi_config_t &config, + boost::uint32_t data, + size_t num_bits + ); + + boost::uint32_t read_write_spi( + unit_t unit, + const uhd::spi_config_t &config, + boost::uint32_t data, + size_t num_bits + ); + void set_fe_connection( + unit_t unit, const std::string& name, + const uhd::usrp::fe_connection_t& fe_conn); + + void add_rx_fe( + const std::string& fe_name, + rx_frontend_core_3000::sptr fe_core); + +private: + const x300_dboard_iface_config_t _config; + uhd::dict<unit_t, ad5623_regs_t> _dac_regs; + uhd::dict<unit_t, double> _clock_rates; + uhd::dict<std::string, rx_frontend_core_3000::sptr> _rx_fes; + void _write_aux_dac(unit_t); +}; + + + +#endif /* INCLUDED_X300_DBOARD_IFACE_HPP */ diff --git a/host/lib/usrp/x300/x300_fw_common.h b/host/lib/usrp/x300/x300_fw_common.h index 549fc9dfa..aae0f769c 100644 --- a/host/lib/usrp/x300/x300_fw_common.h +++ b/host/lib/usrp/x300/x300_fw_common.h @@ -31,9 +31,9 @@ extern "C" { #define X300_REVISION_COMPAT 7 #define X300_REVISION_MIN 2 -#define X300_FW_COMPAT_MAJOR 4 +#define X300_FW_COMPAT_MAJOR 5 #define X300_FW_COMPAT_MINOR 0 -#define X300_FPGA_COMPAT_MAJOR 19 +#define X300_FPGA_COMPAT_MAJOR 0x21 //shared memory sections - in between the stack and the program space #define X300_FW_SHMEM_BASE 0x6000 diff --git a/host/lib/usrp/x300/x300_fw_ctrl.cpp b/host/lib/usrp/x300/x300_fw_ctrl.cpp index 3a8d984fb..25960ede0 100644 --- a/host/lib/usrp/x300/x300_fw_ctrl.cpp +++ b/host/lib/usrp/x300/x300_fw_ctrl.cpp @@ -37,6 +37,11 @@ class x300_ctrl_iface : public wb_iface public: enum {num_retries = 3}; + x300_ctrl_iface(bool enable_errors = true) : errors(enable_errors) + { + /* NOP */ + } + void flush(void) { boost::mutex::scoped_lock lock(reg_access); @@ -52,11 +57,11 @@ public: { return this->__poke32(addr, data); } - catch(const std::exception &ex) + catch(const uhd::io_error &ex) { - const std::string error_msg = str(boost::format( + std::string error_msg = str(boost::format( "x300 fw communication failure #%u\n%s") % i % ex.what()); - UHD_MSG(error) << error_msg << std::endl; + if (errors) UHD_MSG(error) << error_msg << std::endl; if (i == num_retries) throw uhd::io_error(error_msg); } } @@ -72,11 +77,11 @@ public: boost::uint32_t data = this->__peek32(addr); return data; } - catch(const std::exception &ex) + catch(const uhd::io_error &ex) { - const std::string error_msg = str(boost::format( + std::string error_msg = str(boost::format( "x300 fw communication failure #%u\n%s") % i % ex.what()); - UHD_MSG(error) << error_msg << std::endl; + if (errors) UHD_MSG(error) << error_msg << std::endl; if (i == num_retries) throw uhd::io_error(error_msg); } } @@ -84,6 +89,8 @@ public: } protected: + bool errors; + virtual void __poke32(const wb_addr_type addr, const boost::uint32_t data) = 0; virtual boost::uint32_t __peek32(const wb_addr_type addr) = 0; virtual void __flush() = 0; @@ -98,8 +105,8 @@ protected: class x300_ctrl_iface_enet : public x300_ctrl_iface { public: - x300_ctrl_iface_enet(uhd::transport::udp_simple::sptr udp): - udp(udp), seq(0) + x300_ctrl_iface_enet(uhd::transport::udp_simple::sptr udp, bool enable_errors = true): + x300_ctrl_iface(enable_errors), udp(udp), seq(0) { try { @@ -187,8 +194,8 @@ private: class x300_ctrl_iface_pcie : public x300_ctrl_iface { public: - x300_ctrl_iface_pcie(niriok_proxy::sptr drv_proxy): - _drv_proxy(drv_proxy) + x300_ctrl_iface_pcie(niriok_proxy::sptr drv_proxy, bool enable_errors = true): + x300_ctrl_iface(enable_errors), _drv_proxy(drv_proxy) { nirio_status status = 0; nirio_status_chain(_drv_proxy->set_attribute(RIO_ADDRESS_SPACE, BUS_INTERFACE), status); @@ -289,12 +296,12 @@ private: static const boost::uint32_t INIT_TIMEOUT_IN_MS = 5000; }; -wb_iface::sptr x300_make_ctrl_iface_enet(uhd::transport::udp_simple::sptr udp) +wb_iface::sptr x300_make_ctrl_iface_enet(uhd::transport::udp_simple::sptr udp, bool enable_errors = true) { - return wb_iface::sptr(new x300_ctrl_iface_enet(udp)); + return wb_iface::sptr(new x300_ctrl_iface_enet(udp, enable_errors)); } -wb_iface::sptr x300_make_ctrl_iface_pcie(niriok_proxy::sptr drv_proxy) +wb_iface::sptr x300_make_ctrl_iface_pcie(niriok_proxy::sptr drv_proxy, bool enable_errors = true) { - return wb_iface::sptr(new x300_ctrl_iface_pcie(drv_proxy)); + return wb_iface::sptr(new x300_ctrl_iface_pcie(drv_proxy, enable_errors)); } diff --git a/host/lib/usrp/x300/x300_impl.cpp b/host/lib/usrp/x300/x300_impl.cpp index c545c6461..7f7fcad09 100644 --- a/host/lib/usrp/x300/x300_impl.cpp +++ b/host/lib/usrp/x300/x300_impl.cpp @@ -1,5 +1,5 @@ // -// Copyright 2013-2015 Ettus Research LLC +// Copyright 2013-2016 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 @@ -18,9 +18,9 @@ #include "x300_impl.hpp" #include "x300_lvbitx.hpp" #include "x310_lvbitx.hpp" +#include "apply_corrections.hpp" #include <boost/algorithm/string.hpp> #include <boost/asio.hpp> -#include "apply_corrections.hpp" #include <uhd/utils/static.hpp> #include <uhd/utils/msg.hpp> #include <uhd/utils/paths.hpp> @@ -32,12 +32,14 @@ #include <boost/make_shared.hpp> #include <boost/functional/hash.hpp> #include <boost/assign/list_of.hpp> -#include <fstream> #include <uhd/transport/udp_zero_copy.hpp> #include <uhd/transport/udp_constants.hpp> +#include <uhd/transport/zero_copy_recv_offload.hpp> #include <uhd/transport/nirio_zero_copy.hpp> #include <uhd/transport/nirio/niusrprio_session.h> #include <uhd/utils/platform.hpp> +#include <uhd/types/sid.hpp> +#include <fstream> #define NIUSRPRIO_DEFAULT_RPC_PORT "5444" @@ -45,24 +47,41 @@ using namespace uhd; using namespace uhd::usrp; +using namespace uhd::rfnoc; using namespace uhd::transport; using namespace uhd::niusrprio; +using namespace uhd::usrp::gpio_atr; using namespace uhd::usrp::x300; namespace asio = boost::asio; +static std::string get_fpga_option(wb_iface::sptr zpu_ctrl) { + //Possible options: + //1G = {0:1G, 1:1G} w/ DRAM, HG = {0:1G, 1:10G} w/ DRAM, XG = {0:10G, 1:10G} w/ DRAM + //HA = {0:1G, 1:Aurora} w/ DRAM, XA = {0:10G, 1:Aurora} w/ DRAM + + std::string option; + uint32_t sfp0_type = zpu_ctrl->peek32(SR_ADDR(SET0_BASE, ZPU_RB_SFP0_TYPE)); + uint32_t sfp1_type = zpu_ctrl->peek32(SR_ADDR(SET0_BASE, ZPU_RB_SFP1_TYPE)); + + if (sfp0_type == RB_SFP_1G_ETH and sfp1_type == RB_SFP_1G_ETH) { + option = "1G"; + } else if (sfp0_type == RB_SFP_1G_ETH and sfp1_type == RB_SFP_10G_ETH) { + option = "HG"; + } else if (sfp0_type == RB_SFP_10G_ETH and sfp1_type == RB_SFP_10G_ETH) { + option = "XG"; + } else if (sfp0_type == RB_SFP_1G_ETH and sfp1_type == RB_SFP_AURORA) { + option = "HA"; + } else if (sfp0_type == RB_SFP_10G_ETH and sfp1_type == RB_SFP_AURORA) { + option = "XA"; + } else { + option = "HG"; //Default + } + return option; +} + /*********************************************************************** * Discovery over the udp and pcie transport **********************************************************************/ -static std::string get_fpga_option(wb_iface::sptr zpu_ctrl) { - //1G = {0:1G, 1:1G} w/ DRAM, HG = {0:1G, 1:10G} w/ DRAM, XG = {0:10G, 1:10G} w/ DRAM - //HGS = {0:1G, 1:10G} w/ SRAM, XGS = {0:10G, 1:10G} w/ SRAM - - //In the default configuration, UHD does not support the HG and XG images so - //they are never autodetected. - bool eth0XG = (zpu_ctrl->peek32(SR_ADDR(SET0_BASE, ZPU_RB_ETH_TYPE0)) == 0x1); - bool eth1XG = (zpu_ctrl->peek32(SR_ADDR(SET0_BASE, ZPU_RB_ETH_TYPE1)) == 0x1); - return (eth0XG && eth1XG) ? "XGS" : (eth1XG ? "HGS" : "1G"); -} //@TODO: Refactor the find functions to collapse common code for ethernet and PCIe static device_addrs_t x300_find_with_addr(const device_addr_t &hint) @@ -96,7 +115,12 @@ static device_addrs_t x300_find_with_addr(const device_addr_t &hint) //This operation can throw due to compatibility mismatch. try { - wb_iface::sptr zpu_ctrl = x300_make_ctrl_iface_enet(udp_simple::make_connected(new_addr["addr"], BOOST_STRINGIZE(X300_FW_COMMS_UDP_PORT))); + wb_iface::sptr zpu_ctrl = x300_make_ctrl_iface_enet( + udp_simple::make_connected(new_addr["addr"], + BOOST_STRINGIZE(X300_FW_COMMS_UDP_PORT)), + false /* Suppress timeout errors */ + ); + if (x300_impl::is_claimed(zpu_ctrl)) continue; //claimed by another process new_addr["fpga"] = get_fpga_option(zpu_ctrl); @@ -193,7 +217,7 @@ static device_addrs_t x300_find_pcie(const device_addr_t &hint, bool explicit_qu if (get_pcie_zpu_iface_registry().has_key(resource_d)) { zpu_ctrl = get_pcie_zpu_iface_registry()[resource_d].lock(); } else { - zpu_ctrl = x300_make_ctrl_iface_pcie(kernel_proxy); + zpu_ctrl = x300_make_ctrl_iface_pcie(kernel_proxy, false /* suppress timeout errors */); //We don't put this zpu_ctrl in the registry because we need //a persistent niriok_proxy associated with the object } @@ -215,7 +239,7 @@ static device_addrs_t x300_find_pcie(const device_addr_t &hint, bool explicit_qu //set these values as empty string so the device may still be found //and the filter's below can still operate on the discovered device if (not hint.has_key("fpga")) { - new_addr["fpga"] = "HGS"; + new_addr["fpga"] = "HG"; } new_addr["name"] = ""; new_addr["serial"] = ""; @@ -348,18 +372,18 @@ static void x300_load_fw(wb_iface::sptr fw_reg_ctrl, const std::string &file_nam if ((i & 0x1fff) == 0) UHD_MSG(status) << "." << std::flush; } + //Wait for fimrware to reboot. 3s is an upper bound + boost::this_thread::sleep(boost::posix_time::milliseconds(3000)); UHD_MSG(status) << " done!" << std::endl; } -x300_impl::x300_impl(const uhd::device_addr_t &dev_addr) +x300_impl::x300_impl(const uhd::device_addr_t &dev_addr) + : device3_impl() + , _sid_framer(0) { UHD_MSG(status) << "X300 initialization sequence..." << std::endl; - _type = device::USRP; _ignore_cal_file = dev_addr.has_key("ignore-cal-file"); - _async_md.reset(new async_md_type(1000/*messages deep*/)); - _tree = uhd::property_tree::make(); _tree->create<std::string>("/name").set("X-Series Device"); - _sid_framer = 0; const device_addrs_t device_args = separate_device_addr(dev_addr); _mb.resize(device_args.size()); @@ -369,13 +393,134 @@ x300_impl::x300_impl(const uhd::device_addr_t &dev_addr) } } +void x300_impl::mboard_members_t::discover_eth( + const mboard_eeprom_t mb_eeprom, + const std::vector<std::string> &ip_addrs) +{ + // Clear any previous addresses added + eth_conns.clear(); + + // Index the MB EEPROM addresses + std::vector<std::string> mb_eeprom_addrs; + const size_t num_mb_eeprom_addrs = 4; + for (size_t i = 0; i < num_mb_eeprom_addrs; i++) { + const std::string key = "ip-addr" + boost::to_string(i); + + // Show a warning if there exists duplicate addresses in the mboard eeprom + if (std::find(mb_eeprom_addrs.begin(), mb_eeprom_addrs.end(), mb_eeprom[key]) != mb_eeprom_addrs.end()) { + UHD_MSG(warning) << str(boost::format( + "Duplicate IP address %s found in mboard EEPROM. " + "Device may not function properly.\nView and reprogram the values " + "using the usrp_burn_mb_eeprom utility.\n") % mb_eeprom[key]); + } + mb_eeprom_addrs.push_back(mb_eeprom[key]); + } + + BOOST_FOREACH(const std::string& addr, ip_addrs) { + x300_eth_conn_t conn_iface; + conn_iface.addr = addr; + conn_iface.type = X300_IFACE_NONE; + + // Decide from the mboard eeprom what IP corresponds + // to an interface + for (size_t i = 0; i < mb_eeprom_addrs.size(); i++) { + if (addr == mb_eeprom_addrs[i]) { + // Choose the interface based on the index parity + if (i % 2 == 0) { + conn_iface.type = X300_IFACE_ETH0; + } else { + conn_iface.type = X300_IFACE_ETH1; + } + break; + } + } + + // Check default IP addresses if we couldn't + // determine the IP from the mboard eeprom + if (conn_iface.type == X300_IFACE_NONE) { + UHD_MSG(warning) << str(boost::format( + "Address %s not found in mboard EEPROM. Address may be wrong or " + "the EEPROM may be corrupt.\n Attempting to continue with default " + "IP addresses.\n") % conn_iface.addr + ); + + if (addr == boost::asio::ip::address_v4( + boost::uint32_t(X300_DEFAULT_IP_ETH0_1G)).to_string()) { + conn_iface.type = X300_IFACE_ETH0; + } else if (addr == boost::asio::ip::address_v4( + boost::uint32_t(X300_DEFAULT_IP_ETH1_1G)).to_string()) { + conn_iface.type = X300_IFACE_ETH1; + } else if (addr == boost::asio::ip::address_v4( + boost::uint32_t(X300_DEFAULT_IP_ETH0_10G)).to_string()) { + conn_iface.type = X300_IFACE_ETH0; + } else if (addr == boost::asio::ip::address_v4( + boost::uint32_t(X300_DEFAULT_IP_ETH1_10G)).to_string()) { + conn_iface.type = X300_IFACE_ETH1; + } else { + throw uhd::assertion_error(str(boost::format( + "X300 Initialization Error: Failed to match address %s with " + "any addresses for the device. Please check the address.") + % conn_iface.addr + )); + } + } + + // Save to a vector of connections + if (conn_iface.type != X300_IFACE_NONE) { + // Check the address before we add it + try + { + wb_iface::sptr zpu_ctrl = x300_make_ctrl_iface_enet( + udp_simple::make_connected(conn_iface.addr, + BOOST_STRINGIZE(X300_FW_COMMS_UDP_PORT)), + false /* Suppress timeout errors */ + ); + + // Peek the ZPU ctrl to make sure this connection works + zpu_ctrl->peek32(0); + } + + // If the address does not work, throw an error + catch(std::exception &) + { + throw uhd::io_error(str(boost::format( + "X300 Initialization Error: Invalid address %s") + % conn_iface.addr)); + } + eth_conns.push_back(conn_iface); + } + } + + if (eth_conns.size() == 0) + throw uhd::assertion_error("X300 Initialization Error: No ethernet interfaces specified."); +} + void x300_impl::setup_mb(const size_t mb_i, const uhd::device_addr_t &dev_addr) { const fs_path mb_path = "/mboards/"+boost::lexical_cast<std::string>(mb_i); mboard_members_t &mb = _mb[mb_i]; mb.initialization_done = false; - mb.addr = dev_addr.has_key("resource") ? dev_addr["resource"] : dev_addr["addr"]; + std::vector<std::string> eth_addrs; + // Not choosing eth0 based on resource might cause user issues + std::string eth0_addr = dev_addr.has_key("resource") ? dev_addr["resource"] : dev_addr["addr"]; + eth_addrs.push_back(eth0_addr); + + mb.next_src_addr = 0; //Host source address for blocks + if (dev_addr.has_key("second_addr")) { + std::string eth1_addr = dev_addr["second_addr"]; + + // Ensure we do not have duplicate addresses + if (eth1_addr != eth0_addr) + eth_addrs.push_back(eth1_addr); + } + + // Initially store the first address provided to setup communication + // Once we read the eeprom, we use it to map IP to its interface + x300_eth_conn_t init; + init.addr = eth_addrs[0]; + mb.eth_conns.push_back(init); + mb.xport_path = dev_addr.has_key("resource") ? "nirio" : "eth"; mb.if_pkt_is_big_endian = mb.xport_path != "nirio"; @@ -413,6 +558,8 @@ void x300_impl::setup_mb(const size_t mb_i, const uhd::device_addr_t &dev_addr) const boost::uint32_t tx_data_fifos[2] = {X300_RADIO_DEST_PREFIX_TX, X300_RADIO_DEST_PREFIX_TX + 3}; mb.rio_fpga_interface->get_kernel_proxy()->get_rio_quirks().register_tx_streams(tx_data_fifos, 2); + _tree->create<size_t>(mb_path / "mtu/recv").set(X300_PCIE_RX_DATA_FRAME_SIZE); + _tree->create<size_t>(mb_path / "mtu/send").set(X300_PCIE_TX_DATA_FRAME_SIZE); _tree->create<double>(mb_path / "link_max_rate").set(X300_MAX_RATE_PCIE); } @@ -452,7 +599,28 @@ void x300_impl::setup_mb(const size_t mb_i, const uhd::device_addr_t &dev_addr) // Detect the frame size on the path to the USRP try { - _max_frame_sizes = determine_max_frame_size(mb.addr, req_max_frame_size); + frame_size_t pri_frame_sizes = determine_max_frame_size( + eth_addrs.at(0), req_max_frame_size + ); + + _max_frame_sizes = pri_frame_sizes; + if (eth_addrs.size() > 1) { + frame_size_t sec_frame_sizes = determine_max_frame_size( + eth_addrs.at(1), req_max_frame_size + ); + + // Choose the minimum of the max frame sizes + // to ensure we don't exceed any one of the links' MTU + _max_frame_sizes.recv_frame_size = std::min( + pri_frame_sizes.recv_frame_size, + sec_frame_sizes.recv_frame_size + ); + + _max_frame_sizes.send_frame_size = std::min( + pri_frame_sizes.send_frame_size, + sec_frame_sizes.send_frame_size + ); + } } catch(std::exception &e) { UHD_MSG(error) << e.what() << std::endl; } @@ -483,6 +651,8 @@ void x300_impl::setup_mb(const size_t mb_i, const uhd::device_addr_t &dev_addr) << std::endl; } + _tree->create<size_t>(mb_path / "mtu/recv").set(_max_frame_sizes.recv_frame_size); + _tree->create<size_t>(mb_path / "mtu/send").set(std::min(_max_frame_sizes.send_frame_size, X300_1GE_DATA_FRAME_MAX_SIZE)); _tree->create<double>(mb_path / "link_max_rate").set(X300_MAX_RATE_10GIGE); } @@ -490,15 +660,15 @@ void x300_impl::setup_mb(const size_t mb_i, const uhd::device_addr_t &dev_addr) UHD_MSG(status) << "Setup basic communication..." << std::endl; if (mb.xport_path == "nirio") { boost::mutex::scoped_lock(pcie_zpu_iface_registry_mutex); - if (get_pcie_zpu_iface_registry().has_key(mb.addr)) { + if (get_pcie_zpu_iface_registry().has_key(mb.get_pri_eth().addr)) { throw uhd::assertion_error("Someone else has a ZPU transport to the device open. Internal error!"); } else { mb.zpu_ctrl = x300_make_ctrl_iface_pcie(mb.rio_fpga_interface->get_kernel_proxy()); - get_pcie_zpu_iface_registry()[mb.addr] = boost::weak_ptr<wb_iface>(mb.zpu_ctrl); + get_pcie_zpu_iface_registry()[mb.get_pri_eth().addr] = boost::weak_ptr<wb_iface>(mb.zpu_ctrl); } } else { - mb.zpu_ctrl = x300_make_ctrl_iface_enet(udp_simple::make_connected(mb.addr, - BOOST_STRINGIZE(X300_FW_COMMS_UDP_PORT))); + mb.zpu_ctrl = x300_make_ctrl_iface_enet(udp_simple::make_connected( + mb.get_pri_eth().addr, BOOST_STRINGIZE(X300_FW_COMMS_UDP_PORT))); } mb.claimer_task = uhd::task::make(boost::bind(&x300_impl::claimer_loop, this, mb.zpu_ctrl)); @@ -528,7 +698,7 @@ void x300_impl::setup_mb(const size_t mb_i, const uhd::device_addr_t &dev_addr) mb.zpu_spi = spi_core_3000::make(mb.zpu_ctrl, SR_ADDR(SET0_BASE, ZPU_SR_SPI), SR_ADDR(SET0_BASE, ZPU_RB_SPI)); mb.zpu_i2c = i2c_core_100_wb32::make(mb.zpu_ctrl, I2C1_BASE); - mb.zpu_i2c->set_clock_rate(X300_BUS_CLOCK_RATE); + mb.zpu_i2c->set_clock_rate(X300_BUS_CLOCK_RATE/2); //////////////////////////////////////////////////////////////////// // print network routes mapping @@ -565,7 +735,7 @@ void x300_impl::setup_mb(const size_t mb_i, const uhd::device_addr_t &dev_addr) const mboard_eeprom_t mb_eeprom(*eeprom16, "X300"); _tree->create<mboard_eeprom_t>(mb_path / "eeprom") .set(mb_eeprom) - .subscribe(boost::bind(&x300_impl::set_mb_eeprom, this, mb.zpu_i2c, _1)); + .add_coerced_subscriber(boost::bind(&x300_impl::set_mb_eeprom, this, mb.zpu_i2c, _1)); bool recover_mb_eeprom = dev_addr.has_key("recover_mb_eeprom"); if (recover_mb_eeprom) { @@ -597,27 +767,9 @@ void x300_impl::setup_mb(const size_t mb_i, const uhd::device_addr_t &dev_addr) //////////////////////////////////////////////////////////////////// // determine routing based on address match //////////////////////////////////////////////////////////////////// - mb.router_dst_here = X300_XB_DST_E0; //some default if eeprom not match - if (mb.xport_path == "nirio") { - mb.router_dst_here = X300_XB_DST_PCI; - } else { - if (mb.addr == mb_eeprom["ip-addr0"]) mb.router_dst_here = X300_XB_DST_E0; - else if (mb.addr == mb_eeprom["ip-addr1"]) mb.router_dst_here = X300_XB_DST_E1; - else if (mb.addr == mb_eeprom["ip-addr2"]) mb.router_dst_here = X300_XB_DST_E0; - else if (mb.addr == mb_eeprom["ip-addr3"]) mb.router_dst_here = X300_XB_DST_E1; - else if (mb.addr == boost::asio::ip::address_v4(boost::uint32_t(X300_DEFAULT_IP_ETH0_1G)).to_string()) mb.router_dst_here = X300_XB_DST_E0; - else if (mb.addr == boost::asio::ip::address_v4(boost::uint32_t(X300_DEFAULT_IP_ETH1_1G)).to_string()) mb.router_dst_here = X300_XB_DST_E1; - else if (mb.addr == boost::asio::ip::address_v4(boost::uint32_t(X300_DEFAULT_IP_ETH0_10G)).to_string()) mb.router_dst_here = X300_XB_DST_E0; - else if (mb.addr == boost::asio::ip::address_v4(boost::uint32_t(X300_DEFAULT_IP_ETH1_10G)).to_string()) mb.router_dst_here = X300_XB_DST_E1; - } - - //////////////////////////////////////////////////////////////////// - // read dboard eeproms - //////////////////////////////////////////////////////////////////// - for (size_t i = 0; i < 8; i++) - { - if (i == 0 or i == 2) continue; //not used - mb.db_eeproms[i].load(*mb.zpu_i2c, 0x50 | i); + if (mb.xport_path != "nirio") { + // Discover ethernet interfaces + mb.discover_eth(mb_eeprom, eth_addrs); } //////////////////////////////////////////////////////////////////// @@ -682,15 +834,14 @@ void x300_impl::setup_mb(const size_t mb_i, const uhd::device_addr_t &dev_addr) //Initialize clock source to use internal reference and generate //a valid radio clock. This may change after configuration is done. //This will configure the LMK and wait for lock - update_clock_source(mb, "internal"); + update_clock_source(mb, X300_DEFAULT_CLOCK_SOURCE); //////////////////////////////////////////////////////////////////// // create clock properties //////////////////////////////////////////////////////////////////// - _tree->create<double>(mb_path / "tick_rate") - .publish(boost::bind(&x300_clock_ctrl::get_master_clock_rate, mb.clock)); - - _tree->create<time_spec_t>(mb_path / "time" / "cmd"); + _tree->create<double>(mb_path / "master_clock_rate") + .set_publisher(boost::bind(&x300_clock_ctrl::get_master_clock_rate, mb.clock)) + ; UHD_MSG(status) << "Radio 1x clock:" << (mb.clock->get_master_clock_rate()/1e6) << std::endl; @@ -717,7 +868,7 @@ void x300_impl::setup_mb(const size_t mb_i, const uhd::device_addr_t &dev_addr) BOOST_FOREACH(const std::string &name, mb.gps->get_sensors()) { _tree->create<sensor_value_t>(mb_path / "sensors" / name) - .publish(boost::bind(&gps_ctrl::get_sensor, mb.gps, name)); + .set_publisher(boost::bind(&gps_ctrl::get_sensor, mb.gps, name)); } } else @@ -733,69 +884,27 @@ void x300_impl::setup_mb(const size_t mb_i, const uhd::device_addr_t &dev_addr) mb.zpu_ctrl->poke32(SR_ADDR(SETXB_BASE, i), 0); } - //////////////////////////////////////////////////////////////////// - // setup radios - //////////////////////////////////////////////////////////////////// - this->setup_radio(mb_i, "A", dev_addr); - this->setup_radio(mb_i, "B", dev_addr); - - //////////////////////////////////////////////////////////////////// - // ADC test and cal - //////////////////////////////////////////////////////////////////// - if (dev_addr.has_key("self_cal_adc_delay")) { - self_cal_adc_xfer_delay(mb, true /* Apply ADC delay */); - } - if (dev_addr.has_key("ext_adc_self_test")) { - extended_adc_test(mb, dev_addr.cast<double>("ext_adc_self_test", 30)); - } else if ( ! dev_addr.has_key("disable_adc_self_test") ) { - self_test_adcs(mb); - } - - //////////////////////////////////////////////////////////////////// - // front panel gpio - //////////////////////////////////////////////////////////////////// - mb.fp_gpio = gpio_core_200::make(mb.radio_perifs[0].ctrl, radio::sr_addr(radio::FP_GPIO), radio::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(&x300_impl::set_fp_gpio, this, mb.fp_gpio, attr.first, _1)); - } - _tree->create<boost::uint32_t>(mb_path / "gpio" / "FP0" / "READBACK") - .publish(boost::bind(&x300_impl::get_fp_gpio, this, mb.fp_gpio)); - - //////////////////////////////////////////////////////////////////// - // register the time keepers - only one can be the highlander - //////////////////////////////////////////////////////////////////// - _tree->create<time_spec_t>(mb_path / "time" / "now") - .publish(boost::bind(&time_core_3000::get_time_now, mb.radio_perifs[0].time64)) - .subscribe(boost::bind(&x300_impl::sync_times, this, mb, _1)) - .set(0.0); - _tree->create<time_spec_t>(mb_path / "time" / "pps") - .publish(boost::bind(&time_core_3000::get_time_last_pps, mb.radio_perifs[0].time64)) - .subscribe(boost::bind(&time_core_3000::set_time_next_pps, mb.radio_perifs[0].time64, _1)) - .subscribe(boost::bind(&time_core_3000::set_time_next_pps, mb.radio_perifs[1].time64, _1)); //////////////////////////////////////////////////////////////////// // setup time sources and properties //////////////////////////////////////////////////////////////////// _tree->create<std::string>(mb_path / "time_source" / "value") .set("internal") - .subscribe(boost::bind(&x300_impl::update_time_source, this, boost::ref(mb), _1)); + .add_coerced_subscriber(boost::bind(&x300_impl::update_time_source, this, boost::ref(mb), _1)); static const std::vector<std::string> time_sources = boost::assign::list_of("internal")("external")("gpsdo"); _tree->create<std::vector<std::string> >(mb_path / "time_source" / "options").set(time_sources); //setup the time output, default to ON _tree->create<bool>(mb_path / "time_source" / "output") - .subscribe(boost::bind(&x300_impl::set_time_source_out, this, boost::ref(mb), _1)) + .add_coerced_subscriber(boost::bind(&x300_impl::set_time_source_out, this, boost::ref(mb), _1)) .set(true); //////////////////////////////////////////////////////////////////// // setup clock sources and properties //////////////////////////////////////////////////////////////////// _tree->create<std::string>(mb_path / "clock_source" / "value") - .set("internal") - .subscribe(boost::bind(&x300_impl::update_clock_source, this, boost::ref(mb), _1)); + .set(X300_DEFAULT_CLOCK_SOURCE) + .add_coerced_subscriber(boost::bind(&x300_impl::update_clock_source, this, boost::ref(mb), _1)); static const std::vector<std::string> clock_source_options = boost::assign::list_of("internal")("external")("gpsdo"); _tree->create<std::vector<std::string> >(mb_path / "clock_source" / "options").set(clock_source_options); @@ -811,54 +920,70 @@ void x300_impl::setup_mb(const size_t mb_i, const uhd::device_addr_t &dev_addr) //setup the clock output, default to ON _tree->create<bool>(mb_path / "clock_source" / "output") - .subscribe(boost::bind(&x300_clock_ctrl::set_ref_out, mb.clock, _1)); + .add_coerced_subscriber(boost::bind(&x300_clock_ctrl::set_ref_out, mb.clock, _1)); //initialize tick rate (must be done before setting time) - _tree->access<double>(mb_path / "tick_rate") - .subscribe(boost::bind(&x300_impl::set_tick_rate, this, boost::ref(mb), _1)) - .subscribe(boost::bind(&x300_impl::update_tick_rate, this, boost::ref(mb), _1)) - .set(mb.clock->get_master_clock_rate()); - - //////////////////////////////////////////////////////////////////// - // create frontend mapping - //////////////////////////////////////////////////////////////////// - std::vector<size_t> default_map(2, 0); default_map[1] = 1; - _tree->create<std::vector<size_t> >(mb_path / "rx_chan_dsp_mapping").set(default_map); - _tree->create<std::vector<size_t> >(mb_path / "tx_chan_dsp_mapping").set(default_map); - _tree->create<subdev_spec_t>(mb_path / "rx_subdev_spec") - .subscribe(boost::bind(&x300_impl::update_subdev_spec, this, "rx", mb_i, _1)); - _tree->create<subdev_spec_t>(mb_path / "tx_subdev_spec") - .subscribe(boost::bind(&x300_impl::update_subdev_spec, this, "tx", mb_i, _1)); + _tree->create<double>(mb_path / "tick_rate") + .add_coerced_subscriber(boost::bind(&device3_impl::update_tx_streamers, this, _1)) + .add_coerced_subscriber(boost::bind(&device3_impl::update_rx_streamers, this, _1)) + .set(mb.clock->get_master_clock_rate()) + ; //////////////////////////////////////////////////////////////////// // and do the misc mboard sensors //////////////////////////////////////////////////////////////////// _tree->create<sensor_value_t>(mb_path / "sensors" / "ref_locked") - .publish(boost::bind(&x300_impl::get_ref_locked, this, mb)); + .set_publisher(boost::bind(&x300_impl::get_ref_locked, this, mb)); + + //////////////// RFNOC ///////////////// + const size_t n_rfnoc_blocks = mb.zpu_ctrl->peek32(SR_ADDR(SET0_BASE, ZPU_RB_NUM_CE)); + enumerate_rfnoc_blocks( + mb_i, + n_rfnoc_blocks, + X300_XB_DST_PCI + 1, /* base port */ + uhd::sid_t(X300_SRC_ADDR0, 0, X300_DST_ADDR + mb_i, 0), + dev_addr, + mb.if_pkt_is_big_endian ? ENDIANNESS_BIG : ENDIANNESS_LITTLE + ); + //////////////// RFNOC ///////////////// + + // If we have a radio, we must configure its codec control: + const std::string radio_blockid_hint = str(boost::format("%d/Radio") % mb_i); + std::vector<rfnoc::block_id_t> radio_ids = + find_blocks<rfnoc::x300_radio_ctrl_impl>(radio_blockid_hint); + if (not radio_ids.empty()) { + if (radio_ids.size() > 2) { + UHD_MSG(warning) << "Too many Radio Blocks found. Using only the first two." << std::endl; + radio_ids.resize(2); + } - //////////////////////////////////////////////////////////////////// - // do some post-init tasks - //////////////////////////////////////////////////////////////////// - subdev_spec_t rx_fe_spec, tx_fe_spec; - rx_fe_spec.push_back(subdev_spec_pair_t("A", - _tree->list(mb_path / "dboards" / "A" / "rx_frontends").at(0))); - rx_fe_spec.push_back(subdev_spec_pair_t("B", - _tree->list(mb_path / "dboards" / "B" / "rx_frontends").at(0))); - tx_fe_spec.push_back(subdev_spec_pair_t("A", - _tree->list(mb_path / "dboards" / "A" / "tx_frontends").at(0))); - tx_fe_spec.push_back(subdev_spec_pair_t("B", - _tree->list(mb_path / "dboards" / "B" / "tx_frontends").at(0))); - - _tree->access<subdev_spec_t>(mb_path / "rx_subdev_spec").set(rx_fe_spec); - _tree->access<subdev_spec_t>(mb_path / "tx_subdev_spec").set(tx_fe_spec); - - mb.regmap_db = boost::make_shared<uhd::soft_regmap_db_t>(); - mb.regmap_db->add(*mb.fw_regmap); - mb.regmap_db->add(*mb.radio_perifs[0].regmap); - mb.regmap_db->add(*mb.radio_perifs[1].regmap); - - _tree->create<uhd::soft_regmap_accessor_t::sptr>(mb_path / "registers") - .set(mb.regmap_db); + BOOST_FOREACH(const rfnoc::block_id_t &id, radio_ids) { + rfnoc::x300_radio_ctrl_impl::sptr radio(get_block_ctrl<rfnoc::x300_radio_ctrl_impl>(id)); + mb.radios.push_back(radio); + radio->setup_radio(mb.zpu_i2c, mb.clock, dev_addr.has_key("self_cal_adc_delay")); + } + + //////////////////////////////////////////////////////////////////// + // ADC test and cal + //////////////////////////////////////////////////////////////////// + if (dev_addr.has_key("self_cal_adc_delay")) { + rfnoc::x300_radio_ctrl_impl::self_cal_adc_xfer_delay( + mb.radios, mb.clock, + boost::bind(&x300_impl::wait_for_clk_locked, this, mb, fw_regmap_t::clk_status_reg_t::LMK_LOCK, _1), + true /* Apply ADC delay */); + } + if (dev_addr.has_key("ext_adc_self_test")) { + rfnoc::x300_radio_ctrl_impl::extended_adc_test( + mb.radios, + dev_addr.cast<double>("ext_adc_self_test", 30)); + } else if (not dev_addr.has_key("recover_mb_eeprom")){ + for (size_t i = 0; i < mb.radios.size(); i++) { + mb.radios.at(i)->self_test_adc(); + } + } + } else { + UHD_MSG(status) << "No Radio Block found. Assuming radio-less operation." << std::endl; + } mb.initialization_done = true; } @@ -869,14 +994,6 @@ x300_impl::~x300_impl(void) { BOOST_FOREACH(mboard_members_t &mb, _mb) { - //Disable/reset ADC/DAC - mb.radio_perifs[0].regmap->misc_outs_reg.set(radio_regmap_t::misc_outs_reg_t::ADC_RESET, 1); - mb.radio_perifs[0].regmap->misc_outs_reg.set(radio_regmap_t::misc_outs_reg_t::DAC_RESET_N, 0); - mb.radio_perifs[0].regmap->misc_outs_reg.set(radio_regmap_t::misc_outs_reg_t::DAC_ENABLED, 0); - mb.radio_perifs[0].regmap->misc_outs_reg.flush(); - mb.radio_perifs[1].regmap->misc_outs_reg.set(radio_regmap_t::misc_outs_reg_t::DAC_ENABLED, 0); - mb.radio_perifs[1].regmap->misc_outs_reg.flush(); - //kill the claimer task and unclaim the device mb.claimer_task.reset(); { //Critical section @@ -885,7 +1002,7 @@ x300_impl::~x300_impl(void) mb.zpu_ctrl->poke32(SR_ADDR(X300_FW_SHMEM_BASE, X300_FW_SHMEM_CLAIM_SRC), 0); //If the process is killed, the entire registry will disappear so we //don't need to worry about unclean shutdowns here. - get_pcie_zpu_iface_registry().pop(mb.addr); + get_pcie_zpu_iface_registry().pop(mb.get_pri_eth().addr); } } } @@ -895,270 +1012,144 @@ x300_impl::~x300_impl(void) } } -void x300_impl::setup_radio(const size_t mb_i, const std::string &slot_name, const uhd::device_addr_t &dev_addr) +uint32_t x300_impl::allocate_pcie_dma_chan(const uhd::sid_t &tx_sid, const xport_type_t xport_type) { - const fs_path mb_path = "/mboards/"+boost::lexical_cast<std::string>(mb_i); - UHD_ASSERT_THROW(mb_i < _mb.size()); - mboard_members_t &mb = _mb[mb_i]; - const size_t radio_index = mb.get_radio_index(slot_name); - radio_perifs_t &perif = mb.radio_perifs[radio_index]; - - UHD_MSG(status) << boost::format("Initialize Radio%d control...") % radio_index << std::endl; - - //////////////////////////////////////////////////////////////////// - // radio control - //////////////////////////////////////////////////////////////////// - boost::uint8_t dest = (radio_index == 0)? X300_XB_DST_R0 : X300_XB_DST_R1; - boost::uint32_t ctrl_sid; - both_xports_t xport = this->make_transport(mb_i, dest, X300_RADIO_DEST_PREFIX_CTRL, device_addr_t(), ctrl_sid); - perif.ctrl = radio_ctrl_core_3000::make(mb.if_pkt_is_big_endian, xport.recv, xport.send, ctrl_sid, slot_name); - - perif.regmap = boost::make_shared<radio_regmap_t>(radio_index); - perif.regmap->initialize(*perif.ctrl, true); - - //Only Radio0 has the ADC/DAC reset bits. Those bits are reserved for Radio1 - if (radio_index == 0) { - perif.regmap->misc_outs_reg.set(radio_regmap_t::misc_outs_reg_t::ADC_RESET, 1); - perif.regmap->misc_outs_reg.set(radio_regmap_t::misc_outs_reg_t::DAC_RESET_N, 0); - perif.regmap->misc_outs_reg.flush(); - perif.regmap->misc_outs_reg.set(radio_regmap_t::misc_outs_reg_t::ADC_RESET, 0); - perif.regmap->misc_outs_reg.set(radio_regmap_t::misc_outs_reg_t::DAC_RESET_N, 1); - perif.regmap->misc_outs_reg.flush(); - } - perif.regmap->misc_outs_reg.write(radio_regmap_t::misc_outs_reg_t::DAC_ENABLED, 1); - - this->register_loopback_self_test(perif.ctrl); - - //////////////////////////////////////////////////////////////// - // Setup peripherals - //////////////////////////////////////////////////////////////// - perif.spi = spi_core_3000::make(perif.ctrl, radio::sr_addr(radio::SPI), radio::RB32_SPI); - perif.adc = x300_adc_ctrl::make(perif.spi, DB_ADC_SEN); - perif.dac = x300_dac_ctrl::make(perif.spi, DB_DAC_SEN, mb.clock->get_master_clock_rate()); - perif.leds = gpio_core_200_32wo::make(perif.ctrl, radio::sr_addr(radio::LEDS)); - perif.rx_fe = rx_frontend_core_200::make(perif.ctrl, radio::sr_addr(radio::RX_FRONT)); - perif.rx_fe->set_dc_offset(rx_frontend_core_200::DEFAULT_DC_OFFSET_VALUE); - perif.rx_fe->set_dc_offset_auto(rx_frontend_core_200::DEFAULT_DC_OFFSET_ENABLE); - perif.tx_fe = tx_frontend_core_200::make(perif.ctrl, radio::sr_addr(radio::TX_FRONT)); - perif.tx_fe->set_dc_offset(tx_frontend_core_200::DEFAULT_DC_OFFSET_VALUE); - perif.tx_fe->set_iq_balance(tx_frontend_core_200::DEFAULT_IQ_BALANCE_VALUE); - perif.framer = rx_vita_core_3000::make(perif.ctrl, radio::sr_addr(radio::RX_CTRL)); - perif.ddc = rx_dsp_core_3000::make(perif.ctrl, radio::sr_addr(radio::RX_DSP)); - perif.ddc->set_link_rate(10e9/8); //whatever - perif.deframer = tx_vita_core_3000::make(perif.ctrl, radio::sr_addr(radio::TX_CTRL)); - perif.duc = tx_dsp_core_3000::make(perif.ctrl, radio::sr_addr(radio::TX_DSP)); - perif.duc->set_link_rate(10e9/8); //whatever - - //////////////////////////////////////////////////////////////////// - // create time control objects - //////////////////////////////////////////////////////////////////// - time_core_3000::readback_bases_type time64_rb_bases; - time64_rb_bases.rb_now = radio::RB64_TIME_NOW; - time64_rb_bases.rb_pps = radio::RB64_TIME_PPS; - perif.time64 = time_core_3000::make(perif.ctrl, radio::sr_addr(radio::TIME), time64_rb_bases); - - //Capture delays are calibrated every time. The status is only printed is the user - //asks to run the xfer self cal using "self_cal_adc_delay" - self_cal_adc_capture_delay(mb, radio_index, dev_addr.has_key("self_cal_adc_delay")); - - _tree->access<time_spec_t>(mb_path / "time" / "cmd") - .subscribe(boost::bind(&radio_ctrl_core_3000::set_time, perif.ctrl, _1)); - - //////////////////////////////////////////////////////////////// - // create codec control objects - //////////////////////////////////////////////////////////////// - _tree->create<int>(mb_path / "rx_codecs" / slot_name / "gains"); //phony property so this dir exists - _tree->create<int>(mb_path / "tx_codecs" / slot_name / "gains"); //phony property so this dir exists - _tree->create<std::string>(mb_path / "rx_codecs" / slot_name / "name").set("ads62p48"); - _tree->create<std::string>(mb_path / "tx_codecs" / slot_name / "name").set("ad9146"); - - _tree->create<meta_range_t>(mb_path / "rx_codecs" / slot_name / "gains" / "digital" / "range").set(meta_range_t(0, 6.0, 0.5)); - _tree->create<double>(mb_path / "rx_codecs" / slot_name / "gains" / "digital" / "value") - .subscribe(boost::bind(&x300_adc_ctrl::set_gain, perif.adc, _1)).set(0); - - //////////////////////////////////////////////////////////////////// - // front end corrections - //////////////////////////////////////////////////////////////////// - perif.rx_fe->populate_subtree(_tree->subtree(mb_path / "rx_frontends" / slot_name)); - perif.tx_fe->populate_subtree(_tree->subtree(mb_path / "tx_frontends" / slot_name)); - - //////////////////////////////////////////////////////////////////// - // connect rx dsp control objects - //////////////////////////////////////////////////////////////////// - const fs_path rx_dsp_path = mb_path / "rx_dsps" / str(boost::format("%u") % radio_index); - perif.ddc->populate_subtree(_tree->subtree(rx_dsp_path)); - _tree->access<double>(rx_dsp_path / "rate" / "value") - .subscribe(boost::bind(&x300_impl::update_rx_samp_rate, this, boost::ref(mb), radio_index, _1)) - ; - _tree->create<stream_cmd_t>(rx_dsp_path / "stream_cmd") - .subscribe(boost::bind(&rx_vita_core_3000::issue_stream_command, perif.framer, _1)); - - //////////////////////////////////////////////////////////////////// - // connect tx dsp control objects - //////////////////////////////////////////////////////////////////// - const fs_path tx_dsp_path = mb_path / "tx_dsps" / str(boost::format("%u") % radio_index); - perif.duc->populate_subtree(_tree->subtree(tx_dsp_path)); - _tree->access<double>(tx_dsp_path / "rate" / "value") - .subscribe(boost::bind(&x300_impl::update_tx_samp_rate, this, boost::ref(mb), radio_index, _1)) - ; - - //////////////////////////////////////////////////////////////////// - // create RF frontend interfacing - //////////////////////////////////////////////////////////////////// - const fs_path db_path = (mb_path / "dboards" / slot_name); - const size_t j = (slot_name == "B")? 0x2 : 0x0; - _tree->create<dboard_eeprom_t>(db_path / "rx_eeprom") - .set(mb.db_eeproms[X300_DB0_RX_EEPROM | j]) - .subscribe(boost::bind(&x300_impl::set_db_eeprom, this, mb.zpu_i2c, (0x50 | X300_DB0_RX_EEPROM | j), _1)); - _tree->create<dboard_eeprom_t>(db_path / "tx_eeprom") - .set(mb.db_eeproms[X300_DB0_TX_EEPROM | j]) - .subscribe(boost::bind(&x300_impl::set_db_eeprom, this, mb.zpu_i2c, (0x50 | X300_DB0_TX_EEPROM | j), _1)); - _tree->create<dboard_eeprom_t>(db_path / "gdb_eeprom") - .set(mb.db_eeproms[X300_DB0_GDB_EEPROM | j]) - .subscribe(boost::bind(&x300_impl::set_db_eeprom, this, mb.zpu_i2c, (0x50 | X300_DB0_GDB_EEPROM | j), _1)); - - //create a new dboard interface - x300_dboard_iface_config_t db_config; - db_config.gpio = gpio_core_200::make(perif.ctrl, radio::sr_addr(radio::GPIO), radio::RB32_GPIO); - db_config.spi = perif.spi; - db_config.rx_spi_slaveno = DB_RX_SEN; - db_config.tx_spi_slaveno = DB_TX_SEN; - db_config.i2c = mb.zpu_i2c; - db_config.clock = mb.clock; - db_config.which_rx_clk = (slot_name == "A")? X300_CLOCK_WHICH_DB0_RX : X300_CLOCK_WHICH_DB1_RX; - db_config.which_tx_clk = (slot_name == "A")? X300_CLOCK_WHICH_DB0_TX : X300_CLOCK_WHICH_DB1_TX; - db_config.dboard_slot = (slot_name == "A")? 0 : 1; - db_config.cmd_time_ctrl = perif.ctrl; - _dboard_ifaces[db_path] = x300_make_dboard_iface(db_config); - - //create a new dboard manager - _tree->create<dboard_iface::sptr>(db_path / "iface").set(_dboard_ifaces[db_path]); - _dboard_managers[db_path] = dboard_manager::make( - mb.db_eeproms[X300_DB0_RX_EEPROM | j].id, - mb.db_eeproms[X300_DB0_TX_EEPROM | j].id, - mb.db_eeproms[X300_DB0_GDB_EEPROM | j].id, - _dboard_ifaces[db_path], - _tree->subtree(db_path) - ); - - //now that dboard is created -- register into rx antenna event - const std::string fe_name = _tree->list(db_path / "rx_frontends").front(); - _tree->access<std::string>(db_path / "rx_frontends" / fe_name / "antenna" / "value") - .subscribe(boost::bind(&x300_impl::update_atr_leds, this, mb.radio_perifs[radio_index].leds, _1)); - this->update_atr_leds(mb.radio_perifs[radio_index].leds, ""); //init anyway, even if never called - - //bind frontend corrections to the dboard freq props - const fs_path db_tx_fe_path = db_path / "tx_frontends"; - BOOST_FOREACH(const std::string &name, _tree->list(db_tx_fe_path)) { - _tree->access<double>(db_tx_fe_path / name / "freq" / "value") - .subscribe(boost::bind(&x300_impl::set_tx_fe_corrections, this, mb_path, slot_name, _1)); - } - const fs_path db_rx_fe_path = db_path / "rx_frontends"; - BOOST_FOREACH(const std::string &name, _tree->list(db_rx_fe_path)) { - _tree->access<double>(db_rx_fe_path / name / "freq" / "value") - .subscribe(boost::bind(&x300_impl::set_rx_fe_corrections, this, mb_path, slot_name, _1)); - } -} + static const uint32_t CTRL_CHANNEL = 0; + static const uint32_t FIRST_DATA_CHANNEL = 1; + if (xport_type == CTRL) { + return CTRL_CHANNEL; + } else { + // sid_t has no comparison defined + uint32_t raw_sid = tx_sid.get(); -void x300_impl::set_rx_fe_corrections(const uhd::fs_path &mb_path, const std::string &fe_name, const double lo_freq) -{ - if(not _ignore_cal_file){ - apply_rx_fe_corrections(this->get_tree()->subtree(mb_path), fe_name, lo_freq); - } -} + if (_dma_chan_pool.count(raw_sid) == 0) { + _dma_chan_pool[raw_sid] = _dma_chan_pool.size() + FIRST_DATA_CHANNEL; + UHD_LOG << "[X300] Assigning PCIe DMA channel " << _dma_chan_pool[raw_sid] + << " to SID " << tx_sid.to_pp_string_hex() << std::endl; + } -void x300_impl::set_tx_fe_corrections(const uhd::fs_path &mb_path, const std::string &fe_name, const double lo_freq) -{ - if(not _ignore_cal_file){ - apply_tx_fe_corrections(this->get_tree()->subtree(mb_path), fe_name, lo_freq); + if (_dma_chan_pool.size() + FIRST_DATA_CHANNEL > X300_PCIE_MAX_CHANNELS) { + throw uhd::runtime_error("Trying to allocate more DMA channels than are available"); + } + return _dma_chan_pool[raw_sid]; } } -boost::uint32_t get_pcie_dma_channel(boost::uint8_t destination, boost::uint8_t prefix) -{ - static const boost::uint32_t RADIO_GRP_SIZE = 3; - static const boost::uint32_t RADIO0_GRP = 0; - static const boost::uint32_t RADIO1_GRP = 1; - - boost::uint32_t radio_grp = (destination == X300_XB_DST_R0) ? RADIO0_GRP : RADIO1_GRP; - return ((radio_grp * RADIO_GRP_SIZE) + prefix); +static boost::uint32_t extract_sid_from_pkt(void* pkt, size_t) { + return uhd::sid_t(uhd::wtohx(static_cast<const boost::uint32_t*>(pkt)[1])).get_dst(); } - -x300_impl::both_xports_t x300_impl::make_transport( - const size_t mb_index, - const boost::uint8_t& destination, - const boost::uint8_t& prefix, - const uhd::device_addr_t& args, - boost::uint32_t& sid) -{ +uhd::both_xports_t x300_impl::make_transport( + const uhd::sid_t &address, + const xport_type_t xport_type, + const uhd::device_addr_t& args +) { + const size_t mb_index = address.get_dst_addr() - X300_DST_ADDR; mboard_members_t &mb = _mb[mb_index]; - both_xports_t xports; - - sid_config_t config; - config.router_addr_there = X300_DEVICE_THERE; - config.dst_prefix = prefix; - config.router_dst_there = destination; - config.router_dst_here = mb.router_dst_here; - sid = this->allocate_sid(mb, config); - - static const uhd::device_addr_t DEFAULT_XPORT_ARGS; - - const uhd::device_addr_t& xport_args = - (prefix != X300_RADIO_DEST_PREFIX_CTRL) ? args : DEFAULT_XPORT_ARGS; - + const uhd::device_addr_t& xport_args = (xport_type == CTRL) ? uhd::device_addr_t() : args; zero_copy_xport_params default_buff_args; + both_xports_t xports; if (mb.xport_path == "nirio") { - default_buff_args.send_frame_size = - (prefix == X300_RADIO_DEST_PREFIX_TX) - ? X300_PCIE_TX_DATA_FRAME_SIZE - : X300_PCIE_MSG_FRAME_SIZE; - - default_buff_args.recv_frame_size = - (prefix == X300_RADIO_DEST_PREFIX_RX) - ? X300_PCIE_RX_DATA_FRAME_SIZE - : X300_PCIE_MSG_FRAME_SIZE; - - default_buff_args.num_send_frames = - (prefix == X300_RADIO_DEST_PREFIX_TX) - ? X300_PCIE_DATA_NUM_FRAMES - : X300_PCIE_MSG_NUM_FRAMES; - - default_buff_args.num_recv_frames = - (prefix == X300_RADIO_DEST_PREFIX_RX) - ? X300_PCIE_DATA_NUM_FRAMES - : X300_PCIE_MSG_NUM_FRAMES; - - xports.recv = nirio_zero_copy::make( - mb.rio_fpga_interface, - get_pcie_dma_channel(destination, prefix), - default_buff_args, - xport_args); + xports.send_sid = this->allocate_sid(mb, address, X300_SRC_ADDR0, X300_XB_DST_PCI); + xports.recv_sid = xports.send_sid.reversed(); + + uint32_t dma_channel_num = allocate_pcie_dma_chan(xports.send_sid, xport_type); + if (xport_type == CTRL) { + //Transport for control stream + if (_ctrl_dma_xport.get() == NULL) { + //One underlying DMA channel will handle + //all control traffic + zero_copy_xport_params ctrl_buff_args; + ctrl_buff_args.send_frame_size = X300_PCIE_MSG_FRAME_SIZE; + ctrl_buff_args.recv_frame_size = X300_PCIE_MSG_FRAME_SIZE; + ctrl_buff_args.num_send_frames = X300_PCIE_MSG_NUM_FRAMES * X300_PCIE_MAX_MUXED_XPORTS; + ctrl_buff_args.num_recv_frames = X300_PCIE_MSG_NUM_FRAMES * X300_PCIE_MAX_MUXED_XPORTS; + + zero_copy_if::sptr base_xport = nirio_zero_copy::make( + mb.rio_fpga_interface, dma_channel_num, + ctrl_buff_args, uhd::device_addr_t()); + _ctrl_dma_xport = muxed_zero_copy_if::make(base_xport, extract_sid_from_pkt, X300_PCIE_MAX_MUXED_XPORTS); + } + //Create a virtual control transport + xports.recv = _ctrl_dma_xport->make_stream(xports.recv_sid.get_dst()); + } else { + //Transport for data stream + default_buff_args.send_frame_size = + (xport_type == TX_DATA) + ? X300_PCIE_TX_DATA_FRAME_SIZE + : X300_PCIE_MSG_FRAME_SIZE; + + default_buff_args.recv_frame_size = + (xport_type == RX_DATA) + ? X300_PCIE_RX_DATA_FRAME_SIZE + : X300_PCIE_MSG_FRAME_SIZE; + + default_buff_args.num_send_frames = + (xport_type == TX_DATA) + ? X300_PCIE_DATA_NUM_FRAMES + : X300_PCIE_MSG_NUM_FRAMES; + + default_buff_args.num_recv_frames = + (xport_type == RX_DATA) + ? X300_PCIE_DATA_NUM_FRAMES + : X300_PCIE_MSG_NUM_FRAMES; + + xports.recv = nirio_zero_copy::make( + mb.rio_fpga_interface, dma_channel_num, + default_buff_args, xport_args); + } xports.send = xports.recv; + // Router config word is: + // - Upper 16 bits: Destination address (e.g. 0.0) + // - Lower 16 bits: DMA channel + uint32_t router_config_word = (xports.recv_sid.get_dst() << 16) | dma_channel_num; + mb.rio_fpga_interface->get_kernel_proxy()->poke(PCIE_ROUTER_REG(0), router_config_word); + //For the nirio transport, buffer size is depends on the frame size and num frames xports.recv_buff_size = xports.recv->get_num_recv_frames() * xports.recv->get_recv_frame_size(); xports.send_buff_size = xports.send->get_num_send_frames() * xports.send->get_send_frame_size(); } else if (mb.xport_path == "eth") { + // Decide on the IP/Interface pair based on the endpoint index + std::string interface_addr = mb.eth_conns[mb.next_src_addr].addr; + const uint32_t xbar_src_addr = + mb.next_src_addr==0 ? X300_SRC_ADDR0 : X300_SRC_ADDR1; + const uint32_t xbar_src_dst = + mb.eth_conns[mb.next_src_addr].type==X300_IFACE_ETH0 ? X300_XB_DST_E0 : X300_XB_DST_E1; + mb.next_src_addr = (mb.next_src_addr + 1) % mb.eth_conns.size(); + + xports.send_sid = this->allocate_sid(mb, address, xbar_src_addr, xbar_src_dst); + xports.recv_sid = xports.send_sid.reversed(); /* Determine what the recommended frame size is for this * connection type.*/ size_t eth_data_rec_frame_size = 0; - if (mb.loaded_fpga_image == "HGS") { - if (mb.router_dst_here == X300_XB_DST_E0) { + fs_path mboard_path = fs_path("/mboards/"+boost::lexical_cast<std::string>(mb_index) / "link_max_rate"); + + if (mb.loaded_fpga_image == "HG") { + size_t max_link_rate = 0; + if (xbar_src_dst == X300_XB_DST_E0) { eth_data_rec_frame_size = X300_1GE_DATA_FRAME_MAX_SIZE; - _tree->access<double>("/mboards/"+boost::lexical_cast<std::string>(mb_index) / "link_max_rate").set(X300_MAX_RATE_1GIGE); - } else if (mb.router_dst_here == X300_XB_DST_E1) { + max_link_rate += X300_MAX_RATE_1GIGE; + } else if (xbar_src_dst == X300_XB_DST_E1) { eth_data_rec_frame_size = X300_10GE_DATA_FRAME_MAX_SIZE; - _tree->access<double>("/mboards/"+boost::lexical_cast<std::string>(mb_index) / "link_max_rate").set(X300_MAX_RATE_10GIGE); + max_link_rate += X300_MAX_RATE_10GIGE; } - } else if (mb.loaded_fpga_image == "XGS") { + _tree->access<double>(mboard_path).set(max_link_rate); + } else if (mb.loaded_fpga_image == "XG" or mb.loaded_fpga_image == "XA") { eth_data_rec_frame_size = X300_10GE_DATA_FRAME_MAX_SIZE; - _tree->access<double>("/mboards/"+boost::lexical_cast<std::string>(mb_index) / "link_max_rate").set(X300_MAX_RATE_10GIGE); + size_t max_link_rate = X300_MAX_RATE_10GIGE; + max_link_rate *= mb.eth_conns.size(); + _tree->access<double>(mboard_path).set(max_link_rate); + } else if (mb.loaded_fpga_image == "HA") { + eth_data_rec_frame_size = X300_1GE_DATA_FRAME_MAX_SIZE; + size_t max_link_rate = X300_MAX_RATE_1GIGE; + max_link_rate *= mb.eth_conns.size(); + _tree->access<double>(mboard_path).set(max_link_rate); } if (eth_data_rec_frame_size == 0) { @@ -1192,33 +1183,43 @@ x300_impl::both_xports_t x300_impl::make_transport( // Make sure frame sizes do not exceed the max available value supported by UHD default_buff_args.send_frame_size = - (prefix == X300_RADIO_DEST_PREFIX_TX) + (xport_type == TX_DATA) ? std::min(system_max_send_frame_size, X300_10GE_DATA_FRAME_MAX_SIZE) : std::min(system_max_send_frame_size, X300_ETH_MSG_FRAME_SIZE); default_buff_args.recv_frame_size = - (prefix == X300_RADIO_DEST_PREFIX_RX) + (xport_type == RX_DATA) ? std::min(system_max_recv_frame_size, X300_10GE_DATA_FRAME_MAX_SIZE) : std::min(system_max_recv_frame_size, X300_ETH_MSG_FRAME_SIZE); default_buff_args.num_send_frames = - (prefix == X300_RADIO_DEST_PREFIX_TX) + (xport_type == TX_DATA) ? X300_ETH_DATA_NUM_FRAMES : X300_ETH_MSG_NUM_FRAMES; default_buff_args.num_recv_frames = - (prefix == X300_RADIO_DEST_PREFIX_RX) + (xport_type == RX_DATA) ? X300_ETH_DATA_NUM_FRAMES : X300_ETH_MSG_NUM_FRAMES; //make a new transport - fpga has no idea how to talk to us on this yet udp_zero_copy::buff_params buff_params; - xports.recv = udp_zero_copy::make(mb.addr, + + xports.recv = udp_zero_copy::make( + interface_addr, BOOST_STRINGIZE(X300_VITA_UDP_PORT), default_buff_args, buff_params, xport_args); + // Create a threaded transport for the receive chain only + // Note that this shouldn't affect PCIe + if (xport_type == RX_DATA) { + xports.recv = zero_copy_recv_offload::make( + xports.recv, + X300_THREAD_BUFFER_TIMEOUT + ); + } xports.send = xports.recv; //For the UDP transport the buffer size if the size of the socket buffer @@ -1233,12 +1234,12 @@ x300_impl::both_xports_t x300_impl::make_transport( //send a mini packet with SID into the ZPU //ZPU will reprogram the ethernet framer UHD_LOG << "programming packet for new xport on " - << mb.addr << std::hex << "sid 0x" << sid << std::dec << std::endl; + << interface_addr << " sid " << xports.send_sid << std::endl; //YES, get a __send__ buffer from the __recv__ socket //-- this is the only way to program the framer for recv: managed_send_buffer::sptr buff = xports.recv->get_send_buff(); buff->cast<boost::uint32_t *>()[0] = 0; //eth dispatch looks for != 0 - buff->cast<boost::uint32_t *>()[1] = uhd::htonx(sid); + buff->cast<boost::uint32_t *>()[1] = uhd::htonx(xports.send_sid.get()); buff->commit(8); buff.reset(); @@ -1251,48 +1252,31 @@ x300_impl::both_xports_t x300_impl::make_transport( //ethernet framer has been programmed before we return. mb.zpu_ctrl->peek32(0); } - return xports; } -boost::uint32_t x300_impl::allocate_sid(mboard_members_t &mb, const sid_config_t &config) -{ - const std::string &xport_path = mb.xport_path; - const boost::uint32_t stream = (config.dst_prefix | (config.router_dst_there << 2)) & 0xff; - - const boost::uint32_t sid = 0 - | (X300_DEVICE_HERE << 24) - | (_sid_framer << 16) - | (config.router_addr_there << 8) - | (stream << 0) - ; - UHD_LOG << std::hex - << " sid 0x" << sid - << " framer 0x" << _sid_framer - << " stream 0x" << stream - << " router_dst_there 0x" << int(config.router_dst_there) - << " router_addr_there 0x" << int(config.router_addr_there) - << std::dec << std::endl; +uhd::sid_t x300_impl::allocate_sid( + mboard_members_t &mb, + const uhd::sid_t &address, + const uint32_t src_addr, + const uint32_t src_dst +) { + uhd::sid_t sid = address; + sid.set_src_addr(src_addr); + sid.set_src_endpoint(_sid_framer); + // TODO Move all of this setup_mb() // Program the X300 to recognise it's own local address. - mb.zpu_ctrl->poke32(SR_ADDR(SET0_BASE, ZPU_SR_XB_LOCAL), config.router_addr_there); + mb.zpu_ctrl->poke32(SR_ADDR(SET0_BASE, ZPU_SR_XB_LOCAL), address.get_dst_addr()); // Program CAM entry for outgoing packets matching a X300 resource (for example a Radio) - // This type of packet does matches the XB_LOCAL address and is looked up in the upper half of the CAM - mb.zpu_ctrl->poke32(SR_ADDR(SETXB_BASE, 256 + (stream)), config.router_dst_there); + // This type of packet matches the XB_LOCAL address and is looked up in the upper half of the CAM + mb.zpu_ctrl->poke32(SR_ADDR(SETXB_BASE, 256 + address.get_dst_endpoint()), address.get_dst_xbarport()); // Program CAM entry for returning packets to us (for example GR host via Eth0) // This type of packet does not match the XB_LOCAL address and is looked up in the lower half of the CAM - mb.zpu_ctrl->poke32(SR_ADDR(SETXB_BASE, 0 + (X300_DEVICE_HERE)), config.router_dst_here); - - if (xport_path == "nirio") { - boost::uint32_t router_config_word = ((_sid_framer & 0xff) << 16) | //Return SID - get_pcie_dma_channel(config.router_dst_there, config.dst_prefix); //Dest - mb.rio_fpga_interface->get_kernel_proxy()->poke(PCIE_ROUTER_REG(0), router_config_word); - } + mb.zpu_ctrl->poke32(SR_ADDR(SETXB_BASE, 0 + src_addr), src_dst); - UHD_LOG << std::hex - << "done router config for sid 0x" << sid - << std::dec << std::endl; + UHD_LOG << "done router config for sid " << sid << std::endl; //increment for next setup _sid_framer++; @@ -1300,57 +1284,9 @@ boost::uint32_t x300_impl::allocate_sid(mboard_members_t &mb, const sid_config_t return sid; } -void x300_impl::update_atr_leds(gpio_core_200_32wo::sptr leds, const std::string &rx_ant) -{ - const bool is_txrx = (rx_ant == "TX/RX"); - const int rx_led = (1 << 2); - const int tx_led = (1 << 1); - const int txrx_led = (1 << 0); - leds->set_atr_reg(dboard_iface::ATR_REG_IDLE, 0); - leds->set_atr_reg(dboard_iface::ATR_REG_RX_ONLY, is_txrx? txrx_led : rx_led); - leds->set_atr_reg(dboard_iface::ATR_REG_TX_ONLY, tx_led); - leds->set_atr_reg(dboard_iface::ATR_REG_FULL_DUPLEX, rx_led | tx_led); -} - -void x300_impl::set_tick_rate(mboard_members_t &mb, const double rate) -{ - BOOST_FOREACH(radio_perifs_t &perif, mb.radio_perifs) { - perif.ctrl->set_tick_rate(rate); - perif.time64->set_tick_rate(rate); - perif.framer->set_tick_rate(rate); - perif.ddc->set_tick_rate(rate); - perif.deframer->set_tick_rate(rate); - perif.duc->set_tick_rate(rate); - } -} - -void x300_impl::register_loopback_self_test(wb_iface::sptr iface) -{ - bool test_fail = false; - UHD_MSG(status) << "Performing register loopback test... " << std::flush; - size_t hash = size_t(time(NULL)); - for (size_t i = 0; i < 100; i++) - { - boost::hash_combine(hash, i); - iface->poke32(radio::sr_addr(radio::TEST), boost::uint32_t(hash)); - test_fail = iface->peek32(radio::RB32_TEST) != boost::uint32_t(hash); - if (test_fail) break; //exit loop on any failure - } - UHD_MSG(status) << ((test_fail)? " fail" : "pass") << std::endl; -} - -void x300_impl::radio_loopback(wb_iface::sptr iface, const bool on) -{ - iface->poke32(radio::sr_addr(radio::LOOPBACK), (on ? 0x1 : 0x0)); - UHD_MSG(status) << ((on)? "Radio Loopback On" : "Radio Loopback Off") << std::endl; -} - - - /*********************************************************************** * clock and time control logic **********************************************************************/ - void x300_impl::set_time_source_out(mboard_members_t &mb, const bool enb) { mb.fw_regmap->clock_ctrl_reg.write(fw_regmap_t::clk_ctrl_reg_t::PPS_OUT_EN, enb?1:0); @@ -1423,18 +1359,8 @@ void x300_impl::update_clock_source(mboard_members_t &mb, const std::string &sou } // Reset ADCs and DACs - for (size_t r = 0; r < mboard_members_t::NUM_RADIOS; r++) { - radio_perifs_t &perif = mb.radio_perifs[r]; - if (perif.regmap && r==0) { //ADC/DAC reset lines only exist in Radio0 - perif.regmap->misc_outs_reg.set(radio_regmap_t::misc_outs_reg_t::ADC_RESET, 1); - perif.regmap->misc_outs_reg.set(radio_regmap_t::misc_outs_reg_t::DAC_RESET_N, 0); - perif.regmap->misc_outs_reg.flush(); - perif.regmap->misc_outs_reg.set(radio_regmap_t::misc_outs_reg_t::ADC_RESET, 0); - perif.regmap->misc_outs_reg.set(radio_regmap_t::misc_outs_reg_t::DAC_RESET_N, 1); - perif.regmap->misc_outs_reg.flush(); - } - if (perif.adc) perif.adc->reset(); - if (perif.dac) perif.dac->reset(); + BOOST_FOREACH(rfnoc::x300_radio_ctrl_impl::sptr r, mb.radios) { + r->reset_codec(); } } @@ -1464,8 +1390,12 @@ void x300_impl::update_time_source(mboard_members_t &mb, const std::string &sour void x300_impl::sync_times(mboard_members_t &mb, const uhd::time_spec_t& t) { - BOOST_FOREACH(radio_perifs_t &perif, mb.radio_perifs) - perif.time64->set_time_sync(t); + std::vector<rfnoc::block_id_t> radio_ids = find_blocks<rfnoc::x300_radio_ctrl_impl>("Radio"); + BOOST_FOREACH(const rfnoc::block_id_t &id, radio_ids) { + get_block_ctrl<rfnoc::x300_radio_ctrl_impl>(id)->set_time_sync(t); + } + + mb.fw_regmap->clock_ctrl_reg.write(fw_regmap_t::clk_ctrl_reg_t::TIME_SYNC, 0); mb.fw_regmap->clock_ctrl_reg.write(fw_regmap_t::clk_ctrl_reg_t::TIME_SYNC, 1); mb.fw_regmap->clock_ctrl_reg.write(fw_regmap_t::clk_ctrl_reg_t::TIME_SYNC, 0); } @@ -1522,30 +1452,6 @@ void x300_impl::set_mb_eeprom(i2c_iface::sptr i2c, const mboard_eeprom_t &mb_eep } /*********************************************************************** - * front-panel GPIO - **********************************************************************/ - -boost::uint32_t x300_impl::get_fp_gpio(gpio_core_200::sptr gpio) -{ - return boost::uint32_t(gpio->read_gpio(dboard_iface::UNIT_RX)); -} - -void x300_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(); - } -} - -/*********************************************************************** * claimer logic **********************************************************************/ @@ -1687,9 +1593,12 @@ void x300_impl::check_fpga_compat(const fs_path &mb_path, const mboard_members_t % image_loader_path % (members.xport_path == "eth" ? "addr" : "resource") - % members.addr); + % members.get_pri_eth().addr); - throw uhd::runtime_error(str(boost::format( + std::cout << "=========================================================" << std::endl; + std::cout << "Warning:" << std::endl; + //throw uhd::runtime_error(str(boost::format( + std::cout << (str(boost::format( "Expected FPGA compatibility number %d, but got %d:\n" "The FPGA image on your device is not compatible with this host code build.\n" "Download the appropriate FPGA images for this version of UHD.\n" @@ -1701,9 +1610,17 @@ void x300_impl::check_fpga_compat(const fs_path &mb_path, const mboard_members_t ) % int(X300_FPGA_COMPAT_MAJOR) % compat_major % print_utility_error("uhd_images_downloader.py") % image_loader_cmd)); + std::cout << "=========================================================" << std::endl; } _tree->create<std::string>(mb_path / "fpga_version").set(str(boost::format("%u.%u") % compat_major % compat_minor)); + + const boost::uint32_t git_hash = members.zpu_ctrl->peek32(SR_ADDR(SET0_BASE, + ZPU_RB_GIT_HASH)); + _tree->create<std::string>(mb_path / "fpga_version_hash").set( + str(boost::format("%07x%s") + % (git_hash & 0x0FFFFFFF) + % ((git_hash & 0xF000000) ? "-dirty" : ""))); } x300_impl::x300_mboard_t x300_impl::get_mb_type_from_pcie(const std::string& resource, const std::string& rpc_port) diff --git a/host/lib/usrp/x300/x300_impl.hpp b/host/lib/usrp/x300/x300_impl.hpp index e04b06d65..d491e2bc0 100644 --- a/host/lib/usrp/x300/x300_impl.hpp +++ b/host/lib/usrp/x300/x300_impl.hpp @@ -19,51 +19,36 @@ #define INCLUDED_X300_IMPL_HPP #include <uhd/property_tree.hpp> -#include <uhd/device.hpp> +#include "../device3/device3_impl.hpp" #include <uhd/usrp/mboard_eeprom.hpp> -#include <uhd/usrp/dboard_manager.hpp> -#include <uhd/usrp/dboard_eeprom.hpp> #include <uhd/usrp/subdev_spec.hpp> #include <uhd/types/sensors.hpp> +#include "x300_radio_ctrl_impl.hpp" #include "x300_clock_ctrl.hpp" #include "x300_fw_common.h" #include <uhd/transport/udp_simple.hpp> //mtu -#include <uhd/utils/tasks.hpp> -#include "spi_core_3000.hpp" -#include "x300_adc_ctrl.hpp" -#include "x300_dac_ctrl.hpp" -#include "rx_vita_core_3000.hpp" -#include "tx_vita_core_3000.hpp" -#include "time_core_3000.hpp" -#include "rx_dsp_core_3000.hpp" -#include "tx_dsp_core_3000.hpp" #include "i2c_core_100_wb32.hpp" -#include "radio_ctrl_core_3000.hpp" -#include "rx_frontend_core_200.hpp" -#include "tx_frontend_core_200.hpp" -#include "gpio_core_200.hpp" #include <boost/weak_ptr.hpp> #include <uhd/usrp/gps_ctrl.hpp> -#include <uhd/usrp/mboard_eeprom.hpp> -#include <uhd/transport/bounded_buffer.hpp> #include <uhd/transport/nirio/niusrprio_session.h> #include <uhd/transport/vrt_if_packet.hpp> +#include <uhd/transport/muxed_zero_copy_if.hpp> #include "recv_packet_demuxer_3000.hpp" #include "x300_regs.hpp" +///////////// RFNOC ///////////////////// +#include <uhd/rfnoc/block_ctrl.hpp> +///////////// RFNOC ///////////////////// +#include <boost/dynamic_bitset.hpp> static const std::string X300_FW_FILE_NAME = "usrp_x300_fw.bin"; +static const std::string X300_DEFAULT_CLOCK_SOURCE = "internal"; -static const double X300_DEFAULT_TICK_RATE = 200e6; //Hz -static const double X300_DEFAULT_DBOARD_CLK_RATE = 50e6; //Hz -static const double X300_BUS_CLOCK_RATE = 166.666667e6; //Hz - -static const size_t X300_TX_HW_BUFF_SIZE = 520*1024; //512K SRAM buffer + 8K 2Clk FIFO -static const size_t X300_TX_FC_RESPONSE_FREQ = 8; //per flow-control window +static const double X300_DEFAULT_TICK_RATE = 200e6; //Hz +static const double X300_DEFAULT_DBOARD_CLK_RATE = 50e6; //Hz +static const double X300_BUS_CLOCK_RATE = 166.666667e6; //Hz static const size_t X300_RX_SW_BUFF_SIZE_ETH = 0x2000000;//32MiB For an ~8k frame size any size >32MiB is just wasted buffer space static const size_t X300_RX_SW_BUFF_SIZE_ETH_MACOS = 0x100000; //1Mib -static const double X300_RX_SW_BUFF_FULL_FACTOR = 0.90; //Buffer should ideally be 90% full. -static const size_t X300_RX_FC_REQUEST_FREQ = 32; //per flow-control window //The FIFO closest to the DMA controller is 1023 elements deep for RX and 1029 elements deep for TX //where an element is 8 bytes. For best throughput ensure that the data frame fits in these buffers. @@ -73,93 +58,66 @@ static const size_t X300_PCIE_TX_DATA_FRAME_SIZE = 8192; //bytes static const size_t X300_PCIE_DATA_NUM_FRAMES = 2048; static const size_t X300_PCIE_MSG_FRAME_SIZE = 256; //bytes static const size_t X300_PCIE_MSG_NUM_FRAMES = 64; +static const size_t X300_PCIE_MAX_CHANNELS = 6; +static const size_t X300_PCIE_MAX_MUXED_XPORTS = 32; static const size_t X300_10GE_DATA_FRAME_MAX_SIZE = 8000; //bytes static const size_t X300_1GE_DATA_FRAME_MAX_SIZE = 1472; //bytes static const size_t X300_ETH_MSG_FRAME_SIZE = uhd::transport::udp_simple::mtu; //bytes +static const double X300_THREAD_BUFFER_TIMEOUT = 0.1; // Time in seconds + static const size_t X300_ETH_MSG_NUM_FRAMES = 64; static const size_t X300_ETH_DATA_NUM_FRAMES = 32; static const double X300_DEFAULT_SYSREF_RATE = 10e6; -static const size_t X300_TX_MAX_HDR_LEN = // bytes - sizeof(boost::uint32_t) // Header - + sizeof(uhd::transport::vrt::if_packet_info_t().sid) // SID - + sizeof(uhd::transport::vrt::if_packet_info_t().tsf); // Timestamp -static const size_t X300_RX_MAX_HDR_LEN = // bytes - sizeof(boost::uint32_t) // Header - + sizeof(uhd::transport::vrt::if_packet_info_t().sid) // SID - + sizeof(uhd::transport::vrt::if_packet_info_t().tsf); // Timestamp - static const size_t X300_MAX_RATE_PCIE = 800000000; // bytes/s static const size_t X300_MAX_RATE_10GIGE = 800000000; // bytes/s static const size_t X300_MAX_RATE_1GIGE = 100000000; // bytes/s #define X300_RADIO_DEST_PREFIX_TX 0 -#define X300_RADIO_DEST_PREFIX_CTRL 1 -#define X300_RADIO_DEST_PREFIX_RX 2 - -#define X300_XB_DST_E0 0 -#define X300_XB_DST_E1 1 -#define X300_XB_DST_R0 2 // Radio 0 -> Slot A -#define X300_XB_DST_R1 3 // Radio 1 -> Slot B -#define X300_XB_DST_CE0 4 -#define X300_XB_DST_CE1 5 -#define X300_XB_DST_CE2 5 -#define X300_XB_DST_PCI 7 - -#define X300_DEVICE_THERE 2 -#define X300_DEVICE_HERE 0 - -//eeprom addrs for various boards -enum + +#define X300_XB_DST_E0 0 +#define X300_XB_DST_E1 1 +#define X300_XB_DST_PCI 2 +#define X300_XB_DST_R0 3 // Radio 0 -> Slot A +#define X300_XB_DST_R1 4 // Radio 1 -> Slot B +#define X300_XB_DST_CE0 5 + +#define X300_SRC_ADDR0 0 +#define X300_SRC_ADDR1 1 +#define X300_DST_ADDR 2 + +// Ethernet ports +enum x300_eth_iface_t { - X300_DB0_RX_EEPROM = 0x5, - X300_DB0_TX_EEPROM = 0x4, - X300_DB0_GDB_EEPROM = 0x1, - X300_DB1_RX_EEPROM = 0x7, - X300_DB1_TX_EEPROM = 0x6, - X300_DB1_GDB_EEPROM = 0x3, + X300_IFACE_NONE = 0, + X300_IFACE_ETH0 = 1, + X300_IFACE_ETH1 = 2, }; -struct x300_dboard_iface_config_t +struct x300_eth_conn_t { - gpio_core_200::sptr gpio; - spi_core_3000::sptr spi; - size_t rx_spi_slaveno; - size_t tx_spi_slaveno; - i2c_core_100_wb32::sptr i2c; - x300_clock_ctrl::sptr clock; - x300_clock_which_t which_rx_clk; - x300_clock_which_t which_tx_clk; - boost::uint8_t dboard_slot; - uhd::timed_wb_iface::sptr cmd_time_ctrl; + std::string addr; + x300_eth_iface_t type; }; -uhd::usrp::dboard_iface::sptr x300_make_dboard_iface(const x300_dboard_iface_config_t &); + uhd::uart_iface::sptr x300_make_uart_iface(uhd::wb_iface::sptr iface); -uhd::wb_iface::sptr x300_make_ctrl_iface_enet(uhd::transport::udp_simple::sptr udp); -uhd::wb_iface::sptr x300_make_ctrl_iface_pcie(uhd::niusrprio::niriok_proxy::sptr drv_proxy); +uhd::wb_iface::sptr x300_make_ctrl_iface_enet(uhd::transport::udp_simple::sptr udp, bool enable_errors = true); +uhd::wb_iface::sptr x300_make_ctrl_iface_pcie(uhd::niusrprio::niriok_proxy::sptr drv_proxy, bool enable_errors = true); uhd::device_addrs_t x300_find(const uhd::device_addr_t &hint_); -class x300_impl : public uhd::device +class x300_impl : public uhd::usrp::device3_impl { public: - typedef uhd::transport::bounded_buffer<uhd::async_metadata_t> async_md_type; x300_impl(const uhd::device_addr_t &); void setup_mb(const size_t which, const uhd::device_addr_t &); ~x300_impl(void); - //the io interface - uhd::rx_streamer::sptr get_rx_stream(const uhd::stream_args_t &); - uhd::tx_streamer::sptr get_tx_stream(const uhd::stream_args_t &); - - //support old async call - bool recv_async_msg(uhd::async_metadata_t &, double); - // used by x300_find_with_addr to find X300 devices. static boost::mutex claimer_mutex; //All claims and checks in this process are serialized static bool is_claimed(uhd::wb_iface::sptr); @@ -170,43 +128,37 @@ public: static x300_mboard_t get_mb_type_from_pcie(const std::string& resource, const std::string& rpc_port); static x300_mboard_t get_mb_type_from_eeprom(const uhd::usrp::mboard_eeprom_t& mb_eeprom); -private: - boost::shared_ptr<async_md_type> _async_md; - - //perifs in the radio core - struct radio_perifs_t - { - //Interfaces - radio_ctrl_core_3000::sptr ctrl; - spi_core_3000::sptr spi; - x300_adc_ctrl::sptr adc; - x300_dac_ctrl::sptr dac; - time_core_3000::sptr time64; - rx_vita_core_3000::sptr framer; - rx_dsp_core_3000::sptr ddc; - tx_vita_core_3000::sptr deframer; - tx_dsp_core_3000::sptr duc; - gpio_core_200_32wo::sptr leds; - rx_frontend_core_200::sptr rx_fe; - tx_frontend_core_200::sptr tx_fe; - //Registers - uhd::usrp::x300::radio_regmap_t::sptr regmap; - }; +protected: + void subdev_to_blockid( + const uhd::usrp::subdev_spec_pair_t &spec, const size_t mb_i, + uhd::rfnoc::block_id_t &block_id, uhd::device_addr_t &block_args + ); + uhd::usrp::subdev_spec_pair_t blockid_to_subdev( + const uhd::rfnoc::block_id_t &blockid, const uhd::device_addr_t &block_args + ); - //overflow recovery impl - void handle_overflow(radio_perifs_t &perif, boost::weak_ptr<uhd::rx_streamer> streamer); +private: //vector of member objects per motherboard struct mboard_members_t { - uhd::dict<size_t, boost::weak_ptr<uhd::rx_streamer> > rx_streamers; - uhd::dict<size_t, boost::weak_ptr<uhd::tx_streamer> > tx_streamers; - bool initialization_done; uhd::task::sptr claimer_task; - std::string addr; std::string xport_path; - int router_dst_here; + + std::vector<x300_eth_conn_t> eth_conns; + size_t next_src_addr; + + // Discover the ethernet connections per motherboard + void discover_eth(const uhd::usrp::mboard_eeprom_t mb_eeprom, + const std::vector<std::string> &ip_addrs); + + // Get the primary ethernet connection + inline const x300_eth_conn_t& get_pri_eth() const + { + return eth_conns[0]; + } + uhd::device_addr_t send_args; uhd::device_addr_t recv_args; bool if_pkt_is_big_endian; @@ -217,20 +169,9 @@ private: spi_core_3000::sptr zpu_spi; i2c_core_100_wb32::sptr zpu_i2c; - //perifs in each radio - static const size_t NUM_RADIOS = 2; - radio_perifs_t radio_perifs[NUM_RADIOS]; //!< This is hardcoded s.t. radio_perifs[0] points to slot A and [1] to B - uhd::usrp::dboard_eeprom_t db_eeproms[8]; - //! Return the index of a radio component, given a slot name. This means DSPs, radio_perifs - size_t get_radio_index(const std::string &slot_name) { - UHD_ASSERT_THROW(slot_name == "A" or slot_name == "B"); - return slot_name == "A" ? 0 : 1; - } - //other perifs on mboard x300_clock_ctrl::sptr clock; uhd::gps_ctrl::sptr gps; - gpio_core_200::sptr fp_gpio; uhd::usrp::x300::fw_regmap_t::sptr fw_regmap; @@ -240,57 +181,25 @@ private: size_t hw_rev; std::string current_refclk_src; - uhd::soft_regmap_db_t::sptr regmap_db; + std::vector<uhd::rfnoc::x300_radio_ctrl_impl::sptr> radios; }; std::vector<mboard_members_t> _mb; //task for periodically reclaiming the device from others void claimer_loop(uhd::wb_iface::sptr); - boost::mutex _transport_setup_mutex; - - void register_loopback_self_test(uhd::wb_iface::sptr iface); - - void radio_loopback(uhd::wb_iface::sptr iface, const bool on); - - /*! \brief Initialize the radio component on a given slot. - * - * Call this function once per slot (A and B) and motherboard to initialize all the radio components. - * This will: - * - Reset and init DACs and ADCs - * - Setup controls for DAC, ADC, SPI and LEDs - * - Self test ADC - * - Sync DACs (for MIMO) - * - Initialize the property tree for control objects etc. (gain, rate...) - * - * \param mb_i Motherboard index - * \param slot_name Slot name (A or B). - */ - void setup_radio(const size_t, const std::string &slot_name, const uhd::device_addr_t &dev_addr); - size_t _sid_framer; - struct sid_config_t - { - boost::uint8_t router_addr_there; - boost::uint8_t dst_prefix; //2bits - boost::uint8_t router_dst_there; - boost::uint8_t router_dst_here; - }; - boost::uint32_t allocate_sid(mboard_members_t &mb, const sid_config_t &config); - struct both_xports_t - { - uhd::transport::zero_copy_if::sptr recv; - uhd::transport::zero_copy_if::sptr send; - size_t recv_buff_size; - size_t send_buff_size; - }; - both_xports_t make_transport( - const size_t mb_index, - const boost::uint8_t& destination, - const boost::uint8_t& prefix, - const uhd::device_addr_t& args, - boost::uint32_t& sid); + uhd::sid_t allocate_sid( + mboard_members_t &mb, + const uhd::sid_t &address, + const uint32_t src_addr, + const uint32_t src_dst); + uhd::both_xports_t make_transport( + const uhd::sid_t &address, + const xport_type_t xport_type, + const uhd::device_addr_t& args + ); struct frame_size_t { @@ -306,6 +215,15 @@ private: */ frame_size_t determine_max_frame_size(const std::string &addr, const frame_size_t &user_mtu); + std::map<uint32_t, uint32_t> _dma_chan_pool; + uhd::transport::muxed_zero_copy_if::sptr _ctrl_dma_xport; + + /*! Allocate or return a previously allocated PCIe channel pair + * + * Note the SID is always the transmit SID (i.e. from host to device). + */ + uint32_t allocate_pcie_dma_chan(const uhd::sid_t &tx_sid, const xport_type_t xport_type); + //////////////////////////////////////////////////////////////////// // //Caching for transport interface re-use -- like sharing a DMA. @@ -328,28 +246,9 @@ private: //////////////////////////////////////////////////////////////////// uhd::dict<std::string, uhd::usrp::dboard_manager::sptr> _dboard_managers; - uhd::dict<std::string, uhd::usrp::dboard_iface::sptr> _dboard_ifaces; - void set_rx_fe_corrections(const uhd::fs_path &mb_path, const std::string &fe_name, const double lo_freq); - void set_tx_fe_corrections(const uhd::fs_path &mb_path, const std::string &fe_name, const double lo_freq); bool _ignore_cal_file; - - /*! Update the IQ MUX settings for the radio peripheral according to given subdev spec. - * - * Also checks if the given subdev is valid for this device and updates the channel to DSP mapping. - * - * \param tx_rx "tx" or "rx", depending where you're setting the subdev spec - * \param mb_i Mainboard index number. - * \param spec Subdev spec - */ - void update_subdev_spec(const std::string &tx_rx, const size_t mb_i, const uhd::usrp::subdev_spec_t &spec); - - void set_tick_rate(mboard_members_t &, const double); - void update_tick_rate(mboard_members_t &, const double); - void update_rx_samp_rate(mboard_members_t&, const size_t, const double); - void update_tx_samp_rate(mboard_members_t&, const size_t, const double); - void update_clock_control(mboard_members_t&); void initialize_clock_control(mboard_members_t &mb); void set_time_source_out(mboard_members_t&, const bool); @@ -367,21 +266,15 @@ private: void check_fw_compat(const uhd::fs_path &mb_path, uhd::wb_iface::sptr iface); void check_fpga_compat(const uhd::fs_path &mb_path, const mboard_members_t &members); - void update_atr_leds(gpio_core_200_32wo::sptr, const std::string &ant); - boost::uint32_t get_fp_gpio(gpio_core_200::sptr); - void set_fp_gpio(gpio_core_200::sptr, const gpio_attr_t, const boost::uint32_t); - - void self_cal_adc_capture_delay(mboard_members_t& mb, const size_t radio_i, bool print_status = false); - double self_cal_adc_xfer_delay(mboard_members_t& mb, bool apply_delay = false); - void self_test_adcs(mboard_members_t& mb, boost::uint32_t ramp_time_ms = 100); - - void extended_adc_test(mboard_members_t& mb, double duration_s); + /// More IO stuff + uhd::device_addr_t get_tx_hints(size_t mb_index); + uhd::device_addr_t get_rx_hints(size_t mb_index); + uhd::endianness_t get_transport_endianness(size_t mb_index) { + return _mb[mb_index].if_pkt_is_big_endian ? uhd::ENDIANNESS_BIG : uhd::ENDIANNESS_LITTLE; + }; - //**PRECONDITION** - //This function assumes that all the VITA times in "radios" are synchronized - //to a common reference. Currently, this function is called in get_tx_stream - //which also has the same precondition. - static void synchronize_dacs(const std::vector<radio_perifs_t*>& mboards); + void post_streamer_hooks(uhd::direction_t dir); }; #endif /* INCLUDED_X300_IMPL_HPP */ +// vim: sw=4 expandtab: diff --git a/host/lib/usrp/x300/x300_io_impl.cpp b/host/lib/usrp/x300/x300_io_impl.cpp index e3515af0c..614a98590 100644 --- a/host/lib/usrp/x300/x300_io_impl.cpp +++ b/host/lib/usrp/x300/x300_io_impl.cpp @@ -15,18 +15,19 @@ // along with this program. If not, see <http://www.gnu.org/licenses/>. // +#define DEVICE3_STREAMER + #include "x300_regs.hpp" #include "x300_impl.hpp" -#include "validate_subdev_spec.hpp" #include "../../transport/super_recv_packet_handler.hpp" #include "../../transport/super_send_packet_handler.hpp" #include <uhd/transport/nirio_zero_copy.hpp> #include "async_packet_handler.hpp" #include <uhd/transport/bounded_buffer.hpp> -#include <uhd/transport/chdr.hpp> #include <boost/bind.hpp> #include <uhd/utils/tasks.hpp> #include <uhd/utils/log.hpp> +#include <uhd/utils/msg.hpp> #include <boost/foreach.hpp> #include <boost/make_shared.hpp> @@ -35,588 +36,62 @@ using namespace uhd::usrp; using namespace uhd::transport; /*********************************************************************** - * update streamer rates + * Hooks for get_tx_stream() and get_rx_stream() **********************************************************************/ -void x300_impl::update_tick_rate(mboard_members_t &mb, const double rate) +device_addr_t x300_impl::get_rx_hints(size_t mb_index) { - BOOST_FOREACH(const size_t &dspno, mb.rx_streamers.keys()) - { - boost::shared_ptr<sph::recv_packet_streamer> my_streamer = - boost::dynamic_pointer_cast<sph::recv_packet_streamer>(mb.rx_streamers[dspno].lock()); - if (my_streamer) my_streamer->set_tick_rate(rate); - } - BOOST_FOREACH(const size_t &dspno, mb.tx_streamers.keys()) + device_addr_t rx_hints = _mb[mb_index].recv_args; + // (default to a large recv buff) + if (not rx_hints.has_key("recv_buff_size")) { - boost::shared_ptr<sph::send_packet_streamer> my_streamer = - boost::dynamic_pointer_cast<sph::send_packet_streamer>(mb.tx_streamers[dspno].lock()); - if (my_streamer) my_streamer->set_tick_rate(rate); - } -} - -void x300_impl::update_rx_samp_rate(mboard_members_t &mb, const size_t dspno, const double rate) -{ - if (not mb.rx_streamers.has_key(dspno)) return; - boost::shared_ptr<sph::recv_packet_streamer> my_streamer = - boost::dynamic_pointer_cast<sph::recv_packet_streamer>(mb.rx_streamers[dspno].lock()); - if (not my_streamer) return; - my_streamer->set_samp_rate(rate); - const double adj = mb.radio_perifs[dspno].ddc->get_scaling_adjustment(); - my_streamer->set_scale_factor(adj); -} - -void x300_impl::update_tx_samp_rate(mboard_members_t &mb, const size_t dspno, const double rate) -{ - if (not mb.tx_streamers.has_key(dspno)) return; - boost::shared_ptr<sph::send_packet_streamer> my_streamer = - boost::dynamic_pointer_cast<sph::send_packet_streamer>(mb.tx_streamers[dspno].lock()); - if (not my_streamer) return; - my_streamer->set_samp_rate(rate); - const double adj = mb.radio_perifs[dspno].duc->get_scaling_adjustment(); - my_streamer->set_scale_factor(adj); -} - -/*********************************************************************** - * Setup dboard muxing for IQ - **********************************************************************/ -void x300_impl::update_subdev_spec(const std::string &tx_rx, const size_t mb_i, const subdev_spec_t &spec) -{ - UHD_ASSERT_THROW(tx_rx == "tx" or tx_rx == "rx"); - UHD_ASSERT_THROW(mb_i < _mb.size()); - const std::string mb_name = boost::lexical_cast<std::string>(mb_i); - fs_path mb_root = "/mboards/" + mb_name; - - //sanity checking - validate_subdev_spec(_tree, spec, tx_rx, mb_name); - UHD_ASSERT_THROW(spec.size() <= 2); - if (spec.size() == 1) { - UHD_ASSERT_THROW(spec[0].db_name == "A" || spec[0].db_name == "B"); - } - else if (spec.size() == 2) { - UHD_ASSERT_THROW( - (spec[0].db_name == "A" && spec[1].db_name == "B") || - (spec[0].db_name == "B" && spec[1].db_name == "A") - ); - } - - std::vector<size_t> chan_to_dsp_map(spec.size(), 0); - // setup mux for this spec - for (size_t i = 0; i < spec.size(); i++) - { - const int radio_idx = _mb[mb_i].get_radio_index(spec[i].db_name); - chan_to_dsp_map[i] = radio_idx; - - //extract connection - const std::string conn = _tree->access<std::string>(mb_root / "dboards" / spec[i].db_name / (tx_rx + "_frontends") / spec[i].sd_name / "connection").get(); - - if (tx_rx == "tx") { - //swap condition - _mb[mb_i].radio_perifs[radio_idx].tx_fe->set_mux(conn); - } else { - //swap condition - const bool fe_swapped = (conn == "QI" or conn == "Q"); - _mb[mb_i].radio_perifs[radio_idx].ddc->set_mux(conn, fe_swapped); - //see usrp/io_impl.cpp if multiple DSPs share the frontend: - _mb[mb_i].radio_perifs[radio_idx].rx_fe->set_mux(fe_swapped); + if (_mb[mb_index].xport_path != "nirio") { + //For the ethernet transport, the buffer has to be set before creating + //the transport because it is independent of the frame size and # frames + //For nirio, the buffer size is not configurable by the user + #if defined(UHD_PLATFORM_MACOS) || defined(UHD_PLATFORM_BSD) + //limit buffer resize on macos or it will error + rx_hints["recv_buff_size"] = boost::lexical_cast<std::string>(X300_RX_SW_BUFF_SIZE_ETH_MACOS); + #elif defined(UHD_PLATFORM_LINUX) || defined(UHD_PLATFORM_WIN32) + //set to half-a-second of buffering at max rate + rx_hints["recv_buff_size"] = boost::lexical_cast<std::string>(X300_RX_SW_BUFF_SIZE_ETH); + #endif } } - - _tree->access<std::vector<size_t> >(mb_root / (tx_rx + "_chan_dsp_mapping")).set(chan_to_dsp_map); -} - - -/*********************************************************************** - * RX flow control handler - **********************************************************************/ -static size_t get_rx_flow_control_window(size_t frame_size, size_t sw_buff_size, const device_addr_t& rx_args) -{ - double fullness_factor = rx_args.cast<double>("recv_buff_fullness", X300_RX_SW_BUFF_FULL_FACTOR); - - if (fullness_factor < 0.01 || fullness_factor > 1) { - throw uhd::value_error("recv_buff_fullness must be between 0.01 and 1 inclusive (1% to 100%)"); - } - - size_t window_in_pkts = (static_cast<size_t>(sw_buff_size * fullness_factor) / frame_size); - if (window_in_pkts == 0) { - throw uhd::value_error("recv_buff_size must be larger than the recv_frame_size."); - } - return window_in_pkts; + return rx_hints; } -static void handle_rx_flowctrl(const boost::uint32_t sid, zero_copy_if::sptr xport, bool big_endian, boost::shared_ptr<boost::uint32_t> seq32_state, const size_t last_seq) -{ - managed_send_buffer::sptr buff = xport->get_send_buff(0.0); - if (not buff) - { - throw uhd::runtime_error("handle_rx_flowctrl timed out getting a send buffer"); - } - boost::uint32_t *pkt = buff->cast<boost::uint32_t *>(); - - //recover seq32 - boost::uint32_t &seq32 = *seq32_state; - const size_t seq12 = seq32 & 0xfff; - if (last_seq < seq12) seq32 += (1 << 12); - seq32 &= ~0xfff; - seq32 |= last_seq; - - //load packet info - vrt::if_packet_info_t packet_info; - packet_info.packet_type = vrt::if_packet_info_t::PACKET_TYPE_CONTEXT; - packet_info.num_payload_words32 = 2; - packet_info.num_payload_bytes = packet_info.num_payload_words32*sizeof(boost::uint32_t); - packet_info.packet_count = seq32; - packet_info.sob = false; - packet_info.eob = false; - packet_info.sid = sid; - packet_info.has_sid = true; - packet_info.has_cid = false; - packet_info.has_tsi = false; - packet_info.has_tsf = false; - packet_info.has_tlr = false; - - //load header - if (big_endian) - vrt::chdr::if_hdr_pack_be(pkt, packet_info); - else - vrt::chdr::if_hdr_pack_le(pkt, packet_info); - - //load payload - pkt[packet_info.num_header_words32+0] = uhd::htonx<boost::uint32_t>(0); - pkt[packet_info.num_header_words32+1] = uhd::htonx<boost::uint32_t>(seq32); - - //send the buffer over the interface - buff->commit(sizeof(boost::uint32_t)*(packet_info.num_packet_words32)); -} - -/*********************************************************************** - * TX flow control handler - **********************************************************************/ -struct x300_tx_fc_guts_t -{ - x300_tx_fc_guts_t(void): - stream_channel(0), - device_channel(0), - last_seq_out(0), - last_seq_ack(0), - seq_queue(1){} - size_t stream_channel; - size_t device_channel; - size_t last_seq_out; - size_t last_seq_ack; - bounded_buffer<size_t> seq_queue; - boost::shared_ptr<x300_impl::async_md_type> async_queue; - boost::shared_ptr<x300_impl::async_md_type> old_async_queue; -}; - -#define X300_ASYNC_EVENT_CODE_FLOW_CTRL 0 - -/*! - * If the return value of this function is F, the last tx'd packet - * has index N and the last ack'd packet has index M, the amount of - * FC credit we have is C = F + M - N (i.e. we can send C more packets - * before getting another ack). - */ -static size_t get_tx_flow_control_window(size_t frame_size, const device_addr_t& tx_args) +device_addr_t x300_impl::get_tx_hints(size_t mb_index) { - double hw_buff_size = tx_args.cast<double>("send_buff_size", X300_TX_HW_BUFF_SIZE); - size_t window_in_pkts = (static_cast<size_t>(hw_buff_size) / frame_size); - if (window_in_pkts == 0) { - throw uhd::value_error("send_buff_size must be larger than the send_frame_size."); - } - return window_in_pkts; + device_addr_t tx_hints = _mb[mb_index].send_args; + return tx_hints; } -static void handle_tx_async_msgs(boost::shared_ptr<x300_tx_fc_guts_t> guts, zero_copy_if::sptr xport, bool big_endian, x300_clock_ctrl::sptr clock) +void x300_impl::post_streamer_hooks(direction_t dir) { - managed_recv_buffer::sptr buff = xport->get_recv_buff(); - if (not buff) return; - - //extract packet info - vrt::if_packet_info_t if_packet_info; - if_packet_info.num_packet_words32 = buff->size()/sizeof(boost::uint32_t); - const boost::uint32_t *packet_buff = buff->cast<const boost::uint32_t *>(); - - //unpacking can fail - boost::uint32_t (*endian_conv)(boost::uint32_t) = uhd::ntohx; - try - { - if (big_endian) - { - vrt::chdr::if_hdr_unpack_be(packet_buff, if_packet_info); - endian_conv = uhd::ntohx; - } - else - { - vrt::chdr::if_hdr_unpack_le(packet_buff, if_packet_info); - endian_conv = uhd::wtohx; - } - } - catch(const std::exception &ex) - { - UHD_MSG(error) << "Error parsing async message packet: " << ex.what() << std::endl; + if (dir != TX_DIRECTION) { return; } - //fill in the async metadata - async_metadata_t metadata; - load_metadata_from_buff( - endian_conv, metadata, if_packet_info, packet_buff, - clock->get_master_clock_rate(), guts->stream_channel); - - //The FC response and the burst ack are two indicators that the radio - //consumed packets. Use them to update the FC metadata - if (metadata.event_code == X300_ASYNC_EVENT_CODE_FLOW_CTRL or - metadata.event_code == async_metadata_t::EVENT_CODE_BURST_ACK - ) { - const size_t seq = metadata.user_payload[0]; - guts->seq_queue.push_with_pop_on_full(seq); - } - - //FC responses don't propagate up to the user so filter them here - if (metadata.event_code != X300_ASYNC_EVENT_CODE_FLOW_CTRL) { - guts->async_queue->push_with_pop_on_full(metadata); - metadata.channel = guts->device_channel; - guts->old_async_queue->push_with_pop_on_full(metadata); - standard_async_msg_prints(metadata); - } -} - -static managed_send_buffer::sptr get_tx_buff_with_flowctrl( - task::sptr /*holds ref*/, - boost::shared_ptr<x300_tx_fc_guts_t> guts, - zero_copy_if::sptr xport, - size_t fc_pkt_window, - const double timeout -){ - while (true) - { - // delta is the amount of FC credit we've used up - const size_t delta = (guts->last_seq_out & 0xfff) - (guts->last_seq_ack & 0xfff); - // If we want to send another packet, we must have FC credit left - if ((delta & 0xfff) < fc_pkt_window) break; - - // If credit is all used up, we check seq_queue for more. - const bool ok = guts->seq_queue.pop_with_timed_wait(guts->last_seq_ack, timeout); - if (not ok) return managed_send_buffer::sptr(); //timeout waiting for flow control - } - - managed_send_buffer::sptr buff = xport->get_send_buff(timeout); - if (buff) { - guts->last_seq_out++; //update seq, this will actually be a send - } - return buff; -} - -/*********************************************************************** - * Async Data - **********************************************************************/ -bool x300_impl::recv_async_msg( - async_metadata_t &async_metadata, double timeout -){ - return _async_md->pop_with_timed_wait(async_metadata, timeout); -} - -/*********************************************************************** - * Receive streamer - **********************************************************************/ -rx_streamer::sptr x300_impl::get_rx_stream(const uhd::stream_args_t &args_) -{ - boost::mutex::scoped_lock lock(_transport_setup_mutex); - stream_args_t args = args_; - - //setup defaults for unspecified values - if (not args.otw_format.empty() and args.otw_format != "sc16") - { - throw uhd::value_error("x300_impl::get_rx_stream only supports otw_format sc16"); - } - args.otw_format = "sc16"; - args.channels = args.channels.empty()? std::vector<size_t>(1, 0) : args.channels; - - boost::shared_ptr<sph::recv_packet_streamer> my_streamer; - for (size_t stream_i = 0; stream_i < args.channels.size(); stream_i++) - { - // Find the mainboard and subdev that corresponds to channel args.channels[stream_i] - const size_t chan = args.channels[stream_i]; - size_t mb_chan = chan, mb_index; - for (mb_index = 0; mb_index < _mb.size(); mb_index++) { - const subdev_spec_t &curr_subdev_spec = - _tree->access<subdev_spec_t>("/mboards/" + boost::lexical_cast<std::string>(mb_index) / "rx_subdev_spec").get(); - if (mb_chan < curr_subdev_spec.size()) { - break; - } else { - mb_chan -= curr_subdev_spec.size(); - } + // Loop through all tx streamers. Find all radios connected to one + // streamer. Sync those. + BOOST_FOREACH(const boost::weak_ptr<uhd::tx_streamer> &streamer_w, _tx_streamers.vals()) { + const boost::shared_ptr<sph::send_packet_streamer> streamer = + boost::dynamic_pointer_cast<sph::send_packet_streamer>(streamer_w.lock()); + if (not streamer) { + continue; } - // Find the DSP that corresponds to this mainboard and subdev - UHD_ASSERT_THROW(mb_index < _mb.size()); - mboard_members_t &mb = _mb[mb_index]; - const std::vector<size_t> dsp_map = _tree->access<std::vector<size_t> >("/mboards/" + boost::lexical_cast<std::string>(mb_index) / "rx_chan_dsp_mapping") - .get(); //.at(mb_chan); - UHD_ASSERT_THROW(mb_chan < dsp_map.size()); - const size_t radio_index = dsp_map[mb_chan]; - UHD_ASSERT_THROW(radio_index < 2); - radio_perifs_t &perif = mb.radio_perifs[radio_index]; - - //setup the dsp transport hints (default to a large recv buff) - device_addr_t device_addr = mb.recv_args; - if (not device_addr.has_key("recv_buff_size")) - { - if (mb.xport_path != "nirio") { - //For the ethernet transport, the buffer has to be set before creating - //the transport because it is independent of the frame size and # frames - //For nirio, the buffer size is not configurable by the user - #if defined(UHD_PLATFORM_MACOS) || defined(UHD_PLATFORM_BSD) - //limit buffer resize on macos or it will error - device_addr["recv_buff_size"] = boost::lexical_cast<std::string>(X300_RX_SW_BUFF_SIZE_ETH_MACOS); - #elif defined(UHD_PLATFORM_LINUX) || defined(UHD_PLATFORM_WIN32) - //set to half-a-second of buffering at max rate - device_addr["recv_buff_size"] = boost::lexical_cast<std::string>(X300_RX_SW_BUFF_SIZE_ETH); - #endif - } + std::vector<rfnoc::x300_radio_ctrl_impl::sptr> radio_ctrl_blks = + streamer->get_terminator()->find_downstream_node<rfnoc::x300_radio_ctrl_impl>(); + try { + //UHD_MSG(status) << "[X300] syncing " << radio_ctrl_blks.size() << " radios " << std::endl; + rfnoc::x300_radio_ctrl_impl::synchronize_dacs(radio_ctrl_blks); } - - //allocate sid and create transport - boost::uint8_t dest = (radio_index == 0)? X300_XB_DST_R0 : X300_XB_DST_R1; - boost::uint32_t data_sid; - UHD_LOG << "creating rx stream " << device_addr.to_string() << std::endl; - both_xports_t xport = this->make_transport(mb_index, dest, X300_RADIO_DEST_PREFIX_RX, device_addr, data_sid); - UHD_LOG << boost::format("data_sid = 0x%08x, actual recv_buff_size = %d\n") % data_sid % xport.recv_buff_size << std::endl; - - // To calculate the max number of samples per packet, we assume the maximum header length - // to avoid fragmentation should the entire header be used. - const size_t bpp = xport.recv->get_recv_frame_size() - X300_RX_MAX_HDR_LEN; // bytes per packet - const size_t bpi = convert::get_bytes_per_item(args.otw_format); // bytes per item - const size_t spp = unsigned(args.args.cast<double>("spp", bpp/bpi)); // samples per packet - - //make the new streamer given the samples per packet - if (not my_streamer) my_streamer = boost::make_shared<sph::recv_packet_streamer>(spp); - my_streamer->resize(args.channels.size()); - - //init some streamer stuff - std::string conv_endianness; - if (mb.if_pkt_is_big_endian) { - my_streamer->set_vrt_unpacker(&vrt::chdr::if_hdr_unpack_be); - conv_endianness = "be"; - } else { - my_streamer->set_vrt_unpacker(&vrt::chdr::if_hdr_unpack_le); - conv_endianness = "le"; + catch(const uhd::io_error &ex) { + throw uhd::io_error(str(boost::format("Failed to sync DACs! %s ") % ex.what())); } - - //set the converter - uhd::convert::id_type id; - id.input_format = args.otw_format + "_item32_" + conv_endianness; - id.num_inputs = 1; - id.output_format = args.cpu_format; - id.num_outputs = 1; - my_streamer->set_converter(id); - - perif.framer->clear(); - perif.framer->set_nsamps_per_packet(spp); //seems to be a good place to set this - perif.framer->set_sid((data_sid << 16) | (data_sid >> 16)); - perif.framer->setup(args); - perif.ddc->setup(args); - - //flow control setup - const size_t fc_window = get_rx_flow_control_window(xport.recv->get_recv_frame_size(), xport.recv_buff_size, device_addr); - const size_t fc_handle_window = std::max<size_t>(1, fc_window / X300_RX_FC_REQUEST_FREQ); - - UHD_LOG << "RX Flow Control Window = " << fc_window << ", RX Flow Control Handler Window = " << fc_handle_window << std::endl; - - perif.framer->configure_flow_control(fc_window); - - boost::shared_ptr<boost::uint32_t> seq32(new boost::uint32_t(0)); - //Give the streamer a functor to get the recv_buffer - //bind requires a zero_copy_if::sptr to add a streamer->xport lifetime dependency - my_streamer->set_xport_chan_get_buff( - stream_i, - boost::bind(&zero_copy_if::get_recv_buff, xport.recv, _1), - true /*flush*/ - ); - //Give the streamer a functor to handle overflows - //bind requires a weak_ptr to break the a streamer->streamer circular dependency - //Using "this" is OK because we know that x300_impl will outlive the streamer - my_streamer->set_overflow_handler( - stream_i, - boost::bind(&x300_impl::handle_overflow, this, boost::ref(perif), boost::weak_ptr<uhd::rx_streamer>(my_streamer)) - ); - //Give the streamer a functor to send flow control messages - //handle_rx_flowctrl is static and has no lifetime issues - my_streamer->set_xport_handle_flowctrl( - stream_i, boost::bind(&handle_rx_flowctrl, data_sid, xport.send, mb.if_pkt_is_big_endian, seq32, _1), - fc_handle_window, - true/*init*/ - ); - //Give the streamer a functor issue stream cmd - //bind requires a rx_vita_core_3000::sptr to add a streamer->framer lifetime dependency - my_streamer->set_issue_stream_cmd( - stream_i, boost::bind(&rx_vita_core_3000::issue_stream_command, perif.framer, _1) - ); - - //Store a weak pointer to prevent a streamer->x300_impl->streamer circular dependency - mb.rx_streamers[radio_index] = boost::weak_ptr<sph::recv_packet_streamer>(my_streamer); - - //sets all tick and samp rates on this streamer - const fs_path mb_path = "/mboards/"+boost::lexical_cast<std::string>(mb_index); - _tree->access<double>(mb_path / "tick_rate").update(); - _tree->access<double>(mb_path / "rx_dsps" / boost::lexical_cast<std::string>(radio_index) / "rate" / "value").update(); - } - - return my_streamer; -} - -void x300_impl::handle_overflow(x300_impl::radio_perifs_t &perif, boost::weak_ptr<uhd::rx_streamer> streamer) -{ - boost::shared_ptr<sph::recv_packet_streamer> my_streamer = - boost::dynamic_pointer_cast<sph::recv_packet_streamer>(streamer.lock()); - if (not my_streamer) return; //If the rx_streamer has expired then overflow handling makes no sense. - - if (my_streamer->get_num_channels() == 1) - { - perif.framer->handle_overflow(); - return; - } - - ///////////////////////////////////////////////////////////// - // MIMO overflow recovery time - ///////////////////////////////////////////////////////////// - //find out if we were in continuous mode before stopping - const bool in_continuous_streaming_mode = perif.framer->in_continuous_streaming_mode(); - //stop streaming - my_streamer->issue_stream_cmd(stream_cmd_t::STREAM_MODE_STOP_CONTINUOUS); - //flush transports - my_streamer->flush_all(0.001); - //restart streaming - if (in_continuous_streaming_mode) - { - stream_cmd_t stream_cmd(stream_cmd_t::STREAM_MODE_START_CONTINUOUS); - stream_cmd.stream_now = false; - stream_cmd.time_spec = perif.time64->get_time_now() + time_spec_t(0.01); - my_streamer->issue_stream_cmd(stream_cmd); } } -/*********************************************************************** - * Transmit streamer - **********************************************************************/ -tx_streamer::sptr x300_impl::get_tx_stream(const uhd::stream_args_t &args_) -{ - boost::mutex::scoped_lock lock(_transport_setup_mutex); - stream_args_t args = args_; - - //setup defaults for unspecified values - if (not args.otw_format.empty() and args.otw_format != "sc16") - { - throw uhd::value_error("x300_impl::get_tx_stream only supports otw_format sc16"); - } - args.otw_format = "sc16"; - args.channels = args.channels.empty()? std::vector<size_t>(1, 0) : args.channels; - - //shared async queue for all channels in streamer - boost::shared_ptr<async_md_type> async_md(new async_md_type(1000/*messages deep*/)); - - std::vector<radio_perifs_t*> radios_list; - boost::shared_ptr<sph::send_packet_streamer> my_streamer; - for (size_t stream_i = 0; stream_i < args.channels.size(); stream_i++) - { - // Find the mainboard and subdev that corresponds to channel args.channels[stream_i] - const size_t chan = args.channels[stream_i]; - size_t mb_chan = chan, mb_index; - for (mb_index = 0; mb_index < _mb.size(); mb_index++) { - const subdev_spec_t &curr_subdev_spec = - _tree->access<subdev_spec_t>("/mboards/" + boost::lexical_cast<std::string>(mb_index) / "tx_subdev_spec").get(); - if (mb_chan < curr_subdev_spec.size()) { - break; - } else { - mb_chan -= curr_subdev_spec.size(); - } - } - // Find the DSP that corresponds to this mainboard and subdev - mboard_members_t &mb = _mb[mb_index]; - const size_t radio_index = _tree->access<std::vector<size_t> >("/mboards/" + boost::lexical_cast<std::string>(mb_index) / "tx_chan_dsp_mapping") - .get().at(mb_chan); - radio_perifs_t &perif = mb.radio_perifs[radio_index]; - radios_list.push_back(&perif); - - //setup the dsp transport hints (TODO) - device_addr_t device_addr = mb.send_args; - - //allocate sid and create transport - boost::uint8_t dest = (radio_index == 0)? X300_XB_DST_R0 : X300_XB_DST_R1; - boost::uint32_t data_sid; - UHD_LOG << "creating tx stream " << device_addr.to_string() << std::endl; - both_xports_t xport = this->make_transport(mb_index, dest, X300_RADIO_DEST_PREFIX_TX, device_addr, data_sid); - UHD_LOG << boost::format("data_sid = 0x%08x\n") % data_sid << std::endl; - - // To calculate the max number of samples per packet, we assume the maximum header length - // to avoid fragmentation should the entire header be used. - const size_t bpp = xport.send->get_send_frame_size() - X300_TX_MAX_HDR_LEN; - const size_t bpi = convert::get_bytes_per_item(args.otw_format); - const size_t spp = unsigned(args.args.cast<double>("spp", bpp/bpi)); - - //make the new streamer given the samples per packet - if (not my_streamer) my_streamer = boost::make_shared<sph::send_packet_streamer>(spp); - my_streamer->resize(args.channels.size()); - - std::string conv_endianness; - if (mb.if_pkt_is_big_endian) { - my_streamer->set_vrt_packer(&vrt::chdr::if_hdr_pack_be); - conv_endianness = "be"; - } else { - my_streamer->set_vrt_packer(&vrt::chdr::if_hdr_pack_le); - conv_endianness = "le"; - } - - //set the converter - uhd::convert::id_type id; - id.input_format = args.cpu_format; - id.num_inputs = 1; - id.output_format = args.otw_format + "_item32_" + conv_endianness; - id.num_outputs = 1; - my_streamer->set_converter(id); - - perif.deframer->clear(); - perif.deframer->setup(args); - perif.duc->setup(args); - - //flow control setup - size_t fc_window = get_tx_flow_control_window(xport.send->get_send_frame_size(), device_addr); //In packets - const size_t fc_handle_window = std::max<size_t>(1, fc_window/X300_TX_FC_RESPONSE_FREQ); - - UHD_LOG << "TX Flow Control Window = " << fc_window << ", TX Flow Control Handler Window = " << fc_handle_window << std::endl; - - perif.deframer->configure_flow_control(0/*cycs off*/, fc_handle_window); - boost::shared_ptr<x300_tx_fc_guts_t> guts(new x300_tx_fc_guts_t()); - guts->stream_channel = stream_i; - guts->device_channel = chan; - guts->async_queue = async_md; - guts->old_async_queue = _async_md; - task::sptr task = task::make(boost::bind(&handle_tx_async_msgs, guts, xport.recv, mb.if_pkt_is_big_endian, mb.clock)); - - //Give the streamer a functor to get the send buffer - //get_tx_buff_with_flowctrl is static so bind has no lifetime issues - //xport.send (sptr) is required to add streamer->data-transport lifetime dependency - //task (sptr) is required to add a streamer->async-handler lifetime dependency - my_streamer->set_xport_chan_get_buff( - stream_i, - boost::bind(&get_tx_buff_with_flowctrl, task, guts, xport.send, fc_window, _1) - ); - //Give the streamer a functor handled received async messages - my_streamer->set_async_receiver( - boost::bind(&async_md_type::pop_with_timed_wait, async_md, _1, _2) - ); - my_streamer->set_xport_chan_sid(stream_i, true, data_sid); - my_streamer->set_enable_trailer(false); //TODO not implemented trailer support yet - - //Store a weak pointer to prevent a streamer->x300_impl->streamer circular dependency - mb.tx_streamers[radio_index] = boost::weak_ptr<sph::send_packet_streamer>(my_streamer); - - //sets all tick and samp rates on this streamer - const fs_path mb_path = "/mboards/"+boost::lexical_cast<std::string>(mb_index); - _tree->access<double>(mb_path / "tick_rate").update(); - _tree->access<double>(mb_path / "tx_dsps" / boost::lexical_cast<std::string>(radio_index) / "rate" / "value").update(); - } - - synchronize_dacs(radios_list); - return my_streamer; -} +// vim: sw=4 expandtab: diff --git a/host/lib/usrp/x300/x300_radio_ctrl_impl.cpp b/host/lib/usrp/x300/x300_radio_ctrl_impl.cpp new file mode 100644 index 000000000..e1b724db6 --- /dev/null +++ b/host/lib/usrp/x300/x300_radio_ctrl_impl.cpp @@ -0,0 +1,894 @@ +// +// Copyright 2015-2016 Ettus Research +// +// 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 +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +#include "x300_radio_ctrl_impl.hpp" + +#include "x300_dboard_iface.hpp" +#include "wb_iface_adapter.hpp" +#include "gpio_atr_3000.hpp" +#include "apply_corrections.hpp" +#include <uhd/usrp/dboard_eeprom.hpp> +#include <uhd/utils/msg.hpp> +#include <uhd/usrp/dboard_iface.hpp> +#include <uhd/rfnoc/node_ctrl_base.hpp> +#include <uhd/transport/chdr.hpp> +#include <boost/algorithm/string.hpp> +#include <boost/make_shared.hpp> +#include <boost/date_time/posix_time/posix_time_io.hpp> + +using namespace uhd; +using namespace uhd::usrp; +using namespace uhd::rfnoc; +using namespace uhd::usrp::x300; + +static const size_t IO_MASTER_RADIO = 0; + +/**************************************************************************** + * Structors + ***************************************************************************/ +UHD_RFNOC_RADIO_BLOCK_CONSTRUCTOR(x300_radio_ctrl) + , _ignore_cal_file(false) +{ + UHD_RFNOC_BLOCK_TRACE() << "x300_radio_ctrl_impl::ctor() " << std::endl; + + //////////////////////////////////////////////////////////////////// + // Set up basic info + //////////////////////////////////////////////////////////////////// + _radio_type = (get_block_id().get_block_count() == 0) ? PRIMARY : SECONDARY; + _radio_slot = (get_block_id().get_block_count() == 0) ? "A" : "B"; + _radio_clk_rate = _tree->access<double>("master_clock_rate").get(); + + //////////////////////////////////////////////////////////////////// + // Set up peripherals + //////////////////////////////////////////////////////////////////// + wb_iface::sptr ctrl = _get_ctrl(IO_MASTER_RADIO); + _regs = boost::make_shared<radio_regmap_t>(_radio_type==PRIMARY?0:1); + _regs->initialize(*ctrl, true); + + //Only Radio0 has the ADC/DAC reset bits. Those bits are reserved for Radio1 + if (_radio_type==PRIMARY) { + _regs->misc_outs_reg.set(radio_regmap_t::misc_outs_reg_t::ADC_RESET, 1); + _regs->misc_outs_reg.set(radio_regmap_t::misc_outs_reg_t::DAC_RESET_N, 0); + _regs->misc_outs_reg.flush(); + _regs->misc_outs_reg.set(radio_regmap_t::misc_outs_reg_t::ADC_RESET, 0); + _regs->misc_outs_reg.set(radio_regmap_t::misc_outs_reg_t::DAC_RESET_N, 1); + _regs->misc_outs_reg.flush(); + } + _regs->misc_outs_reg.write(radio_regmap_t::misc_outs_reg_t::DAC_ENABLED, 1); + + //////////////////////////////////////////////////////////////// + // Setup peripherals + //////////////////////////////////////////////////////////////// + _spi = spi_core_3000::make(ctrl, + radio_ctrl_impl::regs::sr_addr(radio_ctrl_impl::regs::SPI), + radio_ctrl_impl::regs::RB_SPI); + _leds = gpio_atr::gpio_atr_3000::make_write_only(ctrl, regs::sr_addr(regs::LEDS)); + _leds->set_atr_mode(usrp::gpio_atr::MODE_ATR, usrp::gpio_atr::gpio_atr_3000::MASK_SET_ALL); + _adc = x300_adc_ctrl::make(_spi, DB_ADC_SEN); + _dac = x300_dac_ctrl::make(_spi, DB_DAC_SEN, _radio_clk_rate); + + if (_radio_type==PRIMARY) { + _fp_gpio = gpio_atr::gpio_atr_3000::make(ctrl, regs::sr_addr(regs::FP_GPIO), regs::RB_FP_GPIO); + BOOST_FOREACH(const gpio_atr::gpio_attr_map_t::value_type attr, gpio_atr::gpio_attr_map) { + _tree->create<boost::uint32_t>(fs_path("gpio") / "FP0" / attr.second) + .set(0) + .add_coerced_subscriber(boost::bind(&gpio_atr::gpio_atr_3000::set_gpio_attr, _fp_gpio, attr.first, _1)); + } + _tree->create<boost::uint32_t>(fs_path("gpio") / "FP0" / "READBACK") + .set_publisher(boost::bind(&gpio_atr::gpio_atr_3000::read_gpio, _fp_gpio)); + } + + //////////////////////////////////////////////////////////////// + // create legacy codec control objects + //////////////////////////////////////////////////////////////// + _tree->create<int>("rx_codecs" / _radio_slot / "gains"); //phony property so this dir exists + _tree->create<int>("tx_codecs" / _radio_slot / "gains"); //phony property so this dir exists + _tree->create<std::string>("rx_codecs" / _radio_slot / "name").set("ads62p48"); + _tree->create<std::string>("tx_codecs" / _radio_slot / "name").set("ad9146"); + + _tree->create<meta_range_t>("rx_codecs" / _radio_slot / "gains" / "digital" / "range").set(meta_range_t(0, 6.0, 0.5)); + _tree->create<double>("rx_codecs" / _radio_slot / "gains" / "digital" / "value") + .add_coerced_subscriber(boost::bind(&x300_adc_ctrl::set_gain, _adc, _1)).set(0) + ; + + //////////////////////////////////////////////////////////////// + // create front-end objects + //////////////////////////////////////////////////////////////// + for (size_t i = 0; i < _get_num_radios(); i++) { + _rx_fe_map[i].core = rx_frontend_core_3000::make(_get_ctrl(i), regs::sr_addr(x300_regs::RX_FE_BASE)); + _rx_fe_map[i].core->set_adc_rate(_radio_clk_rate); + _rx_fe_map[i].core->set_dc_offset(rx_frontend_core_3000::DEFAULT_DC_OFFSET_VALUE); + _rx_fe_map[i].core->set_dc_offset_auto(rx_frontend_core_3000::DEFAULT_DC_OFFSET_ENABLE); + _rx_fe_map[i].core->populate_subtree(_tree->subtree(_root_path / "rx_fe_corrections" / i)); + + _tx_fe_map[i].core = tx_frontend_core_200::make(_get_ctrl(i), regs::sr_addr(x300_regs::TX_FE_BASE)); + _tx_fe_map[i].core->set_dc_offset(tx_frontend_core_200::DEFAULT_DC_OFFSET_VALUE); + _tx_fe_map[i].core->set_iq_balance(tx_frontend_core_200::DEFAULT_IQ_BALANCE_VALUE); + _tx_fe_map[i].core->populate_subtree(_tree->subtree(_root_path / "tx_fe_corrections" / i)); + } + + //////////////////////////////////////////////////////////////// + // Update default SPP (overwrites the default value from the XML file) + //////////////////////////////////////////////////////////////// + const size_t max_bytes_header = uhd::transport::vrt::chdr::max_if_hdr_words64 * sizeof(uint64_t); + const size_t default_spp = (_tree->access<size_t>("mtu/recv").get() - max_bytes_header) + / (2 * sizeof(int16_t)); + _tree->access<int>(get_arg_path("spp") / "value").set(default_spp); +} + +x300_radio_ctrl_impl::~x300_radio_ctrl_impl() +{ + // Tear down our part of the tree: + _tree->remove(fs_path("rx_codecs" / _radio_slot)); + _tree->remove(fs_path("tx_codecs" / _radio_slot)); + _tree->remove(_root_path / "rx_fe_corrections"); + _tree->remove(_root_path / "tx_fe_corrections"); + if (_radio_type==PRIMARY) { + BOOST_FOREACH(const gpio_atr::gpio_attr_map_t::value_type attr, gpio_atr::gpio_attr_map) { + _tree->remove(fs_path("gpio") / "FP0" / attr.second); + } + _tree->remove(fs_path("gpio") / "FP0" / "READBACK"); + } + + // Reset peripherals + if (_radio_type==PRIMARY) { + _regs->misc_outs_reg.set(radio_regmap_t::misc_outs_reg_t::ADC_RESET, 1); + _regs->misc_outs_reg.set(radio_regmap_t::misc_outs_reg_t::DAC_RESET_N, 0); + } + _regs->misc_outs_reg.write(radio_regmap_t::misc_outs_reg_t::DAC_ENABLED, 0); + _regs->misc_outs_reg.flush(); +} + +/**************************************************************************** + * API calls + ***************************************************************************/ +double x300_radio_ctrl_impl::set_rate(double /* rate */) +{ + // On X3x0, tick rate can't actually be changed at runtime + return get_rate(); +} + +void x300_radio_ctrl_impl::set_tx_antenna(const std::string &ant, const size_t chan) +{ + _tree->access<std::string>( + fs_path("dboards" / _radio_slot / "tx_frontends" / _tx_fe_map.at(chan).db_fe_name / "antenna" / "value") + ).set(ant); +} + +void x300_radio_ctrl_impl::set_rx_antenna(const std::string &ant, const size_t chan) +{ + _tree->access<std::string>( + fs_path("dboards" / _radio_slot / "rx_frontends" / _rx_fe_map.at(chan).db_fe_name / "antenna" / "value") + ).set(ant); +} + +double x300_radio_ctrl_impl::set_tx_frequency(const double freq, const size_t chan) +{ + return _tree->access<double>( + fs_path("dboards" / _radio_slot / "tx_frontends" / _tx_fe_map.at(chan).db_fe_name / "freq" / "value") + ).set(freq).get(); +} + +double x300_radio_ctrl_impl::get_tx_frequency(const size_t chan) +{ + return _tree->access<double>( + fs_path("dboards" / _radio_slot / "tx_frontends" / _tx_fe_map.at(chan).db_fe_name / "freq" / "value") + ).get(); +} + +double x300_radio_ctrl_impl::set_rx_frequency(const double freq, const size_t chan) +{ + return _tree->access<double>( + fs_path("dboards" / _radio_slot / "rx_frontends" / _rx_fe_map.at(chan).db_fe_name / "freq" / "value") + ).set(freq).get(); +} + +double x300_radio_ctrl_impl::get_rx_frequency(const size_t chan) +{ + return _tree->access<double>( + fs_path("dboards" / _radio_slot / "rx_frontends" / _rx_fe_map.at(chan).db_fe_name / "freq" / "value") + ).get(); +} + +double x300_radio_ctrl_impl::set_tx_gain(const double gain, const size_t chan) +{ + //TODO: This is extremely hacky! + fs_path path("dboards" / _radio_slot / "tx_frontends" / _tx_fe_map.at(chan).db_fe_name / "gains"); + std::vector<std::string> gain_stages = _tree->list(path); + if (gain_stages.size() == 1) { + const double actual_gain = _tree->access<double>(path / gain_stages[0] / "value").set(gain).get(); + radio_ctrl_impl::set_tx_gain(actual_gain, chan); + return gain; + } else { + UHD_MSG(warning) << "set_tx_gain: could not apply gain for this daughterboard."; + radio_ctrl_impl::set_tx_gain(0.0, chan); + return 0.0; + } +} + +double x300_radio_ctrl_impl::set_rx_gain(const double gain, const size_t chan) +{ + //TODO: This is extremely hacky! + fs_path path("dboards" / _radio_slot / "rx_frontends" / _rx_fe_map.at(chan).db_fe_name / "gains"); + std::vector<std::string> gain_stages = _tree->list(path); + if (gain_stages.size() == 1) { + const double actual_gain = _tree->access<double>(path / gain_stages[0] / "value").set(gain).get(); + radio_ctrl_impl::set_rx_gain(actual_gain, chan); + return gain; + } else { + UHD_MSG(warning) << "set_rx_gain: could not apply gain for this daughterboard."; + radio_ctrl_impl::set_tx_gain(0.0, chan); + return 0.0; + } +} + + +template <typename map_type> +static size_t _get_chan_from_map(std::map<size_t, map_type> map, const std::string &fe) +{ + // TODO replace with 'auto' when possible + typedef typename std::map<size_t, map_type>::iterator chan_iterator; + for (chan_iterator it = map.begin(); it != map.end(); ++it) { + if (it->second.db_fe_name == fe) { + return it->first; + } + + } + throw uhd::runtime_error(str( + boost::format("Invalid daughterboard frontend name: %s") + % fe + )); +} + +size_t x300_radio_ctrl_impl::get_chan_from_dboard_fe(const std::string &fe, const uhd::direction_t direction) +{ + switch (direction) { + case uhd::TX_DIRECTION: + return _get_chan_from_map(_tx_fe_map, fe); + case uhd::RX_DIRECTION: + return _get_chan_from_map(_rx_fe_map, fe); + default: + UHD_THROW_INVALID_CODE_PATH(); + } +} + +std::string x300_radio_ctrl_impl::get_dboard_fe_from_chan(const size_t chan, const uhd::direction_t direction) +{ + switch (direction) { + case uhd::TX_DIRECTION: + return _tx_fe_map.at(chan).db_fe_name; + case uhd::RX_DIRECTION: + return _rx_fe_map.at(chan).db_fe_name; + default: + UHD_THROW_INVALID_CODE_PATH(); + } +} + +double x300_radio_ctrl_impl::get_output_samp_rate(size_t chan) +{ + // TODO: chan should never be ANY_PORT, but due to our current graph search + // method, this can actually happen: + if (chan == ANY_PORT) { + chan = 0; + for (size_t i = 0; i < _get_num_radios(); i++) { + if (_is_streamer_active(uhd::RX_DIRECTION, chan)) { + chan = i; + break; + } + } + } + return _rx_fe_map.at(chan).core->get_output_rate(); +} + +/**************************************************************************** + * Radio control and setup + ***************************************************************************/ +void x300_radio_ctrl_impl::setup_radio(uhd::i2c_iface::sptr zpu_i2c, x300_clock_ctrl::sptr clock, bool verbose) +{ + _self_cal_adc_capture_delay(verbose); + + //////////////////////////////////////////////////////////////////// + // create RF frontend interfacing + //////////////////////////////////////////////////////////////////// + static const size_t BASE_ADDR = 0x50; + static const size_t RX_EEPROM_ADDR = 0x5; + static const size_t TX_EEPROM_ADDR = 0x4; + static const size_t GDB_EEPROM_ADDR = 0x1; + const static std::vector<size_t> EEPROM_ADDRS = + boost::assign::list_of(RX_EEPROM_ADDR)(TX_EEPROM_ADDR)(GDB_EEPROM_ADDR); + const static std::vector<std::string> EEPROM_PATHS = + boost::assign::list_of("rx_eeprom")("tx_eeprom")("gdb_eeprom"); + + const size_t DB_OFFSET = (_radio_slot == "A") ? 0x0 : 0x2; + const fs_path db_path = ("dboards" / _radio_slot); + for (size_t i = 0; i < EEPROM_ADDRS.size(); i++) { + const size_t addr = EEPROM_ADDRS[i] + DB_OFFSET; + //Load EEPROM + _db_eeproms[addr].load(*zpu_i2c, BASE_ADDR | addr); + //Add to tree + _tree->create<dboard_eeprom_t>(db_path / EEPROM_PATHS[i]) + .set(_db_eeproms[addr]) + .add_coerced_subscriber(boost::bind(&dboard_eeprom_t::store, + _db_eeproms[addr], boost::ref(*zpu_i2c), (BASE_ADDR | addr))); + } + + //create a new dboard interface + x300_dboard_iface_config_t db_config; + db_config.gpio = gpio_atr::db_gpio_atr_3000::make(_get_ctrl(IO_MASTER_RADIO), + radio_ctrl_impl::regs::sr_addr(radio_ctrl_impl::regs::GPIO), radio_ctrl_impl::regs::RB_DB_GPIO); + db_config.spi = _spi; + db_config.rx_spi_slaveno = DB_RX_SEN; + db_config.tx_spi_slaveno = DB_TX_SEN; + db_config.i2c = zpu_i2c; + db_config.clock = clock; + db_config.which_rx_clk = (_radio_slot == "A") ? X300_CLOCK_WHICH_DB0_RX : X300_CLOCK_WHICH_DB1_RX; + db_config.which_tx_clk = (_radio_slot == "A") ? X300_CLOCK_WHICH_DB0_TX : X300_CLOCK_WHICH_DB1_TX; + db_config.dboard_slot = (_radio_slot == "A")? 0 : 1; + db_config.cmd_time_ctrl = _get_ctrl(IO_MASTER_RADIO); + + //create a new dboard manager + boost::shared_ptr<x300_dboard_iface> db_iface = boost::make_shared<x300_dboard_iface>(db_config); + _db_manager = dboard_manager::make( + _db_eeproms[RX_EEPROM_ADDR + DB_OFFSET].id, + _db_eeproms[TX_EEPROM_ADDR + DB_OFFSET].id, + _db_eeproms[GDB_EEPROM_ADDR + DB_OFFSET].id, + db_iface, _tree->subtree(db_path), + true // defer daughterboard intitialization + ); + + size_t rx_chan = 0, tx_chan = 0; + BOOST_FOREACH(const std::string& fe, _db_manager->get_rx_frontends()) { + if (rx_chan >= _get_num_radios()) { + break; + } + _rx_fe_map[rx_chan].db_fe_name = fe; + db_iface->add_rx_fe(fe, _rx_fe_map[rx_chan].core); + const fs_path fe_path(db_path / "rx_frontends" / fe); + const std::string conn = _tree->access<std::string>(fe_path / "connection").get(); + const double if_freq = (_tree->exists(fe_path / "if_freq/value")) ? + _tree->access<double>(fe_path / "if_freq/value").get() : 0.0; + _rx_fe_map[rx_chan].core->set_fe_connection(usrp::fe_connection_t(conn, if_freq)); + rx_chan++; + } + BOOST_FOREACH(const std::string& fe, _db_manager->get_tx_frontends()) { + if (tx_chan >= _get_num_radios()) { + break; + } + _tx_fe_map[tx_chan].db_fe_name = fe; + const fs_path fe_path(db_path / "tx_frontends" / fe); + const std::string conn = _tree->access<std::string>(fe_path / "connection").get(); + _tx_fe_map[tx_chan].core->set_mux(conn); + tx_chan++; + } + UHD_ASSERT_THROW(rx_chan or tx_chan); + + // Initialize the daughterboards now that frontend cores and connections exist + _db_manager->initialize_dboards(); + + //now that dboard is created -- register into rx antenna event + if (not _rx_fe_map.empty() + and _tree->exists(db_path / "rx_frontends" / _rx_fe_map[0].db_fe_name / "antenna" / "value")) { + _tree->access<std::string>(db_path / "rx_frontends" / _rx_fe_map[0].db_fe_name / "antenna" / "value") + .add_coerced_subscriber(boost::bind(&x300_radio_ctrl_impl::_update_atr_leds, this, _1)); + } + _update_atr_leds(""); //init anyway, even if never called + + //bind frontend corrections to the dboard freq props + const fs_path db_tx_fe_path = db_path / "tx_frontends"; + BOOST_FOREACH(const std::string &name, _tree->list(db_tx_fe_path)) { + _tree->access<double>(db_tx_fe_path / name / "freq" / "value") + .add_coerced_subscriber(boost::bind(&x300_radio_ctrl_impl::set_tx_fe_corrections, this, db_path, _root_path / "tx_fe_corrections" / name, _1)); + } + const fs_path db_rx_fe_path = db_path / "rx_frontends"; + BOOST_FOREACH(const std::string &name, _tree->list(db_rx_fe_path)) { + _tree->access<double>(db_rx_fe_path / name / "freq" / "value") + .add_coerced_subscriber(boost::bind(&x300_radio_ctrl_impl::set_rx_fe_corrections, this, db_path, _root_path / "rx_fe_corrections" / name,_1)); + } + + //////////////////////////////////////////////////////////////// + // Set tick rate + //////////////////////////////////////////////////////////////// + const double tick_rate = get_output_samp_rate(0); + if (_radio_type==PRIMARY) { + // Slot A is the highlander timekeeper + _tree->access<double>("tick_rate").set(tick_rate); + } + radio_ctrl_impl::set_rate(tick_rate); +} + +void x300_radio_ctrl_impl::set_rx_fe_corrections( + const fs_path &db_path, + const fs_path &rx_fe_corr_path, + const double lo_freq +) { + if (not _ignore_cal_file) { + apply_rx_fe_corrections(_tree, db_path, rx_fe_corr_path, lo_freq); + } +} + +void x300_radio_ctrl_impl::set_tx_fe_corrections( + const fs_path &db_path, + const fs_path &tx_fe_corr_path, + const double lo_freq +) { + if (not _ignore_cal_file) { + apply_tx_fe_corrections(_tree, db_path, tx_fe_corr_path, lo_freq); + } +} + +void x300_radio_ctrl_impl::reset_codec() +{ + if (_radio_type==PRIMARY) { //ADC/DAC reset lines only exist in Radio0 + _regs->misc_outs_reg.set(radio_regmap_t::misc_outs_reg_t::ADC_RESET, 1); + _regs->misc_outs_reg.set(radio_regmap_t::misc_outs_reg_t::DAC_RESET_N, 0); + _regs->misc_outs_reg.flush(); + _regs->misc_outs_reg.set(radio_regmap_t::misc_outs_reg_t::ADC_RESET, 0); + _regs->misc_outs_reg.set(radio_regmap_t::misc_outs_reg_t::DAC_RESET_N, 1); + _regs->misc_outs_reg.flush(); + } + UHD_ASSERT_THROW(bool(_adc)); + UHD_ASSERT_THROW(bool(_dac)); + _adc->reset(); + _dac->reset(); +} + +void x300_radio_ctrl_impl::self_test_adc(boost::uint32_t ramp_time_ms) +{ + //Bypass all front-end corrections + for (size_t i = 0; i < _get_num_radios(); i++) { + _rx_fe_map[i].core->bypass_all(true); + } + + //Test basic patterns + _adc->set_test_word("ones", "ones"); _check_adc(0xfffcfffc); + _adc->set_test_word("zeros", "zeros"); _check_adc(0x00000000); + _adc->set_test_word("ones", "zeros"); _check_adc(0xfffc0000); + _adc->set_test_word("zeros", "ones"); _check_adc(0x0000fffc); + for (size_t k = 0; k < 14; k++) { + _adc->set_test_word("zeros", "custom", 1 << k); + _check_adc(1 << (k+2)); + } + for (size_t k = 0; k < 14; k++) { + _adc->set_test_word("custom", "zeros", 1 << k); + _check_adc(1 << (k+18)); + } + + //Turn on ramp pattern test + _adc->set_test_word("ramp", "ramp"); + _regs->misc_outs_reg.write(radio_regmap_t::misc_outs_reg_t::ADC_CHECKER_ENABLED, 0); + //Sleep added for SPI transactions to finish and ramp to start before checker is enabled. + boost::this_thread::sleep(boost::posix_time::microsec(1000)); + _regs->misc_outs_reg.write(radio_regmap_t::misc_outs_reg_t::ADC_CHECKER_ENABLED, 1); + + boost::this_thread::sleep(boost::posix_time::milliseconds(ramp_time_ms)); + _regs->misc_ins_reg.refresh(); + + std::string i_status, q_status; + if (_regs->misc_ins_reg.get(radio_regmap_t::misc_ins_reg_t::ADC_CHECKER1_I_LOCKED)) + if (_regs->misc_ins_reg.get(radio_regmap_t::misc_ins_reg_t::ADC_CHECKER1_I_ERROR)) + i_status = "Bit Errors!"; + else + i_status = "Good"; + else + i_status = "Not Locked!"; + + if (_regs->misc_ins_reg.get(radio_regmap_t::misc_ins_reg_t::ADC_CHECKER1_Q_LOCKED)) + if (_regs->misc_ins_reg.get(radio_regmap_t::misc_ins_reg_t::ADC_CHECKER1_Q_ERROR)) + q_status = "Bit Errors!"; + else + q_status = "Good"; + else + q_status = "Not Locked!"; + + //Return to normal mode + _adc->set_test_word("normal", "normal"); + + if ((i_status != "Good") or (q_status != "Good")) { + throw uhd::runtime_error( + (boost::format("ADC self-test failed for %s. Ramp checker status: {ADC_A=%s, ADC_B=%s}")%unique_id()%i_status%q_status).str()); + } + + //Restore front-end corrections + for (size_t i = 0; i < _get_num_radios(); i++) { + _rx_fe_map[i].core->bypass_all(false); + } +} + +void x300_radio_ctrl_impl::extended_adc_test(const std::vector<x300_radio_ctrl_impl::sptr>& radios, double duration_s) +{ + static const size_t SECS_PER_ITER = 5; + UHD_MSG(status) << boost::format("Running Extended ADC Self-Test (Duration=%.0fs, %ds/iteration)...\n") + % duration_s % SECS_PER_ITER; + + size_t num_iters = static_cast<size_t>(ceil(duration_s/SECS_PER_ITER)); + size_t num_failures = 0; + for (size_t iter = 0; iter < num_iters; iter++) { + //Print date and time + boost::posix_time::time_facet *facet = new boost::posix_time::time_facet("%d-%b-%Y %H:%M:%S"); + std::ostringstream time_strm; + time_strm.imbue(std::locale(std::locale::classic(), facet)); + time_strm << boost::posix_time::second_clock::local_time(); + //Run self-test + UHD_MSG(status) << boost::format("-- [%s] Iteration %06d... ") % time_strm.str() % (iter+1); + try { + for (size_t i = 0; i < radios.size(); i++) { + radios[i]->self_test_adc((SECS_PER_ITER*1000)/radios.size()); + } + UHD_MSG(status) << "passed" << std::endl; + } catch(std::exception &e) { + num_failures++; + UHD_MSG(status) << e.what() << std::endl; + } + } + if (num_failures == 0) { + UHD_MSG(status) << "Extended ADC Self-Test PASSED\n"; + } else { + throw uhd::runtime_error( + (boost::format("Extended ADC Self-Test FAILED!!! (%d/%d failures)\n") % num_failures % num_iters).str()); + } +} + +void x300_radio_ctrl_impl::synchronize_dacs(const std::vector<x300_radio_ctrl_impl::sptr>& radios) +{ + if (radios.size() < 2) return; //Nothing to synchronize + + //**PRECONDITION** + //This function assumes that all the VITA times in "radios" are synchronized + //to a common reference. Currently, this function is called in get_tx_stream + //which also has the same precondition. + + //Reinitialize and resync all DACs + for (size_t i = 0; i < radios.size(); i++) { + radios[i]->_dac->reset(); + } + + //Get a rough estimate of the cumulative command latency + boost::posix_time::ptime t_start = boost::posix_time::microsec_clock::local_time(); + for (size_t i = 0; i < radios.size(); i++) { + radios[i]->user_reg_read64(regs::RB_TIME_NOW); //Discard value. We are just timing the call + } + boost::posix_time::time_duration t_elapsed = + boost::posix_time::microsec_clock::local_time() - t_start; + + //Add 100% of headroom + uncertaintly to the command time + boost::uint64_t t_sync_us = (t_elapsed.total_microseconds() * 2) + 13000 /*Scheduler latency*/; + + //Pick radios[0] as the time reference. + uhd::time_spec_t sync_time = + radios[0]->_time64->get_time_now() + uhd::time_spec_t(((double)t_sync_us)/1e6); + + //Send the sync command + for (size_t i = 0; i < radios.size(); i++) { + radios[i]->set_command_tick_rate(radios[i]->_radio_clk_rate, IO_MASTER_RADIO); + radios[i]->_regs->misc_outs_reg.set(radio_regmap_t::misc_outs_reg_t::DAC_SYNC, 0); + radios[i]->set_command_time(sync_time, IO_MASTER_RADIO); + //Arm FRAMEP/N sync pulse by asserting a rising edge + radios[i]->_regs->misc_outs_reg.set(radio_regmap_t::misc_outs_reg_t::DAC_SYNC, 1); + radios[i]->_regs->misc_outs_reg.set(radio_regmap_t::misc_outs_reg_t::DAC_SYNC, 0); + radios[i]->set_command_time(uhd::time_spec_t(0.0), IO_MASTER_RADIO); + } + + //Wait and check status + boost::this_thread::sleep(boost::posix_time::microseconds(t_sync_us)); + for (size_t i = 0; i < radios.size(); i++) { + radios[i]->_dac->verify_sync(); + } +} + +double x300_radio_ctrl_impl::self_cal_adc_xfer_delay( + const std::vector<x300_radio_ctrl_impl::sptr>& radios, + x300_clock_ctrl::sptr clock, + boost::function<void(double)> wait_for_clk_locked, + bool apply_delay) +{ + UHD_MSG(status) << "Running ADC transfer delay self-cal: " << std::flush; + + //Effective resolution of the self-cal. + static const size_t NUM_DELAY_STEPS = 100; + + double master_clk_period = (1.0e9 / clock->get_master_clock_rate()); //in ns + double delay_start = 0.0; + double delay_range = 2 * master_clk_period; + double delay_incr = delay_range / NUM_DELAY_STEPS; + + UHD_MSG(status) << "Measuring..." << std::flush; + double cached_clk_delay = clock->get_clock_delay(X300_CLOCK_WHICH_ADC0); + double fpga_clk_delay = clock->get_clock_delay(X300_CLOCK_WHICH_FPGA); + + //Iterate through several values of delays and measure ADC data integrity + std::vector< std::pair<double,bool> > results; + for (size_t i = 0; i < NUM_DELAY_STEPS; i++) { + //Delay the ADC clock (will set both Ch0 and Ch1 delays) + double delay = clock->set_clock_delay(X300_CLOCK_WHICH_ADC0, delay_incr*i + delay_start); + wait_for_clk_locked(0.1); + + boost::uint32_t err_code = 0; + for (size_t r = 0; r < radios.size(); r++) { + //Test each channel (I and Q) individually so as to not accidentally trigger + //on the data from the other channel if there is a swap + + // -- Test I Channel -- + //Put ADC in ramp test mode. Tie the other channel to all ones. + radios[r]->_adc->set_test_word("ramp", "ones"); + //Turn on the pattern checker in the FPGA. It will lock when it sees a zero + //and count deviations from the expected value + radios[r]->_regs->misc_outs_reg.write(radio_regmap_t::misc_outs_reg_t::ADC_CHECKER_ENABLED, 0); + radios[r]->_regs->misc_outs_reg.write(radio_regmap_t::misc_outs_reg_t::ADC_CHECKER_ENABLED, 1); + //50ms @ 200MHz = 10 million samples + boost::this_thread::sleep(boost::posix_time::milliseconds(50)); + if (radios[r]->_regs->misc_ins_reg.read(radio_regmap_t::misc_ins_reg_t::ADC_CHECKER1_I_LOCKED)) { + err_code += radios[r]->_regs->misc_ins_reg.get(radio_regmap_t::misc_ins_reg_t::ADC_CHECKER1_I_ERROR); + } else { + err_code += 100; //Increment error code by 100 to indicate no lock + } + + // -- Test Q Channel -- + //Put ADC in ramp test mode. Tie the other channel to all ones. + radios[r]->_adc->set_test_word("ones", "ramp"); + //Turn on the pattern checker in the FPGA. It will lock when it sees a zero + //and count deviations from the expected value + radios[r]->_regs->misc_outs_reg.write(radio_regmap_t::misc_outs_reg_t::ADC_CHECKER_ENABLED, 0); + radios[r]->_regs->misc_outs_reg.write(radio_regmap_t::misc_outs_reg_t::ADC_CHECKER_ENABLED, 1); + //50ms @ 200MHz = 10 million samples + boost::this_thread::sleep(boost::posix_time::milliseconds(50)); + if (radios[r]->_regs->misc_ins_reg.read(radio_regmap_t::misc_ins_reg_t::ADC_CHECKER1_Q_LOCKED)) { + err_code += radios[r]->_regs->misc_ins_reg.get(radio_regmap_t::misc_ins_reg_t::ADC_CHECKER1_Q_ERROR); + } else { + err_code += 100; //Increment error code by 100 to indicate no lock + } + } + //UHD_MSG(status) << (boost::format("XferDelay=%fns, Error=%d\n") % delay % err_code); + results.push_back(std::pair<double,bool>(delay, err_code==0)); + } + + //Calculate the valid window + int win_start_idx = -1, win_stop_idx = -1, cur_start_idx = -1, cur_stop_idx = -1; + for (size_t i = 0; i < results.size(); i++) { + std::pair<double,bool>& item = results[i]; + if (item.second) { //If data is stable + if (cur_start_idx == -1) { //This is the first window + cur_start_idx = i; + cur_stop_idx = i; + } else { //We are extending the window + cur_stop_idx = i; + } + } else { + if (cur_start_idx == -1) { //We haven't yet seen valid data + //Do nothing + } else if (win_start_idx == -1) { //We passed the first valid window + win_start_idx = cur_start_idx; + win_stop_idx = cur_stop_idx; + } else { //Update cached window if current window is larger + double cur_win_len = results[cur_stop_idx].first - results[cur_start_idx].first; + double cached_win_len = results[win_stop_idx].first - results[win_start_idx].first; + if (cur_win_len > cached_win_len) { + win_start_idx = cur_start_idx; + win_stop_idx = cur_stop_idx; + } + } + //Reset current window + cur_start_idx = -1; + cur_stop_idx = -1; + } + } + if (win_start_idx == -1) { + throw uhd::runtime_error("self_cal_adc_xfer_delay: Self calibration failed. Convergence error."); + } + + double win_center = (results[win_stop_idx].first + results[win_start_idx].first) / 2.0; + double win_length = results[win_stop_idx].first - results[win_start_idx].first; + if (win_length < master_clk_period/4) { + throw uhd::runtime_error("self_cal_adc_xfer_delay: Self calibration failed. Valid window too narrow."); + } + + //Cycle slip the relative delay by a clock cycle to prevent sample misalignment + //fpga_clk_delay > 0 and 0 < win_center < 2*(1/MCR) so one cycle slip is all we need + bool cycle_slip = (win_center-fpga_clk_delay >= master_clk_period); + if (cycle_slip) { + win_center -= master_clk_period; + } + + if (apply_delay) { + UHD_MSG(status) << "Validating..." << std::flush; + //Apply delay + win_center = clock->set_clock_delay(X300_CLOCK_WHICH_ADC0, win_center); //Sets ADC0 and ADC1 + wait_for_clk_locked(0.1); + //Validate + for (size_t r = 0; r < radios.size(); r++) { + radios[r]->self_test_adc(2000); + } + } else { + //Restore delay + clock->set_clock_delay(X300_CLOCK_WHICH_ADC0, cached_clk_delay); //Sets ADC0 and ADC1 + } + + //Teardown + for (size_t r = 0; r < radios.size(); r++) { + radios[r]->_adc->set_test_word("normal", "normal"); + radios[r]->_regs->misc_outs_reg.write(radio_regmap_t::misc_outs_reg_t::ADC_CHECKER_ENABLED, 0); + } + UHD_MSG(status) << (boost::format(" done (FPGA->ADC=%.3fns%s, Window=%.3fns)\n") % + (win_center-fpga_clk_delay) % (cycle_slip?" +cyc":"") % win_length); + + return win_center; +} +/**************************************************************************** + * Helpers + ***************************************************************************/ +void x300_radio_ctrl_impl::_update_atr_leds(const std::string &rx_ant) +{ + const bool is_txrx = (rx_ant == "TX/RX"); + const int rx_led = (1 << 2); + const int tx_led = (1 << 1); + const int txrx_led = (1 << 0); + _leds->set_atr_reg(gpio_atr::ATR_REG_IDLE, 0); + _leds->set_atr_reg(gpio_atr::ATR_REG_RX_ONLY, is_txrx? txrx_led : rx_led); + _leds->set_atr_reg(gpio_atr::ATR_REG_TX_ONLY, tx_led); + _leds->set_atr_reg(gpio_atr::ATR_REG_FULL_DUPLEX, rx_led | tx_led); +} + +void x300_radio_ctrl_impl::_self_cal_adc_capture_delay(bool print_status) +{ + if (print_status) UHD_MSG(status) << "Running ADC capture delay self-cal..." << std::flush; + + static const boost::uint32_t NUM_DELAY_STEPS = 32; //The IDELAYE2 element has 32 steps + static const boost::uint32_t NUM_RETRIES = 2; //Retry self-cal if it fails in warmup situations + static const boost::int32_t MIN_WINDOW_LEN = 4; + + boost::int32_t win_start = -1, win_stop = -1; + boost::uint32_t iter = 0; + while (iter++ < NUM_RETRIES) { + for (boost::uint32_t dly_tap = 0; dly_tap < NUM_DELAY_STEPS; dly_tap++) { + //Apply delay + _regs->misc_outs_reg.write(radio_regmap_t::misc_outs_reg_t::ADC_DATA_DLY_VAL, dly_tap); + _regs->misc_outs_reg.write(radio_regmap_t::misc_outs_reg_t::ADC_DATA_DLY_STB, 1); + _regs->misc_outs_reg.write(radio_regmap_t::misc_outs_reg_t::ADC_DATA_DLY_STB, 0); + + boost::uint32_t err_code = 0; + + // -- Test I Channel -- + //Put ADC in ramp test mode. Tie the other channel to all ones. + _adc->set_test_word("ramp", "ones"); + //Turn on the pattern checker in the FPGA. It will lock when it sees a zero + //and count deviations from the expected value + _regs->misc_outs_reg.write(radio_regmap_t::misc_outs_reg_t::ADC_CHECKER_ENABLED, 0); + _regs->misc_outs_reg.write(radio_regmap_t::misc_outs_reg_t::ADC_CHECKER_ENABLED, 1); + //10ms @ 200MHz = 2 million samples + boost::this_thread::sleep(boost::posix_time::milliseconds(10)); + if (_regs->misc_ins_reg.read(radio_regmap_t::misc_ins_reg_t::ADC_CHECKER0_I_LOCKED)) { + err_code += _regs->misc_ins_reg.get(radio_regmap_t::misc_ins_reg_t::ADC_CHECKER0_I_ERROR); + } else { + err_code += 100; //Increment error code by 100 to indicate no lock + } + + // -- Test Q Channel -- + //Put ADC in ramp test mode. Tie the other channel to all ones. + _adc->set_test_word("ones", "ramp"); + //Turn on the pattern checker in the FPGA. It will lock when it sees a zero + //and count deviations from the expected value + _regs->misc_outs_reg.write(radio_regmap_t::misc_outs_reg_t::ADC_CHECKER_ENABLED, 0); + _regs->misc_outs_reg.write(radio_regmap_t::misc_outs_reg_t::ADC_CHECKER_ENABLED, 1); + //10ms @ 200MHz = 2 million samples + boost::this_thread::sleep(boost::posix_time::milliseconds(10)); + if (_regs->misc_ins_reg.read(radio_regmap_t::misc_ins_reg_t::ADC_CHECKER0_Q_LOCKED)) { + err_code += _regs->misc_ins_reg.get(radio_regmap_t::misc_ins_reg_t::ADC_CHECKER0_Q_ERROR); + } else { + err_code += 100; //Increment error code by 100 to indicate no lock + } + + if (err_code == 0) { + if (win_start == -1) { //This is the first window + win_start = dly_tap; + win_stop = dly_tap; + } else { //We are extending the window + win_stop = dly_tap; + } + } else { + if (win_start != -1) { //A valid window turned invalid + if (win_stop - win_start >= MIN_WINDOW_LEN) { + break; //Valid window found + } else { + win_start = -1; //Reset window + } + } + } + //UHD_MSG(status) << (boost::format("CapTap=%d, Error=%d\n") % dly_tap % err_code); + } + + //Retry the self-cal if it fails + if ((win_start == -1 || (win_stop - win_start) < MIN_WINDOW_LEN) && iter < NUM_RETRIES /*not last iteration*/) { + win_start = -1; + win_stop = -1; + boost::this_thread::sleep(boost::posix_time::milliseconds(2000)); + } else { + break; + } + } + _adc->set_test_word("normal", "normal"); + _regs->misc_outs_reg.write(radio_regmap_t::misc_outs_reg_t::ADC_CHECKER_ENABLED, 0); + + if (win_start == -1) { + throw uhd::runtime_error("self_cal_adc_capture_delay: Self calibration failed. Convergence error."); + } + + if (win_stop-win_start < MIN_WINDOW_LEN) { + throw uhd::runtime_error("self_cal_adc_capture_delay: Self calibration failed. Valid window too narrow."); + } + + boost::uint32_t ideal_tap = (win_stop + win_start) / 2; + _regs->misc_outs_reg.write(radio_regmap_t::misc_outs_reg_t::ADC_DATA_DLY_VAL, ideal_tap); + _regs->misc_outs_reg.write(radio_regmap_t::misc_outs_reg_t::ADC_DATA_DLY_STB, 1); + _regs->misc_outs_reg.write(radio_regmap_t::misc_outs_reg_t::ADC_DATA_DLY_STB, 0); + + if (print_status) { + double tap_delay = (1.0e12 / _radio_clk_rate) / (2*32); //in ps + UHD_MSG(status) << boost::format(" done (Tap=%d, Window=%d, TapDelay=%.3fps, Iter=%d)\n") % ideal_tap % (win_stop-win_start) % tap_delay % iter; + } +} + +void x300_radio_ctrl_impl::_check_adc(const boost::uint32_t val) +{ + //Wait for previous control transaction to flush + user_reg_read64(regs::RB_TEST); + //Wait for ADC test pattern to propagate + boost::this_thread::sleep(boost::posix_time::microsec(5)); + //Read value of RX readback register and verify + boost::uint32_t adc_rb = static_cast<boost::uint32_t>(user_reg_read64(regs::RB_TEST)>>32); + adc_rb ^= 0xfffc0000; //adapt for I inversion in FPGA + if (val != adc_rb) { + throw uhd::runtime_error( + (boost::format("ADC self-test failed for %s. (Exp=0x%x, Got=0x%x)")%unique_id()%val%adc_rb).str()); + } +} + +/**************************************************************************** + * Helpers + ***************************************************************************/ +bool x300_radio_ctrl_impl::check_radio_config() +{ + UHD_RFNOC_BLOCK_TRACE() << "x300_radio_ctrl_impl::check_radio_config() " << std::endl; + const fs_path rx_fe_path = fs_path("dboards" / _radio_slot / "rx_frontends"); + for (size_t chan = 0; chan < _get_num_radios(); chan++) { + if (_tree->exists(rx_fe_path / _rx_fe_map.at(chan).db_fe_name / "enabled")) { + const bool chan_active = _is_streamer_active(uhd::RX_DIRECTION, chan); + if (chan_active) { + _tree->access<bool>(rx_fe_path / _rx_fe_map.at(chan).db_fe_name / "enabled") + .set(chan_active) + ; + } + } + } + + const fs_path tx_fe_path = fs_path("dboards" / _radio_slot / "tx_frontends"); + for (size_t chan = 0; chan < _get_num_radios(); chan++) { + if (_tree->exists(tx_fe_path / _tx_fe_map.at(chan).db_fe_name / "enabled")) { + const bool chan_active = _is_streamer_active(uhd::TX_DIRECTION, chan); + if (chan_active) { + _tree->access<bool>(tx_fe_path / _tx_fe_map.at(chan).db_fe_name / "enabled") + .set(chan_active) + ; + } + } + } + + return true; +} + +/**************************************************************************** + * Register block + ***************************************************************************/ +UHD_RFNOC_BLOCK_REGISTER(x300_radio_ctrl, "X300Radio"); diff --git a/host/lib/usrp/x300/x300_radio_ctrl_impl.hpp b/host/lib/usrp/x300/x300_radio_ctrl_impl.hpp new file mode 100644 index 000000000..770519eba --- /dev/null +++ b/host/lib/usrp/x300/x300_radio_ctrl_impl.hpp @@ -0,0 +1,198 @@ +// +// Copyright 2015-2016 Ettus Research +// +// 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 +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +#ifndef INCLUDED_LIBUHD_RFNOC_X300_RADIO_CTRL_IMPL_HPP +#define INCLUDED_LIBUHD_RFNOC_X300_RADIO_CTRL_IMPL_HPP + +#include "radio_ctrl_impl.hpp" +#include "x300_clock_ctrl.hpp" +#include "spi_core_3000.hpp" +#include "x300_adc_ctrl.hpp" +#include "x300_dac_ctrl.hpp" +#include "x300_regs.hpp" +#include "rx_frontend_core_3000.hpp" +#include "tx_frontend_core_200.hpp" +#include <uhd/usrp/dboard_eeprom.hpp> +#include <uhd/usrp/dboard_manager.hpp> +#include <uhd/usrp/gpio_defs.hpp> + +namespace uhd { + namespace rfnoc { + +/*! \brief Provide access to an X300 radio. + */ +class x300_radio_ctrl_impl : public radio_ctrl_impl +{ +public: + typedef boost::shared_ptr<x300_radio_ctrl_impl> sptr; + + /************************************************************************ + * Structors + ***********************************************************************/ + UHD_RFNOC_RADIO_BLOCK_CONSTRUCTOR_DECL(x300_radio_ctrl) + virtual ~x300_radio_ctrl_impl(); + + /************************************************************************ + * API calls + ***********************************************************************/ + double set_rate(double rate); + + void set_tx_antenna(const std::string &ant, const size_t chan); + void set_rx_antenna(const std::string &ant, const size_t chan); + + double set_tx_frequency(const double freq, const size_t chan); + double set_rx_frequency(const double freq, const size_t chan); + double get_tx_frequency(const size_t chan); + double get_rx_frequency(const size_t chan); + + double set_tx_gain(const double gain, const size_t chan); + double set_rx_gain(const double gain, const size_t chan); + + size_t get_chan_from_dboard_fe(const std::string &fe, const direction_t dir); + std::string get_dboard_fe_from_chan(const size_t chan, const direction_t dir); + + double get_output_samp_rate(size_t port); + + /************************************************************************ + * Hardware setup and control + ***********************************************************************/ + /*! Set up the radio. No API calls may be made before this one. + */ + void setup_radio( + uhd::i2c_iface::sptr zpu_i2c, x300_clock_ctrl::sptr clock, bool verbose); + + void reset_codec(); + + void self_test_adc( + boost::uint32_t ramp_time_ms = 100); + + static void extended_adc_test( + const std::vector<x300_radio_ctrl_impl::sptr>&, double duration_s); + + static void synchronize_dacs( + const std::vector<x300_radio_ctrl_impl::sptr>& radios); + + static double self_cal_adc_xfer_delay( + const std::vector<x300_radio_ctrl_impl::sptr>& radios, + x300_clock_ctrl::sptr clock, + boost::function<void(double)> wait_for_clk_locked, + bool apply_delay); + +protected: + virtual bool check_radio_config(); + +private: + class radio_regmap_t : public uhd::soft_regmap_t { + public: + typedef boost::shared_ptr<radio_regmap_t> sptr; + class misc_outs_reg_t : public uhd::soft_reg32_wo_t { + public: + UHD_DEFINE_SOFT_REG_FIELD(DAC_ENABLED, /*width*/ 1, /*shift*/ 0); //[0] + UHD_DEFINE_SOFT_REG_FIELD(DAC_RESET_N, /*width*/ 1, /*shift*/ 1); //[1] + UHD_DEFINE_SOFT_REG_FIELD(ADC_RESET, /*width*/ 1, /*shift*/ 2); //[2] + UHD_DEFINE_SOFT_REG_FIELD(ADC_DATA_DLY_STB, /*width*/ 1, /*shift*/ 3); //[3] + UHD_DEFINE_SOFT_REG_FIELD(ADC_DATA_DLY_VAL, /*width*/ 5, /*shift*/ 4); //[8:4] + UHD_DEFINE_SOFT_REG_FIELD(ADC_CHECKER_ENABLED, /*width*/ 1, /*shift*/ 9); //[9] + UHD_DEFINE_SOFT_REG_FIELD(DAC_SYNC, /*width*/ 1, /*shift*/ 10); //[10] + + misc_outs_reg_t(): uhd::soft_reg32_wo_t(regs::sr_addr(regs::MISC_OUTS)) { + //Initial values + set(DAC_ENABLED, 0); + set(DAC_RESET_N, 0); + set(ADC_RESET, 0); + set(ADC_DATA_DLY_STB, 0); + set(ADC_DATA_DLY_VAL, 16); + set(ADC_CHECKER_ENABLED, 0); + set(DAC_SYNC, 0); + } + } misc_outs_reg; + + class misc_ins_reg_t : public uhd::soft_reg64_ro_t { + public: + UHD_DEFINE_SOFT_REG_FIELD(ADC_CHECKER0_Q_LOCKED, /*width*/ 1, /*shift*/ 32); //[0] + UHD_DEFINE_SOFT_REG_FIELD(ADC_CHECKER0_I_LOCKED, /*width*/ 1, /*shift*/ 33); //[1] + UHD_DEFINE_SOFT_REG_FIELD(ADC_CHECKER1_Q_LOCKED, /*width*/ 1, /*shift*/ 34); //[2] + UHD_DEFINE_SOFT_REG_FIELD(ADC_CHECKER1_I_LOCKED, /*width*/ 1, /*shift*/ 35); //[3] + UHD_DEFINE_SOFT_REG_FIELD(ADC_CHECKER0_Q_ERROR, /*width*/ 1, /*shift*/ 36); //[4] + UHD_DEFINE_SOFT_REG_FIELD(ADC_CHECKER0_I_ERROR, /*width*/ 1, /*shift*/ 37); //[5] + UHD_DEFINE_SOFT_REG_FIELD(ADC_CHECKER1_Q_ERROR, /*width*/ 1, /*shift*/ 38); //[6] + UHD_DEFINE_SOFT_REG_FIELD(ADC_CHECKER1_I_ERROR, /*width*/ 1, /*shift*/ 39); //[7] + + misc_ins_reg_t(): uhd::soft_reg64_ro_t(regs::RB_MISC_IO) { } + } misc_ins_reg; + + radio_regmap_t(int radio_num) : soft_regmap_t("radio" + boost::lexical_cast<std::string>(radio_num) + "_regmap") { + add_to_map(misc_outs_reg, "misc_outs_reg", PRIVATE); + add_to_map(misc_ins_reg, "misc_ins_reg", PRIVATE); + } + }; + + struct x300_regs { + static const uint32_t TX_FE_BASE = 224; + static const uint32_t RX_FE_BASE = 232; + }; + + void _update_atr_leds(const std::string &rx_ant); + + void _self_cal_adc_capture_delay(bool print_status); + + void _check_adc(const boost::uint32_t val); + + void set_rx_fe_corrections(const uhd::fs_path &db_path, const uhd::fs_path &rx_fe_corr_path, const double lo_freq); + void set_tx_fe_corrections(const uhd::fs_path &db_path, const uhd::fs_path &tx_fe_corr_path, const double lo_freq); + +private: // members + enum radio_connection_t { PRIMARY, SECONDARY }; + + radio_connection_t _radio_type; + std::string _radio_slot; + //! Radio clock rate is the rate at which the ADC and DAC are running at. + // Not necessarily this block's sampling rate (tick rate). + double _radio_clk_rate; + + radio_regmap_t::sptr _regs; + usrp::gpio_atr::gpio_atr_3000::sptr _leds; + spi_core_3000::sptr _spi; + x300_adc_ctrl::sptr _adc; + x300_dac_ctrl::sptr _dac; + usrp::gpio_atr::gpio_atr_3000::sptr _fp_gpio; + + std::map<size_t, usrp::dboard_eeprom_t> _db_eeproms; + usrp::dboard_manager::sptr _db_manager; + + struct rx_fe_perif { + std::string name; + std::string db_fe_name; + rx_frontend_core_3000::sptr core; + }; + struct tx_fe_perif { + std::string name; + std::string db_fe_name; + tx_frontend_core_200::sptr core; + }; + + std::map<size_t, rx_fe_perif> _rx_fe_map; + std::map<size_t, tx_fe_perif> _tx_fe_map; + + bool _ignore_cal_file; + +}; /* class radio_ctrl_impl */ + +}} /* namespace uhd::rfnoc */ + +#endif /* INCLUDED_LIBUHD_RFNOC_X300_RADIO_CTRL_IMPL_HPP */ +// vim: sw=4 et: diff --git a/host/lib/usrp/x300/x300_regs.hpp b/host/lib/usrp/x300/x300_regs.hpp index 3e0966c83..c5ed1460b 100644 --- a/host/lib/usrp/x300/x300_regs.hpp +++ b/host/lib/usrp/x300/x300_regs.hpp @@ -22,45 +22,8 @@ #include <stdint.h> #include <uhd/utils/soft_register.hpp> -namespace uhd { namespace usrp { namespace radio { - -static UHD_INLINE uint32_t sr_addr(const uint32_t offset) -{ - return offset * 4; -} - -static const uint32_t DACSYNC = 5; -static const uint32_t LOOPBACK = 6; -static const uint32_t TEST = 7; -static const uint32_t SPI = 8; -static const uint32_t GPIO = 16; -static const uint32_t MISC_OUTS = 24; -static const uint32_t READBACK = 32; -static const uint32_t TX_CTRL = 64; -static const uint32_t RX_CTRL = 96; -static const uint32_t TIME = 128; -static const uint32_t RX_DSP = 144; -static const uint32_t TX_DSP = 184; -static const uint32_t LEDS = 195; -static const uint32_t FP_GPIO = 200; -static const uint32_t RX_FRONT = 208; -static const uint32_t TX_FRONT = 216; - -static const uint32_t RB32_GPIO = 0; -static const uint32_t RB32_SPI = 4; -static const uint32_t RB64_TIME_NOW = 8; -static const uint32_t RB64_TIME_PPS = 16; -static const uint32_t RB32_TEST = 24; -static const uint32_t RB32_RX = 28; -static const uint32_t RB32_FP_GPIO = 32; -static const uint32_t RB32_MISC_INS = 36; - -}}} // namespace - -#define localparam static const int - -localparam BL_ADDRESS = 0; -localparam BL_DATA = 1; +static const int BL_ADDRESS = 0; +static const int BL_DATA = 1; //wishbone settings map - relevant to host code #define SET0_BASE 0xa000 @@ -70,13 +33,15 @@ localparam BL_DATA = 1; #define I2C1_BASE 0xff00 #define SR_ADDR(base, offset) ((base) + (offset)*4) -localparam ZPU_SR_LEDS = 00; -localparam ZPU_SR_SW_RST = 01; -localparam ZPU_SR_CLOCK_CTRL = 02; -localparam ZPU_SR_XB_LOCAL = 03; -localparam ZPU_SR_SPI = 32; -localparam ZPU_SR_ETHINT0 = 40; -localparam ZPU_SR_ETHINT1 = 56; +static const int ZPU_SR_LEDS = 00; +static const int ZPU_SR_SW_RST = 01; +static const int ZPU_SR_CLOCK_CTRL = 02; +static const int ZPU_SR_XB_LOCAL = 03; +static const int ZPU_SR_SPI = 32; +static const int ZPU_SR_ETHINT0 = 40; +static const int ZPU_SR_ETHINT1 = 56; +static const int ZPU_SR_DRAM_FIFO0 = 72; +static const int ZPU_SR_DRAM_FIFO1 = 80; //reset bits #define ZPU_SR_SW_RST_ETH_PHY (1<<0) @@ -84,11 +49,17 @@ localparam ZPU_SR_ETHINT1 = 56; #define ZPU_SR_SW_RST_RADIO_CLK_PLL (1<<2) #define ZPU_SR_SW_RST_ADC_IDELAYCTRL (1<<3) -localparam ZPU_RB_SPI = 2; -localparam ZPU_RB_CLK_STATUS = 3; -localparam ZPU_RB_COMPAT_NUM = 6; -localparam ZPU_RB_ETH_TYPE0 = 4; -localparam ZPU_RB_ETH_TYPE1 = 5; +static const int ZPU_RB_SPI = 2; +static const int ZPU_RB_CLK_STATUS = 3; +static const int ZPU_RB_COMPAT_NUM = 6; +static const int ZPU_RB_NUM_CE = 7; +static const int ZPU_RB_GIT_HASH = 10; +static const int ZPU_RB_SFP0_TYPE = 4; +static const int ZPU_RB_SFP1_TYPE = 5; + +static const uint32_t RB_SFP_1G_ETH = 0; +static const uint32_t RB_SFP_10G_ETH = 1; +static const uint32_t RB_SFP_AURORA = 2; //spi slaves on radio #define DB_DAC_SEN (1 << 7) @@ -244,49 +215,6 @@ namespace uhd { namespace usrp { namespace x300 { } }; - class radio_regmap_t : public uhd::soft_regmap_t { - public: - typedef boost::shared_ptr<radio_regmap_t> sptr; - class misc_outs_reg_t : public uhd::soft_reg32_wo_t { - public: - UHD_DEFINE_SOFT_REG_FIELD(DAC_ENABLED, /*width*/ 1, /*shift*/ 0); //[0] - UHD_DEFINE_SOFT_REG_FIELD(DAC_RESET_N, /*width*/ 1, /*shift*/ 1); //[1] - UHD_DEFINE_SOFT_REG_FIELD(ADC_RESET, /*width*/ 1, /*shift*/ 2); //[2] - UHD_DEFINE_SOFT_REG_FIELD(ADC_DATA_DLY_STB, /*width*/ 1, /*shift*/ 3); //[3] - UHD_DEFINE_SOFT_REG_FIELD(ADC_DATA_DLY_VAL, /*width*/ 5, /*shift*/ 4); //[8:4] - UHD_DEFINE_SOFT_REG_FIELD(ADC_CHECKER_ENABLED, /*width*/ 1, /*shift*/ 9); //[9] - - misc_outs_reg_t(): uhd::soft_reg32_wo_t(uhd::usrp::radio::sr_addr(uhd::usrp::radio::MISC_OUTS)) { - //Initial values - set(DAC_ENABLED, 0); - set(DAC_RESET_N, 0); - set(ADC_RESET, 0); - set(ADC_DATA_DLY_STB, 0); - set(ADC_DATA_DLY_VAL, 16); - set(ADC_CHECKER_ENABLED, 0); - } - } misc_outs_reg; - - class misc_ins_reg_t : public uhd::soft_reg32_ro_t { - public: - UHD_DEFINE_SOFT_REG_FIELD(ADC_CHECKER0_Q_LOCKED, /*width*/ 1, /*shift*/ 0); //[0] - UHD_DEFINE_SOFT_REG_FIELD(ADC_CHECKER0_I_LOCKED, /*width*/ 1, /*shift*/ 1); //[1] - UHD_DEFINE_SOFT_REG_FIELD(ADC_CHECKER1_Q_LOCKED, /*width*/ 1, /*shift*/ 2); //[2] - UHD_DEFINE_SOFT_REG_FIELD(ADC_CHECKER1_I_LOCKED, /*width*/ 1, /*shift*/ 3); //[3] - UHD_DEFINE_SOFT_REG_FIELD(ADC_CHECKER0_Q_ERROR, /*width*/ 1, /*shift*/ 4); //[4] - UHD_DEFINE_SOFT_REG_FIELD(ADC_CHECKER0_I_ERROR, /*width*/ 1, /*shift*/ 5); //[5] - UHD_DEFINE_SOFT_REG_FIELD(ADC_CHECKER1_Q_ERROR, /*width*/ 1, /*shift*/ 6); //[6] - UHD_DEFINE_SOFT_REG_FIELD(ADC_CHECKER1_I_ERROR, /*width*/ 1, /*shift*/ 7); //[7] - - misc_ins_reg_t(): uhd::soft_reg32_ro_t(uhd::usrp::radio::RB32_MISC_INS) { } - } misc_ins_reg; - - radio_regmap_t(int radio_num) : soft_regmap_t("radio" + boost::lexical_cast<std::string>(radio_num) + "_regmap") { - add_to_map(misc_outs_reg, "misc_outs_reg", PUBLIC); - add_to_map(misc_ins_reg, "misc_ins_reg", PUBLIC); - } - }; - }}} #endif /* INCLUDED_X300_REGS_HPP */ |