//
// Copyright 2012-2015 Ettus Research LLC
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// 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 .
//
#include "ad9361_ctrl.hpp"
#include
#include
#include
#include
#include
#include
#include
#include
#include
using namespace uhd;
using namespace uhd::usrp;
/***********************************************************************
* AD9361 IO Implementation Classes
**********************************************************************/
class ad9361_io_spi : public ad9361_io
{
public:
ad9361_io_spi(uhd::spi_iface::sptr spi_iface, uint32_t slave_num) :
_spi_iface(spi_iface), _slave_num(slave_num) { }
virtual ~ad9361_io_spi() { }
virtual uint8_t peek8(uint32_t reg)
{
boost::lock_guard lock(_mutex);
uhd::spi_config_t config;
config.mosi_edge = uhd::spi_config_t::EDGE_FALL;
config.miso_edge = uhd::spi_config_t::EDGE_FALL; //TODO (Ashish): FPGA SPI workaround. This should be EDGE_RISE
uint32_t rd_word = AD9361_SPI_READ_CMD |
((uint32_t(reg) << AD9361_SPI_ADDR_SHIFT) & AD9361_SPI_ADDR_MASK);
uint32_t val = (_spi_iface->read_spi(_slave_num, config, rd_word, AD9361_SPI_NUM_BITS));
val &= 0xFF;
return static_cast(val);
}
virtual void poke8(uint32_t reg, uint8_t val)
{
boost::lock_guard lock(_mutex);
uhd::spi_config_t config;
config.mosi_edge = uhd::spi_config_t::EDGE_FALL;
config.miso_edge = uhd::spi_config_t::EDGE_FALL; //TODO (Ashish): FPGA SPI workaround. This should be EDGE_RISE
uint32_t wr_word = AD9361_SPI_WRITE_CMD |
((uint32_t(reg) << AD9361_SPI_ADDR_SHIFT) & AD9361_SPI_ADDR_MASK) |
((uint32_t(val) << AD9361_SPI_DATA_SHIFT) & AD9361_SPI_DATA_MASK);
_spi_iface->write_spi(_slave_num, config, wr_word, AD9361_SPI_NUM_BITS);
}
private:
uhd::spi_iface::sptr _spi_iface;
uint32_t _slave_num;
boost::mutex _mutex;
static const uint32_t AD9361_SPI_WRITE_CMD = 0x00800000;
static const uint32_t AD9361_SPI_READ_CMD = 0x00000000;
static const uint32_t AD9361_SPI_ADDR_MASK = 0x003FFF00;
static const uint32_t AD9361_SPI_ADDR_SHIFT = 8;
static const uint32_t AD9361_SPI_DATA_MASK = 0x000000FF;
static const uint32_t AD9361_SPI_DATA_SHIFT = 0;
static const uint32_t AD9361_SPI_NUM_BITS = 24;
};
/***********************************************************************
* AD9361 Control API Class
**********************************************************************/
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), _safe_spi(io_iface), _timed_spi(io_iface)
{
_device.initialize();
}
void set_timed_spi(uhd::spi_iface::sptr spi_iface, uint32_t slave_num)
{
_timed_spi = boost::make_shared(spi_iface, slave_num);
_use_timed_spi();
}
void set_safe_spi(uhd::spi_iface::sptr spi_iface, uint32_t slave_num)
{
_safe_spi = boost::make_shared(spi_iface, slave_num);
}
double set_gain(const std::string &which, const double value)
{
boost::lock_guard lock(_mutex);
ad9361_device_t::direction_t direction = _get_direction_from_antenna(which);
ad9361_device_t::chain_t chain =_get_chain_from_antenna(which);
double return_val = _device.set_gain(direction, chain, value);
return return_val;
}
void set_agc(const std::string &which, bool enable)
{
boost::lock_guard lock(_mutex);
ad9361_device_t::chain_t chain =_get_chain_from_antenna(which);
_device.set_agc(chain, enable);
}
void set_agc_mode(const std::string &which, const std::string &mode)
{
boost::lock_guard lock(_mutex);
ad9361_device_t::chain_t chain =_get_chain_from_antenna(which);
if(mode == "slow") {
_device.set_agc_mode(chain, ad9361_device_t::GAIN_MODE_SLOW_AGC);
} else if (mode == "fast"){
_device.set_agc_mode(chain, ad9361_device_t::GAIN_MODE_FAST_AGC);
} else {
throw uhd::runtime_error("ad9361_ctrl got an invalid AGC option.");
}
}
//! set a new clock rate, return the exact value
double set_clock_rate(const double rate)
{
boost::lock_guard 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);
if (clipped_rate != rate) {
UHD_LOGGER_WARNING("AD936X") << boost::format(
"The requested master_clock_rate %f MHz exceeds bounds imposed by UHD.\n"
"The master_clock_rate has been forced to %f MHz.\n"
) % (rate/1e6) % (clipped_rate/1e6) ;
}
double return_rate = _device.set_clock_rate(clipped_rate);
_use_timed_spi();
return return_rate;
}
//! set which RX and TX chains/antennas are active
void set_active_chains(bool tx1, bool tx2, bool rx1, bool rx2)
{
boost::lock_guard 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
double tune(const std::string &which, const double freq)
{
boost::lock_guard lock(_mutex);
//clip to known bounds
const meta_range_t freq_range = ad9361_ctrl::get_rf_freq_range();
const double clipped_freq = freq_range.clip(freq);
const double value = ad9361_ctrl::get_rf_freq_range().clip(clipped_freq);
ad9361_device_t::direction_t direction = _get_direction_from_antenna(which);
double return_val = _device.tune(direction, value);
return return_val;
}
//! get the current frequency for the given frontend
double get_freq(const std::string &which)
{
boost::lock_guard lock(_mutex);
ad9361_device_t::direction_t direction = _get_direction_from_antenna(which);
return _device.get_freq(direction);
}
//! turn on/off data port loopback
void data_port_loopback(const bool on)
{
boost::lock_guard lock(_mutex);
_device.data_port_loopback(on);
}
//! read internal RSSI sensor
sensor_value_t get_rssi(const std::string &which)
{
boost::lock_guard lock(_mutex);
ad9361_device_t::chain_t chain =_get_chain_from_antenna(which);
return sensor_value_t("RSSI", _device.get_rssi(chain), "dB");
}
//! read the internal temp sensor. Average over 3 results
sensor_value_t get_temperature()
{
return sensor_value_t("temp", _device.get_average_temperature(), "C");
}
void set_dc_offset_auto(const std::string &which, const bool on)
{
boost::lock_guard lock(_mutex);
ad9361_device_t::direction_t direction = _get_direction_from_antenna(which);
_device.set_dc_offset_auto(direction,on);
}
void set_iq_balance_auto(const std::string &which, const bool on)
{
boost::lock_guard lock(_mutex);
ad9361_device_t::direction_t direction = _get_direction_from_antenna(which);
_device.set_iq_balance_auto(direction,on);
}
double set_bw_filter(const std::string &which, const double bw)
{
boost::lock_guard lock(_mutex);
ad9361_device_t::direction_t direction = _get_direction_from_antenna(which);
return _device.set_bw_filter(direction, bw);
}
std::vector get_filter_names(const std::string &which)
{
boost::lock_guard lock(_mutex);
ad9361_device_t::direction_t direction = _get_direction_from_antenna(which);
return _device.get_filter_names(direction);
}
filter_info_base::sptr get_filter(const std::string &which, const std::string &filter_name)
{
boost::lock_guard 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.get_filter(direction, chain, filter_name);
}
void set_filter(const std::string &which, const std::string &filter_name, const filter_info_base::sptr filter)
{
boost::lock_guard lock(_mutex);
ad9361_device_t::direction_t direction = _get_direction_from_antenna(which);
ad9361_device_t::chain_t chain = _get_chain_from_antenna(which);
_device.set_filter(direction, chain, filter_name, filter);
}
void output_digital_test_tone(bool enb)
{
_device.digital_test_tone(enb);
}
private:
static ad9361_device_t::direction_t _get_direction_from_antenna(const std::string& antenna)
{
std::string sub = antenna.substr(0, 2);
if (sub == "RX") {
return ad9361_device_t::RX;
} else if (sub == "TX") {
return ad9361_device_t::TX;
} else {
throw uhd::runtime_error("ad9361_ctrl got an invalid channel string.");
}
return ad9361_device_t::RX;
}
static ad9361_device_t::chain_t _get_chain_from_antenna(const std::string& antenna)
{
std::string sub = antenna.substr(2, 1);
if (sub == "1") {
return ad9361_device_t::CHAIN_1;
} else if (sub == "2") {
return ad9361_device_t::CHAIN_2;
} else {
throw uhd::runtime_error("ad9361_ctrl::set_gain got an invalid channel string.");
}
return ad9361_device_t::CHAIN_1;
}
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,
uint32_t slave_num
) {
boost::shared_ptr spi_io_iface = boost::make_shared(spi_iface, slave_num);
return sptr(new ad9361_ctrl_impl(client_settings, spi_io_iface));
}