aboutsummaryrefslogtreecommitdiffstats
path: root/mpm/lib/mykonos/ad937x_ctrl.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'mpm/lib/mykonos/ad937x_ctrl.cpp')
-rw-r--r--mpm/lib/mykonos/ad937x_ctrl.cpp565
1 files changed, 565 insertions, 0 deletions
diff --git a/mpm/lib/mykonos/ad937x_ctrl.cpp b/mpm/lib/mykonos/ad937x_ctrl.cpp
new file mode 100644
index 000000000..b4ff38139
--- /dev/null
+++ b/mpm/lib/mykonos/ad937x_ctrl.cpp
@@ -0,0 +1,565 @@
+#include "adi/mykonos.h"
+
+#include "ad937x_ctrl.hpp"
+#include "ad937x_device.hpp"
+#include <sstream>
+#include <functional>
+#include <iostream>
+#include <cmath>
+
+uhd::meta_range_t ad937x_ctrl::get_rf_freq_range(void)
+{
+ return uhd::meta_range_t(300e6, 6e9);
+}
+
+uhd::meta_range_t ad937x_ctrl::get_bw_filter_range(void)
+{
+ // TODO: fix
+ return uhd::meta_range_t(0, 1);
+}
+
+std::vector<double> ad937x_ctrl::get_clock_rates(void)
+{
+ // TODO: fix
+ return { 125e6 };
+}
+
+uhd::meta_range_t ad937x_ctrl::get_gain_range(const std::string &which)
+{
+ auto dir = _get_direction_from_antenna(which);
+ switch (dir)
+ {
+ case uhd::direction_t::RX_DIRECTION:
+ return uhd::meta_range_t(0, 30, 0.5);
+ case uhd::direction_t::TX_DIRECTION:
+ return uhd::meta_range_t(0, 41.95, 0.05);
+ default:
+ throw uhd::runtime_error("ad937x_ctrl got an invalid channel string.");
+ return uhd::meta_range_t();
+ }
+}
+
+std::vector<size_t> ad937x_ctrl::_get_valid_fir_lengths(const std::string& which)
+{
+ auto dir = _get_direction_from_antenna(which);
+ switch (dir)
+ {
+ case uhd::direction_t::RX_DIRECTION:
+ return { 24, 48, 72 };
+ case uhd::direction_t::TX_DIRECTION:
+ return { 16, 32, 48, 64, 80, 96 };
+ default:
+ throw uhd::runtime_error("ad937x_ctrl got an invalid channel string.");
+ return std::vector<size_t>();
+ }
+}
+
+uhd::direction_t ad937x_ctrl::_get_direction_from_antenna(const std::string& antenna)
+{
+ auto sub = antenna.substr(0, 2);
+ if (sub == "RX") {
+ return uhd::direction_t::RX_DIRECTION;
+ }
+ else if (sub == "TX") {
+ return uhd::direction_t::TX_DIRECTION;
+ }
+ else {
+ throw uhd::runtime_error("ad937x_ctrl got an invalid channel string.");
+ }
+ return uhd::direction_t::RX_DIRECTION;
+}
+
+ad937x_device::chain_t ad937x_ctrl::_get_chain_from_antenna(const std::string& antenna)
+{
+ auto sub = antenna.substr(2, 1);
+ if (sub == "1") {
+ return ad937x_device::chain_t::ONE;
+ }
+ else if (sub == "2") {
+ return ad937x_device::chain_t::TWO;
+ }
+ else {
+ throw uhd::runtime_error("ad937x_ctrl got an invalid channel string.");
+ }
+ return ad937x_device::chain_t::ONE;
+}
+
+class ad937x_ctrl_impl : public ad937x_ctrl
+{
+public:
+ // change to uhd::spi_iface
+ static sptr make(spi_lock::sptr spi_l, mpm::spi_iface::sptr iface);
+
+ ad937x_ctrl_impl(spi_lock::sptr spi_l, mpm::spi_iface::sptr iface) :
+ spi_l(spi_l),
+ iface(iface)
+ {
+
+ }
+
+ virtual uint8_t get_product_id()
+ {
+ std::lock_guard<spi_lock>(*spi_l);
+ return device.get_product_id();
+ }
+
+ virtual uint8_t get_device_rev()
+ {
+ std::lock_guard<spi_lock>(*spi_l);
+ return device.get_device_rev();
+ }
+ virtual std::string get_api_version()
+ {
+ std::lock_guard<spi_lock>(*spi_l);
+ auto api = device.get_api_version();
+ std::ostringstream ss;
+ ss << api.silicon_ver << "."
+ << api.major_ver << "."
+ << api.minor_ver << "."
+ << api.build_ver;
+ return ss.str();
+ }
+
+ virtual std::string get_arm_version()
+ {
+ std::lock_guard<spi_lock>(*spi_l);
+ auto arm = device.get_arm_version();
+ std::ostringstream ss;
+ ss << arm.major_ver << "."
+ << arm.minor_ver << "."
+ << arm.rc_ver;
+ return ss.str();
+ }
+
+ virtual double set_bw_filter(const std::string &which, const double value)
+ {
+ // TODO implement
+ return double();
+ }
+
+ virtual double set_gain(const std::string &which, const double value)
+ {
+ auto dir = _get_direction_from_antenna(which);
+ auto chain = _get_chain_from_antenna(which);
+ return device.set_gain(dir, chain, value);
+ }
+
+ virtual void set_agc(const std::string &which, const bool enable)
+ {
+ auto dir = _get_direction_from_antenna(which);
+ if (dir != uhd::direction_t::RX_DIRECTION)
+ {
+ throw uhd::runtime_error("ad937x_ctrl::set_agc was called on a non-RX channel");
+ }
+ return device.set_agc(dir, enable);
+ }
+
+ virtual void set_agc_mode(const std::string &which, const std::string &mode)
+ {
+ auto dir = _get_direction_from_antenna(which);
+ if (dir != uhd::direction_t::RX_DIRECTION)
+ {
+ throw uhd::runtime_error("ad937x_ctrl::set_agc was called on a non-RX channel");
+ }
+
+ ad937x_device::gain_mode_t gain_mode;
+ if (mode == "automatic")
+ {
+ gain_mode = ad937x_device::gain_mode_t::AUTOMATIC;
+ }
+ else if (mode == "manual") {
+ gain_mode = ad937x_device::gain_mode_t::MANUAL;
+ }
+ else if (mode == "hybrid") {
+ gain_mode = ad937x_device::gain_mode_t::HYBRID;
+ }
+ else {
+ throw uhd::runtime_error("ad937x_ctrl::set_agc_mode was called on a non-RX channel");
+ }
+
+ device.set_agc_mode(dir, gain_mode);
+ }
+
+ virtual double set_clock_rate(const double value)
+ {
+ auto rates = get_clock_rates();
+ auto coerced_value = value;
+ if (std::find(rates.begin(), rates.end(), value) == rates.end())
+ {
+ coerced_value = rates[0];
+ }
+
+ return device.set_clock_rate(coerced_value);
+ }
+
+ virtual void enable_channel(const std::string &which, const bool enable)
+ {
+ auto dir = _get_direction_from_antenna(which);
+ auto chain = _get_chain_from_antenna(which);
+ return device.enable_channel(dir, chain, enable);
+ }
+
+ virtual double set_freq(const std::string &which, const double value)
+ {
+ auto dir = _get_direction_from_antenna(which);
+ auto clipped_value = get_rf_freq_range().clip(value);
+ return device.tune(dir, clipped_value);
+ }
+
+ virtual double get_freq(const std::string &which)
+ {
+ auto dir = _get_direction_from_antenna(which);
+ return device.get_freq(dir);
+ }
+
+ virtual void set_fir(const std::string &which, int8_t gain, const std::vector<int16_t> & fir)
+ {
+ auto lengths = _get_valid_fir_lengths(which);
+ if (std::find(lengths.begin(), lengths.end(), fir.size()) == lengths.end())
+ {
+ throw uhd::value_error("ad937x_ctrl::set_fir got filter of invalid length");
+ }
+
+ ad937x_fir(gain, fir);
+
+ }
+ virtual std::vector<int16_t> get_fir(const std::string &which) = 0;
+
+ virtual int16_t get_temperature() = 0;
+
+private:
+ ad937x_device device;
+ spi_lock::sptr spi_l;
+ mpm::spi_iface::sptr iface;
+};
+
+const double ad937x_ctrl_impl::MIN_FREQ = 300e6;
+const double ad937x_ctrl_impl::MAX_FREQ = 6e9;
+
+/*
+ad937x_ctrl::sptr ad937x_ctrl_impl::make(
+ spi_lock::sptr spi_l,
+ mpm::spi_iface::sptr iface)
+{
+ return std::make_shared<ad937x_ctrl_impl>(spi_l, iface);
+}
+
+
+void ad937x_ctrl::initialize()
+{
+ //headlessinit(mykonos_config.device);
+ // TODO: finish initialization
+ {
+ std::lock_guard<spi_lock> lock(*spi_l);
+ call_api_function(std::bind(MYKONOS_initialize, mykonos_config.device));
+ }
+ get_product_id();
+}
+
+ad937x_ctrl::ad937x_ctrl(
+ spi_lock::sptr spi_l,
+ mpm::spi_iface::sptr iface) :
+ spi_l(spi_l),
+ iface(iface)
+{
+ mpm_sps.spi_iface = iface.get();
+
+ //TODO assert iface->get_chip_select() is 1-8
+ mpm_sps.spi_settings.chipSelectIndex = static_cast<uint8_t>(iface->get_chip_select());
+ mpm_sps.spi_settings.writeBitPolarity = 1;
+ mpm_sps.spi_settings.longInstructionWord = 1; // set to 1 by initialize
+ mpm_sps.spi_settings.MSBFirst =
+ (iface->get_endianness() == mpm::spi_iface::spi_endianness_t::LSB_FIRST) ? 0 : 1;
+ mpm_sps.spi_settings.CPHA = 0; // set to 0 by initialize
+ mpm_sps.spi_settings.CPOL = 0; // set to 0 by initialize
+ mpm_sps.spi_settings.enSpiStreaming = 1;
+ mpm_sps.spi_settings.autoIncAddrUp = 1;
+ mpm_sps.spi_settings.fourWireMode =
+ (iface->get_wire_mode() == mpm::spi_iface::spi_wire_mode_t::THREE_WIRE_MODE) ? 0 : 1;
+ mpm_sps.spi_settings.spiClkFreq_Hz = 25000000;
+
+ initialize();
+}
+
+// helper function to unify error handling
+// bind is bad, but maybe this is justifiable
+void ad937x_ctrl::call_api_function(std::function<mykonosErr_t()> func)
+{
+ auto error = func();
+ if (error != MYKONOS_ERR_OK)
+ {
+ std::cout << getMykonosErrorMessage(error);
+ // TODO: make UHD exception
+ //throw std::exception(getMykonosErrorMessage(error));
+ }
+}
+
+uint8_t ad937x_ctrl::get_product_id()
+{
+ std::lock_guard<spi_lock> lock(*spi_l);
+ uint8_t id;
+ call_api_function(std::bind(MYKONOS_getProductId, mykonos_config.device, &id));
+ return id;
+}
+
+double ad937x_ctrl::set_clock_rate(const double req_rate)
+{
+ auto rate = static_cast<decltype(mykonos_config.device->clocks->deviceClock_kHz)>(req_rate / 1000);
+ mykonos_config.device->clocks->deviceClock_kHz = rate;
+ {
+ std::lock_guard<spi_lock> lock(*spi_l);
+ call_api_function(std::bind(MYKONOS_initDigitalClocks, mykonos_config.device));
+ }
+ return static_cast<decltype(set_clock_rate(0))>(rate);
+}
+
+void ad937x_ctrl::_set_active_tx_chains(bool tx1, bool tx2)
+{
+ decltype(mykonos_config.device->tx->txChannels) newTxChannel;
+ if (tx1 && tx2)
+ {
+ newTxChannel = TX1_TX2;
+ }
+ else if (tx1) {
+ newTxChannel = TX1;
+ }
+ else if (tx2) {
+ newTxChannel = TX2;
+ }
+ else {
+ newTxChannel = TXOFF;
+ }
+ mykonos_config.device->tx->txChannels = newTxChannel;
+}
+
+void ad937x_ctrl::_set_active_rx_chains(bool rx1, bool rx2)
+{
+ decltype(mykonos_config.device->rx->rxChannels) newRxChannel;
+ if (rx1 && rx2)
+ {
+ newRxChannel = RX1_RX2;
+ }
+ else if (rx1) {
+ newRxChannel = RX1;
+ }
+ else if (rx2) {
+ newRxChannel = RX2;
+ }
+ else {
+ newRxChannel = RXOFF;
+ }
+ mykonos_config.device->rx->rxChannels = newRxChannel;
+}
+
+void ad937x_ctrl::set_active_chains(direction_t direction, bool channel1, bool channel2)
+{
+ switch (direction)
+ {
+ case TX: _set_active_tx_chains(channel1, channel2); break;
+ case RX: _set_active_rx_chains(channel1, channel2); break;
+ default:
+ // TODO: bad code path exception
+ throw std::exception();
+ }
+ // TODO: make this apply the setting
+}
+
+double ad937x_ctrl::tune(direction_t direction, const double value)
+{
+ // I'm not really sure why we set the PLL value in the config AND as a function parameter
+ // but here it is
+
+ mykonosRfPllName_t pll;
+ uint64_t integer_value = static_cast<uint64_t>(value);
+ switch (direction)
+ {
+ case TX:
+ pll = TX_PLL;
+ mykonos_config.device->tx->txPllLoFrequency_Hz = integer_value;
+ break;
+ case RX:
+ pll = RX_PLL;
+ mykonos_config.device->rx->rxPllLoFrequency_Hz = integer_value;
+ break;
+ default:
+ // TODO: bad code path exception
+ throw std::exception();
+ }
+
+ {
+ std::lock_guard<spi_lock> lock(*spi_l);
+ call_api_function(std::bind(MYKONOS_setRfPllFrequency, mykonos_config.device, pll, integer_value));
+ }
+
+ // TODO: coercion here causes extra device accesses, when the formula is provided on pg 119 of the user guide
+ // Furthermore, because coerced is returned as an integer, it's not even accurate
+ uint64_t coerced_pll;
+ {
+ std::lock_guard<spi_lock> lock(*spi_l);
+ call_api_function(std::bind(MYKONOS_getRfPllFrequency, mykonos_config.device, pll, &coerced_pll));
+ }
+ return static_cast<double>(coerced_pll);
+}
+
+double ad937x_ctrl::get_freq(direction_t direction)
+{
+ mykonosRfPllName_t pll;
+ switch (direction)
+ {
+ case TX: pll = TX_PLL; break;
+ case RX: pll = RX_PLL; break;
+ default:
+ // TODO: bad code path exception
+ throw std::exception();
+ }
+
+ // TODO: coercion here causes extra device accesses, when the formula is provided on pg 119 of the user guide
+ // Furthermore, because coerced is returned as an integer, it's not even accurate
+ uint64_t coerced_pll;
+ {
+ std::lock_guard<spi_lock> lock(*spi_l);
+ call_api_function(std::bind(MYKONOS_getRfPllFrequency, mykonos_config.device, pll, &coerced_pll));
+ }
+ return static_cast<double>(coerced_pll);
+ return double();
+}
+
+// RX Gain values are table entries given in mykonos_user.h
+// An array of gain values is programmed at initialization, which the API will then use for its gain values
+// In general, Gain Value = (255 - Gain Table Index)
+uint8_t ad937x_ctrl::_convert_rx_gain(double inGain, double &coercedGain)
+{
+ // TODO: use uhd::meta_range?
+ const static double min_gain = 0;
+ const static double max_gain = 30;
+ const static double gain_step = 0.5;
+
+ coercedGain = inGain;
+ if (coercedGain < min_gain)
+ {
+ coercedGain = min_gain;
+ }
+ if (coercedGain > max_gain)
+ {
+ coercedGain = max_gain;
+ }
+
+ // round to nearest step
+ coercedGain = std::round(coercedGain * (1.0 / gain_step)) / (1.0 / gain_step);
+
+ // gain should be a value 0-60, add 195 to make 195-255
+ return static_cast<uint8_t>((coercedGain * 2) + 195);
+}
+
+// TX gain is completely different from RX gain for no good reason so deal with it
+// TX is set as attenuation using a value from 0-41950 mdB
+// Only increments of 50 mdB are valid
+uint16_t ad937x_ctrl::_convert_tx_gain(double inGain, double &coercedGain)
+{
+ // TODO: use uhd::meta_range?
+ const static double min_gain = 0;
+ const static double max_gain = 41.95;
+ const static double gain_step = 0.05;
+
+ coercedGain = inGain;
+ if (coercedGain < min_gain)
+ {
+ coercedGain = min_gain;
+ }
+ if (coercedGain > max_gain)
+ {
+ coercedGain = max_gain;
+ }
+
+ coercedGain = std::round(coercedGain * (1.0 / gain_step)) / (1.0 / gain_step);
+
+ // attenuation is inverted and in mdB not dB
+ return static_cast<uint16_t>((max_gain - (coercedGain)) * 1000);
+}
+
+
+double ad937x_ctrl::set_gain(direction_t direction, chain_t chain, const double value)
+{
+ double coerced_value;
+ switch (direction)
+ {
+ case TX:
+ {
+ uint16_t attenuation = _convert_tx_gain(value, coerced_value);
+ std::function<mykonosErr_t(mykonosDevice_t*, uint16_t)> func;
+ switch (chain)
+ {
+ case CHAIN_1:
+ func = MYKONOS_setTx1Attenuation;
+ break;
+ case CHAIN_2:
+ func = MYKONOS_setTx2Attenuation;
+ break;
+ default:
+ // TODO: bad code path exception
+ throw std::exception();
+ }
+ std::lock_guard<spi_lock> lock(*spi_l);
+ call_api_function(std::bind(func, mykonos_config.device, attenuation));
+ break;
+ }
+ case RX:
+ {
+ uint8_t gain = _convert_rx_gain(value, coerced_value);
+ std::function<mykonosErr_t(mykonosDevice_t*, uint8_t)> func;
+ switch (chain)
+ {
+ case CHAIN_1:
+ func = MYKONOS_setRx1ManualGain;
+ break;
+ case CHAIN_2:
+ func = MYKONOS_setRx2ManualGain;
+ break;
+ default:
+ // TODO: bad code path exception
+ throw std::exception();
+ }
+ std::lock_guard<spi_lock> lock(*spi_l);
+ call_api_function(std::bind(func, mykonos_config.device, gain));
+ break;
+ }
+ default:
+ // TODO: bad code path exception
+ throw std::exception();
+ }
+ return coerced_value;
+}
+
+double ad937x_ctrl::set_agc_mode(direction_t direction, chain_t chain, gain_mode_t mode)
+{
+ std::lock_guard<spi_lock> lock(*spi_l);
+ switch (direction)
+ {
+ case RX:
+ switch (mode)
+ {
+ case GAIN_MODE_MANUAL:
+ call_api_function(std::bind(MYKONOS_resetRxAgc, mykonos_config.device));
+ break;
+ case GAIN_MODE_SLOW_AGC:
+ case GAIN_MODE_FAST_AGC:
+ // TODO: differentiate these
+ call_api_function(std::bind(MYKONOS_setupRxAgc, mykonos_config.device));
+ break;
+ default:
+ // TODO: bad code path exception
+ throw std::exception();
+ }
+ default:
+ // TODO: bad code path exception
+ throw std::exception();
+ }
+ return double();
+}
+
+ad937x_ctrl::sptr ad937x_ctrl::make(spi_lock::sptr spi_l, mpm::spi_iface::sptr iface)
+{
+ return std::make_shared<ad937x_ctrl>(spi_l, iface);
+}
+*/
+