From 84ec1dc96884711e92b0ab3af15edf75bfec314a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Silva?= Date: Sat, 6 Apr 2024 20:53:17 +0100 Subject: [PATCH] soapy: Fix AD9361 error message soapy: Tune AD9361 to ISM band on startup soapy: Set AD9361 TX attenuation higher on startup soapy: Set AD9361 gain mode to manual on startup soapy: Round I2S clock dividers soapy: Round SPI clock dividers soapy: Add TX flush logic to RF Timestamping module soapy: Add methods to wait for TX/RX/Counter enable/disable soapy: Work on CLI tool soapy: Add methods to round SI5351 frequency to nearest valid value soapy: Gate streaming trace messages at compile time with macros, to reduce overhead soapy: Implement KWArgs parsing on device instantiation soapy: Implement TX streaming soapy: Misc changes MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: João Silva --- software/soapy/src/AD9361.cpp | 17 +- software/soapy/src/AXII2S.cpp | 18 + software/soapy/src/AXIRFTStamp.cpp | 223 ++- software/soapy/src/AXISPI.cpp | 3 + software/soapy/src/IcyRadioTool.cpp | 618 ++++++++- software/soapy/src/Si5351.cpp | 329 ++++- software/soapy/src/SoapyIcyRadio.cpp | 1303 +++++++++++------- software/soapy/src/SoapyRegistration.cpp | 10 +- software/soapy/src/SoapySettings.cpp | 445 +++++- software/soapy/src/SoapyStreaming.cpp | 780 +++++++++-- software/soapy/src/include/AXIAD9361.hpp | 1 + software/soapy/src/include/AXIRFTStamp.hpp | 22 + software/soapy/src/include/Log.hpp | 14 + software/soapy/src/include/Si5351.hpp | 16 +- software/soapy/src/include/SoapyIcyRadio.hpp | 71 +- 15 files changed, 3146 insertions(+), 724 deletions(-) create mode 100644 software/soapy/src/include/Log.hpp diff --git a/software/soapy/src/AD9361.cpp b/software/soapy/src/AD9361.cpp index a93e652f..0d0b1089 100644 --- a/software/soapy/src/AD9361.cpp +++ b/software/soapy/src/AD9361.cpp @@ -679,11 +679,6 @@ void AD9361::writeReg(uint16_t reg, uint8_t *src, uint8_t count) if(count > 8) throw std::invalid_argument("AD9361: Max SPI size exceeded"); - // SoapySDR_logf(SOAPY_SDR_DEBUG, "AD9361: Writing %d bytes to register 0x%03X", count, reg); - - // for(uint8_t i = 0; i < count; i++) - // SoapySDR_logf(SOAPY_SDR_DEBUG, "AD9361: 0x%02X", src[i] & 0xFF); - uint16_t cmd = BIT(15); // Write command cmd |= (count - 1) << 12; // Number of bytes to read cmd |= reg; // Register address @@ -738,7 +733,7 @@ AD9361::AD9361(AD9361::SPIConfig spi, AD9361::GPIOConfig reset_gpio, AD9361::GPI uint8_t pid = this->readReg(AD9361_REG_PRODUCT_ID); if((pid & PRODUCT_ID_MASK) != PRODUCT_ID_9361) - throw std::runtime_error("AD9361: Product ID mismatch (" + std::to_string(pid) + " != " + std::to_string(PRODUCT_ID_9361) + ")"); + throw std::runtime_error("AD9361: Product ID mismatch (" + std::to_string(pid & PRODUCT_ID_MASK) + " != " + std::to_string(PRODUCT_ID_9361) + ")"); } AD9361::~AD9361() { @@ -795,8 +790,8 @@ void AD9361::init() this->pdata->ensm_pin_ctrl = false; /* LO Control */ - this->pdata->rx_synth_freq = 98000000UL; // TODO: Auto - this->pdata->tx_synth_freq = 100000000UL; // TODO: Auto + this->pdata->rx_synth_freq = 433000000ULL; // TODO: Auto + this->pdata->tx_synth_freq = 433000000ULL; // TODO: Auto this->pdata->lo_powerdown_managed_en = true; /* Rate & BW Control */ @@ -822,7 +817,7 @@ void AD9361::init() this->pdata->rf_tx_output_sel = 0; /* TX Attenuation Control */ - this->pdata->tx_atten = 10000; + this->pdata->tx_atten = 80000; this->pdata->update_tx_gain_via_alert = false; /* Reference Clock Control */ @@ -840,8 +835,8 @@ void AD9361::init() this->pdata->clkout_mode = AD9361::ClkOutMode::DISABLE; /* Gain Control */ - this->pdata->gain_ctrl.rx1_mode = AD9361::RFGainCtrlMode::RF_GAIN_SLOWATTACK_AGC; - this->pdata->gain_ctrl.rx2_mode = AD9361::RFGainCtrlMode::RF_GAIN_SLOWATTACK_AGC; + this->pdata->gain_ctrl.rx1_mode = AD9361::RFGainCtrlMode::RF_GAIN_MGC; + this->pdata->gain_ctrl.rx2_mode = AD9361::RFGainCtrlMode::RF_GAIN_MGC; this->pdata->gain_ctrl.adc_large_overload_thresh = 58; this->pdata->gain_ctrl.adc_ovr_sample_size = 4; this->pdata->gain_ctrl.adc_small_overload_thresh = 47; diff --git a/software/soapy/src/AXII2S.cpp b/software/soapy/src/AXII2S.cpp index ad8d95e3..e1c20eb0 100644 --- a/software/soapy/src/AXII2S.cpp +++ b/software/soapy/src/AXII2S.cpp @@ -49,6 +49,9 @@ void AXII2S::setMCLKClockFrequency(uint64_t input_freq, uint64_t mclk_freq) { uint64_t mclk_div = input_freq / mclk_freq; + if(mclk_div & 1) + mclk_div++; // Round up to nearest even number + this->setMCLKClockDivider(mclk_div); } uint64_t AXII2S::getMCLKClockFrequency(uint64_t input_freq) @@ -80,6 +83,9 @@ void AXII2S::setBCLKClockFrequency(uint64_t input_freq, uint64_t bclk_freq) { uint64_t bclk_div = input_freq / bclk_freq; + if(bclk_div & 1) + bclk_div++; // Round up to nearest even number + this->setBCLKClockDivider(bclk_div); } uint64_t AXII2S::getBCLKClockFrequency(uint64_t input_freq) @@ -111,6 +117,9 @@ void AXII2S::setLRCLKClockFrequency(uint64_t input_freq, uint64_t lrclk_freq) { uint64_t lrclk_div = input_freq / lrclk_freq; + if(lrclk_div & 1) + lrclk_div++; // Round up to nearest even number + this->setLRCLKClockDivider(lrclk_div); } uint64_t AXII2S::getLRCLKClockFrequency(uint64_t input_freq) @@ -152,6 +161,15 @@ void AXII2S::setClockFrequencies(uint64_t input_freq, uint64_t mclk_freq, uint64 uint64_t bclk_div = input_freq / bclk_freq; uint64_t lrclk_div = input_freq / lrclk_freq; + if(mclk_div & 1) + mclk_div++; // Round up to nearest even number + + if(bclk_div & 1) + bclk_div++; // Round up to nearest even number + + if(lrclk_div & 1) + lrclk_div++; // Round up to nearest even number + this->setClockDividers(mclk_div, bclk_div, lrclk_div); } uint64_t AXII2S::setClockFrequencies(uint64_t input_freq, uint64_t mclk_freq, uint64_t samp_rate) diff --git a/software/soapy/src/AXIRFTStamp.cpp b/software/soapy/src/AXIRFTStamp.cpp index f415a60b..39d528fe 100644 --- a/software/soapy/src/AXIRFTStamp.cpp +++ b/software/soapy/src/AXIRFTStamp.cpp @@ -119,6 +119,61 @@ void AXIRFTStamp::triggerClockResync(bool wait, uint32_t timeout_ms) } } +void AXIRFTStamp::flushTX(uint32_t timeout_ms) +{ + std::unique_lock lock(this->mutex); + + if(this->isTXEnabled()) + throw std::runtime_error("AXI RF Timestamping: Cannot flush TX while at least one TX is enabled"); + + if(this->isTXCounterEnabled()) + throw std::runtime_error("AXI RF Timestamping: Cannot flush TX while at least one TX counter is enabled"); + + this->writeReg(AXI_RF_TSTAMP_REG_CH_CTL_STAT_ALL, AXI_RF_TSTAMP_REG_CH_CTL_STAT_TX_FLUSH_EN); + + uint64_t timeout = (uint64_t)timeout_ms * 100ULL; + + lock.unlock(); + while(--timeout && (this->isTXDMAReady(AXIRFTStamp::Channel::CH0) || this->isTXDMAReady(AXIRFTStamp::Channel::CH1))) + usleep(10); + lock.lock(); + + this->writeReg(AXI_RF_TSTAMP_REG_CH_CTL_STAT_ALL, AXI_RF_TSTAMP_REG_CH_CTL_STAT_TX_FLUSH_DIS); + + if(this->isTXDMAReady(AXIRFTStamp::Channel::CH0)) + throw std::runtime_error("AXI RF Timestamping: Timed out waiting for TX0 flush"); + + if(this->isTXDMAReady(AXIRFTStamp::Channel::CH1)) + throw std::runtime_error("AXI RF Timestamping: Timed out waiting for TX1 flush"); +} +void AXIRFTStamp::flushTX(AXIRFTStamp::Channel ch, uint32_t timeout_ms) +{ + if(ch >= AXIRFTStamp::Channel::CH_MAX) + throw std::invalid_argument("AXI RF Timestamping: Invalid channel number: " + std::to_string(ch) + " (Max = " + std::to_string(AXIRFTStamp::Channel::CH_MAX) + ")"); + + std::unique_lock lock(this->mutex); + + if(this->isTXEnabled(ch)) + throw std::runtime_error("AXI RF Timestamping: Cannot flush TX while TX is enabled"); + + if(this->isTXCounterEnabled(ch)) + throw std::runtime_error("AXI RF Timestamping: Cannot flush TX while TX counter is enabled"); + + this->writeReg(AXI_RF_TSTAMP_REG_CH_CTL_STAT(ch), AXI_RF_TSTAMP_REG_CH_CTL_STAT_TX_FLUSH_EN); + + uint64_t timeout = (uint64_t)timeout_ms * 100ULL; + + lock.unlock(); + while(--timeout && this->isTXDMAReady(ch)) + usleep(10); + lock.lock(); + + this->writeReg(AXI_RF_TSTAMP_REG_CH_CTL_STAT(ch), AXI_RF_TSTAMP_REG_CH_CTL_STAT_TX_FLUSH_DIS); + + if(this->isTXDMAReady(ch)) + throw std::runtime_error("AXI RF Timestamping: Timed out waiting for TX flush"); +} + void AXIRFTStamp::enableTX(bool enable) { std::lock_guard lock(this->mutex); @@ -151,6 +206,46 @@ bool AXIRFTStamp::isTXEnabled(AXIRFTStamp::Channel ch) return !!(this->readReg(AXI_RF_TSTAMP_REG_CH_CTL_STAT(ch)) & AXI_RF_TSTAMP_REG_CH_CTL_STAT_TX_STAT); } +void AXIRFTStamp::waitTXEnabled(uint32_t timeout_ms) +{ + uint64_t timeout = (uint64_t)timeout_ms * 100ULL; + + while(--timeout && !this->isTXEnabled()) + usleep(10); + + if(!this->isTXEnabled()) + throw std::runtime_error("AXI RF Timestamping: Timed out waiting for TX enable"); +} +void AXIRFTStamp::waitTXEnabled(AXIRFTStamp::Channel ch, uint32_t timeout_ms) +{ + uint64_t timeout = (uint64_t)timeout_ms * 100ULL; + + while(--timeout && !this->isTXEnabled(ch)) + usleep(10); + + if(!this->isTXEnabled(ch)) + throw std::runtime_error("AXI RF Timestamping: Timed out waiting for TX" + std::to_string(ch) + " enable"); +} +void AXIRFTStamp::waitTXDisabled(uint32_t timeout_ms) +{ + uint64_t timeout = (uint64_t)timeout_ms * 100ULL; + + while(--timeout && this->isTXEnabled()) + usleep(10); + + if(this->isTXEnabled()) + throw std::runtime_error("AXI RF Timestamping: Timed out waiting for TX disable"); +} +void AXIRFTStamp::waitTXDisabled(AXIRFTStamp::Channel ch, uint32_t timeout_ms) +{ + uint64_t timeout = (uint64_t)timeout_ms * 100ULL; + + while(--timeout && this->isTXEnabled(ch)) + usleep(10); + + if(this->isTXEnabled(ch)) + throw std::runtime_error("AXI RF Timestamping: Timed out waiting for TX" + std::to_string(ch) + " disable"); +} void AXIRFTStamp::enableRX(bool enable) { std::lock_guard lock(this->mutex); @@ -183,6 +278,46 @@ bool AXIRFTStamp::isRXEnabled(AXIRFTStamp::Channel ch) return !!(this->readReg(AXI_RF_TSTAMP_REG_CH_CTL_STAT(ch)) & AXI_RF_TSTAMP_REG_CH_CTL_STAT_RX_STAT); } +void AXIRFTStamp::waitRXEnabled(uint32_t timeout_ms) +{ + uint64_t timeout = (uint64_t)timeout_ms * 100ULL; + + while(--timeout && !this->isRXEnabled()) + usleep(10); + + if(!this->isRXEnabled()) + throw std::runtime_error("AXI RF Timestamping: Timed out waiting for RX enable"); +} +void AXIRFTStamp::waitRXEnabled(AXIRFTStamp::Channel ch, uint32_t timeout_ms) +{ + uint64_t timeout = (uint64_t)timeout_ms * 100ULL; + + while(--timeout && !this->isRXEnabled(ch)) + usleep(10); + + if(!this->isRXEnabled(ch)) + throw std::runtime_error("AXI RF Timestamping: Timed out waiting for RX" + std::to_string(ch) + " enable"); +} +void AXIRFTStamp::waitRXDisabled(uint32_t timeout_ms) +{ + uint64_t timeout = (uint64_t)timeout_ms * 100ULL; + + while(--timeout && this->isRXEnabled()) + usleep(10); + + if(this->isRXEnabled()) + throw std::runtime_error("AXI RF Timestamping: Timed out waiting for RX disable"); +} +void AXIRFTStamp::waitRXDisabled(AXIRFTStamp::Channel ch, uint32_t timeout_ms) +{ + uint64_t timeout = (uint64_t)timeout_ms * 100ULL; + + while(--timeout && this->isRXEnabled(ch)) + usleep(10); + + if(this->isRXEnabled(ch)) + throw std::runtime_error("AXI RF Timestamping: Timed out waiting for RX" + std::to_string(ch) + " disable"); +} void AXIRFTStamp::enableCounter(bool enable) { @@ -215,6 +350,46 @@ bool AXIRFTStamp::isTXCounterEnabled(AXIRFTStamp::Channel ch) return !!(this->readReg(AXI_RF_TSTAMP_REG_CH_CTL_STAT(ch)) & AXI_RF_TSTAMP_REG_CH_CTL_STAT_CNT_TX_STAT); } +void AXIRFTStamp::waitTXCounterEnabled(uint32_t timeout_ms) +{ + uint64_t timeout = (uint64_t)timeout_ms * 100ULL; + + while(--timeout && !this->isTXCounterEnabled()) + usleep(10); + + if(!this->isTXCounterEnabled()) + throw std::runtime_error("AXI RF Timestamping: Timed out waiting for TX counter enable"); +} +void AXIRFTStamp::waitTXCounterEnabled(AXIRFTStamp::Channel ch, uint32_t timeout_ms) +{ + uint64_t timeout = (uint64_t)timeout_ms * 100ULL; + + while(--timeout && !this->isTXCounterEnabled(ch)) + usleep(10); + + if(!this->isTXCounterEnabled(ch)) + throw std::runtime_error("AXI RF Timestamping: Timed out waiting for TX" + std::to_string(ch) + " counter enable"); +} +void AXIRFTStamp::waitTXCounterDisabled(uint32_t timeout_ms) +{ + uint64_t timeout = (uint64_t)timeout_ms * 100ULL; + + while(--timeout && this->isTXCounterEnabled()) + usleep(10); + + if(this->isTXCounterEnabled()) + throw std::runtime_error("AXI RF Timestamping: Timed out waiting for TX counter disable"); +} +void AXIRFTStamp::waitTXCounterDisabled(AXIRFTStamp::Channel ch, uint32_t timeout_ms) +{ + uint64_t timeout = (uint64_t)timeout_ms * 100ULL; + + while(--timeout && this->isTXCounterEnabled(ch)) + usleep(10); + + if(this->isTXCounterEnabled(ch)) + throw std::runtime_error("AXI RF Timestamping: Timed out waiting for TX" + std::to_string(ch) + " counter disable"); +} void AXIRFTStamp::enableRXCounter(bool enable) { this->writeReg(AXI_RF_TSTAMP_REG_CH_CTL_STAT_ALL, enable ? AXI_RF_TSTAMP_REG_CH_CTL_STAT_CNT_RX_EN : AXI_RF_TSTAMP_REG_CH_CTL_STAT_CNT_RX_DIS); @@ -237,6 +412,46 @@ bool AXIRFTStamp::isRXCounterEnabled(AXIRFTStamp::Channel ch) return !!(this->readReg(AXI_RF_TSTAMP_REG_CH_CTL_STAT(ch)) & AXI_RF_TSTAMP_REG_CH_CTL_STAT_CNT_RX_STAT); } +void AXIRFTStamp::waitRXCounterEnabled(uint32_t timeout_ms) +{ + uint64_t timeout = (uint64_t)timeout_ms * 100ULL; + + while(--timeout && !this->isRXCounterEnabled()) + usleep(10); + + if(!this->isRXCounterEnabled()) + throw std::runtime_error("AXI RF Timestamping: Timed out waiting for RX counter enable"); +} +void AXIRFTStamp::waitRXCounterEnabled(AXIRFTStamp::Channel ch, uint32_t timeout_ms) +{ + uint64_t timeout = (uint64_t)timeout_ms * 100ULL; + + while(--timeout && !this->isRXCounterEnabled(ch)) + usleep(10); + + if(!this->isRXCounterEnabled(ch)) + throw std::runtime_error("AXI RF Timestamping: Timed out waiting for RX" + std::to_string(ch) + " counter enable"); +} +void AXIRFTStamp::waitRXCounterDisabled(uint32_t timeout_ms) +{ + uint64_t timeout = (uint64_t)timeout_ms * 100ULL; + + while(--timeout && this->isRXCounterEnabled()) + usleep(10); + + if(this->isRXCounterEnabled()) + throw std::runtime_error("AXI RF Timestamping: Timed out waiting for RX counter disable"); +} +void AXIRFTStamp::waitRXCounterDisabled(AXIRFTStamp::Channel ch, uint32_t timeout_ms) +{ + uint64_t timeout = (uint64_t)timeout_ms * 100ULL; + + while(--timeout && this->isRXCounterEnabled(ch)) + usleep(10); + + if(this->isRXCounterEnabled(ch)) + throw std::runtime_error("AXI RF Timestamping: Timed out waiting for RX" + std::to_string(ch) + " counter disable"); +} void AXIRFTStamp::armCounterLatch(bool req_arm) { @@ -375,7 +590,7 @@ void AXIRFTStamp::waitTXDMAReady(AXIRFTStamp::Channel ch, uint32_t timeout_ms) usleep(10); if(!this->isTXDMAReady(ch)) - throw std::runtime_error("AXI RF Timestamping: Timed out waiting for TX DMA ready"); + throw std::runtime_error("AXI RF Timestamping: Timed out waiting for TX" + std::to_string(ch) + " DMA ready"); } void AXIRFTStamp::waitRXDMAReady(AXIRFTStamp::Channel ch, uint32_t timeout_ms) { @@ -385,7 +600,7 @@ void AXIRFTStamp::waitRXDMAReady(AXIRFTStamp::Channel ch, uint32_t timeout_ms) usleep(10); if(!this->isRXDMAReady(ch)) - throw std::runtime_error("AXI RF Timestamping: Timed out waiting for RX DMA ready"); + throw std::runtime_error("AXI RF Timestamping: Timed out waiting for RX" + std::to_string(ch) + " DMA ready"); } void AXIRFTStamp::waitTXDataReady(AXIRFTStamp::Channel ch, uint32_t timeout_ms) { @@ -395,7 +610,7 @@ void AXIRFTStamp::waitTXDataReady(AXIRFTStamp::Channel ch, uint32_t timeout_ms) usleep(10); if(!this->isTXDataReady(ch)) - throw std::runtime_error("AXI RF Timestamping: Timed out waiting for TX data ready"); + throw std::runtime_error("AXI RF Timestamping: Timed out waiting for TX" + std::to_string(ch) + " data ready"); } void AXIRFTStamp::waitRXDataReady(AXIRFTStamp::Channel ch, uint32_t timeout_ms) { @@ -405,7 +620,7 @@ void AXIRFTStamp::waitRXDataReady(AXIRFTStamp::Channel ch, uint32_t timeout_ms) usleep(10); if(!this->isRXDataReady(ch)) - throw std::runtime_error("AXI RF Timestamping: Timed out waiting for RX data ready"); + throw std::runtime_error("AXI RF Timestamping: Timed out waiting for RX" + std::to_string(ch) + " data ready"); } bool AXIRFTStamp::wasTXDMAReady(AXIRFTStamp::Channel ch) diff --git a/software/soapy/src/AXISPI.cpp b/software/soapy/src/AXISPI.cpp index 5ec05e46..72f181e6 100644 --- a/software/soapy/src/AXISPI.cpp +++ b/software/soapy/src/AXISPI.cpp @@ -330,6 +330,9 @@ void AXISPI::setClockFrequency(uint64_t input_freq, uint64_t sck_freq) { uint64_t sck_div = input_freq / sck_freq; + if(sck_div & 1) + sck_div++; // Round up to nearest even number + this->setClockDivider(sck_div); } uint64_t AXISPI::getClockFrequency(uint64_t input_freq) diff --git a/software/soapy/src/IcyRadioTool.cpp b/software/soapy/src/IcyRadioTool.cpp index 92f1ef1f..1925ba60 100644 --- a/software/soapy/src/IcyRadioTool.cpp +++ b/software/soapy/src/IcyRadioTool.cpp @@ -3,11 +3,21 @@ #include #include #include +#include #include +#include +#include SoapySDR::KwargsList findIcyRadio(const SoapySDR::Kwargs &args); SoapySDR::Device *makeIcyRadio(const SoapySDR::Kwargs &args); +static sig_atomic_t g_done = false; + +static void sigHandler(const int) +{ + g_done = true; +} + bool loadSystemSoapyIcyRadio() { try @@ -91,8 +101,530 @@ bool loadSoapyIcyRadio() return false; } +void testToneTX(SoapyIcyRadio *sdr, double fc = 480e6) +{ + sdr->setSampleRate(SOAPY_SDR_TX, 0, 16 * 1024 * 1024); + sdr->setBandwidth(SOAPY_SDR_TX, 0, 12e6); + sdr->setFrequency(SOAPY_SDR_TX, 0, fc); + sdr->setAntenna(SOAPY_SDR_TX, 0, "TX1A"); + sdr->setGain(SOAPY_SDR_TX, 0, "TX_ATT", 30); + sdr->setGain(SOAPY_SDR_TX, 1, "TX_ATT", 30); + + double full_scale = 0; + const std::string fmt = sdr->getNativeStreamFormat(SOAPY_SDR_TX, 0, full_scale); + const size_t samp_sz = SoapySDR::formatToSize(fmt); + + auto s = sdr->setupStream(SOAPY_SDR_TX, fmt, {0, 1}); + + const size_t mtu = sdr->getStreamMTU(s); + + std::vector> buf(2, std::vector(samp_sz * mtu)); // Native + + // Tone - fs / 8 on channel 0, fs / 16 on channel 1 + { + std::vector> f_buf(2, std::vector(2 * mtu)); + + for(size_t i = 0; i < mtu; i++) + { + f_buf[0][2 * i + 0] = std::cos(2 * M_PI * i / 8); + f_buf[0][2 * i + 1] = std::sin(2 * M_PI * i / 8); + + f_buf[1][2 * i + 0] = std::cos(2 * M_PI * i / 16); + f_buf[1][2 * i + 1] = std::sin(2 * M_PI * i / 16); + } + + SoapySDR::ConverterRegistry::getFunction(SOAPY_SDR_CF32, fmt)(f_buf[0].data(), buf[0].data(), mtu, full_scale); + SoapySDR::ConverterRegistry::getFunction(SOAPY_SDR_CF32, fmt)(f_buf[1].data(), buf[1].data(), mtu, full_scale); + + double f0 = sdr->getFrequency(SOAPY_SDR_TX, 0) + sdr->getSampleRate(SOAPY_SDR_TX, 0) / 8; + double f1 = sdr->getFrequency(SOAPY_SDR_TX, 0) + sdr->getSampleRate(SOAPY_SDR_TX, 0) / 16; + + std::cout << "Channel 0: Tone with f = " << (size_t)f0 << " Hz" << std::endl; + std::cout << "Channel 1: Tone with f = " << (size_t)f1 << " Hz" << std::endl; + } + + std::vector bufs(2); + + for(size_t i = 0; i < buf.size(); i++) + bufs[i] = buf[i].data(); + + std::cout << "Starting TX tone stream loop, press Ctrl+C to exit..." << std::endl; + + sdr->activateStream(s); + + long long timeNs = 0; + + while(!g_done) + { + int flags = 0; + + sdr->writeStream(s, bufs.data(), mtu, flags, timeNs); + } + + g_done = false; + + sdr->deactivateStream(s); + sdr->closeStream(s); +} +void testTimedToneTX(SoapyIcyRadio *sdr, double fc = 480e6) +{ + sdr->setSampleRate(SOAPY_SDR_TX, 0, 16 * 1024 * 1024); + sdr->setBandwidth(SOAPY_SDR_TX, 0, 12e6); + sdr->setFrequency(SOAPY_SDR_TX, 0, fc); + sdr->setAntenna(SOAPY_SDR_TX, 0, "TX1A"); + sdr->setGain(SOAPY_SDR_TX, 0, "TX_ATT", 30); + sdr->setGain(SOAPY_SDR_TX, 1, "TX_ATT", 30); + + double full_scale = 0; + const std::string fmt = sdr->getNativeStreamFormat(SOAPY_SDR_TX, 0, full_scale); + const size_t samp_sz = SoapySDR::formatToSize(fmt); + + auto s = sdr->setupStream(SOAPY_SDR_TX, fmt, {0, 1}); + + const size_t mtu = sdr->getStreamMTU(s); + + std::vector> buf(2, std::vector(samp_sz * mtu)); // Native + + // Tone - fs / 8 on channel 0, fs / 16 on channel 1 + { + std::vector> f_buf(2, std::vector(2 * mtu)); + + for(size_t i = 0; i < mtu; i++) + { + f_buf[0][2 * i + 0] = std::cos(2 * M_PI * i / 8); + f_buf[0][2 * i + 1] = std::sin(2 * M_PI * i / 8); + + f_buf[1][2 * i + 0] = std::cos(2 * M_PI * i / 16); + f_buf[1][2 * i + 1] = std::sin(2 * M_PI * i / 16); + } + + SoapySDR::ConverterRegistry::getFunction(SOAPY_SDR_CF32, fmt)(f_buf[0].data(), buf[0].data(), mtu, full_scale); + SoapySDR::ConverterRegistry::getFunction(SOAPY_SDR_CF32, fmt)(f_buf[1].data(), buf[1].data(), mtu, full_scale); + + double f0 = sdr->getFrequency(SOAPY_SDR_TX, 0) + sdr->getSampleRate(SOAPY_SDR_TX, 0) / 8; + double f1 = sdr->getFrequency(SOAPY_SDR_TX, 0) + sdr->getSampleRate(SOAPY_SDR_TX, 0) / 16; + + std::cout << "Channel 0: Tone with f = " << (size_t)f0 << " Hz and ~50% duty cycle" << std::endl; + std::cout << "Channel 1: Tone with f = " << (size_t)f1 << " Hz and ~50% duty cycle" << std::endl; + } + + std::vector bufs(2); + + for(size_t i = 0; i < buf.size(); i++) + bufs[i] = buf[i].data(); + + std::cout << "Starting timed TX tone stream loop, press Ctrl+C to exit..." << std::endl; + + sdr->activateStream(s); + + long long timeNs = sdr->getHardwareTime(); + + timeNs += 1e9; // 1 second in the future + + size_t n = 0; + + while(!g_done) + { + int flags = !(n % 64) ? SOAPY_SDR_HAS_TIME : 0; + + sdr->writeStream(s, bufs.data(), mtu, flags, timeNs, 10e6); + + if(flags & SOAPY_SDR_HAS_TIME) + timeNs += 1e9; // 1 second in the future + + n++; + } + + g_done = false; + + sdr->deactivateStream(s); + sdr->closeStream(s); +} +void testAWGNTX(SoapyIcyRadio *sdr, double fc = 480e6, double fs = 3e6) +{ + sdr->setSampleRate(SOAPY_SDR_TX, 0, fs); + sdr->setBandwidth(SOAPY_SDR_TX, 0, 0.5 * fs); + sdr->setFrequency(SOAPY_SDR_TX, 0, fc); + sdr->setAntenna(SOAPY_SDR_TX, 0, "TX1A"); + sdr->setGain(SOAPY_SDR_TX, 0, "TX_ATT", 30); + sdr->setGain(SOAPY_SDR_TX, 1, "TX_ATT", 30); + + double full_scale = 0; + const std::string fmt = sdr->getNativeStreamFormat(SOAPY_SDR_TX, 0, full_scale); + const size_t samp_sz = SoapySDR::formatToSize(fmt); + + auto s = sdr->setupStream(SOAPY_SDR_TX, fmt, {0, 1}); + + const size_t mtu = sdr->getStreamMTU(s); + + std::vector> buf(2, std::vector(samp_sz * mtu)); // Native + + // Random data on both channels + { + std::vector> f_buf(2, std::vector(2 * mtu)); + + for(size_t i = 0; i < mtu; i++) + { + // Scale to [-1.0, 1.0] + f_buf[0][2 * i + 0] = 2.0 * (double)std::rand() / RAND_MAX - 1.0; + f_buf[0][2 * i + 1] = 2.0 * (double)std::rand() / RAND_MAX - 1.0; + + f_buf[1][2 * i + 0] = 2.0 * (double)std::rand() / RAND_MAX - 1.0; + f_buf[1][2 * i + 1] = 2.0 * (double)std::rand() / RAND_MAX - 1.0; + } + + SoapySDR::ConverterRegistry::getFunction(SOAPY_SDR_CF32, fmt)(f_buf[0].data(), buf[0].data(), mtu, full_scale); + SoapySDR::ConverterRegistry::getFunction(SOAPY_SDR_CF32, fmt)(f_buf[1].data(), buf[1].data(), mtu, full_scale); + + std::cout << "Channel 0: Random data (White noise) at fc = " << (size_t)fc << " Hz and bandwidth = " << (size_t)(0.5 * fs) << " Hz" << std::endl; + std::cout << "Channel 1: Random data (White noise) at fc = " << (size_t)fc << " Hz and bandwidth = " << (size_t)(0.5 * fs) << " Hz" << std::endl; + } + + std::vector bufs(2); + + for(size_t i = 0; i < buf.size(); i++) + bufs[i] = buf[i].data(); + + std::cout << "Starting TX AWGN stream loop, press Ctrl+C to exit..." << std::endl; + + sdr->activateStream(s); + + long long timeNs = 0; + + while(!g_done) + { + int flags = 0; + + sdr->writeStream(s, bufs.data(), mtu, flags, timeNs); + } + + g_done = false; + + sdr->deactivateStream(s); + sdr->closeStream(s); +} +void testRFDelay(SoapyIcyRadio *sdr, double fc = 480e6, double fs = 61.44e6) +{ + sdr->setSampleRate(SOAPY_SDR_TX, 0, fs); + sdr->setBandwidth(SOAPY_SDR_TX, 0, fs / 8); + sdr->setBandwidth(SOAPY_SDR_RX, 0, fs / 8); + sdr->setFrequency(SOAPY_SDR_TX, 0, fc); + sdr->setFrequency(SOAPY_SDR_RX, 0, fc); + sdr->setAntenna(SOAPY_SDR_TX, 0, "TX1A"); + sdr->setAntenna(SOAPY_SDR_RX, 0, "RX1A"); + sdr->setGain(SOAPY_SDR_TX, 0, "TX_ATT", 10); + sdr->setGain(SOAPY_SDR_RX, 0, "RX_FE", 0); + + double tx_full_scale = 0; + const std::string tx_fmt = sdr->getNativeStreamFormat(SOAPY_SDR_TX, 0, tx_full_scale); + const size_t tx_samp_sz = SoapySDR::formatToSize(tx_fmt); + auto tx_s = sdr->setupStream(SOAPY_SDR_TX, tx_fmt, {0}); + + double rx_full_scale = 0; + const std::string rx_fmt = sdr->getNativeStreamFormat(SOAPY_SDR_RX, 0, rx_full_scale); + const size_t rx_samp_sz = SoapySDR::formatToSize(rx_fmt); + auto rx_s = sdr->setupStream(SOAPY_SDR_RX, rx_fmt, {0}); + + const size_t tx_mtu = sdr->getStreamMTU(tx_s); + const size_t rx_mtu = sdr->getStreamMTU(rx_s); + const size_t mtu = std::min(std::min(tx_mtu, rx_mtu), (size_t)4096U); + const size_t tx_size = mtu / 2; + const size_t rx_size = mtu; + + std::vector tx_buf(tx_samp_sz * tx_size); // Native + void *tx_bufs[] = {tx_buf.data()}; + + std::vector rx_buf(rx_samp_sz * rx_size); // Native + void *rx_bufs[] = {rx_buf.data()}; + + std::vector f_buf(2 * rx_size); // RX size is bigger, we will reuse the buffer + + // Generate sinc pulse + for(size_t i = 0; i < tx_size; i++) + { + float _i = i; + float max = tx_size / 2; + float x = 8 * (_i - max) / max; + + f_buf[2 * i + 0] = (_i == max) ? 1.0 : (std::sin(M_PI * x) / (M_PI * x)); // I + f_buf[2 * i + 1] = 0; // Q + } + + SoapySDR::ConverterRegistry::getFunction(SOAPY_SDR_CF32, tx_fmt)(f_buf.data(), tx_buf.data(), tx_size, tx_full_scale); + + // --------------|--------------|--------------|--------------|--------> Time + // rx starts -> | tx starts -> | tx ends -> | rx ends -> | + + long long now = sdr->getHardwareTime(); + long long rx_start = now + 100e6; // 100 ms in the future + long long rx_stop = rx_start + SoapySDR::ticksToTimeNs(rx_size, sdr->getSampleRate(SOAPY_SDR_RX, 0)); + long long tx_start = rx_start + SoapySDR::ticksToTimeNs(tx_size / 2, sdr->getSampleRate(SOAPY_SDR_TX, 0)); + long long tx_stop = tx_start + SoapySDR::ticksToTimeNs(tx_size, sdr->getSampleRate(SOAPY_SDR_TX, 0)); + + std::cout << "now: " << now << " ns, rx_start: " << rx_start << " ns, tx_start: " << tx_start << " ns, tx_stop: " << tx_stop << " ns, rx_stop: " << rx_stop << " ns" << std::endl; + + int flags = SOAPY_SDR_HAS_TIME; + + sdr->activateStream(tx_s); + sdr->activateStream(rx_s, flags, rx_start); + + flags = SOAPY_SDR_HAS_TIME | SOAPY_SDR_ONE_PACKET; + + sdr->writeStream(tx_s, tx_bufs, tx_size, flags, tx_start, 1e6); + + flags = 0; + long long actual_rx_start = 0; + + sdr->readStream(rx_s, rx_bufs, rx_size, flags, actual_rx_start, 1e6); + + sdr->deactivateStream(rx_s); + sdr->deactivateStream(tx_s); + sdr->closeStream(rx_s); + sdr->closeStream(tx_s); + + std::ofstream tf; + + tf.open("rfdelay_trace.py"); + + tf << "import numpy as np" << std::endl; + tf << "from matplotlib import pyplot as plt" << std::endl; + + tf << std::endl; + + tf << "tx = np.array(["; + + for(size_t i = 0; i < tx_size; i++) + { + float mag = std::sqrt(f_buf[2 * i + 0] * f_buf[2 * i + 0] + f_buf[2 * i + 1] * f_buf[2 * i + 1]); + + tf << mag; + + if(i < tx_size - 1) + tf << ", "; + } + + tf << "])" << std::endl; + + SoapySDR::ConverterRegistry::getFunction(rx_fmt, SOAPY_SDR_CF32)(rx_buf.data(), f_buf.data(), rx_size, 1.0 / rx_full_scale); + + tf << "rx = np.array(["; + + for(size_t i = 0; i < rx_size; i++) + { + float mag = std::sqrt(f_buf[2 * i + 0] * f_buf[2 * i + 0] + f_buf[2 * i + 1] * f_buf[2 * i + 1]); + + tf << mag; + + if(i < rx_size - 1) + tf << ", "; + } + + tf << "])" << std::endl; + + tf << std::endl; + + tf << "tx = tx / np.max(tx)" << std::endl; + tf << "rx = rx / np.max(rx)" << std::endl; + tf << "corr = np.correlate(tx, rx, 'full')" << std::endl; + + tf << std::endl; + + tf << "plt.figure()" << std::endl; + tf << "plt.plot(tx, label='TX')" << std::endl; + tf << "plt.legend()" << std::endl; + + tf << "plt.figure()" << std::endl; + tf << "plt.plot(rx, label='RX')" << std::endl; + tf << "plt.show()" << std::endl; + + tf << "plt.figure()" << std::endl; + tf << "plt.plot(corr, label='Correlation')" << std::endl; + tf << "plt.show()" << std::endl; + + tf.close(); + + // TODO: Find the actual delay +} +void testFullDuplex(SoapyIcyRadio *sdr, double fs = 30.72e6) +{ + // List of sensors to monitor + std::vector> sensors = { + {"xadc_temp", ""}, + {"pmc_temp", ""}, + {"rf_phy_temp", ""}, + {"vin_reg_temp", ""}, + {"vin_reg_5v0_vout", ""}, + {"vin_reg_5v0_iout", ""}, + {"vin_reg_5v0_pout", ""}, + {"vin_reg_1v0_vout", ""}, + {"vin_reg_1v0_iout", ""}, + {"vin_reg_1v0_pout", ""} + }; + + for(auto &sensor : sensors) + { + auto info = sdr->getSensorInfo(sensor.first); + + sensor.second = info.name + ": %s " + info.units; + + printf("-> "); + printf(sensor.second.c_str(), sdr->readSensor(sensor.first).c_str()); + printf("\n"); + } + + fflush(stdout); + + sdr->setSampleRate(SOAPY_SDR_TX, 0, fs); + sdr->setAntenna(SOAPY_SDR_TX, 0, "TX1A"); + sdr->setAntenna(SOAPY_SDR_RX, 0, "RX1A"); + sdr->setGain(SOAPY_SDR_TX, 0, "TX_ATT", 89); + sdr->setGain(SOAPY_SDR_RX, 0, "RX_FE", 0); + + double tx_full_scale = 0; + const std::string tx_fmt = sdr->getNativeStreamFormat(SOAPY_SDR_TX, 0, tx_full_scale); + const size_t tx_samp_sz = SoapySDR::formatToSize(tx_fmt); + auto tx_s = sdr->setupStream(SOAPY_SDR_TX, tx_fmt, {0, 1}); + + double rx_full_scale = 0; + const std::string rx_fmt = sdr->getNativeStreamFormat(SOAPY_SDR_RX, 0, rx_full_scale); + const size_t rx_samp_sz = SoapySDR::formatToSize(rx_fmt); + auto rx_s = sdr->setupStream(SOAPY_SDR_RX, rx_fmt, {0, 1}); + + const size_t tx_mtu = sdr->getStreamMTU(tx_s); + const size_t rx_mtu = sdr->getStreamMTU(rx_s); + + std::vector> tx_buf(2, std::vector(tx_samp_sz * tx_mtu)); // Native + void *tx_bufs[] = {tx_buf[0].data(), tx_buf[1].data()}; + + std::vector> rx_buf(2, std::vector(rx_samp_sz * rx_mtu)); // Native + void *rx_bufs[] = {rx_buf[0].data(), rx_buf[1].data()}; + + size_t tx_overflows = 0; + size_t rx_overflows = 0; + size_t tx_underflows = 0; + size_t rx_underflows = 0; + size_t tx_n_samples = 0; + size_t rx_n_samples = 0; + size_t tx_total_n_samples = 0; + size_t rx_total_n_samples = 0; + const auto t_start = std::chrono::high_resolution_clock::now(); + auto t_last_print = std::chrono::high_resolution_clock::now(); + auto t_last_spin = std::chrono::high_resolution_clock::now(); + size_t spin_i = 0; + + std::cout << "Starting Full Duplex stream loop at " << (size_t)fs << " sps, press Ctrl+C to exit..." << std::endl; + + sdr->activateStream(tx_s); + sdr->activateStream(rx_s); + + while(!g_done) + { + int ret = 0; + int flags = 0; + long long timeNs = 0; + + ret = sdr->writeStream(tx_s, tx_bufs, tx_mtu, flags, timeNs, 0); + + if(ret != SOAPY_SDR_TIMEOUT) + { + if(ret == SOAPY_SDR_OVERFLOW) + tx_overflows++; + else if(ret == SOAPY_SDR_UNDERFLOW) + tx_underflows++; + else if(ret > 0) + tx_n_samples += ret; + } + + ret = sdr->readStream(rx_s, rx_bufs, rx_mtu, flags, timeNs, 0); + + if(ret != SOAPY_SDR_TIMEOUT) + { + if(ret == SOAPY_SDR_OVERFLOW) + rx_overflows++; + else if(ret == SOAPY_SDR_UNDERFLOW) + rx_underflows++; + else if(ret > 0) + rx_n_samples += ret; + } + + const auto now = std::chrono::high_resolution_clock::now(); + + if(t_last_spin + std::chrono::milliseconds(300) < now) + { + static const char spin[] = {"|/-\\"}; + + printf("\b%c", spin[(spin_i++) % 4]); + fflush(stdout); + + t_last_spin = now; + } + + if(t_last_print + std::chrono::seconds(5) < now) + { + const auto total_dt = std::chrono::duration_cast(now - t_start); + const auto dt = std::chrono::duration_cast(now - t_last_print); + + tx_total_n_samples += tx_n_samples; + + const auto tx_sr = (double)tx_n_samples / dt.count(); + const auto total_tx_sr = (double)tx_total_n_samples / total_dt.count(); + const auto tx_dr = tx_sr * tx_buf.size() * tx_samp_sz; + const auto total_tx_dr = total_tx_sr * tx_buf.size() * tx_samp_sz; + + tx_n_samples = 0; + + printf("\bTX: %g Msps (%g MB/s, %g Mbps) [Avg: %g Msps (%g MB/s, %g Mbps)]", tx_sr, tx_dr, tx_dr * 8, total_tx_sr, total_tx_dr, total_tx_dr * 8); + + if(tx_overflows != 0) + printf("\tOverflows %lu", tx_overflows); + + if(tx_underflows != 0) + printf("\tUnderflows %lu", tx_underflows); + + printf("\n "); + + rx_total_n_samples += rx_n_samples; + + const auto rx_sr = (double)rx_n_samples / dt.count(); + const auto total_rx_sr = (double)rx_total_n_samples / total_dt.count(); + const auto rx_dr = rx_sr * rx_buf.size() * rx_samp_sz; + const auto total_rx_dr = total_rx_sr * rx_buf.size() * rx_samp_sz; + + rx_n_samples = 0; + + printf("\bRX: %g Msps (%g MB/s, %g Mbps) [Avg: %g Msps (%g MB/s, %g Mbps)]", rx_sr, rx_dr, rx_dr * 8, total_rx_sr, total_rx_dr, total_rx_dr * 8); + + if(rx_overflows != 0) + printf("\tOverflows %lu", rx_overflows); + + if(rx_underflows != 0) + printf("\tUnderflows %lu", rx_underflows); + + printf("\n "); + + for(const auto &sensor : sensors) + { + printf("\b-> "); + printf(sensor.second.c_str(), sdr->readSensor(sensor.first).c_str()); + printf("\n "); + } + + t_last_print = now; + } + } + + g_done = false; + + sdr->deactivateStream(rx_s); + sdr->deactivateStream(tx_s); + sdr->closeStream(rx_s); + sdr->closeStream(tx_s); +} + int main(int argc, char *argv[]) { + signal(SIGINT, sigHandler); + // if(!loadSoapyIcyRadio()) // { // std::cerr << "Could not find a suitable IcyRadio support module." << std::endl; @@ -102,29 +634,72 @@ int main(int argc, char *argv[]) SoapySDR::Kwargs args; + bool do_tone_test = false; + bool do_timed_tone_test = false; + bool do_awgn_test = false; + bool do_rfdelay_test = false; + bool do_full_duplex_test = false; + static struct option long_options[] = { {"serial", required_argument, nullptr, 'S'}, {"flash-file", required_argument, nullptr, 'f'}, + {"test", required_argument, nullptr, 't'}, {nullptr, no_argument, nullptr, '\0'} }; int c; int option_index = 0; - while((c = getopt_long(argc, argv, "S:f:", long_options, &option_index)) != -1) + while((c = getopt_long(argc, argv, "S:f:t:", long_options, &option_index)) != -1) { switch(c) { case 'S': + { args["serial"] = optarg; + } + break; + case 't': + { + if(std::string(optarg) == "tone") + { + do_tone_test = true; + } + else if(std::string(optarg) == "timed_tone") + { + do_timed_tone_test = true; + } + else if(std::string(optarg) == "awgn") + { + do_awgn_test = true; + } + else if(std::string(optarg) == "rfdelay") + { + do_rfdelay_test = true; + } + else if(std::string(optarg) == "full_duplex") + { + do_full_duplex_test = true; + } + else + { + std::cerr << "Unknown test: " << optarg << std::endl; + + return EXIT_FAILURE; + } + } break; default: return EXIT_FAILURE; } } - std::cout << "Making IcyRadio device (" << SoapySDR::KwargsToString(args) << ")..." << std::endl; + std::cout << "Making IcyRadio device"; + std::cout << (args.size() ? " (" : ""); + std::cout << (args.size() ? SoapySDR::KwargsToString(args) : ""); + std::cout << (args.size() ? ")" : ""); + std::cout << "..." << std::endl; SoapyIcyRadio *sdr = nullptr; @@ -148,9 +723,46 @@ int main(int argc, char *argv[]) return EXIT_FAILURE; } - std::cout << "IcyRadio device " << std::to_string((uintptr_t)sdr) << " created." << std::endl; + std::cout << "IcyRadio device created." << std::endl; + + if(do_tone_test) + { + std::cout << "Running tone test..." << std::endl; + + testToneTX(sdr); + } + + if(do_timed_tone_test) + { + std::cout << "Running timed tone test..." << std::endl; + + testTimedToneTX(sdr); + } + + if(do_awgn_test) + { + std::cout << "Running AWGN test..." << std::endl; + + testAWGNTX(sdr); + } + + if(do_rfdelay_test) + { + std::cout << "Running RF delay test..." << std::endl; + + testRFDelay(sdr); + } + + if(do_full_duplex_test) + { + std::cout << "Running full duplex test..." << std::endl; + + testFullDuplex(sdr); + } delete sdr; + std::cout << "IcyRadio device destroyed." << std::endl; + return EXIT_SUCCESS; } \ No newline at end of file diff --git a/software/soapy/src/Si5351.cpp b/software/soapy/src/Si5351.cpp index a24ed30d..4bd2fd2d 100644 --- a/software/soapy/src/Si5351.cpp +++ b/software/soapy/src/Si5351.cpp @@ -208,6 +208,78 @@ Si5351::MultiSynthDivider Si5351::CalculateMSDivider(uint32_t f1, uint32_t f2) return div; } +Si5351::MultiSynthDivider Si5351::CalculateValidPLLMSDivider(uint32_t f1, uint32_t f2) +{ + Si5351::MultiSynthDivider div = Si5351::CalculateMSDivider(f1, f2); + + // Force the divider to be valid by rounding the out of range parameters + if(div.a < 15) + { + div.a = 15; + div.b = 0; + div.c = 1; + } + + if(div.a >= 90) + { + div.a = 90; + div.b = 0; + div.c = 1; + } + + return div; +} +Si5351::MultiSynthDivider Si5351::CalculateValidIntMSDivider(uint32_t f1, uint32_t f2) +{ + Si5351::MultiSynthDivider div = Si5351::CalculateMSDivider(f1, f2); + + // Force the divider to be valid by rounding the out of range parameters + if((double)div.b / div.c >= 0.5) + div.a++; + + div.b = 0; + div.c = 1; + + if(div.a < 6) + div.a = 6; + + if(div.a > 254) + div.a = 254; + + if(div.a & 1) + div.a--; + + return div; +} +Si5351::MultiSynthDivider Si5351::CalculateValidFracMSDivider(uint32_t f1, uint32_t f2) +{ + Si5351::MultiSynthDivider div = Si5351::CalculateMSDivider(f1, f2); + + // Force the divider to be valid by rounding the out of range parameters + if(div.a < 4) + div.a = 4; + + if(div.a >= 2048) + { + div.a = 2048; + div.b = 0; + div.c = 1; + } + + if(div.a < 8) + { + if(div.b > 0) + { + div.b = 0; + div.c = 1; + } + + if(div.a == 7 || div.a == 5) + div.a--; + } + + return div; +} void Si5351::ValidatePLLMSDivider(Si5351::MultiSynthDivider div) { if(div.a < 15 || div.a > 90) // Integer part limits @@ -588,7 +660,7 @@ void Si5351::resetPLL(Si5351::PLL pll) while(this->readReg(SI5351_REG_STATUS) & SI5351_REG_STATUS_SYS_INIT) // Somehow the SYS_INIT bit gets set when the PLLs are reset usleep(10); } -void Si5351::configPLL(Si5351::PLL pll, uint32_t freq, Si5351::PLLSource src) +void Si5351::configPLL(Si5351::PLL pll, uint32_t freq, Si5351::PLLSource src, bool closest) { switch(pll) { @@ -599,14 +671,17 @@ void Si5351::configPLL(Si5351::PLL pll, uint32_t freq, Si5351::PLLSource src) throw std::invalid_argument("Si5351: Invalid PLL"); } + std::lock_guard lock(this->mutex); + + if(closest) + src = this->getClosestPLLFrequency(freq, src); // Can decide the source aswell + if(freq < 600000000UL) throw std::invalid_argument("Si5351: VCO frequency too low (Valid: 600-900 MHz)"); if(freq > 900000000UL) throw std::invalid_argument("Si5351: VCO frequency too high (Valid: 600-900 MHz)"); - std::lock_guard lock(this->mutex); - if(src == Si5351::PLLSource::PLL_SRC_AUTO) { do @@ -793,7 +868,71 @@ uint32_t Si5351::getPLLSourceFrequency(Si5351::PLL pll) throw std::runtime_error("Si5351: Invalid PLL source"); } } -void Si5351::setPLLFrequency(Si5351::PLL pll, uint32_t freq) +Si5351::PLLSource Si5351::getClosestPLLFrequency(uint32_t &freq, Si5351::PLLSource src) +{ + if(freq < 600000000UL) + freq = 600000000UL; + + if(freq > 900000000UL) + freq = 900000000UL; + + std::lock_guard lock(this->mutex); + + uint32_t f_xtal = this->getXTALFrequency(); + uint32_t f_clkin = this->getDividedCLKINFrequency(); + + uint32_t a_xtal = 0; + uint32_t a_clkin = 0; + + if(f_xtal && src != Si5351::PLLSource::PLL_SRC_CLKIN) + { + Si5351::MultiSynthDivider div = Si5351::CalculateValidPLLMSDivider(freq, f_xtal); + + a_xtal = f_xtal * div.a + (((uint64_t)f_xtal * div.b) / div.c); + } + + if(f_clkin && src != Si5351::PLLSource::PLL_SRC_XTAL) + { + Si5351::MultiSynthDivider div = Si5351::CalculateValidPLLMSDivider(freq, f_clkin); + + a_clkin = f_clkin * div.a + (((uint64_t)f_clkin * div.b) / div.c); + } + + if(a_xtal && !a_clkin) + { + freq = a_xtal; + + return Si5351::PLLSource::PLL_SRC_XTAL; + } + + if(!a_xtal && a_clkin) + { + freq = a_clkin; + + return Si5351::PLLSource::PLL_SRC_CLKIN; + } + + uint32_t d_xtal = D_ABS(a_xtal, freq); + uint32_t d_clkin = D_ABS(a_clkin, freq); + + if(d_xtal < d_clkin) + { + freq = a_xtal; + + return Si5351::PLLSource::PLL_SRC_XTAL; + } + else if(d_clkin < d_xtal) + { + freq = a_clkin; + + return Si5351::PLLSource::PLL_SRC_CLKIN; + } + + freq = a_xtal; // Both are equal + + return Si5351::PLLSource::PLL_SRC_AUTO; +} +void Si5351::setPLLFrequency(Si5351::PLL pll, uint32_t freq, bool closest) { switch(pll) { @@ -812,9 +951,18 @@ void Si5351::setPLLFrequency(Si5351::PLL pll, uint32_t freq) std::lock_guard lock(this->mutex); - Si5351::MultiSynthDivider div = Si5351::CalculateMSDivider(freq, this->getPLLSourceFrequency(pll)); + Si5351::MultiSynthDivider div; - Si5351::ValidatePLLMSDivider(div); + if(closest) + { + div = Si5351::CalculateValidPLLMSDivider(freq, this->getPLLSourceFrequency(pll)); + } + else + { + div = Si5351::CalculateMSDivider(freq, this->getPLLSourceFrequency(pll)); + + Si5351::ValidatePLLMSDivider(div); + } uint32_t p1 = 128 * div.a + ((128 * div.b) / div.c) - 512; uint32_t p2 = 128 * div.b - div.c * ((128 * div.b) / div.c); @@ -868,7 +1016,7 @@ uint32_t Si5351::getPLLFrequency(Si5351::PLL pll) return src_freq * div.a + ((src_freq * div.b) / div.c); } -void Si5351::configMultiSynth(Si5351::MultiSynth ms, uint32_t freq, float phase, Si5351::PLL src) +void Si5351::configMultiSynth(Si5351::MultiSynth ms, uint32_t freq, float phase, Si5351::PLL src, bool closest) { bool integer; @@ -890,14 +1038,17 @@ void Si5351::configMultiSynth(Si5351::MultiSynth ms, uint32_t freq, float phase, throw std::invalid_argument("Si5351: Invalid MultiSynth"); } + std::lock_guard lock(this->mutex); + + if(closest) + src = this->getClosestMultiSynthFrequency(ms, freq, src); // Can decide the source aswell + if(freq < 500000UL) throw std::invalid_argument("Si5351: Frequency too low (Valid: 0.5-200 MHz)"); if(freq > 200000000UL) throw std::invalid_argument("Si5351: Frequency too high (Valid: 0.5-200 MHz)"); - std::lock_guard lock(this->mutex); - if(src == Si5351::PLL::PLL_AUTO) { do @@ -1089,7 +1240,94 @@ uint32_t Si5351::getMultiSynthSourceFrequency(Si5351::MultiSynth ms) return this->getPLLFrequency(this->getMultiSynthSource(ms)); } -void Si5351::setMultiSynthFrequency(Si5351::MultiSynth ms, uint32_t freq) +Si5351::PLL Si5351::getClosestMultiSynthFrequency(Si5351::MultiSynth ms, uint32_t &freq, Si5351::PLL src) +{ + bool integer; + + switch(ms) + { + case Si5351::MultiSynth::MS0: + case Si5351::MultiSynth::MS1: + case Si5351::MultiSynth::MS2: + case Si5351::MultiSynth::MS3: + case Si5351::MultiSynth::MS4: + case Si5351::MultiSynth::MS5: + integer = false; + break; + case Si5351::MultiSynth::MS6: + case Si5351::MultiSynth::MS7: + integer = true; + break; + default: + throw std::invalid_argument("Si5351: Invalid MultiSynth"); + } + + if(freq < 500000UL) + freq = 500000UL; + + if(!integer && freq > 200000000UL) + freq = 200000000UL; + + if(integer && freq > 150000000UL) + freq = 150000000UL; + + std::lock_guard lock(this->mutex); + + uint32_t f_a = this->getPLLFrequency(Si5351::PLL::PLLA); + uint32_t f_b = this->getPLLFrequency(Si5351::PLL::PLLB); + + uint32_t a_a = 0; + uint32_t a_b = 0; + + if(f_a && src != Si5351::PLL::PLLB) + { + Si5351::MultiSynthDivider div = integer ? Si5351::CalculateValidIntMSDivider(f_a, freq) : Si5351::CalculateValidFracMSDivider(f_a, freq); + + a_a = ((uint64_t)f_a * div.c) / (((uint64_t)div.a * div.c) + div.b); + } + + if(f_b && src != Si5351::PLL::PLLA) + { + Si5351::MultiSynthDivider div = integer ? Si5351::CalculateValidIntMSDivider(f_b, freq) : Si5351::CalculateValidFracMSDivider(f_b, freq); + + a_b = ((uint64_t)f_b * div.c) / (((uint64_t)div.a * div.c) + div.b); + } + + if(a_a && !a_b) + { + freq = a_a; + + return Si5351::PLL::PLLA; + } + + if(!a_a && a_b) + { + freq = a_b; + + return Si5351::PLL::PLLB; + } + + uint32_t d_a = D_ABS(a_a, freq); + uint32_t d_b = D_ABS(a_b, freq); + + if(d_a < d_b) + { + freq = a_a; + + return Si5351::PLL::PLLA; + } + else if(d_b < d_a) + { + freq = a_b; + + return Si5351::PLL::PLLB; + } + + freq = a_a; // Both are equal + + return Si5351::PLL::PLL_AUTO; +} +void Si5351::setMultiSynthFrequency(Si5351::MultiSynth ms, uint32_t freq, bool closest) { bool integer; @@ -1122,18 +1360,31 @@ void Si5351::setMultiSynthFrequency(Si5351::MultiSynth ms, uint32_t freq) std::lock_guard lock(this->mutex); - Si5351::MultiSynthDivider div = Si5351::CalculateMSDivider(this->getMultiSynthSourceFrequency(ms), freq); + Si5351::MultiSynthDivider div; - if(integer) + if(closest) { - Si5351::ValidateIntClockMSDivider(div); + if(integer) + div = Si5351::CalculateValidIntMSDivider(this->getMultiSynthSourceFrequency(ms), freq); + else + div = Si5351::CalculateValidFracMSDivider(this->getMultiSynthSourceFrequency(ms), freq); + } + else + { + div = Si5351::CalculateMSDivider(this->getMultiSynthSourceFrequency(ms), freq); + + if(integer) + Si5351::ValidateIntClockMSDivider(div); + else + Si5351::ValidateFracClockMSDivider(div); + } + if(integer) + { this->writeReg(SI5351_REG_MS6_P1 + (ms - Si5351::MultiSynth::MS6), div.a); } else { - Si5351::ValidateFracClockMSDivider(div); - // Frequencies greater than 150 MHz require divide by 4 if(freq > 150000000UL && (div.a != 4 || div.b > 0)) throw std::runtime_error("Si5351: Cannot achieve requested frequency"); @@ -1835,7 +2086,7 @@ void Si5351::setClockOutputEnableMode(Si5351::ClockOutput clk, bool hard) this->rmwReg(SI5351_REG_OEB_MASK, ~BIT(clk), hard ? 0x00 : BIT(clk)); } -void Si5351::configClock(Si5351::ClockOutput clk, uint32_t freq, float phase, Si5351::PLL src) +void Si5351::configClock(Si5351::ClockOutput clk, uint32_t freq, float phase, Si5351::PLL src, bool closest) { switch(clk) { @@ -1854,14 +2105,22 @@ void Si5351::configClock(Si5351::ClockOutput clk, uint32_t freq, float phase, Si Si5351::ClockOutputDivider out_div = Si5351::ClockOutputDivider::CLK_DIV_DIV1; - while(freq < 500000UL && out_div != Si5351::ClockOutputDivider::CLK_DIV_DIV128) + if(freq < 500000UL >> 7) { - freq <<= 1; - out_div = static_cast(out_div << 1); - } + if(!closest) + throw std::invalid_argument("Si5351: Frequency too low"); - if(freq < 500000UL) - throw std::invalid_argument("Si5351: Frequency too low"); + freq = 500000UL; + out_div = Si5351::ClockOutputDivider::CLK_DIV_DIV128; + } + else + { + while(freq < 500000UL && out_div != Si5351::ClockOutputDivider::CLK_DIV_DIV128) + { + freq <<= 1; + out_div = static_cast(out_div << 1); + } + } bool invert = false; @@ -1873,11 +2132,35 @@ void Si5351::configClock(Si5351::ClockOutput clk, uint32_t freq, float phase, Si std::lock_guard lock(this->mutex); - this->configMultiSynth(static_cast(clk), freq, phase, src); + this->configMultiSynth(static_cast(clk), freq, phase, src, closest); this->setClockOutputSource(clk, Si5351::ClockOutputSource::CLK_SRC_MSn); this->setClockOutputDivider(clk, out_div); this->setClockOutputInvert(clk, invert); } +Si5351::PLL Si5351::getClosestClockFrequency(Si5351::ClockOutput clk, uint32_t &freq, Si5351::PLL src) +{ + Si5351::ClockOutputDivider out_div = Si5351::ClockOutputDivider::CLK_DIV_DIV1; + + if(freq < 500000UL >> 7) + { + freq = 500000UL; + out_div = Si5351::ClockOutputDivider::CLK_DIV_DIV128; + } + else + { + while(freq < 500000UL && out_div != Si5351::ClockOutputDivider::CLK_DIV_DIV128) + { + freq <<= 1; + out_div = static_cast(out_div << 1); + } + } + + src = this->getClosestMultiSynthFrequency(static_cast(clk), freq, src); + + freq /= out_div; + + return src; +} void Si5351::setClockFrequency(Si5351::ClockOutput clk, uint32_t freq) { switch(clk) diff --git a/software/soapy/src/SoapyIcyRadio.cpp b/software/soapy/src/SoapyIcyRadio.cpp index 10e9d814..4215e42f 100644 --- a/software/soapy/src/SoapyIcyRadio.cpp +++ b/software/soapy/src/SoapyIcyRadio.cpp @@ -1,5 +1,96 @@ #include "SoapyIcyRadio.hpp" +void SoapyIcyRadio::parseConfig(const SoapySDR::Kwargs &args) +{ + SoapySDR::Kwargs c_args = args; + + // Remove identification arguments + c_args.erase("driver"); + c_args.erase("path"); + c_args.erase("device_id"); + c_args.erase("serial"); + c_args.erase("label"); + + // Parse configuration + DLOGF(SOAPY_SDR_DEBUG, "Parsing configuration string \"%s\"", SoapySDR::KwargsToString(c_args).c_str()); + + // use_ext_clk_in + if(c_args.count("use_ext_clk_in") > 0) + { + this->config.use_clkin = true; + + DLOGF(SOAPY_SDR_INFO, "Using external clock input as clock source"); + } + else + { + this->config.use_clkin = false; + } + + // ext_clk_in_freq + if(args.count("ext_clk_in_freq") > 0) + { + double f = std::stod(c_args.at("ext_clk_in_freq")); + + if(f < 0 || f > UINT32_MAX) + { + this->config.clkin_freq = 10000000U; + + DLOGF(SOAPY_SDR_WARNING, "Invalid external clock input frequency provided (%.6f), using default: 10 MHz", f); + } + else + { + this->config.clkin_freq = f; + + DLOGF(SOAPY_SDR_INFO, "External clock input frequency is %u Hz", this->config.clkin_freq); + } + } + else + { + if(this->config.use_clkin) + DLOGF(SOAPY_SDR_WARNING, "External clock input usage requested, but no frequency provided, using default: 10 MHz"); + + this->config.clkin_freq = 10000000U; + } + + // en_ext_clk_out + if(c_args.count("en_ext_clk_out") > 0) + { + this->config.enable_clkout = true; + + DLOGF(SOAPY_SDR_INFO, "Enabling external clock output"); + } + else + { + this->config.enable_clkout = false; + } + + // ext_clk_out_freq + if(args.count("ext_clk_out_freq") > 0) + { + double f = std::stod(c_args.at("ext_clk_out_freq")); + + if(f < 0 || f > UINT32_MAX) + { + this->config.clkout_freq = 10000000U; + + DLOGF(SOAPY_SDR_WARNING, "Invalid external clock output frequency provided (%.6f), using default: 10 MHz", f); + } + else + { + this->config.clkout_freq = f; + + DLOGF(SOAPY_SDR_INFO, "Requested external clock output frequency is %u Hz", this->config.clkout_freq); + } + } + else + { + if(this->config.enable_clkout) + DLOGF(SOAPY_SDR_WARNING, "External clock output enabled, but no frequency provided, using default: 10 MHz"); + + this->config.clkout_freq = 10000000U; + } +} + void SoapyIcyRadio::setupMemoryMaps() { if(this->fd < 0) @@ -12,11 +103,11 @@ void SoapyIcyRadio::setupMemoryMaps() this->mm_axi_ddr = new MappedRegion(this->fd, AXI_MIG_DDR3_BASE, AXI_MIG_DDR3_SIZE); this->mm_axi_periph = new MappedRegion(this->fd, AXI_PERIPH_BASE, AXI_PERIPH_SIZE); - SoapySDR_logf(SOAPY_SDR_DEBUG, "AXI Flash: Phys = %016llX, Virt = %016llX, Size = %016llX", this->mm_axi_flash->getPhys(), this->mm_axi_flash->getVirt(), this->mm_axi_flash->getSize()); - SoapySDR_logf(SOAPY_SDR_DEBUG, "AXI BRAM: Phys = %016llX, Virt = %016llX, Size = %016llX", this->mm_axi_bram->getPhys(), this->mm_axi_bram->getVirt(), this->mm_axi_bram->getSize()); - SoapySDR_logf(SOAPY_SDR_DEBUG, "AXI DNA: Phys = %016llX, Virt = %016llX, Size = %016llX", this->mm_axi_dna->getPhys(), this->mm_axi_dna->getVirt(), this->mm_axi_dna->getSize()); - SoapySDR_logf(SOAPY_SDR_DEBUG, "AXI DDR: Phys = %016llX, Virt = %016llX, Size = %016llX", this->mm_axi_ddr->getPhys(), this->mm_axi_ddr->getVirt(), this->mm_axi_ddr->getSize()); - SoapySDR_logf(SOAPY_SDR_DEBUG, "AXI Peripherals: Phys = %016llX, Virt = %016llX, Size = %016llX", this->mm_axi_periph->getPhys(), this->mm_axi_periph->getVirt(), this->mm_axi_periph->getSize()); + DLOGF(SOAPY_SDR_TRACE, "AXI Flash: Phys = %016llX, Virt = %016llX, Size = %016llX", this->mm_axi_flash->getPhys(), this->mm_axi_flash->getVirt(), this->mm_axi_flash->getSize()); + DLOGF(SOAPY_SDR_TRACE, "AXI BRAM: Phys = %016llX, Virt = %016llX, Size = %016llX", this->mm_axi_bram->getPhys(), this->mm_axi_bram->getVirt(), this->mm_axi_bram->getSize()); + DLOGF(SOAPY_SDR_TRACE, "AXI DNA: Phys = %016llX, Virt = %016llX, Size = %016llX", this->mm_axi_dna->getPhys(), this->mm_axi_dna->getVirt(), this->mm_axi_dna->getSize()); + DLOGF(SOAPY_SDR_TRACE, "AXI DDR: Phys = %016llX, Virt = %016llX, Size = %016llX", this->mm_axi_ddr->getPhys(), this->mm_axi_ddr->getVirt(), this->mm_axi_ddr->getSize()); + DLOGF(SOAPY_SDR_TRACE, "AXI Peripherals: Phys = %016llX, Virt = %016llX, Size = %016llX", this->mm_axi_periph->getPhys(), this->mm_axi_periph->getVirt(), this->mm_axi_periph->getSize()); // Setup DNA (ROM) this->axi_dna = new AXIDNA(this->mm_axi_dna->getVirt(AXI_DNA_BASE)); @@ -47,6 +138,9 @@ void SoapyIcyRadio::setupMemoryMaps() // Allocate DMA buffer memory uint32_t dma_sz = ICYRADIO_DEFAULT_TOTAL_DMA_POOL_SIZE_BYTES; // TODO: make this configurable + + DLOGF(SOAPY_SDR_DEBUG, "Allocating DMA buffer pool of %u bytes", dma_sz); + uint64_t arg = dma_sz; ioctl(this->fd, ICYRADIO_IOCTL_DMA_FREE); // TODO: Implement getting an existing buffer @@ -57,7 +151,7 @@ void SoapyIcyRadio::setupMemoryMaps() // Map DMA buffer memory this->mm_dma_buffer = new MappedRegion(this->fd, arg | BIT(48), dma_sz); - SoapySDR_logf(SOAPY_SDR_DEBUG, "DMA Buffer: Phys = %016llX, Virt = %016llX, Size = %016llX", this->mm_dma_buffer->getPhys() & (BIT(48) - 1), this->mm_dma_buffer->getVirt(), this->mm_dma_buffer->getSize()); + DLOGF(SOAPY_SDR_TRACE, "DMA Buffer: Phys = %016llX, Virt = %016llX, Size = %016llX", this->mm_dma_buffer->getPhys() & (BIT(48) - 1), this->mm_dma_buffer->getVirt(), this->mm_dma_buffer->getSize()); } void SoapyIcyRadio::freeMemoryMaps() { @@ -202,7 +296,7 @@ void SoapyIcyRadio::initPeripheralsPreClocks() // PCIe uint8_t num_bars = this->axi_pcie->getNumBARs(); - SoapySDR_logf(SOAPY_SDR_DEBUG, "Detected %hhu AXI -> PCIe BARs", num_bars); + DLOGF(SOAPY_SDR_DEBUG, "Detected %hhu AXI -> PCIe BARs", num_bars); if(num_bars < 6) throw std::runtime_error("AXI PCIe: Not enough BARs detected"); @@ -248,25 +342,25 @@ void SoapyIcyRadio::initPeripheralsPreClocks() this->axi_pcie->setBARPCIeAddress(i, 0, 0); } - SoapySDR_logf(SOAPY_SDR_DEBUG, " BAR #%hhu (%s):", i, this->axi_pcie->isBAR64Bit(i) ? "64-bit" : "32-bit"); - SoapySDR_logf(SOAPY_SDR_DEBUG, " AXI Base: 0x%08lX", this->axi_pcie->getBARAXIAddress(i)); - SoapySDR_logf(SOAPY_SDR_DEBUG, " AXI Size: 0x%08lX", this->axi_pcie->getBARAXISize(i)); - SoapySDR_logf(SOAPY_SDR_DEBUG, " PCIe Base: 0x%016llX", this->axi_pcie->getBARPCIeAddress(i)); - SoapySDR_logf(SOAPY_SDR_DEBUG, " PCIe Size: 0x%08lX", this->axi_pcie->getBARPCIeSize(i)); + DLOGF(SOAPY_SDR_TRACE, " BAR #%hhu (%s):", i, this->axi_pcie->isBAR64Bit(i) ? "64-bit" : "32-bit"); + DLOGF(SOAPY_SDR_TRACE, " AXI Base: 0x%08lX", this->axi_pcie->getBARAXIAddress(i)); + DLOGF(SOAPY_SDR_TRACE, " AXI Size: 0x%08lX", this->axi_pcie->getBARAXISize(i)); + DLOGF(SOAPY_SDR_TRACE, " PCIe Base: 0x%016llX", this->axi_pcie->getBARPCIeAddress(i)); + DLOGF(SOAPY_SDR_TRACE, " PCIe Size: 0x%08lX", this->axi_pcie->getBARPCIeSize(i)); try { - SoapySDR_logf(SOAPY_SDR_DEBUG, " AXI Valid Start: 0x%08lX", this->axi_pcie->getBARAXIAddress(i, this->axi_pcie->getBARPCIeAddress(i))); + DLOGF(SOAPY_SDR_TRACE, " AXI Valid Start: 0x%08lX", this->axi_pcie->getBARAXIAddress(i, this->axi_pcie->getBARPCIeAddress(i))); if(pcie_done) - SoapySDR_logf(SOAPY_SDR_DEBUG, " AXI Valid End: 0x%08lX", this->axi_pcie->getBARAXIAddress(i, this->axi_pcie->getBARPCIeAddress(i)) + this->axi_pcie->getBARPCIeSize(i) - 1); + DLOGF(SOAPY_SDR_TRACE, " AXI Valid End: 0x%08lX", this->axi_pcie->getBARAXIAddress(i, this->axi_pcie->getBARPCIeAddress(i)) + this->axi_pcie->getBARPCIeSize(i) - 1); else - SoapySDR_logf(SOAPY_SDR_DEBUG, " AXI Valid End: 0x%08lX", this->axi_pcie->getBARAXIAddress(i) + this->axi_pcie->getBARAXISize(i) - 1); + DLOGF(SOAPY_SDR_TRACE, " AXI Valid End: 0x%08lX", this->axi_pcie->getBARAXIAddress(i) + this->axi_pcie->getBARAXISize(i) - 1); } catch(const std::runtime_error &e) { - SoapySDR_logf(SOAPY_SDR_DEBUG, " AXI Valid Start: N/A"); - SoapySDR_logf(SOAPY_SDR_DEBUG, " AXI Valid End: N/A"); + DLOGF(SOAPY_SDR_TRACE, " AXI Valid Start: N/A"); + DLOGF(SOAPY_SDR_TRACE, " AXI Valid End: N/A"); } } @@ -276,6 +370,13 @@ void SoapyIcyRadio::initPeripheralsPreClocks() // IRQ Controller this->axi_irq_ctrl->init(this->fd); + { + uint32_t ver = this->axi_irq_ctrl->getIPVersion(); + + DLOGF(SOAPY_SDR_TRACE, "AXI IRQ Controller:"); + DLOGF(SOAPY_SDR_TRACE, " IP Version: v%u.%u.%u", AXI_CORE_VERSION_MAJOR(ver), AXI_CORE_VERSION_MINOR(ver), AXI_CORE_VERSION_PATCH(ver)); + } + this->axi_irq_ctrl->configIRQ(AXIIRQCtrl::IRQNumber::AXI_DMAC_RF_TX0, AXIIRQCtrl::IRQMode::LEVEL_HIGH, AXI_IRQ_CTRL_REG_IRQ_CONFIG_IRQ_DEST_PCIE_MSI(0), false); this->axi_irq_ctrl->configIRQ(AXIIRQCtrl::IRQNumber::AXI_DMAC_RF_TX1, AXIIRQCtrl::IRQMode::LEVEL_HIGH, AXI_IRQ_CTRL_REG_IRQ_CONFIG_IRQ_DEST_PCIE_MSI(0), false); this->axi_irq_ctrl->configIRQ(AXIIRQCtrl::IRQNumber::AXI_DMAC_RF_RX0, AXIIRQCtrl::IRQMode::LEVEL_HIGH, AXI_IRQ_CTRL_REG_IRQ_CONFIG_IRQ_DEST_PCIE_MSI(0), false); @@ -293,19 +394,19 @@ void SoapyIcyRadio::initPeripheralsPreClocks() uint32_t id = this->axi_dmac[i]->getPeripheralID(); AXIDMAC::Capabilities caps = this->axi_dmac[i]->getCapabilities(); - SoapySDR_logf(SOAPY_SDR_DEBUG, "DMA Controller #%hhu:", i); - SoapySDR_logf(SOAPY_SDR_DEBUG, " IP Version: v%u.%u.%u", AXI_CORE_VERSION_MAJOR(ver), AXI_CORE_VERSION_MINOR(ver), AXI_CORE_VERSION_PATCH(ver)); - SoapySDR_logf(SOAPY_SDR_DEBUG, " Hardware ID: %u", id); - SoapySDR_logf(SOAPY_SDR_DEBUG, " Configuration / Capabilities:"); - SoapySDR_logf(SOAPY_SDR_DEBUG, " Destination data bus width: %u bits", caps.dest_data_width); - SoapySDR_logf(SOAPY_SDR_DEBUG, " Destination interface: %s", AXIDMAC::InterfaceTypeToString(caps.dest_interface).c_str()); - SoapySDR_logf(SOAPY_SDR_DEBUG, " Source data bus width: %u bits", caps.src_data_width); - SoapySDR_logf(SOAPY_SDR_DEBUG, " Source interface: %s", AXIDMAC::InterfaceTypeToString(caps.src_interface).c_str()); - SoapySDR_logf(SOAPY_SDR_DEBUG, " Bytes per burst: %u", caps.bytes_per_burst); - SoapySDR_logf(SOAPY_SDR_DEBUG, " Cyclic transfers: %s", caps.cyclic_support ? "Supported" : "Not supported"); - SoapySDR_logf(SOAPY_SDR_DEBUG, " Maximum transfer length: %u bytes", caps.max_transfer_size); - SoapySDR_logf(SOAPY_SDR_DEBUG, " Destination address mask: 0x%08X", caps.dest_addr_mask); - SoapySDR_logf(SOAPY_SDR_DEBUG, " Source address mask: 0x%08X", caps.src_addr_mask); + DLOGF(SOAPY_SDR_TRACE, "DMA Controller #%hhu:", i); + DLOGF(SOAPY_SDR_TRACE, " IP Version: v%u.%u.%u", AXI_CORE_VERSION_MAJOR(ver), AXI_CORE_VERSION_MINOR(ver), AXI_CORE_VERSION_PATCH(ver)); + DLOGF(SOAPY_SDR_TRACE, " Hardware ID: %u", id); + DLOGF(SOAPY_SDR_TRACE, " Configuration / Capabilities:"); + DLOGF(SOAPY_SDR_TRACE, " Destination data bus width: %u bits", caps.dest_data_width); + DLOGF(SOAPY_SDR_TRACE, " Destination interface: %s", AXIDMAC::InterfaceTypeToString(caps.dest_interface).c_str()); + DLOGF(SOAPY_SDR_TRACE, " Source data bus width: %u bits", caps.src_data_width); + DLOGF(SOAPY_SDR_TRACE, " Source interface: %s", AXIDMAC::InterfaceTypeToString(caps.src_interface).c_str()); + DLOGF(SOAPY_SDR_TRACE, " Bytes per burst: %u", caps.bytes_per_burst); + DLOGF(SOAPY_SDR_TRACE, " Cyclic transfers: %s", caps.cyclic_support ? "Supported" : "Not supported"); + DLOGF(SOAPY_SDR_TRACE, " Maximum transfer length: %u bytes", caps.max_transfer_size); + DLOGF(SOAPY_SDR_TRACE, " Destination address mask: 0x%08X", caps.dest_addr_mask); + DLOGF(SOAPY_SDR_TRACE, " Source address mask: 0x%08X", caps.src_addr_mask); } this->axi_dmac[AXI_DMAC_RF_TX0_INST]->init( @@ -350,8 +451,8 @@ void SoapyIcyRadio::initPeripheralsPreClocks() { uint32_t ver = this->axi_gpio[i]->getIPVersion(); - SoapySDR_logf(SOAPY_SDR_DEBUG, "GPIO Controller #%hhu:", i); - SoapySDR_logf(SOAPY_SDR_DEBUG, " IP Version: v%u.%u.%u", AXI_CORE_VERSION_MAJOR(ver), AXI_CORE_VERSION_MINOR(ver), AXI_CORE_VERSION_PATCH(ver)); + DLOGF(SOAPY_SDR_TRACE, "GPIO Controller #%hhu:", i); + DLOGF(SOAPY_SDR_TRACE, " IP Version: v%u.%u.%u", AXI_CORE_VERSION_MAJOR(ver), AXI_CORE_VERSION_MINOR(ver), AXI_CORE_VERSION_PATCH(ver)); } // I2C @@ -360,82 +461,86 @@ void SoapyIcyRadio::initPeripheralsPreClocks() this->axi_iic[AXI_IIC_EXP_INST]->init(AXI_ACLK_FREQ, AXIIIC::Speed::FAST); // I2C Scan - std::vector addrs; - - auto addrs_to_str = [](std::vector addrs) -> std::string + #ifdef TRACE_I2C { - std::stringstream s; + std::vector addrs; - s << " Found " << addrs.size() << " devices"; - - if(addrs.size() > 0) + auto addrs_to_str = [](std::vector addrs) -> std::string { - s << " ("; + std::stringstream s; + + s << " Found " << addrs.size() << " devices"; - for(size_t i = 0; i < addrs.size(); i++) + if(addrs.size() > 0) { - s << "0x" << std::hex << std::setw(2) << std::setfill('0') << std::uppercase << (size_t)addrs[i]; + s << " ("; - if(i < addrs.size() - 1) - s << ", "; - } + for(size_t i = 0; i < addrs.size(); i++) + { + s << "0x" << std::hex << std::setw(2) << std::setfill('0') << std::uppercase << (size_t)addrs[i]; - s << ")"; - } + if(i < addrs.size() - 1) + s << ", "; + } - return s.str(); - }; + s << ")"; + } - //// CODEC I2C - SoapySDR_logf(SOAPY_SDR_DEBUG, "Scanning CODEC I2C bus..."); + return s.str(); + }; - this->axi_gpio[AXI_GPIO_SYS_INST]->setValue(AXI_GPIO_CODEC_RSTn_BIT, AXIGPIO::Value::HIGH); - usleep(500); - addrs = this->axi_iic[AXI_IIC_CODEC_INST]->scan(); - this->axi_gpio[AXI_GPIO_SYS_INST]->setValue(AXI_GPIO_CODEC_RSTn_BIT, AXIGPIO::Value::LOW); + //// CODEC I2C + DLOGF(SOAPY_SDR_TRACE, "Scanning CODEC I2C bus..."); - SoapySDR_logf(SOAPY_SDR_DEBUG, "%s", addrs_to_str(addrs).c_str()); + this->axi_gpio[AXI_GPIO_SYS_INST]->setValue(AXI_GPIO_CODEC_RSTn_BIT, AXIGPIO::Value::HIGH); + usleep(500); + addrs = this->axi_iic[AXI_IIC_CODEC_INST]->scan(); + this->axi_gpio[AXI_GPIO_SYS_INST]->setValue(AXI_GPIO_CODEC_RSTn_BIT, AXIGPIO::Value::LOW); - //// SYS I2C - SoapySDR_logf(SOAPY_SDR_DEBUG, "Scanning SYS I2C bus..."); + DLOGF(SOAPY_SDR_TRACE, "%s", addrs_to_str(addrs).c_str()); - this->axi_gpio[AXI_GPIO_SYS_INST]->setValue(AXI_GPIO_PM_I2C_EN_BIT, AXIGPIO::Value::HIGH); - usleep(500); - addrs = this->axi_iic[AXI_IIC_SYS_INST]->scan(); - this->axi_gpio[AXI_GPIO_SYS_INST]->setValue(AXI_GPIO_PM_I2C_EN_BIT, AXIGPIO::Value::LOW); + //// SYS I2C + DLOGF(SOAPY_SDR_TRACE, "Scanning SYS I2C bus..."); - SoapySDR_logf(SOAPY_SDR_DEBUG, "%s", addrs_to_str(addrs).c_str()); + this->axi_gpio[AXI_GPIO_SYS_INST]->setValue(AXI_GPIO_PM_I2C_EN_BIT, AXIGPIO::Value::HIGH); + usleep(500); + addrs = this->axi_iic[AXI_IIC_SYS_INST]->scan(); + this->axi_gpio[AXI_GPIO_SYS_INST]->setValue(AXI_GPIO_PM_I2C_EN_BIT, AXIGPIO::Value::LOW); - //// EXP I2C - SoapySDR_logf(SOAPY_SDR_DEBUG, "Scanning EXP I2C bus..."); + DLOGF(SOAPY_SDR_TRACE, "%s", addrs_to_str(addrs).c_str()); - addrs = this->axi_iic[AXI_IIC_EXP_INST]->scan(); + //// EXP I2C + DLOGF(SOAPY_SDR_TRACE, "Scanning EXP I2C bus..."); - SoapySDR_logf(SOAPY_SDR_DEBUG, "%s", addrs_to_str(addrs).c_str()); + addrs = this->axi_iic[AXI_IIC_EXP_INST]->scan(); - ///////////////// EXP TEST - if(addrs.size() > 0 && addrs.at(0) == 0x22) - { - AuxMCU *exp = new AuxMCU( - { - .controller = this->axi_iic[AXI_IIC_EXP_INST], - .addr = 0x22, - } - ); + DLOGF(SOAPY_SDR_TRACE, "%s", addrs_to_str(addrs).c_str()); - SoapySDR_logf(SOAPY_SDR_DEBUG, "reg0 before %02X", exp->readReg(0x00)); - exp->writeReg(0x00, 0x01); - SoapySDR_logf(SOAPY_SDR_DEBUG, "reg0 after %02X", exp->readReg(0x00)); - SoapySDR_logf(SOAPY_SDR_DEBUG, "reg1 before %02X", exp->readReg(0x01)); - exp->writeReg(0x01, 0x01); - SoapySDR_logf(SOAPY_SDR_DEBUG, "reg1 after %02X", exp->readReg(0x01)); + ///////////////// EXP TEST + if(addrs.size() > 0 && addrs.at(0) == 0x22) + { + AuxMCU *exp = new AuxMCU( + { + .controller = this->axi_iic[AXI_IIC_EXP_INST], + .addr = 0x22, + } + ); + + DLOGF(SOAPY_SDR_DEBUG, "reg0 before %02X", exp->readReg(0x00)); + exp->writeReg(0x00, 0x01); + DLOGF(SOAPY_SDR_DEBUG, "reg0 after %02X", exp->readReg(0x00)); + DLOGF(SOAPY_SDR_DEBUG, "reg1 before %02X", exp->readReg(0x01)); + exp->writeReg(0x01, 0x01); + DLOGF(SOAPY_SDR_DEBUG, "reg1 after %02X", exp->readReg(0x01)); - SoapySDR_logf(SOAPY_SDR_DEBUG, "rom0 %02X", exp->readROM(0x00)); - SoapySDR_logf(SOAPY_SDR_DEBUG, "rom1 %02X", exp->readROM(0x01)); + DLOGF(SOAPY_SDR_DEBUG, "rom0 %02X", exp->readROM(0x00)); + DLOGF(SOAPY_SDR_DEBUG, "rom1 %02X", exp->readROM(0x01)); - delete exp; + delete exp; + } + ///////////////// EXP TEST } - ///////////////// EXP TEST + #endif // SPI for(uint8_t i = 0; i < AXI_SPI_NUM_INSTANCES; i++) @@ -443,12 +548,12 @@ void SoapyIcyRadio::initPeripheralsPreClocks() uint32_t ver = this->axi_spi[i]->getIPVersion(); AXISPI::Capabilities caps = this->axi_spi[i]->getCapabilities(); - SoapySDR_logf(SOAPY_SDR_DEBUG, "(Q)SPI Controller #%hhu:", i); - SoapySDR_logf(SOAPY_SDR_DEBUG, " IP Version: v%u.%u.%u", AXI_CORE_VERSION_MAJOR(ver), AXI_CORE_VERSION_MINOR(ver), AXI_CORE_VERSION_PATCH(ver)); - SoapySDR_logf(SOAPY_SDR_DEBUG, " Configuration / Capabilities:"); - SoapySDR_logf(SOAPY_SDR_DEBUG, " Dual IO: %s", caps.dual_io_supported ? "Supported" : "Not supported"); - SoapySDR_logf(SOAPY_SDR_DEBUG, " Quad IO: %s", caps.quad_io_supported ? "Supported" : "Not supported"); - SoapySDR_logf(SOAPY_SDR_DEBUG, " Memory-mapped (XIP) accesses: %s", caps.mmio_supported ? "Supported" : "Not supported"); + DLOGF(SOAPY_SDR_TRACE, "(Q)SPI Controller #%hhu:", i); + DLOGF(SOAPY_SDR_TRACE, " IP Version: v%u.%u.%u", AXI_CORE_VERSION_MAJOR(ver), AXI_CORE_VERSION_MINOR(ver), AXI_CORE_VERSION_PATCH(ver)); + DLOGF(SOAPY_SDR_TRACE, " Configuration / Capabilities:"); + DLOGF(SOAPY_SDR_TRACE, " Dual IO: %s", caps.dual_io_supported ? "Supported" : "Not supported"); + DLOGF(SOAPY_SDR_TRACE, " Quad IO: %s", caps.quad_io_supported ? "Supported" : "Not supported"); + DLOGF(SOAPY_SDR_TRACE, " Memory-mapped (XIP) accesses: %s", caps.mmio_supported ? "Supported" : "Not supported"); AXISPI::Mode mode; AXISPI::BitOrder bit_order; @@ -487,7 +592,7 @@ void SoapyIcyRadio::initPeripheralsPreClocks() break; default: { - SoapySDR_logf(SOAPY_SDR_DEBUG, " Unknown controller, leaving unconfigured"); + DLOGF(SOAPY_SDR_TRACE, " Unknown controller, leaving unconfigured"); continue; } @@ -500,9 +605,9 @@ void SoapyIcyRadio::initPeripheralsPreClocks() this->axi_spi[i]->enableClock(); this->axi_spi[i]->enable(); - SoapySDR_logf(SOAPY_SDR_DEBUG, " SCK Divider input frequency: %llu Hz", input_freq); - SoapySDR_logf(SOAPY_SDR_DEBUG, " Requested SCK frequency: %llu Hz", sck_freq); - SoapySDR_logf(SOAPY_SDR_DEBUG, " Achieved SCK frequency: %llu Hz", this->axi_spi[i]->getClockFrequency(input_freq)); + DLOGF(SOAPY_SDR_TRACE, " SCK Divider input frequency: %llu Hz", input_freq); + DLOGF(SOAPY_SDR_TRACE, " Requested SCK frequency: %llu Hz", sck_freq); + DLOGF(SOAPY_SDR_TRACE, " Achieved SCK frequency: %llu Hz", this->axi_spi[i]->getClockFrequency(input_freq)); } // Peripherals @@ -516,8 +621,8 @@ void SoapyIcyRadio::initPeripheralsPreClocks() } ); - SoapySDR_logf(SOAPY_SDR_DEBUG, "PMC Unique ID: %s", this->pmc->getUniqueID().c_str()); - SoapySDR_logf(SOAPY_SDR_DEBUG, "PMC Firmware Version: v%hu", this->pmc->getFirmwareVersion()); + DLOGF(SOAPY_SDR_DEBUG, "PMC Unique ID: %s", this->pmc->getUniqueID().c_str()); + DLOGF(SOAPY_SDR_DEBUG, "PMC Firmware Version: v%hu", this->pmc->getFirmwareVersion()); this->vin_reg = new LT7182S( { @@ -532,9 +637,9 @@ void SoapyIcyRadio::initPeripheralsPreClocks() this->vin_reg->init(); - SoapySDR_logf(SOAPY_SDR_DEBUG, "VIN Regulator P/N: %s %s", this->vin_reg->readManufacturerID().c_str(), this->vin_reg->readManufacturerModel().c_str()); - SoapySDR_logf(SOAPY_SDR_DEBUG, "VIN Regulator Revision: %hhu", this->vin_reg->readManufacturerRevision()); - SoapySDR_logf(SOAPY_SDR_DEBUG, "VIN Regulator Serial: %s", this->vin_reg->readManufacturerSerial().c_str()); + DLOGF(SOAPY_SDR_DEBUG, "VIN Regulator P/N: %s %s", this->vin_reg->readManufacturerID().c_str(), this->vin_reg->readManufacturerModel().c_str()); + DLOGF(SOAPY_SDR_DEBUG, "VIN Regulator Revision: %hhu", this->vin_reg->readManufacturerRevision()); + DLOGF(SOAPY_SDR_DEBUG, "VIN Regulator Serial: %s", this->vin_reg->readManufacturerSerial().c_str()); this->spi_flash = new SPIFlash( { @@ -543,248 +648,248 @@ void SoapyIcyRadio::initPeripheralsPreClocks() } ); - SoapySDR_logf(SOAPY_SDR_DEBUG, "SPI Flash JEDEC ID: 0x%06X", this->spi_flash->readJEDECID()); - SoapySDR_logf(SOAPY_SDR_DEBUG, "SPI Flash device name: %s", this->spi_flash->getDeviceName().c_str()); + DLOGF(SOAPY_SDR_DEBUG, "SPI Flash JEDEC ID: 0x%06X", this->spi_flash->readJEDECID()); + DLOGF(SOAPY_SDR_DEBUG, "SPI Flash device name: %s", this->spi_flash->getDeviceName().c_str()); //////////////////////// SPI FLASH TEST - { - uint8_t buf[16 * 4]; - AXISPI::MMIOConfig mmio; - AXISPI::MMIOStats stats; - std::stringstream s; - - // Software read - this->axi_spi[AXI_QUAD_SPI_MM0_FLASH_INST]->disableMMIO(); - this->spi_flash->read(0x000000, buf, sizeof(buf)); - - s.str(""); - SoapySDR_logf(SOAPY_SDR_DEBUG, "SPI Flash software read:"); - for(size_t i = 0; i < sizeof(buf); i++) - { - s << std::hex << std::setw(2) << std::setfill('0') << (size_t)buf[i] << " "; - - if(i % 16 == 15) - { - SoapySDR_logf(SOAPY_SDR_DEBUG, "%s", s.str().c_str()); - s.str(""); - } - } - - // MMIO QIO read - mmio.rd_instr_io_mode = AXISPI::IOMode::SINGLE; - mmio.rd_instr = SPI_FLASH_CMD_READ_FAST_QIO; - mmio.addr_io_mode = AXISPI::IOMode::QUAD; - mmio.addr_bytes = 3; - mmio.mode_bits = 0xF0; - mmio.mode_bits_en = true; - mmio.cont_read_en = false; - mmio.dummy_io_mode = AXISPI::IOMode::QUAD; - mmio.dummy_bytes = 2; - mmio.data_io_mode = AXISPI::IOMode::QUAD; - mmio.cs_high_wait = 0; - mmio.cs_low_wait = 0; - mmio.cs_mask = AXI_QUAD_SPI_MM0_FLASH_SS; - - this->axi_spi[AXI_QUAD_SPI_MM0_FLASH_INST]->configMMIOMode(mmio); - this->axi_spi[AXI_QUAD_SPI_MM0_FLASH_INST]->enableMMIO(); - - s.str(""); - SoapySDR_logf(SOAPY_SDR_DEBUG, "SPI Flash MMIO QIO read:"); - for(size_t i = 0; i < sizeof(buf); i += 4) - { - uint32_t word = *reinterpret_cast(this->mm_axi_flash->getVirt(i)); - - s << std::hex << std::setw(2) << std::setfill('0') << (size_t)((word >> 0) & 0xFF) << " "; - s << std::hex << std::setw(2) << std::setfill('0') << (size_t)((word >> 8) & 0xFF) << " "; - s << std::hex << std::setw(2) << std::setfill('0') << (size_t)((word >> 16) & 0xFF) << " "; - s << std::hex << std::setw(2) << std::setfill('0') << (size_t)((word >> 24) & 0xFF) << " "; - - if(i % 16 == 12) - { - SoapySDR_logf(SOAPY_SDR_DEBUG, "%s", s.str().c_str()); - s.str(""); - } - } - - this->axi_spi[AXI_QUAD_SPI_MM0_FLASH_INST]->disableMMIO(); - - stats = this->axi_spi[AXI_QUAD_SPI_MM0_FLASH_INST]->getMMIOStats(); - - SoapySDR_logf(SOAPY_SDR_DEBUG, "SPI Flash MMIO read request count: %lu", stats.rd_req_cnt); - SoapySDR_logf(SOAPY_SDR_DEBUG, "SPI Flash MMIO continuous read request count: %lu (%.3f %%)", stats.cont_rd_req_cnt, (double)stats.cont_rd_req_cnt / (double)stats.rd_req_cnt * 100.0); - - // MMIO QOUT read - mmio.rd_instr_io_mode = AXISPI::IOMode::SINGLE; - mmio.rd_instr = SPI_FLASH_CMD_READ_FAST_QOUT; - mmio.addr_io_mode = AXISPI::IOMode::SINGLE; - mmio.addr_bytes = 3; - mmio.mode_bits = 0xF0; - mmio.mode_bits_en = false; - mmio.cont_read_en = false; - mmio.dummy_io_mode = AXISPI::IOMode::SINGLE; - mmio.dummy_bytes = 1; - mmio.data_io_mode = AXISPI::IOMode::QUAD; - mmio.cs_high_wait = 0; - mmio.cs_low_wait = 0; - mmio.cs_mask = AXI_QUAD_SPI_MM0_FLASH_SS; - - this->axi_spi[AXI_QUAD_SPI_MM0_FLASH_INST]->configMMIOMode(mmio); - this->axi_spi[AXI_QUAD_SPI_MM0_FLASH_INST]->enableMMIO(); - - s.str(""); - SoapySDR_logf(SOAPY_SDR_DEBUG, "SPI Flash MMIO QOUT read:"); - for(size_t i = 0; i < sizeof(buf); i += 4) - { - uint32_t word = *reinterpret_cast(this->mm_axi_flash->getVirt(i)); - - s << std::hex << std::setw(2) << std::setfill('0') << (size_t)((word >> 0) & 0xFF) << " "; - s << std::hex << std::setw(2) << std::setfill('0') << (size_t)((word >> 8) & 0xFF) << " "; - s << std::hex << std::setw(2) << std::setfill('0') << (size_t)((word >> 16) & 0xFF) << " "; - s << std::hex << std::setw(2) << std::setfill('0') << (size_t)((word >> 24) & 0xFF) << " "; - - if(i % 16 == 12) - { - SoapySDR_logf(SOAPY_SDR_DEBUG, "%s", s.str().c_str()); - s.str(""); - } - } - - this->axi_spi[AXI_QUAD_SPI_MM0_FLASH_INST]->disableMMIO(); - - stats = this->axi_spi[AXI_QUAD_SPI_MM0_FLASH_INST]->getMMIOStats(); - - SoapySDR_logf(SOAPY_SDR_DEBUG, "SPI Flash MMIO read request count: %lu", stats.rd_req_cnt); - SoapySDR_logf(SOAPY_SDR_DEBUG, "SPI Flash MMIO continuous read request count: %lu (%.3f %%)", stats.cont_rd_req_cnt, (double)stats.cont_rd_req_cnt / (double)stats.rd_req_cnt * 100.0); - - // MMIO DIO read - mmio.rd_instr_io_mode = AXISPI::IOMode::SINGLE; - mmio.rd_instr = SPI_FLASH_CMD_READ_FAST_DIO; - mmio.addr_io_mode = AXISPI::IOMode::DUAL; - mmio.addr_bytes = 3; - mmio.mode_bits = 0xF0; - mmio.mode_bits_en = true; - mmio.cont_read_en = false; - mmio.dummy_io_mode = AXISPI::IOMode::QUAD; - mmio.dummy_bytes = 0; - mmio.data_io_mode = AXISPI::IOMode::DUAL; - mmio.cs_high_wait = 0; - mmio.cs_low_wait = 0; - mmio.cs_mask = AXI_QUAD_SPI_MM0_FLASH_SS; - - this->axi_spi[AXI_QUAD_SPI_MM0_FLASH_INST]->configMMIOMode(mmio); - this->axi_spi[AXI_QUAD_SPI_MM0_FLASH_INST]->enableMMIO(); - - s.str(""); - SoapySDR_logf(SOAPY_SDR_DEBUG, "SPI Flash MMIO DIO read:"); - for(size_t i = 0; i < sizeof(buf); i += 4) - { - uint32_t word = *reinterpret_cast(this->mm_axi_flash->getVirt(i)); - - s << std::hex << std::setw(2) << std::setfill('0') << (size_t)((word >> 0) & 0xFF) << " "; - s << std::hex << std::setw(2) << std::setfill('0') << (size_t)((word >> 8) & 0xFF) << " "; - s << std::hex << std::setw(2) << std::setfill('0') << (size_t)((word >> 16) & 0xFF) << " "; - s << std::hex << std::setw(2) << std::setfill('0') << (size_t)((word >> 24) & 0xFF) << " "; - - if(i % 16 == 12) - { - SoapySDR_logf(SOAPY_SDR_DEBUG, "%s", s.str().c_str()); - s.str(""); - } - } - - this->axi_spi[AXI_QUAD_SPI_MM0_FLASH_INST]->disableMMIO(); - - stats = this->axi_spi[AXI_QUAD_SPI_MM0_FLASH_INST]->getMMIOStats(); - - SoapySDR_logf(SOAPY_SDR_DEBUG, "SPI Flash MMIO read request count: %lu", stats.rd_req_cnt); - SoapySDR_logf(SOAPY_SDR_DEBUG, "SPI Flash MMIO continuous read request count: %lu (%.3f %%)", stats.cont_rd_req_cnt, (double)stats.cont_rd_req_cnt / (double)stats.rd_req_cnt * 100.0); - - // MMIO DOUT read - mmio.rd_instr_io_mode = AXISPI::IOMode::SINGLE; - mmio.rd_instr = SPI_FLASH_CMD_READ_FAST_DOUT; - mmio.addr_io_mode = AXISPI::IOMode::SINGLE; - mmio.addr_bytes = 3; - mmio.mode_bits = 0xF0; - mmio.mode_bits_en = false; - mmio.cont_read_en = false; - mmio.dummy_io_mode = AXISPI::IOMode::SINGLE; - mmio.dummy_bytes = 1; - mmio.data_io_mode = AXISPI::IOMode::DUAL; - mmio.cs_high_wait = 0; - mmio.cs_low_wait = 0; - mmio.cs_mask = AXI_QUAD_SPI_MM0_FLASH_SS; - - this->axi_spi[AXI_QUAD_SPI_MM0_FLASH_INST]->configMMIOMode(mmio); - this->axi_spi[AXI_QUAD_SPI_MM0_FLASH_INST]->enableMMIO(); - - s.str(""); - SoapySDR_logf(SOAPY_SDR_DEBUG, "SPI Flash MMIO DOUT read:"); - for(size_t i = 0; i < sizeof(buf); i += 4) - { - uint32_t word = *reinterpret_cast(this->mm_axi_flash->getVirt(i)); - - s << std::hex << std::setw(2) << std::setfill('0') << (size_t)((word >> 0) & 0xFF) << " "; - s << std::hex << std::setw(2) << std::setfill('0') << (size_t)((word >> 8) & 0xFF) << " "; - s << std::hex << std::setw(2) << std::setfill('0') << (size_t)((word >> 16) & 0xFF) << " "; - s << std::hex << std::setw(2) << std::setfill('0') << (size_t)((word >> 24) & 0xFF) << " "; - - if(i % 16 == 12) - { - SoapySDR_logf(SOAPY_SDR_DEBUG, "%s", s.str().c_str()); - s.str(""); - } - } - - this->axi_spi[AXI_QUAD_SPI_MM0_FLASH_INST]->disableMMIO(); - - stats = this->axi_spi[AXI_QUAD_SPI_MM0_FLASH_INST]->getMMIOStats(); - - SoapySDR_logf(SOAPY_SDR_DEBUG, "SPI Flash MMIO read request count: %lu", stats.rd_req_cnt); - SoapySDR_logf(SOAPY_SDR_DEBUG, "SPI Flash MMIO continuous read request count: %lu (%.3f %%)", stats.cont_rd_req_cnt, (double)stats.cont_rd_req_cnt / (double)stats.rd_req_cnt * 100.0); - - // MMIO read - mmio.rd_instr_io_mode = AXISPI::IOMode::SINGLE; - mmio.rd_instr = SPI_FLASH_CMD_READ_FAST; - mmio.addr_io_mode = AXISPI::IOMode::SINGLE; - mmio.addr_bytes = 3; - mmio.mode_bits = 0xF0; - mmio.mode_bits_en = false; - mmio.cont_read_en = false; - mmio.dummy_io_mode = AXISPI::IOMode::SINGLE; - mmio.dummy_bytes = 1; - mmio.data_io_mode = AXISPI::IOMode::SINGLE; - mmio.cs_high_wait = 0; - mmio.cs_low_wait = 0; - mmio.cs_mask = AXI_QUAD_SPI_MM0_FLASH_SS; - - this->axi_spi[AXI_QUAD_SPI_MM0_FLASH_INST]->configMMIOMode(mmio); - this->axi_spi[AXI_QUAD_SPI_MM0_FLASH_INST]->enableMMIO(); - - s.str(""); - SoapySDR_logf(SOAPY_SDR_DEBUG, "SPI Flash MMIO read:"); - for(size_t i = 0; i < sizeof(buf); i += 4) - { - uint32_t word = *reinterpret_cast(this->mm_axi_flash->getVirt(i)); - - s << std::hex << std::setw(2) << std::setfill('0') << (size_t)((word >> 0) & 0xFF) << " "; - s << std::hex << std::setw(2) << std::setfill('0') << (size_t)((word >> 8) & 0xFF) << " "; - s << std::hex << std::setw(2) << std::setfill('0') << (size_t)((word >> 16) & 0xFF) << " "; - s << std::hex << std::setw(2) << std::setfill('0') << (size_t)((word >> 24) & 0xFF) << " "; - - if(i % 16 == 12) - { - SoapySDR_logf(SOAPY_SDR_DEBUG, "%s", s.str().c_str()); - s.str(""); - } - } - - this->axi_spi[AXI_QUAD_SPI_MM0_FLASH_INST]->disableMMIO(); - - stats = this->axi_spi[AXI_QUAD_SPI_MM0_FLASH_INST]->getMMIOStats(); - - SoapySDR_logf(SOAPY_SDR_DEBUG, "SPI Flash MMIO read request count: %lu", stats.rd_req_cnt); - SoapySDR_logf(SOAPY_SDR_DEBUG, "SPI Flash MMIO continuous read request count: %lu (%.3f %%)", stats.cont_rd_req_cnt, (double)stats.cont_rd_req_cnt / (double)stats.rd_req_cnt * 100.0); - } + // { + // uint8_t buf[16 * 4]; + // AXISPI::MMIOConfig mmio; + // AXISPI::MMIOStats stats; + // std::stringstream s; + + // // Software read + // this->axi_spi[AXI_QUAD_SPI_MM0_FLASH_INST]->disableMMIO(); + // this->spi_flash->read(0x000000, buf, sizeof(buf)); + + // s.str(""); + // DLOGF(SOAPY_SDR_DEBUG, "SPI Flash software read:"); + // for(size_t i = 0; i < sizeof(buf); i++) + // { + // s << std::hex << std::setw(2) << std::setfill('0') << (size_t)buf[i] << " "; + + // if(i % 16 == 15) + // { + // DLOGF(SOAPY_SDR_DEBUG, "%s", s.str().c_str()); + // s.str(""); + // } + // } + + // // MMIO QIO read + // mmio.rd_instr_io_mode = AXISPI::IOMode::SINGLE; + // mmio.rd_instr = SPI_FLASH_CMD_READ_FAST_QIO; + // mmio.addr_io_mode = AXISPI::IOMode::QUAD; + // mmio.addr_bytes = 3; + // mmio.mode_bits = 0xF0; + // mmio.mode_bits_en = true; + // mmio.cont_read_en = false; + // mmio.dummy_io_mode = AXISPI::IOMode::QUAD; + // mmio.dummy_bytes = 2; + // mmio.data_io_mode = AXISPI::IOMode::QUAD; + // mmio.cs_high_wait = 0; + // mmio.cs_low_wait = 0; + // mmio.cs_mask = AXI_QUAD_SPI_MM0_FLASH_SS; + + // this->axi_spi[AXI_QUAD_SPI_MM0_FLASH_INST]->configMMIOMode(mmio); + // this->axi_spi[AXI_QUAD_SPI_MM0_FLASH_INST]->enableMMIO(); + + // s.str(""); + // DLOGF(SOAPY_SDR_DEBUG, "SPI Flash MMIO QIO read:"); + // for(size_t i = 0; i < sizeof(buf); i += 4) + // { + // uint32_t word = *reinterpret_cast(this->mm_axi_flash->getVirt(i)); + + // s << std::hex << std::setw(2) << std::setfill('0') << (size_t)((word >> 0) & 0xFF) << " "; + // s << std::hex << std::setw(2) << std::setfill('0') << (size_t)((word >> 8) & 0xFF) << " "; + // s << std::hex << std::setw(2) << std::setfill('0') << (size_t)((word >> 16) & 0xFF) << " "; + // s << std::hex << std::setw(2) << std::setfill('0') << (size_t)((word >> 24) & 0xFF) << " "; + + // if(i % 16 == 12) + // { + // DLOGF(SOAPY_SDR_DEBUG, "%s", s.str().c_str()); + // s.str(""); + // } + // } + + // this->axi_spi[AXI_QUAD_SPI_MM0_FLASH_INST]->disableMMIO(); + + // stats = this->axi_spi[AXI_QUAD_SPI_MM0_FLASH_INST]->getMMIOStats(); + + // DLOGF(SOAPY_SDR_DEBUG, "SPI Flash MMIO read request count: %lu", stats.rd_req_cnt); + // DLOGF(SOAPY_SDR_DEBUG, "SPI Flash MMIO continuous read request count: %lu (%.3f %%)", stats.cont_rd_req_cnt, (double)stats.cont_rd_req_cnt / (double)stats.rd_req_cnt * 100.0); + + // // MMIO QOUT read + // mmio.rd_instr_io_mode = AXISPI::IOMode::SINGLE; + // mmio.rd_instr = SPI_FLASH_CMD_READ_FAST_QOUT; + // mmio.addr_io_mode = AXISPI::IOMode::SINGLE; + // mmio.addr_bytes = 3; + // mmio.mode_bits = 0xF0; + // mmio.mode_bits_en = false; + // mmio.cont_read_en = false; + // mmio.dummy_io_mode = AXISPI::IOMode::SINGLE; + // mmio.dummy_bytes = 1; + // mmio.data_io_mode = AXISPI::IOMode::QUAD; + // mmio.cs_high_wait = 0; + // mmio.cs_low_wait = 0; + // mmio.cs_mask = AXI_QUAD_SPI_MM0_FLASH_SS; + + // this->axi_spi[AXI_QUAD_SPI_MM0_FLASH_INST]->configMMIOMode(mmio); + // this->axi_spi[AXI_QUAD_SPI_MM0_FLASH_INST]->enableMMIO(); + + // s.str(""); + // DLOGF(SOAPY_SDR_DEBUG, "SPI Flash MMIO QOUT read:"); + // for(size_t i = 0; i < sizeof(buf); i += 4) + // { + // uint32_t word = *reinterpret_cast(this->mm_axi_flash->getVirt(i)); + + // s << std::hex << std::setw(2) << std::setfill('0') << (size_t)((word >> 0) & 0xFF) << " "; + // s << std::hex << std::setw(2) << std::setfill('0') << (size_t)((word >> 8) & 0xFF) << " "; + // s << std::hex << std::setw(2) << std::setfill('0') << (size_t)((word >> 16) & 0xFF) << " "; + // s << std::hex << std::setw(2) << std::setfill('0') << (size_t)((word >> 24) & 0xFF) << " "; + + // if(i % 16 == 12) + // { + // DLOGF(SOAPY_SDR_DEBUG, "%s", s.str().c_str()); + // s.str(""); + // } + // } + + // this->axi_spi[AXI_QUAD_SPI_MM0_FLASH_INST]->disableMMIO(); + + // stats = this->axi_spi[AXI_QUAD_SPI_MM0_FLASH_INST]->getMMIOStats(); + + // DLOGF(SOAPY_SDR_DEBUG, "SPI Flash MMIO read request count: %lu", stats.rd_req_cnt); + // DLOGF(SOAPY_SDR_DEBUG, "SPI Flash MMIO continuous read request count: %lu (%.3f %%)", stats.cont_rd_req_cnt, (double)stats.cont_rd_req_cnt / (double)stats.rd_req_cnt * 100.0); + + // // MMIO DIO read + // mmio.rd_instr_io_mode = AXISPI::IOMode::SINGLE; + // mmio.rd_instr = SPI_FLASH_CMD_READ_FAST_DIO; + // mmio.addr_io_mode = AXISPI::IOMode::DUAL; + // mmio.addr_bytes = 3; + // mmio.mode_bits = 0xF0; + // mmio.mode_bits_en = true; + // mmio.cont_read_en = false; + // mmio.dummy_io_mode = AXISPI::IOMode::QUAD; + // mmio.dummy_bytes = 0; + // mmio.data_io_mode = AXISPI::IOMode::DUAL; + // mmio.cs_high_wait = 0; + // mmio.cs_low_wait = 0; + // mmio.cs_mask = AXI_QUAD_SPI_MM0_FLASH_SS; + + // this->axi_spi[AXI_QUAD_SPI_MM0_FLASH_INST]->configMMIOMode(mmio); + // this->axi_spi[AXI_QUAD_SPI_MM0_FLASH_INST]->enableMMIO(); + + // s.str(""); + // DLOGF(SOAPY_SDR_DEBUG, "SPI Flash MMIO DIO read:"); + // for(size_t i = 0; i < sizeof(buf); i += 4) + // { + // uint32_t word = *reinterpret_cast(this->mm_axi_flash->getVirt(i)); + + // s << std::hex << std::setw(2) << std::setfill('0') << (size_t)((word >> 0) & 0xFF) << " "; + // s << std::hex << std::setw(2) << std::setfill('0') << (size_t)((word >> 8) & 0xFF) << " "; + // s << std::hex << std::setw(2) << std::setfill('0') << (size_t)((word >> 16) & 0xFF) << " "; + // s << std::hex << std::setw(2) << std::setfill('0') << (size_t)((word >> 24) & 0xFF) << " "; + + // if(i % 16 == 12) + // { + // DLOGF(SOAPY_SDR_DEBUG, "%s", s.str().c_str()); + // s.str(""); + // } + // } + + // this->axi_spi[AXI_QUAD_SPI_MM0_FLASH_INST]->disableMMIO(); + + // stats = this->axi_spi[AXI_QUAD_SPI_MM0_FLASH_INST]->getMMIOStats(); + + // DLOGF(SOAPY_SDR_DEBUG, "SPI Flash MMIO read request count: %lu", stats.rd_req_cnt); + // DLOGF(SOAPY_SDR_DEBUG, "SPI Flash MMIO continuous read request count: %lu (%.3f %%)", stats.cont_rd_req_cnt, (double)stats.cont_rd_req_cnt / (double)stats.rd_req_cnt * 100.0); + + // // MMIO DOUT read + // mmio.rd_instr_io_mode = AXISPI::IOMode::SINGLE; + // mmio.rd_instr = SPI_FLASH_CMD_READ_FAST_DOUT; + // mmio.addr_io_mode = AXISPI::IOMode::SINGLE; + // mmio.addr_bytes = 3; + // mmio.mode_bits = 0xF0; + // mmio.mode_bits_en = false; + // mmio.cont_read_en = false; + // mmio.dummy_io_mode = AXISPI::IOMode::SINGLE; + // mmio.dummy_bytes = 1; + // mmio.data_io_mode = AXISPI::IOMode::DUAL; + // mmio.cs_high_wait = 0; + // mmio.cs_low_wait = 0; + // mmio.cs_mask = AXI_QUAD_SPI_MM0_FLASH_SS; + + // this->axi_spi[AXI_QUAD_SPI_MM0_FLASH_INST]->configMMIOMode(mmio); + // this->axi_spi[AXI_QUAD_SPI_MM0_FLASH_INST]->enableMMIO(); + + // s.str(""); + // DLOGF(SOAPY_SDR_DEBUG, "SPI Flash MMIO DOUT read:"); + // for(size_t i = 0; i < sizeof(buf); i += 4) + // { + // uint32_t word = *reinterpret_cast(this->mm_axi_flash->getVirt(i)); + + // s << std::hex << std::setw(2) << std::setfill('0') << (size_t)((word >> 0) & 0xFF) << " "; + // s << std::hex << std::setw(2) << std::setfill('0') << (size_t)((word >> 8) & 0xFF) << " "; + // s << std::hex << std::setw(2) << std::setfill('0') << (size_t)((word >> 16) & 0xFF) << " "; + // s << std::hex << std::setw(2) << std::setfill('0') << (size_t)((word >> 24) & 0xFF) << " "; + + // if(i % 16 == 12) + // { + // DLOGF(SOAPY_SDR_DEBUG, "%s", s.str().c_str()); + // s.str(""); + // } + // } + + // this->axi_spi[AXI_QUAD_SPI_MM0_FLASH_INST]->disableMMIO(); + + // stats = this->axi_spi[AXI_QUAD_SPI_MM0_FLASH_INST]->getMMIOStats(); + + // DLOGF(SOAPY_SDR_DEBUG, "SPI Flash MMIO read request count: %lu", stats.rd_req_cnt); + // DLOGF(SOAPY_SDR_DEBUG, "SPI Flash MMIO continuous read request count: %lu (%.3f %%)", stats.cont_rd_req_cnt, (double)stats.cont_rd_req_cnt / (double)stats.rd_req_cnt * 100.0); + + // // MMIO read + // mmio.rd_instr_io_mode = AXISPI::IOMode::SINGLE; + // mmio.rd_instr = SPI_FLASH_CMD_READ_FAST; + // mmio.addr_io_mode = AXISPI::IOMode::SINGLE; + // mmio.addr_bytes = 3; + // mmio.mode_bits = 0xF0; + // mmio.mode_bits_en = false; + // mmio.cont_read_en = false; + // mmio.dummy_io_mode = AXISPI::IOMode::SINGLE; + // mmio.dummy_bytes = 1; + // mmio.data_io_mode = AXISPI::IOMode::SINGLE; + // mmio.cs_high_wait = 0; + // mmio.cs_low_wait = 0; + // mmio.cs_mask = AXI_QUAD_SPI_MM0_FLASH_SS; + + // this->axi_spi[AXI_QUAD_SPI_MM0_FLASH_INST]->configMMIOMode(mmio); + // this->axi_spi[AXI_QUAD_SPI_MM0_FLASH_INST]->enableMMIO(); + + // s.str(""); + // DLOGF(SOAPY_SDR_DEBUG, "SPI Flash MMIO read:"); + // for(size_t i = 0; i < sizeof(buf); i += 4) + // { + // uint32_t word = *reinterpret_cast(this->mm_axi_flash->getVirt(i)); + + // s << std::hex << std::setw(2) << std::setfill('0') << (size_t)((word >> 0) & 0xFF) << " "; + // s << std::hex << std::setw(2) << std::setfill('0') << (size_t)((word >> 8) & 0xFF) << " "; + // s << std::hex << std::setw(2) << std::setfill('0') << (size_t)((word >> 16) & 0xFF) << " "; + // s << std::hex << std::setw(2) << std::setfill('0') << (size_t)((word >> 24) & 0xFF) << " "; + + // if(i % 16 == 12) + // { + // DLOGF(SOAPY_SDR_DEBUG, "%s", s.str().c_str()); + // s.str(""); + // } + // } + + // this->axi_spi[AXI_QUAD_SPI_MM0_FLASH_INST]->disableMMIO(); + + // stats = this->axi_spi[AXI_QUAD_SPI_MM0_FLASH_INST]->getMMIOStats(); + + // DLOGF(SOAPY_SDR_DEBUG, "SPI Flash MMIO read request count: %lu", stats.rd_req_cnt); + // DLOGF(SOAPY_SDR_DEBUG, "SPI Flash MMIO continuous read request count: %lu (%.3f %%)", stats.cont_rd_req_cnt, (double)stats.cont_rd_req_cnt / (double)stats.rd_req_cnt * 100.0); + // } //////////////////////// SPI FLASH TEST this->clk_mngr = new Si5351( @@ -803,7 +908,7 @@ void SoapyIcyRadio::initPeripheralsPreClocks() }*/ ); - SoapySDR_logf(SOAPY_SDR_DEBUG, "Si5351 Revision: %hhu", this->clk_mngr->getRevisionID()); + DLOGF(SOAPY_SDR_DEBUG, "Si5351 Revision: %hhu", this->clk_mngr->getRevisionID()); this->mmw_synth = new IDT8V97003( { @@ -839,8 +944,8 @@ void SoapyIcyRadio::initPeripheralsPreClocks() this->mmw_synth->powerDown(); - SoapySDR_logf(SOAPY_SDR_DEBUG, "8V97003 Revision: %hhu", this->mmw_synth->getChipVersion()); - SoapySDR_logf(SOAPY_SDR_DEBUG, "8V97003 Option: %hhu", this->mmw_synth->getChipOption()); + DLOGF(SOAPY_SDR_DEBUG, "8V97003 Revision: %hhu", this->mmw_synth->getChipVersion()); + DLOGF(SOAPY_SDR_DEBUG, "8V97003 Option: %hhu", this->mmw_synth->getChipOption()); this->rf_phy = new AD9361( { @@ -859,7 +964,7 @@ void SoapyIcyRadio::initPeripheralsPreClocks() } ); - SoapySDR_logf(SOAPY_SDR_DEBUG, "AD9361 Revision: %hhu", this->rf_phy->getChipRevision()); + DLOGF(SOAPY_SDR_DEBUG, "AD9361 Revision: %hhu", this->rf_phy->getChipRevision()); } void SoapyIcyRadio::deinitPeripheralsPreClocks() { @@ -916,14 +1021,46 @@ void SoapyIcyRadio::initPeripheralsPostClocks() this->rf_phy->init(); this->axi_ad9361->init(this->rf_phy); - // SoapySDR_logf(SOAPY_SDR_DEBUG, "Doing RF Phy digital interface tuning..."); + { + uint32_t ver = this->axi_ad9361->getIPVersion(); + + DLOGF(SOAPY_SDR_TRACE, "AXI AD9361:"); + DLOGF(SOAPY_SDR_TRACE, " IP Version: v%u.%u.%u", AXI_CORE_VERSION_MAJOR(ver), AXI_CORE_VERSION_MINOR(ver), AXI_CORE_VERSION_PATCH(ver)); + DLOGF(SOAPY_SDR_TRACE, " Channel count: %u", this->axi_ad9361->getChannelCount()); + + ver = this->axi_ad9361->adc->getIPVersion(); + + DLOGF(SOAPY_SDR_TRACE, " ADC:"); + DLOGF(SOAPY_SDR_TRACE, " IP Version: v%u.%u.%u", AXI_CORE_VERSION_MAJOR(ver), AXI_CORE_VERSION_MINOR(ver), AXI_CORE_VERSION_PATCH(ver)); + DLOGF(SOAPY_SDR_TRACE, " Interface clock: %llu Hz", this->axi_ad9361->adc->getInterfaceClockFrequency(AXI_ACLK_FREQ)); + DLOGF(SOAPY_SDR_TRACE, " Sampling frequency: %llu Hz", this->axi_ad9361->adc->getSamplingFrequency(AXI_ACLK_FREQ)); + + ver = this->axi_ad9361->dac->getIPVersion(); + + DLOGF(SOAPY_SDR_TRACE, " DAC:"); + DLOGF(SOAPY_SDR_TRACE, " IP Version: v%u.%u.%u", AXI_CORE_VERSION_MAJOR(ver), AXI_CORE_VERSION_MINOR(ver), AXI_CORE_VERSION_PATCH(ver)); + DLOGF(SOAPY_SDR_TRACE, " Interface clock: %llu Hz", this->axi_ad9361->dac->getInterfaceClockFrequency(AXI_ACLK_FREQ)); + DLOGF(SOAPY_SDR_TRACE, " Sampling frequency: %llu Hz", this->axi_ad9361->dac->getSamplingFrequency(AXI_ACLK_FREQ)); + } + + this->axi_ad9361->adc->setDataSource(AXIAD9361::ADC::Channel::RX1_I, AXIAD9361::ADC::DataSource::DATA_SRC_INPUT_DATA); + this->axi_ad9361->adc->setDataSource(AXIAD9361::ADC::Channel::RX1_Q, AXIAD9361::ADC::DataSource::DATA_SRC_INPUT_DATA); + this->axi_ad9361->adc->setDataSource(AXIAD9361::ADC::Channel::RX2_I, AXIAD9361::ADC::DataSource::DATA_SRC_INPUT_DATA); + this->axi_ad9361->adc->setDataSource(AXIAD9361::ADC::Channel::RX2_Q, AXIAD9361::ADC::DataSource::DATA_SRC_INPUT_DATA); + + this->axi_ad9361->dac->setDataSource(AXIAD9361::DAC::Channel::TX1_I, AXIAD9361::DAC::DataSource::DATA_SRC_DMA); + this->axi_ad9361->dac->setDataSource(AXIAD9361::DAC::Channel::TX1_Q, AXIAD9361::DAC::DataSource::DATA_SRC_DMA); + this->axi_ad9361->dac->setDataSource(AXIAD9361::DAC::Channel::TX2_I, AXIAD9361::DAC::DataSource::DATA_SRC_DMA); + this->axi_ad9361->dac->setDataSource(AXIAD9361::DAC::Channel::TX2_Q, AXIAD9361::DAC::DataSource::DATA_SRC_DMA); + + // DLOGF(SOAPY_SDR_DEBUG, "Doing RF Phy digital interface tuning..."); // auto intf_delays = this->axi_ad9361->tuneInterfaceTiming(AXIAD9361::InterfaceTuneFlags::DO_CHIP_RX | AXIAD9361::InterfaceTuneFlags::DO_CHIP_TX); - // SoapySDR_logf(SOAPY_SDR_DEBUG, "RF Phy interface delays:"); + // DLOGF(SOAPY_SDR_DEBUG, "RF Phy interface delays:"); // for(auto d : intf_delays) - // SoapySDR_logf(SOAPY_SDR_DEBUG, " %s = %d", d.first.c_str(), d.second); + // DLOGF(SOAPY_SDR_DEBUG, " %s = %d", d.first.c_str(), d.second); // Init RF Timestamping this->axi_rf_tstamp->init( @@ -933,25 +1070,18 @@ void SoapyIcyRadio::initPeripheralsPostClocks() } ); + { + uint32_t ver = this->axi_rf_tstamp->getIPVersion(); + + DLOGF(SOAPY_SDR_TRACE, "AXI RF Timestamping:"); + DLOGF(SOAPY_SDR_TRACE, " IP Version: v%u.%u.%u", AXI_CORE_VERSION_MAJOR(ver), AXI_CORE_VERSION_MINOR(ver), AXI_CORE_VERSION_PATCH(ver)); + } + this->axi_rf_tstamp->disableClockSyncBypass(); // Disable bypass since we are using both channels this->axi_rf_tstamp->triggerClockResync(true, 100); // Wait for sync both channels, 100 ms timeout this->axi_rf_tstamp->enableCounter(); - // AXIDMAC::Transfer test = { - // .id = 0, - // .size = 8192, - // .flags = AXIDMAC::Transfer::Flags::NONE, - // .src_addr = AXI_MIG_DDR3_BASE, - // .dest_addr = 0, - // .cb = nullptr, - // .cb_arg = nullptr, - // }; - - // this->axi_dmac[AXI_DMAC_RF_TX0_INST]->enable(); - // this->axi_dmac[AXI_DMAC_RF_TX0_INST]->submitTransfer(test); - // this->axi_dmac[AXI_DMAC_RF_TX0_INST]->disable(); - // Probe expansion cards // Enable mmWave Synth if present // this->mmw_synth->powerUp(); @@ -981,12 +1111,12 @@ void SoapyIcyRadio::deinitPeripheralsPostClocks() void SoapyIcyRadio::initClocks() { - SoapySDR_logf(SOAPY_SDR_DEBUG, "Setup clock manager..."); + DLOGF(SOAPY_SDR_DEBUG, "Setup clock tree..."); this->clk_mngr->init(); // Inputs - SoapySDR_logf(SOAPY_SDR_DEBUG, "---- Inputs ----"); + DLOGF(SOAPY_SDR_DEBUG, "Clock Inputs:"); this->clk_mngr->configXTAL(26000000UL, Si5351::XTALCapacitance::C_10pF); @@ -995,30 +1125,46 @@ void SoapyIcyRadio::initClocks() usleep(1000); if(!this->clk_mngr->isXTALDetected()) - throw std::runtime_error("Could not detect XTAL, aborting!"); + { + if(!this->config.use_clkin) + throw std::runtime_error("Could not detect a valid clock at the XTAL input, aborting!"); + else + DLOGF(SOAPY_SDR_WARNING, " Could not detect a valid clock at the XTAL input"); + } - SoapySDR_logf(SOAPY_SDR_DEBUG, "XTAL Clock: %.3f MHz", (float)this->clk_mngr->getXTALFrequency() / 1000000); + DLOGF(SOAPY_SDR_DEBUG, " XTAL Clock: %.6f MHz", (float)this->clk_mngr->getXTALFrequency() / 1000000); - if(false) // TODO: Support CLKIN configuration + if(this->config.use_clkin) { - this->clk_mngr->configCLKIN(10000000UL); + this->clk_mngr->configCLKIN(this->config.clkin_freq); timeout = 500; while(--timeout && !this->clk_mngr->isCLKINDetected()) usleep(1000); if(!this->clk_mngr->isCLKINDetected()) - throw std::runtime_error("Could not detect CLKIN, aborting!"); + { + this->config.use_clkin = false; - SoapySDR_logf(SOAPY_SDR_DEBUG, "CLKIN Clock: %.3f MHz", (float)this->clk_mngr->getCLKINFrequency() / 1000000); - SoapySDR_logf(SOAPY_SDR_DEBUG, "CLKIN Divided Clock: %.3f MHz", (float)this->clk_mngr->getDividedCLKINFrequency() / 1000000); + if(!this->clk_mngr->isXTALDetected()) + throw std::runtime_error("Could not detect any valid clock at the CLKIN or XTAL input, aborting!"); + else + DLOGF(SOAPY_SDR_WARNING, " Could not detect a valid clock at the CLKIN input, falling back to XTAL"); + } + else + { + DLOGF(SOAPY_SDR_DEBUG, " CLKIN Clock: %.6f MHz", (float)this->clk_mngr->getCLKINFrequency() / 1000000); + DLOGF(SOAPY_SDR_DEBUG, " CLKIN Divided Clock: %.6f MHz", (float)this->clk_mngr->getDividedCLKINFrequency() / 1000000); + } } // PLLs + DLOGF(SOAPY_SDR_DEBUG, "Clock PLLs:"); + //// PLLA - SoapySDR_logf(SOAPY_SDR_DEBUG, "---- PLL A ----"); + DLOGF(SOAPY_SDR_DEBUG, " PLL A:"); - this->clk_mngr->configPLL(Si5351::PLL::PLLA, 650000000UL, Si5351::PLLSource::PLL_SRC_XTAL); + this->clk_mngr->configPLL(Si5351::PLL::PLLA, 650000000UL, this->config.use_clkin ? Si5351::PLLSource::PLL_SRC_CLKIN : Si5351::PLLSource::PLL_SRC_XTAL, false); timeout = 500; while(--timeout && !this->clk_mngr->isPLLLocked(Si5351::PLL::PLLA)) @@ -1027,13 +1173,13 @@ void SoapyIcyRadio::initClocks() if(!this->clk_mngr->isPLLLocked(Si5351::PLL::PLLA)) throw std::runtime_error("PLLA did not achieve lock, aborting!"); - SoapySDR_logf(SOAPY_SDR_DEBUG, "PLLA Source Clock: %.3f MHz", (float)this->clk_mngr->getPLLSourceFrequency(Si5351::PLL::PLLA) / 1000000); - SoapySDR_logf(SOAPY_SDR_DEBUG, "PLLA Clock: %.3f MHz", (float)this->clk_mngr->getPLLFrequency(Si5351::PLL::PLLA) / 1000000); + DLOGF(SOAPY_SDR_TRACE, " Source Clock: %.6f MHz", (float)this->clk_mngr->getPLLSourceFrequency(Si5351::PLL::PLLA) / 1000000); + DLOGF(SOAPY_SDR_DEBUG, " Output Clock: %.6f MHz", (float)this->clk_mngr->getPLLFrequency(Si5351::PLL::PLLA) / 1000000); //// PLLB - SoapySDR_logf(SOAPY_SDR_DEBUG, "---- PLL B ----"); + DLOGF(SOAPY_SDR_DEBUG, " PLL B:"); - this->clk_mngr->configPLL(Si5351::PLL::PLLB, 800000000UL, Si5351::PLLSource::PLL_SRC_XTAL); + this->clk_mngr->configPLL(Si5351::PLL::PLLB, 800000000UL, this->config.use_clkin ? Si5351::PLLSource::PLL_SRC_CLKIN : Si5351::PLLSource::PLL_SRC_XTAL, false); timeout = 500; while(--timeout && !this->clk_mngr->isPLLLocked(Si5351::PLL::PLLB)) @@ -1042,14 +1188,15 @@ void SoapyIcyRadio::initClocks() if(!this->clk_mngr->isPLLLocked(Si5351::PLL::PLLB)) throw std::runtime_error("PLLB did not achieve lock, aborting!"); - SoapySDR_logf(SOAPY_SDR_DEBUG, "PLLB Source Clock: %.3f MHz", (float)this->clk_mngr->getPLLSourceFrequency(Si5351::PLL::PLLB) / 1000000); - SoapySDR_logf(SOAPY_SDR_DEBUG, "PLLB Clock: %.3f MHz", (float)this->clk_mngr->getPLLFrequency(Si5351::PLL::PLLB) / 1000000); + DLOGF(SOAPY_SDR_TRACE, " Source Clock: %.6f MHz", (float)this->clk_mngr->getPLLSourceFrequency(Si5351::PLL::PLLB) / 1000000); + DLOGF(SOAPY_SDR_DEBUG, " Output Clock: %.6f MHz", (float)this->clk_mngr->getPLLFrequency(Si5351::PLL::PLLB) / 1000000); // Clocks + DLOGF(SOAPY_SDR_DEBUG, "Clock Outputs:"); //// FPGA Clock #0 - SoapySDR_logf(SOAPY_SDR_DEBUG, "---- CLK #%hhu (FPGA_CLK0) ----", Si5351::ClockOutput::CLK_FPGA_CLK0); + DLOGF(SOAPY_SDR_DEBUG, " Clock #%hhu (FPGA_CLK0):", Si5351::ClockOutput::CLK_FPGA_CLK0); - this->clk_mngr->configClock(Si5351::ClockOutput::CLK_FPGA_CLK0, 50000000UL, 0.f, Si5351::PLL::PLLA); + this->clk_mngr->configClock(Si5351::ClockOutput::CLK_FPGA_CLK0, 50000000UL, 0.f, Si5351::PLL::PLLA, false); this->clk_mngr->setClockDisableState(Si5351::ClockOutput::CLK_FPGA_CLK0, Si5351::ClockOutputDisableState::LOW); this->clk_mngr->setClockDriveCurrent(Si5351::ClockOutput::CLK_FPGA_CLK0, Si5351::ClockOutputDriveCurrent::I_8mA); this->clk_mngr->setClockOutputEnableMode(Si5351::ClockOutput::CLK_FPGA_CLK0, true); // Controlled by OE pin @@ -1057,14 +1204,14 @@ void SoapyIcyRadio::initClocks() this->clk_mngr->powerUpClock(Si5351::ClockOutput::CLK_FPGA_CLK0); this->clk_mngr->enableClock(Si5351::ClockOutput::CLK_FPGA_CLK0); - SoapySDR_logf(SOAPY_SDR_DEBUG, "MS%hhu Source Clock: %.3f MHz", Si5351::MultiSynth::MS_FPGA_CLK0, (float)this->clk_mngr->getMultiSynthSourceFrequency(Si5351::MultiSynth::MS_FPGA_CLK0) / 1000000); - SoapySDR_logf(SOAPY_SDR_DEBUG, "MS%hhu Clock: %.3f MHz", Si5351::MultiSynth::MS_FPGA_CLK0, (float)this->clk_mngr->getMultiSynthFrequency(Si5351::MultiSynth::MS_FPGA_CLK0) / 1000000); - SoapySDR_logf(SOAPY_SDR_DEBUG, "CLK%hhu Clock: %.3f MHz", Si5351::ClockOutput::CLK_FPGA_CLK0, (float)this->clk_mngr->getClockFrequency(Si5351::ClockOutput::CLK_FPGA_CLK0) / 1000000); + DLOGF(SOAPY_SDR_TRACE, " Source Clock: %.6f MHz", (float)this->clk_mngr->getMultiSynthSourceFrequency(Si5351::MultiSynth::MS_FPGA_CLK0) / 1000000); + DLOGF(SOAPY_SDR_TRACE, " MS Output Clock: %.6f MHz", (float)this->clk_mngr->getMultiSynthFrequency(Si5351::MultiSynth::MS_FPGA_CLK0) / 1000000); + DLOGF(SOAPY_SDR_DEBUG, " Output Clock: %.6f MHz", (float)this->clk_mngr->getClockFrequency(Si5351::ClockOutput::CLK_FPGA_CLK0) / 1000000); //// FPGA Clock #1 - SoapySDR_logf(SOAPY_SDR_DEBUG, "---- CLK #%hhu (FPGA_CLK1) ----", Si5351::ClockOutput::CLK_FPGA_CLK1); + DLOGF(SOAPY_SDR_DEBUG, " Clock #%hhu (FPGA_CLK1):", Si5351::ClockOutput::CLK_FPGA_CLK1); - this->clk_mngr->configClock(Si5351::ClockOutput::CLK_FPGA_CLK1, 49152000UL, 0.f, Si5351::PLL::PLLA); + this->clk_mngr->configClock(Si5351::ClockOutput::CLK_FPGA_CLK1, 49152000UL, 0.f, Si5351::PLL::PLLA, false); this->clk_mngr->setClockDisableState(Si5351::ClockOutput::CLK_FPGA_CLK1, Si5351::ClockOutputDisableState::LOW); this->clk_mngr->setClockDriveCurrent(Si5351::ClockOutput::CLK_FPGA_CLK1, Si5351::ClockOutputDriveCurrent::I_8mA); this->clk_mngr->setClockOutputEnableMode(Si5351::ClockOutput::CLK_FPGA_CLK1, true); // Controlled by OE pin @@ -1072,14 +1219,14 @@ void SoapyIcyRadio::initClocks() this->clk_mngr->powerUpClock(Si5351::ClockOutput::CLK_FPGA_CLK1); this->clk_mngr->enableClock(Si5351::ClockOutput::CLK_FPGA_CLK1); - SoapySDR_logf(SOAPY_SDR_DEBUG, "MS%hhu Source Clock: %.3f MHz", Si5351::MultiSynth::MS_FPGA_CLK1, (float)this->clk_mngr->getMultiSynthSourceFrequency(Si5351::MultiSynth::MS_FPGA_CLK1) / 1000000); - SoapySDR_logf(SOAPY_SDR_DEBUG, "MS%hhu Clock: %.3f MHz", Si5351::MultiSynth::MS_FPGA_CLK1, (float)this->clk_mngr->getMultiSynthFrequency(Si5351::MultiSynth::MS_FPGA_CLK1) / 1000000); - SoapySDR_logf(SOAPY_SDR_DEBUG, "CLK%hhu Clock: %.3f MHz", Si5351::ClockOutput::CLK_FPGA_CLK1, (float)this->clk_mngr->getClockFrequency(Si5351::ClockOutput::CLK_FPGA_CLK1) / 1000000); + DLOGF(SOAPY_SDR_TRACE, " Source Clock: %.6f MHz", (float)this->clk_mngr->getMultiSynthSourceFrequency(Si5351::MultiSynth::MS_FPGA_CLK1) / 1000000); + DLOGF(SOAPY_SDR_TRACE, " MS Output Clock: %.6f MHz", (float)this->clk_mngr->getMultiSynthFrequency(Si5351::MultiSynth::MS_FPGA_CLK1) / 1000000); + DLOGF(SOAPY_SDR_DEBUG, " Output Clock: %.6f MHz", (float)this->clk_mngr->getClockFrequency(Si5351::ClockOutput::CLK_FPGA_CLK1) / 1000000); //// FPGA Clock #2 - // SoapySDR_logf(SOAPY_SDR_DEBUG, "---- CLK #%hhu (FPGA_CLK2) ----", Si5351::ClockOutput::CLK_FPGA_CLK2); + // DLOGF(SOAPY_SDR_DEBUG, " Clock #%hhu (FPGA_CLK2):", Si5351::ClockOutput::CLK_FPGA_CLK2); - // this->clk_mngr->configClock(Si5351::ClockOutput::CLK_FPGA_CLK2, FREQ, 0.f, Si5351::PLL::PLLA); + // this->clk_mngr->configClock(Si5351::ClockOutput::CLK_FPGA_CLK2, FREQ, 0.f, Si5351::PLL::PLLA, false); // this->clk_mngr->setClockDisableState(Si5351::ClockOutput::CLK_FPGA_CLK2, Si5351::ClockOutputDisableState::LOW); // this->clk_mngr->setClockDriveCurrent(Si5351::ClockOutput::CLK_FPGA_CLK2, Si5351::ClockOutputDriveCurrent::I_8mA); // this->clk_mngr->setClockOutputEnableMode(Si5351::ClockOutput::CLK_FPGA_CLK2, true); // Controlled by OE pin @@ -1087,14 +1234,14 @@ void SoapyIcyRadio::initClocks() // this->clk_mngr->powerUpClock(Si5351::ClockOutput::CLK_FPGA_CLK2); // this->clk_mngr->enableClock(Si5351::ClockOutput::CLK_FPGA_CLK2); - // SoapySDR_logf(SOAPY_SDR_DEBUG, "MS%hhu Source Clock: %.3f MHz", Si5351::MultiSynth::MS_FPGA_CLK2, (float)this->clk_mngr->getMultiSynthSourceFrequency(Si5351::MultiSynth::MS_FPGA_CLK2) / 1000000); - // SoapySDR_logf(SOAPY_SDR_DEBUG, "MS%hhu Clock: %.3f MHz", Si5351::MultiSynth::MS_FPGA_CLK2, (float)this->clk_mngr->getMultiSynthFrequency(Si5351::MultiSynth::MS_FPGA_CLK2) / 1000000); - // SoapySDR_logf(SOAPY_SDR_DEBUG, "CLK%hhu Clock: %.3f MHz", Si5351::ClockOutput::CLK_FPGA_CLK2, (float)this->clk_mngr->getClockFrequency(Si5351::ClockOutput::CLK_FPGA_CLK2) / 1000000); + // DLOGF(SOAPY_SDR_TRACE, " Source Clock: %.6f MHz", (float)this->clk_mngr->getMultiSynthSourceFrequency(Si5351::MultiSynth::MS_FPGA_CLK2) / 1000000); + // DLOGF(SOAPY_SDR_TRACE, " MS Output Clock: %.6f MHz", (float)this->clk_mngr->getMultiSynthFrequency(Si5351::MultiSynth::MS_FPGA_CLK2) / 1000000); + // DLOGF(SOAPY_SDR_DEBUG, " Output Clock: %.6f MHz", (float)this->clk_mngr->getClockFrequency(Si5351::ClockOutput::CLK_FPGA_CLK2) / 1000000); //// FPGA Clock #3 - // SoapySDR_logf(SOAPY_SDR_DEBUG, "---- CLK #%hhu (FPGA_CLK3) ----", Si5351::ClockOutput::CLK_FPGA_CLK3); + // DLOGF(SOAPY_SDR_DEBUG, " Clock #%hhu (FPGA_CLK3):", Si5351::ClockOutput::CLK_FPGA_CLK3); - // this->clk_mngr->configClock(Si5351::ClockOutput::CLK_FPGA_CLK3, FREQ, 0.f, Si5351::PLL::PLLA); + // this->clk_mngr->configClock(Si5351::ClockOutput::CLK_FPGA_CLK3, FREQ, 0.f, Si5351::PLL::PLLA, false); // this->clk_mngr->setClockDisableState(Si5351::ClockOutput::CLK_FPGA_CLK3, Si5351::ClockOutputDisableState::LOW); // this->clk_mngr->setClockDriveCurrent(Si5351::ClockOutput::CLK_FPGA_CLK3, Si5351::ClockOutputDriveCurrent::I_8mA); // this->clk_mngr->setClockOutputEnableMode(Si5351::ClockOutput::CLK_FPGA_CLK3, true); // Controlled by OE pin @@ -1102,14 +1249,14 @@ void SoapyIcyRadio::initClocks() // this->clk_mngr->powerUpClock(Si5351::ClockOutput::CLK_FPGA_CLK3); // this->clk_mngr->enableClock(Si5351::ClockOutput::CLK_FPGA_CLK3); - // SoapySDR_logf(SOAPY_SDR_DEBUG, "MS%hhu Source Clock: %.3f MHz", Si5351::MultiSynth::MS_FPGA_CLK3, (float)this->clk_mngr->getMultiSynthSourceFrequency(Si5351::MultiSynth::MS_FPGA_CLK3) / 1000000); - // SoapySDR_logf(SOAPY_SDR_DEBUG, "MS%hhu Clock: %.3f MHz", Si5351::MultiSynth::MS_FPGA_CLK3, (float)this->clk_mngr->getMultiSynthFrequency(Si5351::MultiSynth::MS_FPGA_CLK3) / 1000000); - // SoapySDR_logf(SOAPY_SDR_DEBUG, "CLK%hhu Clock: %.3f MHz", Si5351::ClockOutput::CLK_FPGA_CLK3, (float)this->clk_mngr->getClockFrequency(Si5351::ClockOutput::CLK_FPGA_CLK3) / 1000000); + // DLOGF(SOAPY_SDR_TRACE, " Source Clock: %.6f MHz", (float)this->clk_mngr->getMultiSynthSourceFrequency(Si5351::MultiSynth::MS_FPGA_CLK3) / 1000000); + // DLOGF(SOAPY_SDR_TRACE, " MS Output Clock: %.6f MHz", (float)this->clk_mngr->getMultiSynthFrequency(Si5351::MultiSynth::MS_FPGA_CLK3) / 1000000); + // DLOGF(SOAPY_SDR_DEBUG, " Output Clock: %.6f MHz", (float)this->clk_mngr->getClockFrequency(Si5351::ClockOutput::CLK_FPGA_CLK3) / 1000000); //// Transceiver Reference clock - SoapySDR_logf(SOAPY_SDR_DEBUG, "---- CLK #%hhu (TRX_REF_CLK) ----", Si5351::ClockOutput::CLK_TRX_REF_CLK); + DLOGF(SOAPY_SDR_DEBUG, " Clock #%hhu (TRX_REF_CLK):", Si5351::ClockOutput::CLK_TRX_REF_CLK); - this->clk_mngr->configClock(Si5351::ClockOutput::CLK_TRX_REF_CLK, 40000000UL, 0.f, Si5351::PLL::PLLB); + this->clk_mngr->configClock(Si5351::ClockOutput::CLK_TRX_REF_CLK, 40000000UL, 0.f, Si5351::PLL::PLLB, false); this->clk_mngr->setClockDisableState(Si5351::ClockOutput::CLK_TRX_REF_CLK, Si5351::ClockOutputDisableState::LOW); this->clk_mngr->setClockDriveCurrent(Si5351::ClockOutput::CLK_TRX_REF_CLK, Si5351::ClockOutputDriveCurrent::I_8mA); this->clk_mngr->setClockOutputEnableMode(Si5351::ClockOutput::CLK_TRX_REF_CLK, true); // Controlled by OE pin @@ -1117,14 +1264,14 @@ void SoapyIcyRadio::initClocks() this->clk_mngr->powerUpClock(Si5351::ClockOutput::CLK_TRX_REF_CLK); this->clk_mngr->enableClock(Si5351::ClockOutput::CLK_TRX_REF_CLK); - SoapySDR_logf(SOAPY_SDR_DEBUG, "MS%hhu Source Clock: %.3f MHz", Si5351::MultiSynth::MS_TRX_REF_CLK, (float)this->clk_mngr->getMultiSynthSourceFrequency(Si5351::MultiSynth::MS_TRX_REF_CLK) / 1000000); - SoapySDR_logf(SOAPY_SDR_DEBUG, "MS%hhu Clock: %.3f MHz", Si5351::MultiSynth::MS_TRX_REF_CLK, (float)this->clk_mngr->getMultiSynthFrequency(Si5351::MultiSynth::MS_TRX_REF_CLK) / 1000000); - SoapySDR_logf(SOAPY_SDR_DEBUG, "CLK%hhu Clock: %.3f MHz", Si5351::ClockOutput::CLK_TRX_REF_CLK, (float)this->clk_mngr->getClockFrequency(Si5351::ClockOutput::CLK_TRX_REF_CLK) / 1000000); + DLOGF(SOAPY_SDR_TRACE, " Source Clock: %.6f MHz", (float)this->clk_mngr->getMultiSynthSourceFrequency(Si5351::MultiSynth::MS_TRX_REF_CLK) / 1000000); + DLOGF(SOAPY_SDR_TRACE, " MS Output Clock: %.6f MHz", (float)this->clk_mngr->getMultiSynthFrequency(Si5351::MultiSynth::MS_TRX_REF_CLK) / 1000000); + DLOGF(SOAPY_SDR_DEBUG, " Output Clock: %.6f MHz", (float)this->clk_mngr->getClockFrequency(Si5351::ClockOutput::CLK_TRX_REF_CLK) / 1000000); //// mmWave Synthesizer Reference clock - SoapySDR_logf(SOAPY_SDR_DEBUG, "---- CLK #%hhu (SYNTH_REF_CLK) ----", Si5351::ClockOutput::CLK_SYNTH_REF_CLK); + DLOGF(SOAPY_SDR_DEBUG, " Clock #%hhu (SYNTH_REF_CLK):", Si5351::ClockOutput::CLK_SYNTH_REF_CLK); - this->clk_mngr->configClock(Si5351::ClockOutput::CLK_SYNTH_REF_CLK, 25000000UL, 0.f, Si5351::PLL::PLLA); + this->clk_mngr->configClock(Si5351::ClockOutput::CLK_SYNTH_REF_CLK, 25000000UL, 0.f, Si5351::PLL::PLLA, false); this->clk_mngr->setClockDisableState(Si5351::ClockOutput::CLK_SYNTH_REF_CLK, Si5351::ClockOutputDisableState::LOW); this->clk_mngr->setClockDriveCurrent(Si5351::ClockOutput::CLK_SYNTH_REF_CLK, Si5351::ClockOutputDriveCurrent::I_8mA); this->clk_mngr->setClockOutputEnableMode(Si5351::ClockOutput::CLK_SYNTH_REF_CLK, true); // Controlled by OE pin @@ -1132,47 +1279,52 @@ void SoapyIcyRadio::initClocks() this->clk_mngr->powerUpClock(Si5351::ClockOutput::CLK_SYNTH_REF_CLK); this->clk_mngr->enableClock(Si5351::ClockOutput::CLK_SYNTH_REF_CLK); - SoapySDR_logf(SOAPY_SDR_DEBUG, "MS%hhu Source Clock: %.3f MHz", Si5351::MultiSynth::MS_SYNTH_REF_CLK, (float)this->clk_mngr->getMultiSynthSourceFrequency(Si5351::MultiSynth::MS_SYNTH_REF_CLK) / 1000000); - SoapySDR_logf(SOAPY_SDR_DEBUG, "MS%hhu Clock: %.3f MHz", Si5351::MultiSynth::MS_SYNTH_REF_CLK, (float)this->clk_mngr->getMultiSynthFrequency(Si5351::MultiSynth::MS_SYNTH_REF_CLK) / 1000000); - SoapySDR_logf(SOAPY_SDR_DEBUG, "CLK%hhu Clock: %.3f MHz", Si5351::ClockOutput::CLK_SYNTH_REF_CLK, (float)this->clk_mngr->getClockFrequency(Si5351::ClockOutput::CLK_SYNTH_REF_CLK) / 1000000); + DLOGF(SOAPY_SDR_TRACE, " Source Clock: %.6f MHz", (float)this->clk_mngr->getMultiSynthSourceFrequency(Si5351::MultiSynth::MS_SYNTH_REF_CLK) / 1000000); + DLOGF(SOAPY_SDR_TRACE, " MS Output Clock: %.6f MHz", (float)this->clk_mngr->getMultiSynthFrequency(Si5351::MultiSynth::MS_SYNTH_REF_CLK) / 1000000); + DLOGF(SOAPY_SDR_DEBUG, " Output Clock: %.6f MHz", (float)this->clk_mngr->getClockFrequency(Si5351::ClockOutput::CLK_SYNTH_REF_CLK) / 1000000); //// External clock output (on frontend interface pin 2_3) - SoapySDR_logf(SOAPY_SDR_DEBUG, "---- CLK #%hhu (EXT_CLK_2_3) ----", Si5351::ClockOutput::CLK_EXT_CLK_2_3); + // DLOGF(SOAPY_SDR_DEBUG, " Clock #%hhu (EXT_CLK_2_3):", Si5351::ClockOutput::CLK_EXT_CLK_2_3); - this->clk_mngr->configClock(Si5351::ClockOutput::CLK_EXT_CLK_2_3, 10000000UL, 0.f, Si5351::PLL::PLLB); - this->clk_mngr->setClockDisableState(Si5351::ClockOutput::CLK_EXT_CLK_2_3, Si5351::ClockOutputDisableState::LOW); - this->clk_mngr->setClockDriveCurrent(Si5351::ClockOutput::CLK_EXT_CLK_2_3, Si5351::ClockOutputDriveCurrent::I_8mA); - this->clk_mngr->setClockOutputEnableMode(Si5351::ClockOutput::CLK_EXT_CLK_2_3, true); // Controlled by OE pin + // this->clk_mngr->configClock(Si5351::ClockOutput::CLK_EXT_CLK_2_3, 10000000UL, 0.f, Si5351::PLL::PLL_AUTO, false); + // this->clk_mngr->setClockDisableState(Si5351::ClockOutput::CLK_EXT_CLK_2_3, Si5351::ClockOutputDisableState::LOW); + // this->clk_mngr->setClockDriveCurrent(Si5351::ClockOutput::CLK_EXT_CLK_2_3, Si5351::ClockOutputDriveCurrent::I_8mA); + // this->clk_mngr->setClockOutputEnableMode(Si5351::ClockOutput::CLK_EXT_CLK_2_3, true); // Controlled by OE pin - this->clk_mngr->powerUpClock(Si5351::ClockOutput::CLK_EXT_CLK_2_3); + // this->clk_mngr->powerUpClock(Si5351::ClockOutput::CLK_EXT_CLK_2_3); // this->clk_mngr->enableClock(Si5351::ClockOutput::CLK_EXT_CLK_2_3); - SoapySDR_logf(SOAPY_SDR_DEBUG, "MS%hhu Source Clock: %.3f MHz", Si5351::MultiSynth::MS_EXT_CLK_2_3, (float)this->clk_mngr->getMultiSynthSourceFrequency(Si5351::MultiSynth::MS_EXT_CLK_2_3) / 1000000); - SoapySDR_logf(SOAPY_SDR_DEBUG, "MS%hhu Clock: %.3f MHz", Si5351::MultiSynth::MS_EXT_CLK_2_3, (float)this->clk_mngr->getMultiSynthFrequency(Si5351::MultiSynth::MS_EXT_CLK_2_3) / 1000000); - SoapySDR_logf(SOAPY_SDR_DEBUG, "CLK%hhu Clock: %.3f MHz", Si5351::ClockOutput::CLK_EXT_CLK_2_3, (float)this->clk_mngr->getClockFrequency(Si5351::ClockOutput::CLK_EXT_CLK_2_3) / 1000000); + // DLOGF(SOAPY_SDR_TRACE, " Source Clock: %.6f MHz", (float)this->clk_mngr->getMultiSynthSourceFrequency(Si5351::MultiSynth::MS_EXT_CLK_2_3) / 1000000); + // DLOGF(SOAPY_SDR_TRACE, " MS Output Clock: %.6f MHz", (float)this->clk_mngr->getMultiSynthFrequency(Si5351::MultiSynth::MS_EXT_CLK_2_3) / 1000000); + // DLOGF(SOAPY_SDR_DEBUG, " Output Clock: %.6f MHz", (float)this->clk_mngr->getClockFrequency(Si5351::ClockOutput::CLK_EXT_CLK_2_3) / 1000000); //// External clock output (on u.FL connector) - SoapySDR_logf(SOAPY_SDR_DEBUG, "---- CLK #%hhu (EXT_CLK_OUT) ----", Si5351::ClockOutput::CLK_EXT_CLK_OUT); + if(this->config.enable_clkout) + { + DLOGF(SOAPY_SDR_DEBUG, " Clock #%hhu (EXT_CLK_OUT):", Si5351::ClockOutput::CLK_EXT_CLK_OUT); + + this->clk_mngr->configClock(Si5351::ClockOutput::CLK_EXT_CLK_OUT, this->config.clkout_freq, 0.f, Si5351::PLL::PLL_AUTO, true); + this->clk_mngr->setClockDisableState(Si5351::ClockOutput::CLK_EXT_CLK_OUT, Si5351::ClockOutputDisableState::LOW); + this->clk_mngr->setClockDriveCurrent(Si5351::ClockOutput::CLK_EXT_CLK_OUT, Si5351::ClockOutputDriveCurrent::I_8mA); + this->clk_mngr->setClockOutputEnableMode(Si5351::ClockOutput::CLK_EXT_CLK_OUT, false); // Controlled via software enable only - this->clk_mngr->configClock(Si5351::ClockOutput::CLK_EXT_CLK_OUT, 10000000UL, 0.f, Si5351::PLL::PLLB); - this->clk_mngr->setClockDisableState(Si5351::ClockOutput::CLK_EXT_CLK_OUT, Si5351::ClockOutputDisableState::LOW); - this->clk_mngr->setClockDriveCurrent(Si5351::ClockOutput::CLK_EXT_CLK_OUT, Si5351::ClockOutputDriveCurrent::I_8mA); - this->clk_mngr->setClockOutputEnableMode(Si5351::ClockOutput::CLK_EXT_CLK_OUT, false); // Controlled via software enable only + this->clk_mngr->powerUpClock(Si5351::ClockOutput::CLK_EXT_CLK_OUT); + this->clk_mngr->enableClock(Si5351::ClockOutput::CLK_EXT_CLK_OUT); - this->clk_mngr->powerUpClock(Si5351::ClockOutput::CLK_EXT_CLK_OUT); - // this->clk_mngr->enableClock(Si5351::ClockOutput::CLK_EXT_CLK_OUT); + DLOGF(SOAPY_SDR_TRACE, " Source Clock: %.6f MHz", (float)this->clk_mngr->getMultiSynthSourceFrequency(Si5351::MultiSynth::MS_EXT_CLK_OUT) / 1000000); + DLOGF(SOAPY_SDR_TRACE, " MS Output Clock: %.6f MHz", (float)this->clk_mngr->getMultiSynthFrequency(Si5351::MultiSynth::MS_EXT_CLK_OUT) / 1000000); + DLOGF(SOAPY_SDR_DEBUG, " Output Clock: %.6f MHz", (float)this->clk_mngr->getClockFrequency(Si5351::ClockOutput::CLK_EXT_CLK_OUT) / 1000000); - SoapySDR_logf(SOAPY_SDR_DEBUG, "MS%hhu Source Clock: %.3f MHz", Si5351::MultiSynth::MS_EXT_CLK_OUT, (float)this->clk_mngr->getMultiSynthSourceFrequency(Si5351::MultiSynth::MS_EXT_CLK_OUT) / 1000000); - SoapySDR_logf(SOAPY_SDR_DEBUG, "MS%hhu Clock: %.3f MHz", Si5351::MultiSynth::MS_EXT_CLK_OUT, (float)this->clk_mngr->getMultiSynthFrequency(Si5351::MultiSynth::MS_EXT_CLK_OUT) / 1000000); - SoapySDR_logf(SOAPY_SDR_DEBUG, "CLK%hhu Clock: %.3f MHz", Si5351::ClockOutput::CLK_EXT_CLK_OUT, (float)this->clk_mngr->getClockFrequency(Si5351::ClockOutput::CLK_EXT_CLK_OUT) / 1000000); + DLOGF(SOAPY_SDR_INFO, "Achieved external clock output frequency is %u Hz", this->clk_mngr->getClockFrequency(Si5351::ClockOutput::CLK_EXT_CLK_OUT)); + } // Wait and global enable - SoapySDR_logf(SOAPY_SDR_DEBUG, "Waiting for all clocks to stabilize..."); + DLOGF(SOAPY_SDR_DEBUG, "Waiting for all clocks to stabilize..."); usleep(50000); this->clk_mngr->globalOutputEnable(); - SoapySDR_logf(SOAPY_SDR_DEBUG, "Clock manager global output enabled: %s", this->clk_mngr->isGlobalOutputEnabled() ? "yes" : "no"); + DLOGF(SOAPY_SDR_DEBUG, "Clock manager global output enabled: %s", this->clk_mngr->isGlobalOutputEnabled() ? "yes" : "no"); usleep(50000); @@ -1184,7 +1336,7 @@ void SoapyIcyRadio::initClocks() if(!this->axi_gpio[AXI_GPIO_SYS_INST]->getValue(AXI_GPIO_CLK_WIZ0_LOCKED_BIT)) throw std::runtime_error("FPGA clk_wiz_0 MMCM did not achieve lock (possible issue with FPGA_CLK0), aborting!"); else - SoapySDR_logf(SOAPY_SDR_DEBUG, "FPGA clk_wiz_0 MMCM locked!"); + DLOGF(SOAPY_SDR_DEBUG, "FPGA clk_wiz_0 MMCM locked!"); // De-assert DDR3 core reset this->axi_gpio[AXI_GPIO_SYS_INST]->setValue(AXI_GPIO_RST_CLK_WIZ0_250M_AUX_RESET_IN_BIT, AXIGPIO::Value::LOW); @@ -1197,7 +1349,7 @@ void SoapyIcyRadio::initClocks() if(!this->axi_gpio[AXI_GPIO_SYS_INST]->getValue(AXI_GPIO_MIG_MMCM_LOCKED_BIT)) throw std::runtime_error("FPGA DDR3 MMCM (mig_7series_0) did not achieve lock, aborting!"); else - SoapySDR_logf(SOAPY_SDR_DEBUG, "FPGA DDR3 MMCM locked!"); + DLOGF(SOAPY_SDR_DEBUG, "FPGA DDR3 MMCM locked!"); // De-assert DDR3 AXI interface reset this->axi_gpio[AXI_GPIO_SYS_INST]->setValue(AXI_GPIO_RST_MIG_166M_AUX_RESET_IN_BIT, AXIGPIO::Value::LOW); @@ -1210,7 +1362,7 @@ void SoapyIcyRadio::initClocks() if(!this->axi_gpio[AXI_GPIO_SYS_INST]->getValue(AXI_GPIO_RST_MIG_166M_PERI_ARESETn_BIT)) throw std::runtime_error("FPGA DDR3 AXI interface did not come out of reset, aborting!"); else - SoapySDR_logf(SOAPY_SDR_DEBUG, "FPGA DDR3 AXI interface reset released!"); + DLOGF(SOAPY_SDR_DEBUG, "FPGA DDR3 AXI interface reset released!"); // De-assert I2S Core reset this->axi_gpio[AXI_GPIO_SYS_INST]->setValue(AXI_GPIO_RST_FPGA_CLK1_49M152_AUX_RESET_IN_BIT, AXIGPIO::Value::LOW); @@ -1223,7 +1375,7 @@ void SoapyIcyRadio::initClocks() if(!this->axi_gpio[AXI_GPIO_SYS_INST]->getValue(AXI_GPIO_RST_FPGA_CLK1_49M152_PERI_ARESETn_BIT)) throw std::runtime_error("FPGA I2S Core did not come out of reset (possible issue with FPGA_CLK1), aborting!"); else - SoapySDR_logf(SOAPY_SDR_DEBUG, "FPGA I2S Core reset released!"); + DLOGF(SOAPY_SDR_DEBUG, "FPGA I2S Core reset released!"); // De-assert RF section logic reset (we do not check if it comes out of reset because there are other conditions that need to be met) this->axi_gpio[AXI_GPIO_TRX_INST]->setValue(AXI_GPIO_RST_AD9361_61M44_AUX_RESET_IN_BIT, AXIGPIO::Value::LOW); @@ -1236,14 +1388,14 @@ void SoapyIcyRadio::initClocks() if(!this->axi_gpio[AXI_GPIO_SYS_INST]->getValue(AXI_GPIO_PCIE_MMCM_LOCKED_BIT)) throw std::runtime_error("FPGA PCIe MMCM (axi_pcie_0) did not achieve lock (how did we get here?), aborting!"); else - SoapySDR_logf(SOAPY_SDR_DEBUG, "FPGA PCIe MMCM locked!"); + DLOGF(SOAPY_SDR_DEBUG, "FPGA PCIe MMCM locked!"); } void SoapyIcyRadio::deinitClocks() { // RF section clock domain reset procedure (this only works if the xcvr is supplying the clock to the RF section) // When this function is called in the cleanup, the RF section is already reset // When it is called during the init procedure, when an already initialized device is detected, it works because the xcvr is supplying the clock) - SoapySDR_logf(SOAPY_SDR_DEBUG, "Resetting RF section..."); + DLOGF(SOAPY_SDR_DEBUG, "Resetting RF section..."); this->axi_gpio[AXI_GPIO_TRX_INST]->setValue(AXI_GPIO_RST_AD9361_61M44_AUX_RESET_IN_BIT, AXIGPIO::Value::HIGH); @@ -1254,10 +1406,10 @@ void SoapyIcyRadio::deinitClocks() if(this->axi_gpio[AXI_GPIO_TRX_INST]->getValue(AXI_GPIO_RST_AD9361_61M44_PERI_ARESETn_BIT)) throw std::runtime_error("Could not reset RF section, aborting!"); - SoapySDR_logf(SOAPY_SDR_DEBUG, "RF section logic is reset!"); + DLOGF(SOAPY_SDR_DEBUG, "RF section logic is reset!"); // I2S clock domain reset procedure - SoapySDR_logf(SOAPY_SDR_DEBUG, "Resetting I2S core..."); + DLOGF(SOAPY_SDR_DEBUG, "Resetting I2S core..."); this->axi_gpio[AXI_GPIO_SYS_INST]->setValue(AXI_GPIO_RST_FPGA_CLK1_49M152_AUX_RESET_IN_BIT, AXIGPIO::Value::HIGH); @@ -1268,11 +1420,11 @@ void SoapyIcyRadio::deinitClocks() if(this->axi_gpio[AXI_GPIO_SYS_INST]->getValue(AXI_GPIO_RST_FPGA_CLK1_49M152_PERI_ARESETn_BIT)) throw std::runtime_error("Could not reset I2S core, aborting!"); - SoapySDR_logf(SOAPY_SDR_DEBUG, "I2S core reset!"); + DLOGF(SOAPY_SDR_DEBUG, "I2S core reset!"); // DDR3 clock domain(s) reset procedure // First, reset the DDR3 AXI interface - SoapySDR_logf(SOAPY_SDR_DEBUG, "Resetting DDR3 AXI interface..."); + DLOGF(SOAPY_SDR_DEBUG, "Resetting DDR3 AXI interface..."); this->axi_gpio[AXI_GPIO_SYS_INST]->setValue(AXI_GPIO_RST_MIG_166M_AUX_RESET_IN_BIT, AXIGPIO::Value::HIGH); @@ -1283,10 +1435,10 @@ void SoapyIcyRadio::deinitClocks() if(this->axi_gpio[AXI_GPIO_SYS_INST]->getValue(AXI_GPIO_RST_MIG_166M_PERI_ARESETn_BIT)) throw std::runtime_error("Could not reset DDR3 AXI interface, aborting!"); - SoapySDR_logf(SOAPY_SDR_DEBUG, "DDR3 AXI interface reset!"); + DLOGF(SOAPY_SDR_DEBUG, "DDR3 AXI interface reset!"); // Then reset the DDR3 core - SoapySDR_logf(SOAPY_SDR_DEBUG, "Resetting DDR3 core..."); + DLOGF(SOAPY_SDR_DEBUG, "Resetting DDR3 core..."); this->axi_gpio[AXI_GPIO_SYS_INST]->setValue(AXI_GPIO_RST_CLK_WIZ0_250M_AUX_RESET_IN_BIT, AXIGPIO::Value::HIGH); @@ -1297,12 +1449,12 @@ void SoapyIcyRadio::deinitClocks() if(this->axi_gpio[AXI_GPIO_SYS_INST]->getValue(AXI_GPIO_RST_CLK_WIZ0_250M_PERI_ARESETn_BIT)) throw std::runtime_error("Could not reset DDR3 core, aborting!"); - SoapySDR_logf(SOAPY_SDR_DEBUG, "DDR3 core reset, DDR3 MMCM is %s!", this->axi_gpio[AXI_GPIO_SYS_INST]->getValue(AXI_GPIO_MIG_MMCM_LOCKED_BIT) ? "locked (how?)" : "unlocked"); + DLOGF(SOAPY_SDR_DEBUG, "DDR3 core reset, DDR3 MMCM is %s!", this->axi_gpio[AXI_GPIO_SYS_INST]->getValue(AXI_GPIO_MIG_MMCM_LOCKED_BIT) ? "locked (how?)" : "unlocked"); // Disable clock manager output this->axi_gpio[AXI_GPIO_SYS_INST]->setValue(AXI_GPIO_CLK_MNGR_OEn_BIT, AXIGPIO::Value::HIGH); - SoapySDR_logf(SOAPY_SDR_DEBUG, "Clock manager global output disabled, clk_wiz_0 MMCM is %s!", this->axi_gpio[AXI_GPIO_SYS_INST]->getValue(AXI_GPIO_CLK_WIZ0_LOCKED_BIT) ? "locked (how?)" : "unlocked"); + DLOGF(SOAPY_SDR_DEBUG, "Clock manager global output disabled, clk_wiz_0 MMCM is %s!", this->axi_gpio[AXI_GPIO_SYS_INST]->getValue(AXI_GPIO_CLK_WIZ0_LOCKED_BIT) ? "locked (how?)" : "unlocked"); } void SoapyIcyRadio::resetSystem() @@ -1315,11 +1467,11 @@ void SoapyIcyRadio::resetSystem() if(!clk_mngr_oen) // Clock manager OE enabled { - SoapySDR_logf(SOAPY_SDR_DEBUG, "Clock manager global output is enabled"); + DLOGF(SOAPY_SDR_TRACE, "Clock manager global output is enabled"); if(clk_wiz_0_locked && ddr_resetn && mig_mmcm_locked && ddr_axi_resetn) { - SoapySDR_logf(SOAPY_SDR_DEBUG, "DDR3 is properly out of reset, looks like the system was left initialized"); + DLOGF(SOAPY_SDR_TRACE, "DDR3 is properly out of reset, looks like the system was left initialized"); this->deinitClocks(); // Will throw if it fails } @@ -1337,11 +1489,11 @@ void SoapyIcyRadio::resetSystem() } else // Clock manager OE disabled { - SoapySDR_logf(SOAPY_SDR_DEBUG, "Clock manager global output is disabled"); + DLOGF(SOAPY_SDR_TRACE, "Clock manager global output is disabled"); if(!clk_wiz_0_locked && !ddr_resetn && !mig_mmcm_locked && !ddr_axi_resetn) { - SoapySDR_logf(SOAPY_SDR_DEBUG, "DDR3 is properly reset, looks like the system was left uninitialized"); + DLOGF(SOAPY_SDR_TRACE, "DDR3 is properly reset, looks like the system was left uninitialized"); } else if(!mig_mmcm_locked && ddr_axi_resetn) // AXI reset is de-asserted, but clocks are not locked, may cause lockups { @@ -1356,7 +1508,7 @@ void SoapyIcyRadio::resetSystem() } } - SoapySDR_logf(SOAPY_SDR_DEBUG, "Initiating system reset..."); + DLOGF(SOAPY_SDR_DEBUG, "Initiating system reset..."); // This will clear the two DDR3 reset bits set previously! this->axi_gpio[AXI_GPIO_SYS_INST]->setValue(AXI_GPIO_SYS_AUX_RESET_BIT, AXIGPIO::Value::HIGH); @@ -1375,7 +1527,7 @@ void SoapyIcyRadio::DMAHandler(void *arg) if(buf->device == nullptr) { - SoapySDR_logf(SOAPY_SDR_WARNING, "DMAHandler: Called with unrelated transfer"); + DLOGF(SOAPY_SDR_WARNING, "DMAHandler: Called with unrelated transfer"); return; } @@ -1390,7 +1542,9 @@ void SoapyIcyRadio::handleDMAData(SoapyIcyRadio::Stream::Channel::DMABuffer *buf std::lock_guard lock(buf->parent->mutex); - SoapySDR_logf(SOAPY_SDR_TRACE, "handleDMAData: Controller %u, transfer %u done", buf->parent->dma->getPeripheralID(), buf->xfer.id); + buf->idle = true; + + DLOGF_S(SOAPY_SDR_TRACE, "handleDMAData: Controller %u, DMA buffer %u, transfer %u done", buf->parent->dma->getPeripheralID(), buf->index, buf->xfer.id); if(buf->parent->parent->direction == SOAPY_SDR_RX) { @@ -1403,57 +1557,61 @@ void SoapyIcyRadio::handleDMAData(SoapyIcyRadio::Stream::Channel::DMABuffer *buf if(hw_time_status == AXIRFTStamp::CounterLatchStatus::LATCH_VALID) { + #ifdef TRACE_STREAM uint64_t hw_time_now = this->axi_rf_tstamp->getCounter(); // Hardware time now + #endif uint64_t sw_time_now = hw_time + buf->xfer.size / ICYRADIO_SAMPLE_SIZE_BYTES; // Software time now, is the start time of this buffer plus this buffer's size - if(buf->parent->next_time_valid) - SoapySDR_logf(SOAPY_SDR_WARNING, "handleDMAData: Channel %u RX buffer hardware timestamp %llu is valid, was %llu (delta %lld)", buf->parent->num, hw_time, buf->parent->next_time, hw_time - buf->parent->next_time); + if(buf->parent->next_dma_user_buf_time_valid) + DLOGF(SOAPY_SDR_WARNING, "handleDMAData: Channel %u RX buffer hardware timestamp %llu is valid, was %llu (delta %lld)", buf->parent->num, hw_time, buf->parent->next_dma_user_buf_time, hw_time - buf->parent->next_dma_user_buf_time); else - SoapySDR_logf(SOAPY_SDR_TRACE, "handleDMAData: Channel %u RX buffer hardware timestamp %llu is valid, next is %llu, next hardware is %llu (delta %lld)", buf->parent->num, hw_time, sw_time_now, hw_time_now, hw_time_now - sw_time_now); + DLOGF_S(SOAPY_SDR_TRACE, "handleDMAData: Channel %u RX buffer hardware timestamp %llu is valid, next is %llu, next hardware is %llu (delta %lld)", buf->parent->num, hw_time, sw_time_now, hw_time_now, hw_time_now - sw_time_now); buf_time = hw_time; buf_time_valid = true; - buf->parent->next_time = sw_time_now; - buf->parent->next_time_valid = true; + buf->parent->next_dma_user_buf_time = sw_time_now; + buf->parent->next_dma_user_buf_time_valid = true; } - else if(buf->parent->next_time_valid) + else if(buf->parent->next_dma_user_buf_time_valid) { + #ifdef TRACE_STREAM uint64_t hw_time_now = this->axi_rf_tstamp->getCounter(); // Hardware time now - uint64_t sw_time_now = buf->parent->next_time + buf->xfer.size / ICYRADIO_SAMPLE_SIZE_BYTES; // Software time now, is the start time of this buffer plus this buffer's size + #endif + uint64_t sw_time_now = buf->parent->next_dma_user_buf_time + buf->xfer.size / ICYRADIO_SAMPLE_SIZE_BYTES; // Software time now, is the start time of this buffer plus this buffer's size - SoapySDR_logf(SOAPY_SDR_TRACE, "handleDMAData: Channel %u RX buffer software timestamp %llu is valid, next is %llu, next hardware is %llu (delta %lld)", buf->parent->num, buf->parent->next_time, sw_time_now, hw_time_now, hw_time_now - sw_time_now); + DLOGF_S(SOAPY_SDR_TRACE, "handleDMAData: Channel %u RX buffer software timestamp %llu is valid, next is %llu, next hardware is %llu (delta %lld)", buf->parent->num, buf->parent->next_dma_user_buf_time, sw_time_now, hw_time_now, hw_time_now - sw_time_now); - buf_time = buf->parent->next_time; + buf_time = buf->parent->next_dma_user_buf_time; buf_time_valid = true; - buf->parent->next_time = sw_time_now; + buf->parent->next_dma_user_buf_time = sw_time_now; } else { - SoapySDR_logf(SOAPY_SDR_WARNING, "handleDMAData: Channel %u RX buffer timestamp missing (hw status is %u, hw time is %llu)", buf->parent->num, hw_time_status, hw_time); + DLOGF(SOAPY_SDR_WARNING, "handleDMAData: Channel %u RX buffer timestamp missing (hw status is %u, hw time is %llu)", buf->parent->num, hw_time_status, hw_time); buf_time = 0; buf_time_valid = false; } - auto user_buf = buf->parent->buffers[buf->parent->next_dma_buf]; + auto user_buf = buf->parent->buffers[buf->parent->next_dma_user_buf]; if(user_buf->valid_size > 0) { - SoapySDR_logf(SOAPY_SDR_TRACE, "handleDMAData: User buffer queue head is %u, buffer still has valid data, new data discarded", buf->parent->next_dma_buf); + DLOGF_S(SOAPY_SDR_TRACE, "handleDMAData: User buffer queue head is %u, buffer still has valid data, new data discarded", buf->parent->next_dma_user_buf); - SoapySDR_logf(SOAPY_SDR_SSI, "O"); + DLOGF(SOAPY_SDR_SSI, "O"); } else if(user_buf->acquired) { - SoapySDR_logf(SOAPY_SDR_TRACE, "handleDMAData: User buffer queue head is %u, buffer is acquired by user, new data discarded", buf->parent->next_dma_buf); + DLOGF_S(SOAPY_SDR_TRACE, "handleDMAData: User buffer queue head is %u, buffer is acquired by user, new data discarded", buf->parent->next_dma_user_buf); - SoapySDR_logf(SOAPY_SDR_SSI, "O"); + DLOGF(SOAPY_SDR_SSI, "O"); } else { - SoapySDR_logf(SOAPY_SDR_TRACE, "handleDMAData: User buffer queue head is %u, buffer is free, copying data", buf->parent->next_dma_buf); + DLOGF_S(SOAPY_SDR_TRACE, "handleDMAData: User buffer queue head is %u, buffer is free, copying data", buf->parent->next_dma_user_buf); std::memcpy(user_buf->addr, buf->virt, buf->xfer.size); @@ -1461,7 +1619,7 @@ void SoapyIcyRadio::handleDMAData(SoapyIcyRadio::Stream::Channel::DMABuffer *buf user_buf->time = buf_time; user_buf->time_valid = buf_time_valid; - buf->parent->next_dma_buf = (buf->parent->next_dma_buf + 1) % buf->parent->buffers.size(); + buf->parent->next_dma_user_buf = (buf->parent->next_dma_user_buf + 1) % buf->parent->buffers.size(); } if(buf->parent->dma->idle()) @@ -1469,7 +1627,7 @@ void SoapyIcyRadio::handleDMAData(SoapyIcyRadio::Stream::Channel::DMABuffer *buf // This should never happen. Even when user cannot read fast enough, the DMA controller should be able to keep up // The data is just discarded and never copied to the user buffer, but the DMA still runs // If this happens, it means the system is not able to keep up with the data rate/interrupt rate - SoapySDR_logf(SOAPY_SDR_WARNING, "handleDMAData: DMA controller is idle, system cannot keep up!"); + DLOGF(SOAPY_SDR_WARNING, "handleDMAData: DMA controller is idle, system cannot keep up!"); } else { @@ -1477,17 +1635,169 @@ void SoapyIcyRadio::handleDMAData(SoapyIcyRadio::Stream::Channel::DMABuffer *buf { buf->parent->dma->submitTransfer(buf->xfer); - SoapySDR_logf(SOAPY_SDR_TRACE, "handleDMAData: Re-submitted transfer, new ID %u", buf->xfer.id); + buf->idle = false; + + DLOGF_S(SOAPY_SDR_TRACE, "handleDMAData: Re-submitted transfer, new ID %u", buf->xfer.id); } catch(const std::exception &e) { - SoapySDR_logf(SOAPY_SDR_ERROR, "handleDMAData: Failed to re-submit transfer: %s", e.what()); + DLOGF(SOAPY_SDR_ERROR, "handleDMAData: Failed to re-submit transfer: %s", e.what()); } } } else if(buf->parent->parent->direction == SOAPY_SDR_TX) { - // TODO + AXIRFTStamp::Channel ts_chan = buf->parent->ts_chan; + + auto dma_buf = buf; + + while(dma_buf) + { + bool submit_xfer = true; + bool cur = (dma_buf == buf); + auto user_buf = dma_buf->parent->buffers[dma_buf->parent->next_dma_user_buf]; + + if(!user_buf->valid_size) + { + submit_xfer = false; + + DLOGF_S(SOAPY_SDR_TRACE, "handleDMAData: User buffer queue head is %u, buffer has no valid data, not submitting new transfer", dma_buf->parent->next_dma_user_buf); + + DLOGF(SOAPY_SDR_SSI, "U"); + + if(dma_buf->parent->dma->idle()) + { + DLOGF_S(SOAPY_SDR_TRACE, "handleDMAData: DMA controller is idle, disabling"); + + this->axi_rf_tstamp->disableTXCounter(ts_chan); + this->axi_rf_tstamp->disableTX(ts_chan); + + dma_buf->parent->dma->disable(); + } + } + else if(user_buf->acquired) + { + submit_xfer = false; + + DLOGF_S(SOAPY_SDR_TRACE, "handleDMAData: User buffer queue head is %u, buffer is acquired by user, not submitting new transfer", dma_buf->parent->next_dma_user_buf); + + DLOGF(SOAPY_SDR_SSI, "U"); + + if(dma_buf->parent->dma->idle()) + { + DLOGF_S(SOAPY_SDR_TRACE, "handleDMAData: DMA controller is idle, disabling"); + + this->axi_rf_tstamp->disableTXCounter(ts_chan); + this->axi_rf_tstamp->disableTX(ts_chan); + + dma_buf->parent->dma->disable(); + } + } + else if(user_buf->time_valid && (!cur || !dma_buf->parent->dma->idle())) + { + // If the next buffer has a time, but the DMA is not idle, we cannot submit a new transfer + // because this would mess the timestamping up, since the DMA is working on another buffer, we cannot disable TX + // Instead, we skip this buffer and wait for the next interrput (the one corresponding to the buffer the DMA is currently working on) + // and then, it will be idle, and we can safely submit the new transfer + submit_xfer = false; + + DLOGF_S(SOAPY_SDR_TRACE, "handleDMAData: User buffer queue head is %u, buffer has time but DMA is not idle, not submitting new transfer", dma_buf->parent->next_dma_user_buf); + } + + if(submit_xfer) + { + DLOGF_S(SOAPY_SDR_TRACE, "handleDMAData: User buffer queue head is %u, buffer has valid data, copying data", dma_buf->parent->next_dma_user_buf); + + std::memcpy(dma_buf->virt, user_buf->addr, user_buf->valid_size); + + dma_buf->xfer.size = user_buf->valid_size; + + if(user_buf->time_valid) + { + uint64_t hw_time_now = this->axi_rf_tstamp->getCounter(); // Hardware time now + + if(user_buf->time > hw_time_now) + { + DLOGF_S(SOAPY_SDR_TRACE, "handleDMAData: User buffer time %llu is valid and not late (%llu ticks in the future), disabling TX and arming trigger", user_buf->time, user_buf->time - hw_time_now); + + this->axi_rf_tstamp->disableTXCounter(ts_chan); + this->axi_rf_tstamp->waitTXCounterDisabled(ts_chan, 100); + this->axi_rf_tstamp->disableTX(ts_chan); + this->axi_rf_tstamp->setTXCounter(ts_chan, user_buf->time); // Safe because we already made sure the counter is disabled + this->axi_rf_tstamp->waitTXDisabled(ts_chan, 100); + this->axi_rf_tstamp->enableTXCounter(ts_chan); + } + else + { + DLOGF(SOAPY_SDR_WARNING, "handleDMAData: User buffer time %llu is late (%llu ticks late), not arming trigger, enabling TX now", user_buf->time, hw_time_now - user_buf->time); + + DLOGF(SOAPY_SDR_SSI, "L"); + + this->axi_rf_tstamp->enableTX(ts_chan); + } + } + else + { + DLOGF_S(SOAPY_SDR_TRACE, "handleDMAData: User buffer time is not valid"); + } + + user_buf->valid_size = 0; + user_buf->time_valid = false; + + try + { + dma_buf->parent->dma->submitTransfer(dma_buf->xfer); + + dma_buf->idle = false; + + if(cur) + DLOGF_S(SOAPY_SDR_TRACE, "handleDMAData: Re-submitted transfer, new ID %u", dma_buf->xfer.id); + else + DLOGF_S(SOAPY_SDR_TRACE, "handleDMAData: Submitted transfer ID %u", dma_buf->xfer.id); + } + catch(const std::exception &e) + { + if(cur) + DLOGF(SOAPY_SDR_ERROR, "handleDMAData: Failed to re-submit transfer: %s", e.what()); + else + DLOGF(SOAPY_SDR_ERROR, "handleDMAData: Failed to submit transfer: %s", e.what()); + } + + dma_buf->parent->next_dma_user_buf = (dma_buf->parent->next_dma_user_buf + 1) % dma_buf->parent->buffers.size(); + + // Find a possible free DMA buffer + SoapyIcyRadio::Stream::Channel::DMABuffer *next_dma_buf = nullptr; + + size_t i = (dma_buf->index + 1) % dma_buf->parent->dma_buffers.size(); + + for(size_t n = 0; n < dma_buf->parent->dma_buffers.size(); n++) + { + auto _buf = dma_buf->parent->dma_buffers[i]; + + if(_buf->idle) + { + next_dma_buf = _buf; + + break; + } + + i = (i + 1) % dma_buf->parent->dma_buffers.size(); + } + + if(next_dma_buf) + DLOGF_S(SOAPY_SDR_TRACE, "handleDMAData: DMA buffer %u is idle, checking if it can be submitted", next_dma_buf->index); + else + DLOGF_S(SOAPY_SDR_TRACE, "handleDMAData: No idle DMA buffer found, everything done"); + + dma_buf = next_dma_buf; + } + else + { + DLOGF_S(SOAPY_SDR_TRACE, "handleDMAData: Not submitting transfer, everything done"); + + dma_buf = nullptr; + } + } } } @@ -1495,7 +1805,7 @@ bool SoapyIcyRadio::requiresDataPathReconfiguration(const std::vector &r { uint8_t phy_channels = this->rf_phy->getChannelCount(); - SoapySDR_logf(SOAPY_SDR_DEBUG, "requiresDataPathReconfiguration: Phy channels: %u, RX channels %u, TX channels %u", phy_channels, rx_channels.size(), tx_channels.size()); + DLOGF(SOAPY_SDR_DEBUG, "requiresDataPathReconfiguration: Phy channels: %u, RX channels %u, TX channels %u", phy_channels, rx_channels.size(), tx_channels.size()); // If the Phy is configured for dual channel, no reconfiguration is needed if(phy_channels > 1) @@ -1510,7 +1820,7 @@ bool SoapyIcyRadio::requiresDataPathReconfiguration(const std::vector &r // If in single channel mode... if(channel_count <= 1) { - SoapySDR_logf(SOAPY_SDR_DEBUG, "requiresDataPathReconfiguration: Single channel mode, current RX %u, wanted RX %u, current TX %u, wanted TX %u", this->rf_phy->pdata->rx1tx1_mode_use_rx_num - 1, rx_channels.size() > 0 ? rx_channels[0] : 0, this->rf_phy->pdata->rx1tx1_mode_use_tx_num - 1, tx_channels.size() > 0 ? tx_channels[0] : 0); + DLOGF(SOAPY_SDR_DEBUG, "requiresDataPathReconfiguration: Single channel mode, current RX %u, wanted RX %u, current TX %u, wanted TX %u", this->rf_phy->pdata->rx1tx1_mode_use_rx_num - 1, rx_channels.size() > 0 ? rx_channels[0] : 0, this->rf_phy->pdata->rx1tx1_mode_use_tx_num - 1, tx_channels.size() > 0 ? tx_channels[0] : 0); // ...and the selected RX channel is not the one currently in use, we need to reconfigure if(rx_channels.size() > 0 && BIT(rx_channels[0]) != this->rf_phy->pdata->rx1tx1_mode_use_rx_num) @@ -1527,7 +1837,7 @@ bool SoapyIcyRadio::requiresDataPathReconfiguration(const double new_rate) const { uint8_t phy_channels = this->rf_phy->getChannelCount(); - SoapySDR_logf(SOAPY_SDR_DEBUG, "requiresDataPathReconfiguration: Phy channels: %u, new rate %u Sps", phy_channels, (size_t)new_rate); + DLOGF(SOAPY_SDR_DEBUG, "requiresDataPathReconfiguration: Phy channels: %u, new rate %u Sps", phy_channels, (size_t)new_rate); // If the Phy is configured for single channel, we allow all sample rates without reconfiguration // i.e., the datapath will be forced to reconfigure if we later request the second channel @@ -1543,7 +1853,7 @@ bool SoapyIcyRadio::requiresDataPathReconfiguration(const double new_rate) const } void SoapyIcyRadio::reconfigureDataPath(bool rx2tx2, size_t rx_ch, size_t tx_ch) { - SoapySDR_logf(SOAPY_SDR_DEBUG, "reconfigureDataPath: %s channel mode, use RX%u, use TX%u", rx2tx2 ? "Dual" : "Single", rx_ch, tx_ch); + DLOGF(SOAPY_SDR_DEBUG, "reconfigureDataPath: %s channel mode, use RX%u, use TX%u", rx2tx2 ? "Dual" : "Single", rx_ch, tx_ch); this->rf_phy->pdata->rx2tx2 = rx2tx2; this->rf_phy->pdata->rx1tx1_mode_use_rx_num = BIT(rx_ch); @@ -1564,7 +1874,7 @@ void SoapyIcyRadio::reconfigureDataPath(bool rx2tx2, size_t rx_ch, size_t tx_ch) } void SoapyIcyRadio::validateSampleRateAndChannelCombination(const double rate, const size_t channel_count) const { - SoapySDR_logf(SOAPY_SDR_DEBUG, "validateSampleRateAndChannelCombination: Rate %u Sps, channel count %u", (size_t)rate, channel_count); + DLOGF(SOAPY_SDR_DEBUG, "validateSampleRateAndChannelCombination: Rate %u Sps, channel count %u", (size_t)rate, channel_count); if(rate > (double)MAX_BASEBAND_RATE / channel_count) throw std::runtime_error("validateSampleRateAndChannelCombination: Rate too high for number of channels, maximum is " + std::to_string(MAX_BASEBAND_RATE / channel_count) + " Sps"); @@ -1673,7 +1983,7 @@ void SoapyIcyRadio::initStreamChannels(SoapyIcyRadio::Stream *stream, const std: { std::unique_ptr chan = std::make_unique(); - SoapySDR_logf(SOAPY_SDR_DEBUG, "initStreamChannels: Setup %s%u", (stream->direction == SOAPY_SDR_RX) ? "RX" : "TX", c); + DLOGF(SOAPY_SDR_DEBUG, "initStreamChannels: Setup %s%u", (stream->direction == SOAPY_SDR_RX) ? "RX" : "TX", c); chan->parent = stream; chan->num = c; @@ -1687,7 +1997,7 @@ void SoapyIcyRadio::initStreamChannels(SoapyIcyRadio::Stream *stream, const std: if(!dma->idle()) throw std::runtime_error("initStreamChannels: DMA controller for channel " + std::to_string(c) + " is not idle"); - SoapySDR_logf(SOAPY_SDR_DEBUG, "initStreamChannels: Using DMA controller %u", dma->getPeripheralID()); + DLOGF(SOAPY_SDR_DEBUG, "initStreamChannels: Using DMA controller %u", dma->getPeripheralID()); chan->dma = dma; @@ -1697,14 +2007,15 @@ void SoapyIcyRadio::initStreamChannels(SoapyIcyRadio::Stream *stream, const std: void *buf_start = this->getDMAPoolStartVirt(stream->direction, c); size_t buf_free_size = this->getDMAPoolSizeBytes(stream->direction, c); - SoapySDR_logf(SOAPY_SDR_TRACE, "initStreamChannels: Using DMA buffer pool at 0x%016llX, available size %u", buf_start, buf_free_size); + DLOGF(SOAPY_SDR_TRACE, "initStreamChannels: Using DMA buffer pool at 0x%016llX, available size %u", buf_start, buf_free_size); for(size_t b = 0; b < ICYRADIO_DEFAULT_DMA_NUM_BUFFERS; b++) { std::unique_ptr buf = std::make_unique(); - SoapySDR_logf(SOAPY_SDR_DEBUG, "initStreamChannels: Setup DMA buffer %u", b); + DLOGF(SOAPY_SDR_DEBUG, "initStreamChannels: Setup DMA buffer %u", b); + buf->index = b; buf->device = this; buf->parent = chan.get(); buf->virt = buf_start; @@ -1722,7 +2033,7 @@ void SoapyIcyRadio::initStreamChannels(SoapyIcyRadio::Stream *stream, const std: { buf_phys_axi = this->axi_pcie->getBARAXIAddress(i, buf_phys); - SoapySDR_logf(SOAPY_SDR_TRACE, "initStreamChannels: Size: 0x%08lX, Virt: 0x%016llX, Phys: 0x%016llX, AXI: 0x%08lX @ PCIe BAR: %u", buf->size, (uintptr_t)buf_start, buf_phys, buf_phys_axi, i); + DLOGF(SOAPY_SDR_TRACE, "initStreamChannels: Size: 0x%08lX, Virt: 0x%016llX, Phys: 0x%016llX, AXI: 0x%08lX @ PCIe BAR: %u", buf->size, (uintptr_t)buf_start, buf_phys, buf_phys_axi, i); break; } @@ -1747,6 +2058,8 @@ void SoapyIcyRadio::initStreamChannels(SoapyIcyRadio::Stream *stream, const std: buf->xfer.cb = SoapyIcyRadio::DMAHandler; buf->xfer.cb_arg = reinterpret_cast(buf.get()); + buf->idle = true; + // Update DMA buffer pool buf_start = reinterpret_cast(reinterpret_cast(buf_start) + buf->size); buf_free_size -= buf->size; @@ -1759,13 +2072,13 @@ void SoapyIcyRadio::initStreamChannels(SoapyIcyRadio::Stream *stream, const std: // User buffers chan->buffers = std::vector(); - SoapySDR_logf(SOAPY_SDR_TRACE, "initStreamChannels: Allocating %u user buffers", ICYRADIO_DEFAULT_NUM_BUFFERS); + DLOGF(SOAPY_SDR_TRACE, "initStreamChannels: Allocating %u user buffers", ICYRADIO_DEFAULT_NUM_BUFFERS); for(size_t b = 0; b < ICYRADIO_DEFAULT_NUM_BUFFERS; b++) { std::unique_ptr buf = std::make_unique(); - SoapySDR_logf(SOAPY_SDR_DEBUG, "initStreamChannels: Setup user buffer %u", b); + DLOGF(SOAPY_SDR_DEBUG, "initStreamChannels: Setup user buffer %u", b); buf->size = ICYRADIO_DEFAULT_DMA_BUFFER_SIZE_BYTES; // TODO: Make configurable buf->addr = std::malloc(buf->size); @@ -1773,7 +2086,7 @@ void SoapyIcyRadio::initStreamChannels(SoapyIcyRadio::Stream *stream, const std: if(!buf->addr) throw std::runtime_error("initStreamChannels: Failed to allocate user buffer"); - SoapySDR_logf(SOAPY_SDR_TRACE, "initStreamChannels: Size: 0x%08lX, Virt: 0x%016llX", buf->size, (uintptr_t)buf->addr); + DLOGF(SOAPY_SDR_TRACE, "initStreamChannels: Size: 0x%08lX, Virt: 0x%016llX", buf->size, (uintptr_t)buf->addr); buf->valid_size = 0; buf->time = 0; @@ -1786,9 +2099,11 @@ void SoapyIcyRadio::initStreamChannels(SoapyIcyRadio::Stream *stream, const std: } chan->next_user_buf = 0; - chan->next_dma_buf = 0; - chan->next_time = 0; - chan->next_time_valid = false; + chan->next_user_buf_time = 0; + chan->next_user_buf_time_valid = false; + chan->next_dma_user_buf = 0; + chan->next_dma_user_buf_time = 0; + chan->next_dma_user_buf_time_valid = false; // Timestamping AXIRFTStamp::Channel ts_chan = this->getTimestampingChannel(stream->direction, c); @@ -1800,20 +2115,24 @@ void SoapyIcyRadio::initStreamChannels(SoapyIcyRadio::Stream *stream, const std: if(stream->direction == SOAPY_SDR_RX) { - SoapySDR_logf(SOAPY_SDR_TRACE, "initStreamChannels: Using timestamping channel RX%u", ts_chan); + DLOGF(SOAPY_SDR_TRACE, "initStreamChannels: Using timestamping channel RX%u", ts_chan); this->axi_rf_tstamp->disarmCounterLatch(ts_chan); this->axi_rf_tstamp->getCounterLatch(ts_chan); // Trigger read to clear valid status this->axi_rf_tstamp->disableRXCounter(ts_chan); this->axi_rf_tstamp->disableRX(ts_chan); + this->axi_rf_tstamp->waitRXCounterDisabled(ts_chan, 100); + this->axi_rf_tstamp->waitRXDisabled(ts_chan, 100); } else { - SoapySDR_logf(SOAPY_SDR_TRACE, "initStreamChannels: Using timestamping channel TX%u", ts_chan); + DLOGF(SOAPY_SDR_TRACE, "initStreamChannels: Using timestamping channel TX%u", ts_chan); this->axi_rf_tstamp->disableTXCounter(ts_chan); this->axi_rf_tstamp->disableTX(ts_chan); + this->axi_rf_tstamp->waitTXCounterDisabled(ts_chan, 100); + this->axi_rf_tstamp->waitTXDisabled(ts_chan, 100); } SoapyIcyRadio::Stream::Channel *_chan = chan.release(); @@ -1937,4 +2256,4 @@ std::vector SoapyIcyRadio::getBusyChannels(const int direction) const } return ret; -} +} \ No newline at end of file diff --git a/software/soapy/src/SoapyRegistration.cpp b/software/soapy/src/SoapyRegistration.cpp index aa9c2c50..e2b5ae87 100644 --- a/software/soapy/src/SoapyRegistration.cpp +++ b/software/soapy/src/SoapyRegistration.cpp @@ -55,7 +55,10 @@ SoapySDR::KwargsList findIcyRadio(const SoapySDR::Kwargs &args) if(fd < 0) { - SoapySDR_logf(SOAPY_SDR_DEBUG, "Failed to open device %s: %s", path.c_str(), strerror(errno)); + if(fd == -EBUSY) + continue; // Silent fail due to the device being open by another process + + DLOGF(SOAPY_SDR_DEBUG, "Failed to open device %s: %s", path.c_str(), std::strerror(errno)); continue; } @@ -64,7 +67,7 @@ SoapySDR::KwargsList findIcyRadio(const SoapySDR::Kwargs &args) if(ioctl(fd, ICYRADIO_IOCTL_SERIAL_QUERY, &serial) < 0) { - SoapySDR_logf(SOAPY_SDR_DEBUG, "Failed to query serial number of device %s: %s", path.c_str(), strerror(errno)); + DLOGF(SOAPY_SDR_DEBUG, "Failed to query serial number of device %s: %s", path.c_str(), std::strerror(errno)); close(fd); @@ -76,7 +79,6 @@ SoapySDR::KwargsList findIcyRadio(const SoapySDR::Kwargs &args) char _serial[16]; snprintf(_serial, sizeof(_serial), "%015lX", serial); - info["path"] = path; info["serial"] = _serial; info["label"] = "IcyRadio - " + info["serial"]; @@ -118,7 +120,7 @@ SoapySDR::Device *makeIcyRadio(const SoapySDR::Kwargs &args) // We may end up here if the user did not specify path, device_id or serial // Or in the rare case that multiple devices have the same serial number - SoapySDR_logf(SOAPY_SDR_DEBUG, "Multiple IcyRadio devices found with given arguments, defaulting to first device"); + DLOGF(SOAPY_SDR_DEBUG, "Multiple IcyRadio devices found with given arguments, defaulting to first device"); } _args["path"] = devices.at(0).at("path"); diff --git a/software/soapy/src/SoapySettings.cpp b/software/soapy/src/SoapySettings.cpp index 6ed803fd..b6e8451a 100644 --- a/software/soapy/src/SoapySettings.cpp +++ b/software/soapy/src/SoapySettings.cpp @@ -39,7 +39,7 @@ SoapyIcyRadio::SoapyIcyRadio(const SoapySDR::Kwargs &args) this->exp_card = nullptr; if(args.count("label") != 0) - SoapySDR_logf(SOAPY_SDR_INFO, "Opening %s", args.at("label").c_str()); + DLOGF(SOAPY_SDR_INFO, "Opening %s", args.at("label").c_str()); if(args.count("path") == 0) throw std::runtime_error("No device path specified"); @@ -49,6 +49,8 @@ SoapyIcyRadio::SoapyIcyRadio(const SoapySDR::Kwargs &args) if(this->fd < 0) throw std::runtime_error("Failed to open device (" + std::string(std::strerror(errno)) + ")"); + this->parseConfig(args); + try { this->setupMemoryMaps(); @@ -209,7 +211,6 @@ bool SoapyIcyRadio::getFullDuplex(const int direction, const size_t channel) con return true; } - std::vector SoapyIcyRadio::listAntennas(const int direction, const size_t channel) const { if(direction != SOAPY_SDR_RX && direction != SOAPY_SDR_TX) @@ -217,13 +218,20 @@ std::vector SoapyIcyRadio::listAntennas(const int direction, const std::vector antennas; - if(direction == SOAPY_SDR_RX) - { - antennas.push_back("RX"); - } - else + if(channel <= 1) { - antennas.push_back("TX"); + // RF Channels + if(direction == SOAPY_SDR_RX) + { + antennas.push_back("RX" + std::to_string(BIT(channel)) + "A"); + // RXnB is not routed + antennas.push_back("RX" + std::to_string(BIT(channel)) + "C"); + } + else + { + antennas.push_back("TX" + std::to_string(BIT(channel)) + "A"); + antennas.push_back("TX" + std::to_string(BIT(channel)) + "B"); + } } return antennas; @@ -232,38 +240,327 @@ void SoapyIcyRadio::setAntenna(const int direction, const size_t channel, const { if(direction != SOAPY_SDR_RX && direction != SOAPY_SDR_TX) throw std::runtime_error("setAntenna: Unknown direction"); + + if(channel <= 1) + { + // RF Channels + if(direction == SOAPY_SDR_RX) + { + if(name == ("RX" + std::to_string(BIT(channel)) + "A")) + { + this->rf_phy->pdata->rf_rx_input_sel = 0; + } + else if(name == ("RX" + std::to_string(BIT(channel)) + "C")) + { + this->rf_phy->pdata->rf_rx_input_sel = 2; + } + else + { + throw std::runtime_error("setAntenna: Unknown name"); + } + } + else + { + if(name == ("TX" + std::to_string(BIT(channel)) + "A")) + { + this->rf_phy->pdata->rf_tx_output_sel = 0; + } + else if(name == ("TX" + std::to_string(BIT(channel)) + "B")) + { + this->rf_phy->pdata->rf_tx_output_sel = 1; + } + else + { + throw std::runtime_error("setAntenna: Unknown name"); + } + } + + this->rf_phy->setupRFPort(false, this->rf_phy->pdata->rf_rx_input_sel, this->rf_phy->pdata->rf_tx_output_sel); + } + } std::string SoapyIcyRadio::getAntenna(const int direction, const size_t channel) const { if(direction != SOAPY_SDR_RX && direction != SOAPY_SDR_TX) throw std::runtime_error("getAntenna: Unknown direction"); - return "RX"; + if(channel <= 1) + { + // RF Channels + if(direction == SOAPY_SDR_RX) + { + if(this->rf_phy->pdata->rf_rx_input_sel == 0) + { + return "RX" + std::to_string(BIT(channel)) + "A"; + } + else if(this->rf_phy->pdata->rf_rx_input_sel == 2) + { + return "RX" + std::to_string(BIT(channel)) + "C"; + } + else + { + return "Unknown"; + } + } + else + { + if(this->rf_phy->pdata->rf_tx_output_sel == 0) + { + return "TX" + std::to_string(BIT(channel)) + "A"; + } + else if(this->rf_phy->pdata->rf_tx_output_sel == 1) + { + return "TX" + std::to_string(BIT(channel)) + "B"; + } + else + { + return "Unknown"; + } + } + } + + return "Unknown"; +} + + +std::vector SoapyIcyRadio::listGains(const int direction, const size_t channel) const +{ + if(direction != SOAPY_SDR_RX && direction != SOAPY_SDR_TX) + throw std::runtime_error("listGains: Unknown direction"); + + std::vector gains; + + if(direction == SOAPY_SDR_RX) + { + gains.push_back("RX_FE"); + } + else + { + gains.push_back("TX_ATT"); + } + + return gains; +} +bool SoapyIcyRadio::hasGainMode(const int direction, const size_t channel) const +{ + if(direction != SOAPY_SDR_RX && direction != SOAPY_SDR_TX) + throw std::runtime_error("hasGainMode: Unknown direction"); + + return false; + // return direction == SOAPY_SDR_RX; +} +void SoapyIcyRadio::setGainMode(const int direction, const size_t channel, const bool automatic) +{ + if(direction != SOAPY_SDR_RX && direction != SOAPY_SDR_TX) + throw std::runtime_error("setGainMode: Unknown direction"); +} +bool SoapyIcyRadio::getGainMode(const int direction, const size_t channel) const +{ + if(direction != SOAPY_SDR_RX && direction != SOAPY_SDR_TX) + throw std::runtime_error("getGainMode: Unknown direction"); + + return false; +} +void SoapyIcyRadio::setGain(const int direction, const size_t channel, const double value) +{ + if(direction != SOAPY_SDR_RX && direction != SOAPY_SDR_TX) + throw std::runtime_error("setGain: Unknown direction"); + + this->setGain(direction, channel, direction == SOAPY_SDR_RX ? "RX_FE" : "TX_ATT", value); +} +void SoapyIcyRadio::setGain(const int direction, const size_t channel, const std::string &name, const double value) +{ + if(direction != SOAPY_SDR_RX && direction != SOAPY_SDR_TX) + throw std::runtime_error("setGain: Unknown direction"); + + if(direction == SOAPY_SDR_RX) + { + if(name == "RX_FE") + { + SoapySDR::Range r = this->getGainRange(direction, channel, name); + + if(value < r.minimum() || value > r.maximum()) + throw std::runtime_error("setGain: Gain out of range"); + + AD9361::RFRXGain g; + + g.gain_db = value; + + this->rf_phy->setRXGain(BIT(channel), &g); + } + else + { + throw std::runtime_error("setGain: Unknown name"); + } + } + else + { + if(name == "TX_ATT") + { + if(value < 0 || value > 89.75) + throw std::runtime_error("setGain: Gain out of range"); + + this->rf_phy->setTXAttenuation(value * 1000, channel == 0, channel == 1, true); + } + else + { + throw std::runtime_error("setGain: Unknown name"); + } + } +} +double SoapyIcyRadio::getGain(const int direction, const size_t channel) const +{ + if(direction != SOAPY_SDR_RX && direction != SOAPY_SDR_TX) + throw std::runtime_error("getGain: Unknown direction"); + + return this->getGain(direction, channel, direction == SOAPY_SDR_RX ? "RX_FE" : "TX_ATT"); +} +double SoapyIcyRadio::getGain(const int direction, const size_t channel, const std::string &name) const +{ + if(direction != SOAPY_SDR_RX && direction != SOAPY_SDR_TX) + throw std::runtime_error("getGain: Unknown direction"); + + if(direction == SOAPY_SDR_RX) + { + if(name == "RX_FE") + { + AD9361::RFRXGain g; + + this->rf_phy->getRXGain(BIT(channel), &g); + + return g.gain_db; + } + else + { + throw std::runtime_error("getGain: Unknown name"); + } + } + else + { + if(name == "TX_ATT") + { + return (double)this->rf_phy->getTXAttenuation(BIT(channel)) / 1000.0; + } + else + { + throw std::runtime_error("getGain: Unknown name"); + } + } +} +SoapySDR::Range SoapyIcyRadio::getGainRange(const int direction, const size_t channel) const +{ + if(direction != SOAPY_SDR_RX && direction != SOAPY_SDR_TX) + throw std::runtime_error("getGainRange: Unknown direction"); + + return this->getGainRange(direction, channel, direction == SOAPY_SDR_RX ? "RX_FE" : "TX_ATT"); +} +SoapySDR::Range SoapyIcyRadio::getGainRange(const int direction, const size_t channel, const std::string &name) const +{ + if(direction != SOAPY_SDR_RX && direction != SOAPY_SDR_TX) + throw std::runtime_error("getGainRange: Unknown direction"); + + if(direction == SOAPY_SDR_RX) + { + if(name == "RX_FE") + { + int8_t min = this->rf_phy->gt_info[this->rf_phy->getCurrentGainTable()].abs_gain_tbl[0]; + int8_t max = this->rf_phy->gt_info[this->rf_phy->getCurrentGainTable()].abs_gain_tbl[this->rf_phy->gt_info[this->rf_phy->getCurrentGainTable()].max_index - 1]; + + return SoapySDR::Range(min, max, 1.0); + } + else + { + throw std::runtime_error("getGainRange: Unknown name"); + } + } + else + { + if(name == "TX_ATT") + { + return SoapySDR::Range(0.0, 89.75, 0.25); + } + else + { + throw std::runtime_error("getGainRange: Unknown name"); + } + } } void SoapyIcyRadio::setFrequency(const int direction, const size_t channel, const double frequency, const SoapySDR::Kwargs &args) { if(direction != SOAPY_SDR_RX && direction != SOAPY_SDR_TX) throw std::runtime_error("setFrequency: Unknown direction"); + + this->setFrequency(direction, channel, "LO", frequency, args); } void SoapyIcyRadio::setFrequency(const int direction, const size_t channel, const std::string &name, const double frequency, const SoapySDR::Kwargs &args) { if(direction != SOAPY_SDR_RX && direction != SOAPY_SDR_TX) throw std::runtime_error("setFrequency: Unknown direction"); + + if(direction == SOAPY_SDR_RX) + { + if(name == "LO") + { + if(frequency < 70e6 || frequency > 6e9) + throw std::runtime_error("setFrequency: Frequency out of range"); + + this->rf_phy->setClockRate(this->rf_phy->ref_clk_scale[AD9361::ClockIndex::RX_RFPLL], frequency); + } + else + { + throw std::runtime_error("setFrequency: Unknown name"); + } + } + else + { + if(name == "LO") + { + if(frequency < 47e6 || frequency > 6e9) + throw std::runtime_error("setFrequency: Frequency out of range"); + + this->rf_phy->setClockRate(this->rf_phy->ref_clk_scale[AD9361::ClockIndex::TX_RFPLL], frequency); + } + else + { + throw std::runtime_error("setFrequency: Unknown name"); + } + } } double SoapyIcyRadio::getFrequency(const int direction, const size_t channel) const { if(direction != SOAPY_SDR_RX && direction != SOAPY_SDR_TX) throw std::runtime_error("getFrequency: Unknown direction"); - return 101.4e6; + return this->getFrequency(direction, channel, "LO"); } double SoapyIcyRadio::getFrequency(const int direction, const size_t channel, const std::string &name) const { if(direction != SOAPY_SDR_RX && direction != SOAPY_SDR_TX) throw std::runtime_error("getFrequency: Unknown direction"); - return 101.4e6; + if(direction == SOAPY_SDR_RX) + { + if(name == "LO") + { + return this->rf_phy->getClockRate(this->rf_phy->ref_clk_scale[AD9361::ClockIndex::RX_RFPLL]); + } + else + { + throw std::runtime_error("getFrequency: Unknown name"); + } + } + else + { + if(name == "LO") + { + return this->rf_phy->getClockRate(this->rf_phy->ref_clk_scale[AD9361::ClockIndex::TX_RFPLL]); + } + else + { + throw std::runtime_error("getFrequency: Unknown name"); + } + } } std::vector SoapyIcyRadio::listFrequencies(const int direction, const size_t channel) const { @@ -272,7 +569,7 @@ std::vector SoapyIcyRadio::listFrequencies(const int direction, con std::vector names; - names.push_back("RF"); + names.push_back("LO"); return names; } @@ -281,11 +578,7 @@ SoapySDR::RangeList SoapyIcyRadio::getFrequencyRange(const int direction, const if(direction != SOAPY_SDR_RX && direction != SOAPY_SDR_TX) throw std::runtime_error("getFrequencyRange: Unknown direction"); - SoapySDR::RangeList ranges; - - ranges.emplace_back(70e6, 6e9); - - return ranges; + return this->getFrequencyRange(direction, channel, "LO"); } SoapySDR::RangeList SoapyIcyRadio::getFrequencyRange(const int direction, const size_t channel, const std::string &name) const { @@ -294,7 +587,28 @@ SoapySDR::RangeList SoapyIcyRadio::getFrequencyRange(const int direction, const SoapySDR::RangeList ranges; - ranges.emplace_back(70e6, 6e9); + if(direction == SOAPY_SDR_RX) + { + if(name == "LO") + { + ranges.emplace_back(70e6, 6e9); + } + else + { + throw std::runtime_error("getFrequencyRange: Unknown name"); + } + } + else + { + if(name == "LO") + { + ranges.emplace_back(47e6, 6e9); + } + else + { + throw std::runtime_error("getFrequencyRange: Unknown name"); + } + } return ranges; } @@ -327,7 +641,7 @@ void SoapyIcyRadio::setSampleRate(const int direction, const size_t channel, con if(this->getStreams(true).size() > 0) throw std::runtime_error("setSampleRate: Sample rate change requires datapath reconfiguration, but there are active streams"); - SoapySDR_logf(SOAPY_SDR_DEBUG, "setSampleRate: Reconfiguring data path for new sample rate %u", (size_t)rate); + DLOGF(SOAPY_SDR_DEBUG, "setSampleRate: Reconfiguring data path for new sample rate %u", (size_t)rate); if(num_chans > 1) this->reconfigureDataPath(true); @@ -352,18 +666,6 @@ double SoapyIcyRadio::getSampleRate(const int direction, const size_t channel) c return this->rf_phy->current_rx_path_clks[AD9361::RXClockIndex::RX_SAMPL_FREQ]; } -std::vector SoapyIcyRadio::listSampleRates(const int direction, const size_t channel) const -{ - if(direction != SOAPY_SDR_RX && direction != SOAPY_SDR_TX) - throw std::runtime_error("listSampleRates: Unknown direction"); - - std::vector rates; - - rates.push_back(3000000UL); - rates.push_back(61440000UL); - - return rates; -} SoapySDR::RangeList SoapyIcyRadio::getSampleRateRange(const int direction, const size_t channel) const { if(direction != SOAPY_SDR_RX && direction != SOAPY_SDR_TX) @@ -371,7 +673,7 @@ SoapySDR::RangeList SoapyIcyRadio::getSampleRateRange(const int direction, const SoapySDR::RangeList ranges; - ranges.emplace_back(3000000UL, 61440000UL); + ranges.emplace_back(2100000UL, 61440000UL); return ranges; } @@ -381,7 +683,7 @@ void SoapyIcyRadio::setBandwidth(const int direction, const size_t channel, cons if(direction != SOAPY_SDR_RX && direction != SOAPY_SDR_TX) throw std::runtime_error("setBandwidth: Unknown direction"); - if(bw < 200000UL || bw > 56000000UL) + if(bw < 200e3 || bw > 56e6) throw std::runtime_error("setBandwidth: Bandwidth out of range"); this->rf_phy->setRFBandwidth(direction == SOAPY_SDR_RX ? bw : this->rf_phy->current_rx_bw_Hz, direction == SOAPY_SDR_TX ? bw : this->rf_phy->current_tx_bw_Hz); @@ -393,18 +695,6 @@ double SoapyIcyRadio::getBandwidth(const int direction, const size_t channel) co return direction == SOAPY_SDR_RX ? this->rf_phy->current_rx_bw_Hz : this->rf_phy->current_tx_bw_Hz; } -std::vector SoapyIcyRadio::listBandwidths(const int direction, const size_t channel) const -{ - if(direction != SOAPY_SDR_RX && direction != SOAPY_SDR_TX) - throw std::runtime_error("listBandwidths: Unknown direction"); - - std::vector bws; - - bws.push_back(200000UL); - bws.push_back(56000000UL); - - return bws; -} SoapySDR::RangeList SoapyIcyRadio::getBandwidthRange(const int direction, const size_t channel) const { if(direction != SOAPY_SDR_RX && direction != SOAPY_SDR_TX) @@ -417,6 +707,46 @@ SoapySDR::RangeList SoapyIcyRadio::getBandwidthRange(const int direction, const return ranges; } + +std::vector SoapyIcyRadio::listTimeSources() const +{ + std::vector sources; + + sources.push_back("rf_sample_clock"); + + return sources; +} +void SoapyIcyRadio::setTimeSource(const std::string &source) +{ + if(source != "rf_sample_clock") + throw std::runtime_error("setTimeSource: Unknown time source"); +} +std::string SoapyIcyRadio::getTimeSource() const +{ + return "rf_sample_clock"; +} +bool SoapyIcyRadio::hasHardwareTime(const std::string &what) const +{ + if(what != "" && what != "rf_timestamp") + throw std::runtime_error("setHardwareTime: Unknown time source"); + + return true; +} +long long SoapyIcyRadio::getHardwareTime(const std::string &what) const +{ + if(what != "" && what != "rf_timestamp") + throw std::runtime_error("setHardwareTime: Unknown time source"); + + return SoapySDR::ticksToTimeNs(this->axi_rf_tstamp->getCounter(), this->getSampleRate(SOAPY_SDR_RX, 0)); +} +void SoapyIcyRadio::setHardwareTime(const long long timeNs, const std::string &what) +{ + if(what != "" && what != "rf_timestamp") + throw std::runtime_error("setHardwareTime: Unknown time source"); + + this->axi_rf_tstamp->setCounter(SoapySDR::timeNsToTicks(timeNs, this->getSampleRate(SOAPY_SDR_RX, 0))); +} + std::vector SoapyIcyRadio::listSensors() const { std::vector sensors; @@ -1067,4 +1397,29 @@ std::string SoapyIcyRadio::readSensor(const std::string &key) const snprintf(buf, sizeof(buf), "%.3f", this->rf_phy->getTemperature()); return buf; +} +std::vector SoapyIcyRadio::listSensors(const int direction, const size_t channel) const +{ + if(direction != SOAPY_SDR_RX && direction != SOAPY_SDR_TX) + throw std::runtime_error("listSensors: Unknown direction"); + + std::vector sensors; + + return sensors; +} +SoapySDR::ArgInfo SoapyIcyRadio::getSensorInfo(const int direction, const size_t channel, const std::string &key) const +{ + if(direction != SOAPY_SDR_RX && direction != SOAPY_SDR_TX) + throw std::runtime_error("getSensorInfo: Unknown direction"); + + SoapySDR::ArgInfo info; + + return info; +} +std::string SoapyIcyRadio::readSensor(const int direction, const size_t channel, const std::string &key) const +{ + if(direction != SOAPY_SDR_RX && direction != SOAPY_SDR_TX) + throw std::runtime_error("readSensor: Unknown direction"); + + return ""; } \ No newline at end of file diff --git a/software/soapy/src/SoapyStreaming.cpp b/software/soapy/src/SoapyStreaming.cpp index 5e1f5c8c..ae4c77be 100644 --- a/software/soapy/src/SoapyStreaming.cpp +++ b/software/soapy/src/SoapyStreaming.cpp @@ -62,7 +62,7 @@ SoapySDR::Stream *SoapyIcyRadio::setupStream(const int direction, const std::str std::lock_guard lock(this->streams_mutex); - SoapySDR_logf(SOAPY_SDR_DEBUG, "setupStream: %s, %u channels, %s (%s)", (direction == SOAPY_SDR_RX) ? "RX" : "TX", channels.size(), format.c_str(), SoapySDR::KwargsToString(args).c_str()); + DLOGF(SOAPY_SDR_DEBUG, "setupStream: %s, %u channels, %s (%s)", (direction == SOAPY_SDR_RX) ? "RX" : "TX", channels.size(), format.c_str(), SoapySDR::KwargsToString(args).c_str()); if(this->isAnyChannelBusy(direction, channels)) throw std::runtime_error("setupStream: One or more channels in this direction are already in use"); @@ -88,7 +88,7 @@ SoapySDR::Stream *SoapyIcyRadio::setupStream(const int direction, const std::str if(this->getStreams(true).size() > 0) throw std::runtime_error("setupStream: New stream requires datapath reconfiguration, but there are active streams"); - SoapySDR_logf(SOAPY_SDR_DEBUG, "setupStream: Reconfiguring data path for new channel count %u", num_chans); + DLOGF(SOAPY_SDR_DEBUG, "setupStream: Reconfiguring data path for new channel count %u", num_chans); if(num_chans > 1) this->reconfigureDataPath(true); @@ -131,17 +131,19 @@ SoapySDR::Stream *SoapyIcyRadio::setupStream(const int direction, const std::str if(s->fmt_convert == nullptr) throw std::runtime_error("setupStream: Format " + format + " not supported"); - SoapySDR_logf(SOAPY_SDR_DEBUG, "setupStream: Converting from %s to %s", direction == SOAPY_SDR_RX ? native.c_str() : format.c_str(), direction == SOAPY_SDR_RX ? format.c_str() : native.c_str()); + DLOGF(SOAPY_SDR_DEBUG, "setupStream: Converting from %s to %s", direction == SOAPY_SDR_RX ? native.c_str() : format.c_str(), direction == SOAPY_SDR_RX ? format.c_str() : native.c_str()); } else { s->fmt_convert = nullptr; - SoapySDR_logf(SOAPY_SDR_DEBUG, "setupStream: Using native format %s", native.c_str()); + DLOGF(SOAPY_SDR_DEBUG, "setupStream: Using native format %s", native.c_str()); } s->cur_buf_handle = 0; s->cur_buf_ptrs = std::vector(channels.size(), nullptr); + s->cur_buf_size = 0; + s->cur_buf_valid_size = 0; s->cur_buf_time = 0; s->cur_buf_time_valid = false; @@ -190,18 +192,15 @@ int SoapyIcyRadio::activateStream(SoapySDR::Stream *stream, const int flags, con std::lock_guard lock(s->mutex); - SoapySDR_logf(SOAPY_SDR_DEBUG, "activateStream: Stream direction is %s", s->direction == SOAPY_SDR_RX ? "RX" : "TX"); + DLOGF_S(SOAPY_SDR_DEBUG, "activateStream: Stream direction is %s", s->direction == SOAPY_SDR_RX ? "RX" : "TX"); if(s->active) { - SoapySDR_logf(SOAPY_SDR_ERROR, "activateStream: Stream is already active"); + DLOGF(SOAPY_SDR_ERROR, "activateStream: Stream is already active"); return SOAPY_SDR_STREAM_ERROR; } - if(numElems > this->getStreamMTU(stream)) - return SOAPY_SDR_NOT_SUPPORTED; - uint64_t ticks = 0; if(flags & SOAPY_SDR_HAS_TIME) @@ -209,15 +208,15 @@ int SoapyIcyRadio::activateStream(SoapySDR::Stream *stream, const int flags, con if(s->direction != SOAPY_SDR_RX) return SOAPY_SDR_NOT_SUPPORTED; - if(timeNs >= this->getHardwareTime("rf_timestamp")) + if(timeNs <= this->getHardwareTime("rf_timestamp")) return SOAPY_SDR_TIME_ERROR; - ticks = SoapySDR::timeNsToTicks(timeNs, this->getSampleRate(SOAPY_SDR_RX, 0)); + ticks = SoapySDR::timeNsToTicks(timeNs, this->getSampleRate(s->direction, 0)); } if(s->channels.empty()) { - SoapySDR_logf(SOAPY_SDR_ERROR, "activateStream: No channels in this stream"); + DLOGF(SOAPY_SDR_ERROR, "activateStream: No channels in this stream"); return SOAPY_SDR_STREAM_ERROR; } @@ -229,7 +228,7 @@ int SoapyIcyRadio::activateStream(SoapySDR::Stream *stream, const int flags, con for(auto &c : s->channels) { - SoapySDR_logf(SOAPY_SDR_TRACE, "activateStream: Channel %u, invalidate %u user buffers", c->num, c->buffers.size()); + DLOGF_S(SOAPY_SDR_TRACE, "activateStream: Channel %u, invalidate %u user buffers", c->num, c->buffers.size()); for(auto &buf : c->buffers) { @@ -240,14 +239,21 @@ int SoapyIcyRadio::activateStream(SoapySDR::Stream *stream, const int flags, con } c->next_user_buf = 0; - c->next_dma_buf = 0; - c->next_time = 0; - c->next_time_valid = false; + c->next_user_buf_time = 0; + c->next_user_buf_time_valid = false; + c->next_dma_user_buf = 0; + c->next_dma_user_buf_time = 0; + c->next_dma_user_buf_time_valid = false; - SoapySDR_logf(SOAPY_SDR_TRACE, "activateStream: DMA Controller %u", c->dma->getPeripheralID()); + DLOGF_S(SOAPY_SDR_TRACE, "activateStream: DMA Controller %u", c->dma->getPeripheralID()); + + if(s->direction == SOAPY_SDR_TX) + this->axi_rf_tstamp->flushTX(c->ts_chan, 100); // Flush pending DMA transfers c->dma->waitIdle(); - c->dma->enable(); + + if(s->direction == SOAPY_SDR_RX) + c->dma->enable(); for(auto &buf : c->dma_buffers) { @@ -255,14 +261,20 @@ int SoapyIcyRadio::activateStream(SoapySDR::Stream *stream, const int flags, con { c->dma->submitTransfer(buf->xfer); - SoapySDR_logf(SOAPY_SDR_TRACE, "activateStream: Submitted transfer ID %u", buf->xfer.id); + buf->idle = false; + + DLOGF_S(SOAPY_SDR_TRACE, "activateStream: Submitted transfer ID %u", buf->xfer.id); + } + else + { + buf->idle = true; } } } - if(s->channels.size() > 1) // Multi channel, atomically work on both channels + if(s->direction == SOAPY_SDR_RX) { - if(s->direction == SOAPY_SDR_RX) + if(s->channels.size() > 1) // Multi channel, atomically work on both channels { this->axi_rf_tstamp->getCounterLatch(AXIRFTStamp::Channel::RX0); // Trigger read to clear valid status this->axi_rf_tstamp->getCounterLatch(AXIRFTStamp::Channel::RX1); // Trigger read to clear valid status @@ -286,14 +298,7 @@ int SoapyIcyRadio::activateStream(SoapySDR::Stream *stream, const int flags, con this->axi_rf_tstamp->enableRX(); // Atomically enables both channels } } - else - { - // TODO - } - } - else // Single channel - { - if(s->direction == SOAPY_SDR_RX) + else // Single channel { AXIRFTStamp::Channel ts_chan = s->channels[0]->ts_chan; @@ -313,10 +318,6 @@ int SoapyIcyRadio::activateStream(SoapySDR::Stream *stream, const int flags, con this->axi_rf_tstamp->enableRX(ts_chan); } } - else - { - // TODO - } } s->active = true; @@ -334,25 +335,19 @@ int SoapyIcyRadio::deactivateStream(SoapySDR::Stream *stream, const int flags, c if(!s->active) { - SoapySDR_logf(SOAPY_SDR_ERROR, "deactivateStream: Stream is not active"); + DLOGF(SOAPY_SDR_ERROR, "deactivateStream: Stream is not active"); return 0; } - // uint64_t ticks = 0; - if(flags & SOAPY_SDR_HAS_TIME) { return SOAPY_SDR_NOT_SUPPORTED; // Not supported for now - // if(timeNs >= this->getHardwareTime("rf_timestamp")) - // return SOAPY_SDR_TIME_ERROR; - - // ticks = SoapySDR::timeNsToTicks(timeNs, this->getSampleRate(SOAPY_SDR_RX, 0)); } if(s->channels.empty()) { - SoapySDR_logf(SOAPY_SDR_ERROR, "deactivateStream: No channels in this stream"); + DLOGF(SOAPY_SDR_ERROR, "deactivateStream: No channels in this stream"); return SOAPY_SDR_STREAM_ERROR; } @@ -361,39 +356,49 @@ int SoapyIcyRadio::deactivateStream(SoapySDR::Stream *stream, const int flags, c { if(s->direction == SOAPY_SDR_RX) { + this->axi_rf_tstamp->disableRXCounter(); // Atomically disables both counters + this->axi_rf_tstamp->disableRX(); // Atomically disables both channels + this->axi_rf_tstamp->waitRXCounterDisabled(100); + this->axi_rf_tstamp->waitRXDisabled(100); + this->axi_rf_tstamp->disarmCounterLatch(); this->axi_rf_tstamp->getCounterLatch(AXIRFTStamp::Channel::RX0); // Trigger read to clear valid status this->axi_rf_tstamp->getCounterLatch(AXIRFTStamp::Channel::RX1); // Trigger read to clear valid status - - this->axi_rf_tstamp->disableRXCounter(); // Atomically disables both counters - this->axi_rf_tstamp->disableRX(); // Atomically disables both channels } else { - // TODO + this->axi_rf_tstamp->disableTXCounter(); // Atomically disables both counters + this->axi_rf_tstamp->disableTX(); // Atomically disables both channels + this->axi_rf_tstamp->waitTXCounterDisabled(100); + this->axi_rf_tstamp->waitTXDisabled(100); } } else // Single channel { + AXIRFTStamp::Channel ts_chan = s->channels[0]->ts_chan; + if(s->direction == SOAPY_SDR_RX) { - AXIRFTStamp::Channel ts_chan = s->channels[0]->ts_chan; + this->axi_rf_tstamp->disableRXCounter(ts_chan); + this->axi_rf_tstamp->disableRX(ts_chan); + this->axi_rf_tstamp->waitRXCounterDisabled(ts_chan, 100); + this->axi_rf_tstamp->waitRXDisabled(ts_chan, 100); this->axi_rf_tstamp->disarmCounterLatch(ts_chan); this->axi_rf_tstamp->getCounterLatch(ts_chan); // Trigger read to clear valid status - - this->axi_rf_tstamp->disableRXCounter(ts_chan); - this->axi_rf_tstamp->disableRX(ts_chan); } else { - // TODO + this->axi_rf_tstamp->disableTXCounter(ts_chan); + this->axi_rf_tstamp->disableTX(ts_chan); + this->axi_rf_tstamp->waitTXCounterDisabled(ts_chan, 100); + this->axi_rf_tstamp->waitTXDisabled(ts_chan, 100); } } for(auto &c : s->channels) { - SoapySDR_logf(SOAPY_SDR_DEBUG, "deactivateStream: Channel %u, DMA Controller %u", c->num, c->dma->getPeripheralID()); + DLOGF_S(SOAPY_SDR_DEBUG, "deactivateStream: Channel %u, DMA Controller %u", c->num, c->dma->getPeripheralID()); for(auto &buf : c->dma_buffers) { @@ -401,6 +406,10 @@ int SoapyIcyRadio::deactivateStream(SoapySDR::Stream *stream, const int flags, c } c->dma->disable(); + + if(s->direction == SOAPY_SDR_TX) + this->axi_rf_tstamp->flushTX(c->ts_chan, 100); + c->dma->waitIdle(); } @@ -432,18 +441,20 @@ int SoapyIcyRadio::readStream(SoapySDR::Stream *stream, void * const *buffs, con if(s->channels.empty()) { - SoapySDR_logf(SOAPY_SDR_ERROR, "readStream: No channels in this stream"); + DLOGF(SOAPY_SDR_ERROR, "readStream: No channels in this stream"); return SOAPY_SDR_STREAM_ERROR; } - SoapySDR_logf(SOAPY_SDR_TRACE, "readStream: Reading %u elements", numElems); + DLOGF_S(SOAPY_SDR_TRACE, "readStream: Reading %u elements", numElems); + + bool buf_acquired = false; std::lock_guard lock(s->cur_buf_mutex); if(s->cur_buf_ptrs[0] == nullptr) { - SoapySDR_logf(SOAPY_SDR_TRACE, "readStream: No current buffer, waiting for one..."); + DLOGF_S(SOAPY_SDR_TRACE, "readStream: No current buffer, waiting for one..."); size_t _handle; void *_buffs[s->channels.size()]; @@ -460,20 +471,23 @@ int SoapyIcyRadio::readStream(SoapySDR::Stream *stream, void * const *buffs, con for(size_t i = 0; i < s->cur_buf_ptrs.size(); i++) s->cur_buf_ptrs[i] = _buffs[i]; - s->cur_buf_rem = _rem; + s->cur_buf_size = _rem; + s->cur_buf_valid_size = _rem; s->cur_buf_time = _timeNs; s->cur_buf_time_valid = (flags & SOAPY_SDR_HAS_TIME); - SoapySDR_logf(SOAPY_SDR_TRACE, "readStream: Got buffer %u with %u elements", s->cur_buf_handle, s->cur_buf_rem); + DLOGF_S(SOAPY_SDR_TRACE, "readStream: Got buffer %u with %u elements", s->cur_buf_handle, s->cur_buf_size); + + buf_acquired = true; } else { - SoapySDR_logf(SOAPY_SDR_TRACE, "readStream: Using current buffer %u, still has %u elements", s->cur_buf_handle, s->cur_buf_rem); - } + DLOGF_S(SOAPY_SDR_TRACE, "readStream: Using current buffer %u, still has %u elements", s->cur_buf_handle, s->cur_buf_valid_size); - size_t _numElems = MIN(numElems, s->cur_buf_rem); + buf_acquired = false; + } - SoapySDR_logf(SOAPY_SDR_TRACE, "First samples: %hd %hd %hd %hd", ((int16_t *)s->cur_buf_ptrs[0])[0], ((int16_t *)s->cur_buf_ptrs[0])[1], ((int16_t *)s->cur_buf_ptrs[0])[2], ((int16_t *)s->cur_buf_ptrs[0])[3]); + size_t _numElems = MIN(numElems, s->cur_buf_valid_size); for(size_t i = 0; i < s->channels.size(); i++) { @@ -489,20 +503,26 @@ int SoapyIcyRadio::readStream(SoapySDR::Stream *stream, void * const *buffs, con flags |= SOAPY_SDR_HAS_TIME; } - s->cur_buf_rem -= _numElems; + s->cur_buf_valid_size -= _numElems; - if(!s->cur_buf_rem) + if(!s->cur_buf_valid_size) { - SoapySDR_logf(SOAPY_SDR_TRACE, "readStream: Current buffer is exhausted, releasing..."); + DLOGF_S(SOAPY_SDR_TRACE, "readStream: Current buffer is exhausted, releasing..."); this->releaseReadBuffer(stream, s->cur_buf_handle); for(size_t i = 0; i < s->cur_buf_ptrs.size(); i++) s->cur_buf_ptrs[i] = nullptr; + s->cur_buf_size = 0; s->cur_buf_handle = 0; s->cur_buf_time = 0; s->cur_buf_time_valid = false; + + if(buf_acquired) + flags |= SOAPY_SDR_ONE_PACKET; + else + flags |= SOAPY_SDR_END_BURST; } else { @@ -517,7 +537,144 @@ int SoapyIcyRadio::readStream(SoapySDR::Stream *stream, void * const *buffs, con } int SoapyIcyRadio::writeStream(SoapySDR::Stream *stream, const void * const *buffs, const size_t numElems, int &flags, const long long timeNs, const long timeoutUs) { - return SOAPY_SDR_NOT_SUPPORTED; + SoapyIcyRadio::Stream *s = this->findStream(stream); + + if(s == nullptr) + throw std::runtime_error("writeStream: Stream not found"); + + if(s->direction != SOAPY_SDR_TX) + return SOAPY_SDR_NOT_SUPPORTED; + + if(!s->active) + this->activateStream(stream); + + if(s->channels.empty()) + { + DLOGF(SOAPY_SDR_ERROR, "writeStream: No channels in this stream"); + + return SOAPY_SDR_STREAM_ERROR; + } + + DLOGF_S(SOAPY_SDR_TRACE, "writeStream: Writting %u elements", numElems); + + std::lock_guard lock(s->cur_buf_mutex); + + if(s->cur_buf_ptrs[0] == nullptr) + { + DLOGF_S(SOAPY_SDR_TRACE, "writeStream: No current buffer, waiting for one..."); + + size_t _handle; + void *_buffs[s->channels.size()]; + + int _rem = this->acquireWriteBuffer(stream, _handle, _buffs, timeoutUs); + + if(_rem < 0) + return _rem; + + s->cur_buf_handle = _handle; + + for(size_t i = 0; i < s->cur_buf_ptrs.size(); i++) + s->cur_buf_ptrs[i] = _buffs[i]; + + s->cur_buf_size = _rem; + s->cur_buf_valid_size = 0; + s->cur_buf_time = 0; + s->cur_buf_time_valid = false; + + DLOGF_S(SOAPY_SDR_TRACE, "writeStream: Got buffer %u for %u elements", s->cur_buf_handle, s->cur_buf_size); + } + else + { + if(flags & SOAPY_SDR_ONE_PACKET) + { + DLOGF(SOAPY_SDR_WARNING, "writeStream: Previous burst not finished, ignoring single packet flag"); + + flags &= ~SOAPY_SDR_ONE_PACKET; + } + + DLOGF_S(SOAPY_SDR_TRACE, "writeStream: Using current buffer %u, still has space for %u elements", s->cur_buf_handle, s->cur_buf_size - s->cur_buf_valid_size); + } + + size_t _numElems = MIN(numElems, s->cur_buf_size - s->cur_buf_valid_size); + + for(size_t i = 0; i < s->channels.size(); i++) + { + if(s->fmt_convert) + s->fmt_convert(buffs[i], s->cur_buf_ptrs[i], _numElems, s->fmt_scale); + else + std::memcpy(s->cur_buf_ptrs[i], buffs[i], _numElems * ICYRADIO_SAMPLE_SIZE_BYTES); + } + + if(flags & SOAPY_SDR_HAS_TIME) + { + if(timeNs <= this->getHardwareTime("rf_timestamp")) + return SOAPY_SDR_TIME_ERROR; // OK to return here, cur_buf_valid_size will not be incremented + + if(!s->cur_buf_time_valid) + { + s->cur_buf_time = timeNs; + s->cur_buf_time_valid = true; + } + else + { + DLOGF(SOAPY_SDR_WARNING, "writeStream: Ignoring timeNs %llu, using previously set value %llu", timeNs, s->cur_buf_time); + } + } + + s->cur_buf_valid_size += _numElems; + + bool release_buf = false; + + if(s->cur_buf_valid_size == s->cur_buf_size) + { + release_buf = true; + + DLOGF_S(SOAPY_SDR_TRACE, "writeStream: Current buffer is full, releasing..."); + } + else if(flags & SOAPY_SDR_END_BURST) + { + release_buf = true; + + DLOGF_S(SOAPY_SDR_TRACE, "writeStream: End of burst, releasing..."); + } + else if(flags & SOAPY_SDR_ONE_PACKET) + { + release_buf = true; + + DLOGF_S(SOAPY_SDR_TRACE, "writeStream: Single packet, releasing..."); + } + + if(release_buf) + { + DLOGF_S(SOAPY_SDR_TRACE, "writeStream: Releasing buffer with %u elements", s->cur_buf_valid_size); + + int _flags = 0; + long long _timeNs = 0; + + if(s->cur_buf_time_valid) + { + _flags = SOAPY_SDR_HAS_TIME; + _timeNs = s->cur_buf_time; + } + + this->releaseWriteBuffer(stream, s->cur_buf_handle, s->cur_buf_valid_size, _flags, _timeNs); + + for(size_t i = 0; i < s->cur_buf_ptrs.size(); i++) + s->cur_buf_ptrs[i] = nullptr; + + s->cur_buf_size = 0; + s->cur_buf_valid_size = 0; + s->cur_buf_handle = 0; + s->cur_buf_time = 0; + s->cur_buf_time_valid = false; + } + else + { + for(size_t i = 0; i < s->cur_buf_ptrs.size(); i++) + s->cur_buf_ptrs[i] = reinterpret_cast(reinterpret_cast(s->cur_buf_ptrs[i]) + _numElems * ICYRADIO_SAMPLE_SIZE_BYTES); + } + + return _numElems; } int SoapyIcyRadio::readStreamStatus(SoapySDR::Stream *stream, size_t &chanMask, int &flags, long long &timeNs, const long timeoutUs) { @@ -535,21 +692,21 @@ size_t SoapyIcyRadio::getNumDirectAccessBuffers(SoapySDR::Stream *stream) if(!s->active) { - SoapySDR_logf(SOAPY_SDR_ERROR, "getNumDirectAccessBuffers: Stream is not active"); + DLOGF(SOAPY_SDR_ERROR, "getNumDirectAccessBuffers: Stream is not active"); return 0; } if(s->channels.empty()) { - SoapySDR_logf(SOAPY_SDR_ERROR, "getNumDirectAccessBuffers: No channels in this stream"); + DLOGF(SOAPY_SDR_ERROR, "getNumDirectAccessBuffers: No channels in this stream"); return 0; } if(!s->channels[0]->buffers.size()) { - SoapySDR_logf(SOAPY_SDR_ERROR, "getNumDirectAccessBuffers: No user buffers in this stream"); + DLOGF(SOAPY_SDR_ERROR, "getNumDirectAccessBuffers: No user buffers in this stream"); return 0; } @@ -567,35 +724,35 @@ int SoapyIcyRadio::getDirectAccessBufferAddrs(SoapySDR::Stream *stream, const si if(!s->active) { - SoapySDR_logf(SOAPY_SDR_ERROR, "getDirectAccessBufferAddrs: Stream is not active"); + DLOGF(SOAPY_SDR_ERROR, "getDirectAccessBufferAddrs: Stream is not active"); return SOAPY_SDR_STREAM_ERROR; } if(s->channels.empty()) { - SoapySDR_logf(SOAPY_SDR_ERROR, "getDirectAccessBufferAddrs: No channels in this stream"); + DLOGF(SOAPY_SDR_ERROR, "getDirectAccessBufferAddrs: No channels in this stream"); return SOAPY_SDR_STREAM_ERROR; } - SoapySDR_logf(SOAPY_SDR_TRACE, "getDirectAccessBufferAddrs: Get user buffer %u addresses", handle); + DLOGF_S(SOAPY_SDR_TRACE, "getDirectAccessBufferAddrs: Get user buffer %u addresses", handle); for(auto &c : s->channels) { std::lock_guard lock(c->mutex); if(handle >= c->buffers.size()) - SoapySDR_logf(SOAPY_SDR_WARNING, "getDirectAccessBufferAddrs: Invalid buffer %u for channel %u", handle, c->num); + DLOGF(SOAPY_SDR_WARNING, "getDirectAccessBufferAddrs: Invalid buffer %u for channel %u", handle, c->num); if(s->direction == SOAPY_SDR_RX && !c->buffers[handle]->valid_size) - SoapySDR_logf(SOAPY_SDR_WARNING, "getDirectAccessBufferAddrs: User owns buffer %u for channel %u but it contains no valid data", handle, c->num); + DLOGF(SOAPY_SDR_WARNING, "getDirectAccessBufferAddrs: User owns buffer %u for channel %u but it contains no valid data", handle, c->num); else if(s->direction == SOAPY_SDR_TX && c->buffers[handle]->valid_size) - SoapySDR_logf(SOAPY_SDR_WARNING, "getDirectAccessBufferAddrs: User owns buffer %u for channel %u but it contains valid data", handle, c->num); + DLOGF(SOAPY_SDR_WARNING, "getDirectAccessBufferAddrs: User owns buffer %u for channel %u but it contains valid data", handle, c->num); if(!c->buffers[handle]->acquired) { - SoapySDR_logf(SOAPY_SDR_WARNING, "getDirectAccessBufferAddrs: Buffer %u for channel %u is not acquired", handle, c->num); + DLOGF(SOAPY_SDR_WARNING, "getDirectAccessBufferAddrs: Buffer %u for channel %u is not acquired", handle, c->num); return SOAPY_SDR_NOT_SUPPORTED; } @@ -619,14 +776,14 @@ int SoapyIcyRadio::acquireReadBuffer(SoapySDR::Stream *stream, size_t &handle, c if(!s->active) { - SoapySDR_logf(SOAPY_SDR_ERROR, "acquireReadBuffer: Stream is not active"); + DLOGF(SOAPY_SDR_ERROR, "acquireReadBuffer: Stream is not active"); return SOAPY_SDR_STREAM_ERROR; } if(s->channels.empty()) { - SoapySDR_logf(SOAPY_SDR_ERROR, "acquireReadBuffer: No channels in this stream"); + DLOGF(SOAPY_SDR_ERROR, "acquireReadBuffer: No channels in this stream"); return SOAPY_SDR_STREAM_ERROR; } @@ -666,7 +823,8 @@ int SoapyIcyRadio::acquireReadBuffer(SoapySDR::Stream *stream, size_t &handle, c if(_timeoutUs <= 10) { - SoapySDR_logf(SOAPY_SDR_WARNING, "acquireReadBuffer: Timeout waiting for a buffer with valid data"); + if(timeoutUs > 0) + DLOGF(SOAPY_SDR_WARNING, "acquireReadBuffer: Timeout waiting for a buffer with valid data"); return SOAPY_SDR_TIMEOUT; } @@ -674,37 +832,72 @@ int SoapyIcyRadio::acquireReadBuffer(SoapySDR::Stream *stream, size_t &handle, c _timeoutUs -= 10; } - SoapySDR_logf(SOAPY_SDR_TRACE, "acquireReadBuffer: Acquiring user buffer %u", _handle); - - handle = _handle; + DLOGF_S(SOAPY_SDR_TRACE, "acquireReadBuffer: Checking user buffer %u for any overflow", _handle); - uint32_t buf_valid_size = 0; - uint64_t ticks = s->channels[0]->buffers[handle]->time; + bool any_overflow = false; for(auto &c : s->channels) { std::lock_guard lock(c->mutex); - *buffs++ = c->buffers[handle]->addr; + if(!c->buffers[_handle]->time_valid) + { + DLOGF(SOAPY_SDR_WARNING, "acquireReadBuffer: Buffer %u for channel %u has no valid timestamp", _handle, c->num); - if(!buf_valid_size) + continue; + } + + if(c->next_user_buf_time_valid && c->buffers[_handle]->time > c->next_user_buf_time) { - buf_valid_size = c->buffers[handle]->valid_size; + DLOGF(SOAPY_SDR_WARNING, "acquireReadBuffer: Buffer %u for channel %u has timestamp %llu, expected %llu (delta %lld)", _handle, c->num, c->buffers[_handle]->time, c->next_user_buf_time, c->buffers[_handle]->time - c->next_user_buf_time); + + any_overflow = true; + + break; } - else + + c->next_user_buf_time = c->buffers[_handle]->time + c->buffers[_handle]->valid_size / ICYRADIO_SAMPLE_SIZE_BYTES; // Next buffer should be this one + buffer size + c->next_user_buf_time_valid = true; + } + + if(any_overflow) + { + DLOGF(SOAPY_SDR_WARNING, "acquireReadBuffer: Buffer %u has overflowed in at least one channel, aborting", _handle); + + for(auto &c : s->channels) { - if(buf_valid_size != c->buffers[handle]->valid_size) - SoapySDR_logf(SOAPY_SDR_WARNING, "acquireReadBuffer: Buffer size mismatch (expected %u, got %u)", buf_valid_size, c->buffers[handle]->valid_size); + std::lock_guard lock(c->mutex); - buf_valid_size = MIN(buf_valid_size, c->buffers[handle]->valid_size); + // Invalidate all expected timestamps so we start over + c->next_user_buf_time = 0; + c->next_user_buf_time_valid = false; } - c->buffers[handle]->acquired = true; + return SOAPY_SDR_OVERFLOW; + } + + DLOGF_S(SOAPY_SDR_TRACE, "acquireReadBuffer: Acquiring user buffer %u", _handle); + + handle = _handle; + + uint32_t buf_valid_size = s->channels[0]->buffers[handle]->valid_size; + uint64_t ticks = s->channels[0]->buffers[handle]->time; + + for(auto &c : s->channels) + { + std::lock_guard lock(c->mutex); + + *buffs++ = c->buffers[handle]->addr; + + if(buf_valid_size != c->buffers[handle]->valid_size) + DLOGF(SOAPY_SDR_WARNING, "acquireReadBuffer: Buffer size mismatch (expected %u, got %u)", buf_valid_size, c->buffers[handle]->valid_size); + + buf_valid_size = MIN(buf_valid_size, c->buffers[handle]->valid_size); if(c->buffers[handle]->time_valid) { if(ticks != c->buffers[handle]->time) - SoapySDR_logf(SOAPY_SDR_WARNING, "acquireReadBuffer: Buffer time mismatch (expected %llu, got %llu)", ticks, c->buffers[handle]->time); + DLOGF(SOAPY_SDR_WARNING, "acquireReadBuffer: Buffer time mismatch (expected %llu, got %llu)", ticks, c->buffers[handle]->time); else ticks = MIN(ticks, c->buffers[handle]->time); @@ -715,6 +908,8 @@ int SoapyIcyRadio::acquireReadBuffer(SoapySDR::Stream *stream, size_t &handle, c flags &= ~SOAPY_SDR_HAS_TIME; } + c->buffers[handle]->acquired = true; + c->next_user_buf = (handle + 1) % c->buffers.size(); } @@ -737,19 +932,19 @@ void SoapyIcyRadio::releaseReadBuffer(SoapySDR::Stream *stream, const size_t han if(!s->active) { - SoapySDR_logf(SOAPY_SDR_ERROR, "releaseReadBuffer: Stream is not active"); + DLOGF(SOAPY_SDR_ERROR, "releaseReadBuffer: Stream is not active"); return; } if(s->channels.empty()) { - SoapySDR_logf(SOAPY_SDR_ERROR, "releaseReadBuffer: No channels in this stream"); + DLOGF(SOAPY_SDR_ERROR, "releaseReadBuffer: No channels in this stream"); return; } - SoapySDR_logf(SOAPY_SDR_TRACE, "releaseReadBuffer: Releasing user buffer %u", handle); + DLOGF_S(SOAPY_SDR_TRACE, "releaseReadBuffer: Releasing user buffer %u", handle); bool any_dma_idle = false; @@ -760,7 +955,7 @@ void SoapyIcyRadio::releaseReadBuffer(SoapySDR::Stream *stream, const size_t han if(c->dma->idle()) { - SoapySDR_logf(SOAPY_SDR_TRACE, "releaseReadBuffer: DMA Controller %u of channel %u is idle", c->dma->getPeripheralID(), c->num); + DLOGF_S(SOAPY_SDR_TRACE, "releaseReadBuffer: DMA Controller %u of channel %u is idle", c->dma->getPeripheralID(), c->num); any_dma_idle = true; @@ -770,15 +965,17 @@ void SoapyIcyRadio::releaseReadBuffer(SoapySDR::Stream *stream, const size_t han if(any_dma_idle) { - SoapySDR_logf(SOAPY_SDR_WARNING, "releaseReadBuffer: At least one DMA Controller is idle"); + DLOGF(SOAPY_SDR_WARNING, "releaseReadBuffer: At least one DMA Controller is idle"); if(s->channels.size() > 1) { - //We need to disable RX (both channels) and re-enable after, to re-sync the timestamps - SoapySDR_logf(SOAPY_SDR_TRACE, "releaseReadBuffer: Re-syncing multichannel timestamps - disable RX and RX counters"); + // We need to disable RX (both channels) and re-enable after, to re-sync the timestamps + DLOGF_S(SOAPY_SDR_TRACE, "releaseReadBuffer: Re-syncing multichannel timestamps - disable RX and RX counters"); this->axi_rf_tstamp->disableRXCounter(); // Atomically disables both counters this->axi_rf_tstamp->disableRX(); // Atomically disables both channels + this->axi_rf_tstamp->waitRXCounterDisabled(100); + this->axi_rf_tstamp->waitRXDisabled(100); while(this->axi_rf_tstamp->getCounterLatchStatus(AXIRFTStamp::Channel::RX0) != AXIRFTStamp::CounterLatchStatus::LATCH_ARMED) this->axi_rf_tstamp->getCounterLatch(AXIRFTStamp::Channel::RX0); // Trigger read to clear valid status @@ -795,7 +992,7 @@ void SoapyIcyRadio::releaseReadBuffer(SoapySDR::Stream *stream, const size_t han std::lock_guard lock(c->mutex); - SoapySDR_logf(SOAPY_SDR_TRACE, "releaseReadBuffer: Channel %u, invalidate %u user buffers", c->num, c->buffers.size()); + DLOGF_S(SOAPY_SDR_TRACE, "releaseReadBuffer: Channel %u, invalidate %u user buffers", c->num, c->buffers.size()); for(auto &buf : c->buffers) { @@ -806,9 +1003,11 @@ void SoapyIcyRadio::releaseReadBuffer(SoapySDR::Stream *stream, const size_t han } c->next_user_buf = 0; - c->next_dma_buf = 0; - c->next_time = 0; - c->next_time_valid = false; + c->next_dma_user_buf = 0; + c->next_dma_user_buf_time = 0; + c->next_dma_user_buf_time_valid = false; + + DLOGF_S(SOAPY_SDR_TRACE, "releaseReadBuffer: DMA Controller %u", c->dma->getPeripheralID()); c->dma->enable(); @@ -816,13 +1015,15 @@ void SoapyIcyRadio::releaseReadBuffer(SoapySDR::Stream *stream, const size_t han { c->dma->submitTransfer(buf->xfer); - SoapySDR_logf(SOAPY_SDR_TRACE, "releaseReadBuffer: Submitted transfer ID %u", buf->xfer.id); + buf->idle = false; + + DLOGF_S(SOAPY_SDR_TRACE, "releaseReadBuffer: Submitted transfer ID %u", buf->xfer.id); } } if(s->channels.size() > 1) { - SoapySDR_logf(SOAPY_SDR_TRACE, "releaseReadBuffer: Re-syncing multichannel timestamps - wait for DMAs and enable RX"); + DLOGF_S(SOAPY_SDR_TRACE, "releaseReadBuffer: Re-syncing multichannel timestamps - wait for DMAs and enable RX"); // Only check the last channel, the first will be set aswell this->axi_rf_tstamp->waitRXDMAReady(s->channels.end()[-1]->ts_chan, 100); @@ -832,25 +1033,25 @@ void SoapyIcyRadio::releaseReadBuffer(SoapySDR::Stream *stream, const size_t han } else { - SoapySDR_logf(SOAPY_SDR_TRACE, "releaseReadBuffer: No DMA Controller is idle, stream is healthy"); + DLOGF_S(SOAPY_SDR_TRACE, "releaseReadBuffer: No DMA Controller is idle, stream is healthy"); for(auto &c : s->channels) { std::unique_lock lock(c->mutex); if(handle >= c->buffers.size()) - SoapySDR_logf(SOAPY_SDR_WARNING, "releaseReadBuffer: Invalid buffer %u for channel %u", handle, c->num); + DLOGF(SOAPY_SDR_WARNING, "releaseReadBuffer: Invalid buffer %u for channel %u", handle, c->num); if(!c->buffers[handle]->acquired) { - SoapySDR_logf(SOAPY_SDR_WARNING, "releaseReadBuffer: Buffer %u for channel %u is not acquired", handle, c->num); + DLOGF(SOAPY_SDR_WARNING, "releaseReadBuffer: Buffer %u for channel %u is not acquired", handle, c->num); return; } if(!c->buffers[handle]->valid_size) { - SoapySDR_logf(SOAPY_SDR_WARNING, "releaseReadBuffer: Buffer %u for channel %u was acquired with no valid data", handle, c->num); + DLOGF(SOAPY_SDR_WARNING, "releaseReadBuffer: Buffer %u for channel %u was acquired with no valid data", handle, c->num); return; } @@ -863,9 +1064,370 @@ void SoapyIcyRadio::releaseReadBuffer(SoapySDR::Stream *stream, const size_t han } int SoapyIcyRadio::acquireWriteBuffer(SoapySDR::Stream *stream, size_t &handle, void **buffs, const long timeoutUs) { - return SOAPY_SDR_NOT_SUPPORTED; + SoapyIcyRadio::Stream *s = this->findStream(stream); + + if(s == nullptr) + throw std::runtime_error("acquireWriteBuffer: Stream not found"); + + if(s->direction != SOAPY_SDR_TX) + return SOAPY_SDR_NOT_SUPPORTED; + + std::lock_guard lock(s->mutex); + + if(!s->active) + { + DLOGF(SOAPY_SDR_ERROR, "acquireWriteBuffer: Stream is not active"); + + return SOAPY_SDR_STREAM_ERROR; + } + + if(s->channels.empty()) + { + DLOGF(SOAPY_SDR_ERROR, "acquireWriteBuffer: No channels in this stream"); + + return SOAPY_SDR_STREAM_ERROR; + } + + size_t _handle; + long _timeoutUs = !timeoutUs ? 1 : timeoutUs; + + while(_timeoutUs) + { + bool all_acquired = true; + + _handle = s->channels[0]->next_user_buf; + + for(const auto &c : s->channels) + { + std::lock_guard lock(c->mutex); + + if(_handle != c->next_user_buf) + { + all_acquired = false; + + break; + } + + if(c->buffers[_handle]->valid_size) + { + all_acquired = false; + + break; + } + } + + if(all_acquired) + break; + + usleep(10); + + if(_timeoutUs <= 10) + { + if(timeoutUs > 0) + DLOGF(SOAPY_SDR_WARNING, "acquireWriteBuffer: Timeout waiting for a buffer with free space"); + + return SOAPY_SDR_TIMEOUT; + } + + _timeoutUs -= 10; + } + + DLOGF_S(SOAPY_SDR_TRACE, "acquireWriteBuffer: Acquiring user buffer %u", _handle); + + handle = _handle; + + uint32_t buf_size = s->channels[0]->buffers[handle]->size; + + for(auto &c : s->channels) + { + std::lock_guard lock(c->mutex); + + *buffs++ = c->buffers[handle]->addr; + + if(buf_size != c->buffers[handle]->size) + DLOGF(SOAPY_SDR_WARNING, "acquireWriteBuffer: Buffer size mismatch (expected %u, got %u)", buf_size, c->buffers[handle]->size); + + buf_size = MIN(buf_size, c->buffers[handle]->size); + + c->buffers[handle]->acquired = true; + + c->next_user_buf = (handle + 1) % c->buffers.size(); + } + + return buf_size / ICYRADIO_SAMPLE_SIZE_BYTES; } void SoapyIcyRadio::releaseWriteBuffer(SoapySDR::Stream *stream, const size_t handle, const size_t numElems, int &flags, const long long timeNs) { + if(!numElems) + throw std::invalid_argument("releaseWriteBuffer: numElems must be greater than zero"); + + SoapyIcyRadio::Stream *s = this->findStream(stream); + + if(s == nullptr) + throw std::runtime_error("releaseWriteBuffer: Stream not found"); + + if(s->direction != SOAPY_SDR_TX) + return; + + std::lock_guard lock(s->mutex); + + if(!s->active) + { + DLOGF(SOAPY_SDR_ERROR, "releaseWriteBuffer: Stream is not active"); + + return; + } + uint64_t ticks = 0; + + if(flags & SOAPY_SDR_HAS_TIME) + ticks = SoapySDR::timeNsToTicks(timeNs, this->getSampleRate(SOAPY_SDR_TX, 0)); + + if(s->channels.empty()) + { + DLOGF(SOAPY_SDR_ERROR, "releaseWriteBuffer: No channels in this stream"); + + return; + } + + DLOGF_S(SOAPY_SDR_TRACE, "releaseWriteBuffer: Releasing user buffer %u", handle); + + bool any_dma_disabled = false; + + // Check if any of the DMAs is disabled, meaning we had an underflow and the ISR disabled them + for(auto &c : s->channels) + { + std::lock_guard lock(c->mutex); + + if(!c->dma->enabled()) + { + DLOGF_S(SOAPY_SDR_TRACE, "releaseWriteBuffer: DMA Controller %u of channel %u is disabled", c->dma->getPeripheralID(), c->num); + + any_dma_disabled = true; + + break; + } + } + + if(any_dma_disabled) + { + DLOGF(SOAPY_SDR_WARNING, "releaseWriteBuffer: At least one DMA Controller is disabled, waiting for all to be"); + + for(auto &c : s->channels) + { + // No mutex locking here since we need the callbacks to run to flush the finished transfers + c->dma->waitIdle(); + + std::lock_guard lock(c->mutex); + + this->axi_rf_tstamp->disableTXCounter(c->ts_chan); + this->axi_rf_tstamp->disableTX(c->ts_chan); + this->axi_rf_tstamp->waitTXCounterDisabled(c->ts_chan, 100); + this->axi_rf_tstamp->waitTXDisabled(c->ts_chan, 100); + + c->dma->disable(); + + for(auto &buf : c->dma_buffers) + buf->idle = true; + } + + for(auto &c : s->channels) + { + std::lock_guard lock(c->mutex); + + c->buffers[handle]->valid_size = 0; + c->buffers[handle]->time_valid = false; + c->buffers[handle]->acquired = false; + + DLOGF_S(SOAPY_SDR_TRACE, "releaseWriteBuffer: Channel %u, DMA Controller %u", c->num, c->dma->getPeripheralID()); + + c->next_dma_user_buf = (handle + 1) % c->buffers.size(); + + std::memcpy(c->dma_buffers[0]->virt, c->buffers[handle]->addr, numElems * ICYRADIO_SAMPLE_SIZE_BYTES); + + c->dma_buffers[0]->xfer.size = numElems * ICYRADIO_SAMPLE_SIZE_BYTES; + + c->dma->enable(); + c->dma->submitTransfer(c->dma_buffers[0]->xfer); + + c->dma_buffers[0]->idle = false; + + DLOGF_S(SOAPY_SDR_TRACE, "releaseWriteBuffer: Submitted transfer ID %u", c->dma_buffers[0]->xfer.id); + + if(flags & SOAPY_SDR_HAS_TIME) + this->axi_rf_tstamp->setTXCounter(c->ts_chan, ticks); + + if(s->channels.size() > 1) + continue; + + DLOGF_S(SOAPY_SDR_TRACE, "releaseWriteBuffer: Single channel, just enable TX/counter"); + + if(flags & SOAPY_SDR_HAS_TIME) + { + uint64_t hw_time_now = this->axi_rf_tstamp->getCounter(); // Hardware time now + + if(ticks > hw_time_now) + { + DLOGF_S(SOAPY_SDR_TRACE, "releaseWriteBuffer: Buffer time %llu is valid and not late, disabling TX and arming trigger", ticks); + + this->axi_rf_tstamp->enableTXCounter(c->ts_chan); + } + else + { + DLOGF(SOAPY_SDR_WARNING, "releaseWriteBuffer: Buffer time %llu is late (hw time is %llu), not arming trigger, enabling TX now", ticks, hw_time_now); + + DLOGF_S(SOAPY_SDR_SSI, "L"); + + this->axi_rf_tstamp->enableTX(c->ts_chan); + } + } + else + { + DLOGF_S(SOAPY_SDR_TRACE, "releaseWriteBuffer: Buffer does not have time, enabling TX now"); + + this->axi_rf_tstamp->enableTX(c->ts_chan); + } + } + + if(s->channels.size() > 1) + { + DLOGF_S(SOAPY_SDR_TRACE, "releaseWriteBuffer: Syncing multichannel timestamps - wait for DMAs and enable TX/counters"); + + // Only check the last channel, the first will be set aswell + this->axi_rf_tstamp->waitTXDMAReady(s->channels.end()[-1]->ts_chan, 100); + + if(flags & SOAPY_SDR_HAS_TIME) + { + uint64_t hw_time_now = this->axi_rf_tstamp->getCounter(); // Hardware time now + + if(ticks > hw_time_now) + { + DLOGF_S(SOAPY_SDR_TRACE, "releaseWriteBuffer: Buffer time %llu is valid and not late, disabling TX and arming trigger", ticks); + + this->axi_rf_tstamp->enableTXCounter(); // Atomically enables both counters + } + else + { + DLOGF(SOAPY_SDR_WARNING, "releaseWriteBuffer: Buffer time %llu is late (hw time is %llu), not arming trigger, enabling TX now", ticks, hw_time_now); + + DLOGF_S(SOAPY_SDR_SSI, "L"); + + this->axi_rf_tstamp->enableTX(); // Atomically enables both channels + } + } + else + { + DLOGF_S(SOAPY_SDR_TRACE, "releaseWriteBuffer: Buffer does not have time, enabling TX now"); + + this->axi_rf_tstamp->enableTX(); // Atomically enables both channels + } + } + } + else + { + DLOGF_S(SOAPY_SDR_TRACE, "releaseWriteBuffer: No DMA Controller is disabled, stream is healthy"); + + for(auto &c : s->channels) + { + std::unique_lock lock(c->mutex); + + if(handle >= c->buffers.size()) + DLOGF(SOAPY_SDR_WARNING, "releaseWriteBuffer: Invalid buffer %u for channel %u", handle, c->num); + + if(!c->buffers[handle]->acquired) + { + DLOGF(SOAPY_SDR_WARNING, "releaseWriteBuffer: Buffer %u for channel %u is not acquired", handle, c->num); + + return; + } + + if(c->buffers[handle]->valid_size) + { + DLOGF(SOAPY_SDR_WARNING, "releaseReadBuffer: Buffer %u for channel %u was acquired with valid data", handle, c->num); + + return; + } + + bool submit_xfer = true; + + if(c->next_dma_user_buf != handle) + { + submit_xfer = false; + + DLOGF_S(SOAPY_SDR_TRACE, "releaseWriteBuffer: Buffer %u for channel %u is not the next one in the queue, not submitting transfer", handle, c->num); + } + else if(flags & SOAPY_SDR_HAS_TIME) + { + submit_xfer = false; + + DLOGF_S(SOAPY_SDR_TRACE, "releaseWriteBuffer: Buffer has time, not submitting transfer"); + } + + SoapyIcyRadio::Stream::Channel::DMABuffer *dma_buf = nullptr; + + if(submit_xfer) + { + // Find a possible free DMA buffer + for(auto &buf : c->dma_buffers) + { + if(buf->idle) + { + dma_buf = buf; + + break; + } + } + + if(!dma_buf) + { + submit_xfer = false; + + DLOGF_S(SOAPY_SDR_TRACE, "releaseWriteBuffer: DMA Controller %u has no transfer slot available, not submitting transfer", c->dma->getPeripheralID()); + } + } + + if(submit_xfer) + { + // Invalidate the user buffer and return it to the pool + c->buffers[handle]->valid_size = 0; + c->buffers[handle]->time_valid = false; + c->buffers[handle]->acquired = false; + + // Submit the data directly to the DMA + DLOGF_S(SOAPY_SDR_TRACE, "releaseWriteBuffer: Submitting transfer for channel %u on DMA Controller %u", c->num, c->dma->getPeripheralID()); + + c->next_dma_user_buf = (handle + 1) % c->buffers.size(); + + std::memcpy(dma_buf->virt, c->buffers[handle]->addr, numElems * ICYRADIO_SAMPLE_SIZE_BYTES); + + dma_buf->xfer.size = numElems * ICYRADIO_SAMPLE_SIZE_BYTES; + + c->dma->submitTransfer(dma_buf->xfer); + + dma_buf->idle = false; + + DLOGF_S(SOAPY_SDR_TRACE, "releaseWriteBuffer: Submitted transfer ID %u", dma_buf->xfer.id); + } + else + { + // Add the buffer to the pool + DLOGF_S(SOAPY_SDR_TRACE, "releaseWriteBuffer: Adding buffer to channel %u pool", c->num); + + c->buffers[handle]->valid_size = numElems * ICYRADIO_SAMPLE_SIZE_BYTES; + + if(flags & SOAPY_SDR_HAS_TIME) + { + c->buffers[handle]->time = ticks; + c->buffers[handle]->time_valid = true; + } + else + { + c->buffers[handle]->time = 0; + c->buffers[handle]->time_valid = false; + } + + c->buffers[handle]->acquired = false; + } + } + } } \ No newline at end of file diff --git a/software/soapy/src/include/AXIAD9361.hpp b/software/soapy/src/include/AXIAD9361.hpp index 469dce35..561237ff 100644 --- a/software/soapy/src/include/AXIAD9361.hpp +++ b/software/soapy/src/include/AXIAD9361.hpp @@ -569,6 +569,7 @@ class AXIAD9361: public AXIPeripheral { return this->tuneChipTiming(false, freqs, restore); } + public: AXIAD9361(void *base_address, AD9361 *phy = nullptr); ~AXIAD9361(); diff --git a/software/soapy/src/include/AXIRFTStamp.hpp b/software/soapy/src/include/AXIRFTStamp.hpp index 0a802187..4117f5b2 100644 --- a/software/soapy/src/include/AXIRFTStamp.hpp +++ b/software/soapy/src/include/AXIRFTStamp.hpp @@ -58,12 +58,15 @@ #define AXI_RF_TSTAMP_REG_CH_CTL_STAT_CNT_RX_DIS BIT(7) #define AXI_RF_TSTAMP_REG_CH_CTL_STAT_CNT_LATCH_ARM_REQ BIT(8) #define AXI_RF_TSTAMP_REG_CH_CTL_STAT_CNT_LATCH_ARM_UNREQ BIT(9) +#define AXI_RF_TSTAMP_REG_CH_CTL_STAT_TX_FLUSH_EN BIT(10) +#define AXI_RF_TSTAMP_REG_CH_CTL_STAT_TX_FLUSH_DIS BIT(11) #define AXI_RF_TSTAMP_REG_CH_CTL_STAT_TX_STAT BIT(16) #define AXI_RF_TSTAMP_REG_CH_CTL_STAT_RX_STAT BIT(17) #define AXI_RF_TSTAMP_REG_CH_CTL_STAT_CNT_TX_STAT BIT(18) #define AXI_RF_TSTAMP_REG_CH_CTL_STAT_CNT_RX_STAT BIT(19) #define AXI_RF_TSTAMP_REG_CH_CTL_STAT_TX_MAN_STAT BIT(20) #define AXI_RF_TSTAMP_REG_CH_CTL_STAT_RX_MAN_STAT BIT(21) +#define AXI_RF_TSTAMP_REG_CH_CTL_STAT_TX_FLUSH_STAT BIT(22) #define AXI_RF_TSTAMP_REG_CH_CTL_STAT_CNT_LATCH_ARM_REQ_STAT BIT(24) #define AXI_RF_TSTAMP_REG_CH_CTL_STAT_CNT_LATCH_ARMED BIT(25) #define AXI_RF_TSTAMP_REG_CH_CTL_STAT_CNT_LATCH_VALID BIT(26) @@ -143,6 +146,9 @@ class AXIRFTStamp: public AXIPeripheral void waitForClockSync(uint32_t timeout_ms = 0); void triggerClockResync(bool wait = false, uint32_t timeout_ms = 0); + void flushTX(uint32_t timeout_ms = 0); + void flushTX(AXIRFTStamp::Channel ch, uint32_t timeout_ms = 0); + void enableTX(bool enable = true); void enableTX(AXIRFTStamp::Channel ch, bool enable = true); inline void disableTX() @@ -155,6 +161,10 @@ class AXIRFTStamp: public AXIPeripheral } bool isTXEnabled(); bool isTXEnabled(AXIRFTStamp::Channel ch); + void waitTXEnabled(uint32_t timeout_ms = 0); + void waitTXEnabled(AXIRFTStamp::Channel ch, uint32_t timeout_ms = 0); + void waitTXDisabled(uint32_t timeout_ms = 0); + void waitTXDisabled(AXIRFTStamp::Channel ch, uint32_t timeout_ms = 0); void enableRX(bool enable = true); void enableRX(AXIRFTStamp::Channel ch, bool enable = true); inline void disableRX() @@ -167,6 +177,10 @@ class AXIRFTStamp: public AXIPeripheral } bool isRXEnabled(); bool isRXEnabled(AXIRFTStamp::Channel ch); + void waitRXEnabled(uint32_t timeout_ms = 0); + void waitRXEnabled(AXIRFTStamp::Channel ch, uint32_t timeout_ms = 0); + void waitRXDisabled(uint32_t timeout_ms = 0); + void waitRXDisabled(AXIRFTStamp::Channel ch, uint32_t timeout_ms = 0); void enableCounter(bool enable = true); inline void disableCounter() @@ -187,6 +201,10 @@ class AXIRFTStamp: public AXIPeripheral } bool isTXCounterEnabled(); bool isTXCounterEnabled(AXIRFTStamp::Channel ch); + void waitTXCounterEnabled(uint32_t timeout_ms = 0); + void waitTXCounterEnabled(AXIRFTStamp::Channel ch, uint32_t timeout_ms = 0); + void waitTXCounterDisabled(uint32_t timeout_ms = 0); + void waitTXCounterDisabled(AXIRFTStamp::Channel ch, uint32_t timeout_ms = 0); void enableRXCounter(bool enable = true); void enableRXCounter(AXIRFTStamp::Channel ch, bool enable = true); inline void disableRXCounter() @@ -199,6 +217,10 @@ class AXIRFTStamp: public AXIPeripheral } bool isRXCounterEnabled(); bool isRXCounterEnabled(AXIRFTStamp::Channel ch); + void waitRXCounterEnabled(uint32_t timeout_ms = 0); + void waitRXCounterEnabled(AXIRFTStamp::Channel ch, uint32_t timeout_ms = 0); + void waitRXCounterDisabled(uint32_t timeout_ms = 0); + void waitRXCounterDisabled(AXIRFTStamp::Channel ch, uint32_t timeout_ms = 0); void armCounterLatch(bool req_arm = true); void armCounterLatch(AXIRFTStamp::Channel ch, bool req_arm = true); diff --git a/software/soapy/src/include/Log.hpp b/software/soapy/src/include/Log.hpp new file mode 100644 index 00000000..80dca831 --- /dev/null +++ b/software/soapy/src/include/Log.hpp @@ -0,0 +1,14 @@ +#pragma once + +#include + +#define DLOGF(LEVEL, FORMAT, ...) SoapySDR_logf(LEVEL, FORMAT, ##__VA_ARGS__) + +// #define TRACE_STREAM // Used to disable SoapySDR_logf calls in the streaming API, to improve performance +#ifdef TRACE_STREAM + #define DLOGF_S(LEVEL, FORMAT, ...) SoapySDR_logf(LEVEL, FORMAT, ##__VA_ARGS__) +#else + #define DLOGF_S(LEVEL, FORMAT, ...) do {} while (0) +#endif + +// #define TRACE_I2C // Used to disable scanning the I2C buses, which can take precious time \ No newline at end of file diff --git a/software/soapy/src/include/Si5351.hpp b/software/soapy/src/include/Si5351.hpp index 394bc2fc..e8b2ffdb 100644 --- a/software/soapy/src/include/Si5351.hpp +++ b/software/soapy/src/include/Si5351.hpp @@ -273,6 +273,9 @@ class Si5351 private: static Si5351::MultiSynthDivider CalculateMSDivider(uint32_t f1, uint32_t f2); + static Si5351::MultiSynthDivider CalculateValidPLLMSDivider(uint32_t f1, uint32_t f2); + static Si5351::MultiSynthDivider CalculateValidIntMSDivider(uint32_t f1, uint32_t f2); + static Si5351::MultiSynthDivider CalculateValidFracMSDivider(uint32_t f1, uint32_t f2); static void ValidatePLLMSDivider(Si5351::MultiSynthDivider div); static void ValidateIntClockMSDivider(Si5351::MultiSynthDivider div); static void ValidateFracClockMSDivider(Si5351::MultiSynthDivider div); @@ -349,18 +352,20 @@ class Si5351 bool isPLLLocked(Si5351::PLL pll); void resetPLL(Si5351::PLL pll); - void configPLL(Si5351::PLL pll, uint32_t freq, Si5351::PLLSource src = Si5351::PLLSource::PLL_SRC_AUTO); + void configPLL(Si5351::PLL pll, uint32_t freq, Si5351::PLLSource src = Si5351::PLLSource::PLL_SRC_AUTO, bool closest = true); void setPLLSource(Si5351::PLL pll, Si5351::PLLSource src); Si5351::PLLSource getPLLSource(Si5351::PLL pll); uint32_t getPLLSourceFrequency(Si5351::PLL pll); - void setPLLFrequency(Si5351::PLL pll, uint32_t freq); + Si5351::PLLSource getClosestPLLFrequency(uint32_t &freq, Si5351::PLLSource src = Si5351::PLLSource::PLL_SRC_AUTO); + void setPLLFrequency(Si5351::PLL pll, uint32_t freq, bool closest = true); uint32_t getPLLFrequency(Si5351::PLL pll); - void configMultiSynth(Si5351::MultiSynth ms, uint32_t freq, float phase = 0.f, Si5351::PLL src = Si5351::PLL::PLL_AUTO); + void configMultiSynth(Si5351::MultiSynth ms, uint32_t freq, float phase = 0.f, Si5351::PLL src = Si5351::PLL::PLL_AUTO, bool closest = true); void setMultiSynthSource(Si5351::MultiSynth ms, Si5351::PLL src); Si5351::PLL getMultiSynthSource(Si5351::MultiSynth ms); uint32_t getMultiSynthSourceFrequency(Si5351::MultiSynth ms); - void setMultiSynthFrequency(Si5351::MultiSynth ms, uint32_t freq); + Si5351::PLL getClosestMultiSynthFrequency(Si5351::MultiSynth ms, uint32_t &freq, Si5351::PLL src = Si5351::PLL::PLL_AUTO); + void setMultiSynthFrequency(Si5351::MultiSynth ms, uint32_t freq, bool closest = true); uint32_t getMultiSynthFrequency(Si5351::MultiSynth ms); void setMultiSynthPhaseOffset(Si5351::MultiSynth ms, float phase); float getMultiSynthPhaseOffset(Si5351::MultiSynth ms); @@ -415,7 +420,8 @@ class Si5351 void setClockOutputEnableMode(Si5351::ClockOutput clk, bool hard = true); // Convenience methods that abstract the internal clock tree (MS -> Output Buffer mapping, output dividers for lower frequencies, etc...) - void configClock(Si5351::ClockOutput clk, uint32_t freq, float phase = 0.f, Si5351::PLL src = Si5351::PLL::PLL_AUTO); + void configClock(Si5351::ClockOutput clk, uint32_t freq, float phase = 0.f, Si5351::PLL src = Si5351::PLL::PLL_AUTO, bool closest = true); + Si5351::PLL getClosestClockFrequency(Si5351::ClockOutput clk, uint32_t &freq, Si5351::PLL src = Si5351::PLL::PLL_AUTO); void setClockFrequency(Si5351::ClockOutput clk, uint32_t freq); uint32_t getClockFrequency(Si5351::ClockOutput clk); void setClockPhaseOffset(Si5351::ClockOutput clk, float phase); diff --git a/software/soapy/src/include/SoapyIcyRadio.hpp b/software/soapy/src/include/SoapyIcyRadio.hpp index 2a1cfb0a..3c86b118 100644 --- a/software/soapy/src/include/SoapyIcyRadio.hpp +++ b/software/soapy/src/include/SoapyIcyRadio.hpp @@ -12,10 +12,10 @@ #include #include #include -#include #include #include #include +#include "Log.hpp" #include "ioctl.hpp" #include "MappedRegion.hpp" #include "AXI.hpp" @@ -49,6 +49,13 @@ class SoapyIcyRadio: public SoapySDR::Device { private: + struct Config + { + bool use_clkin; // Use external clock input (CLKIN) instead of the internal TCXO (XTAL) + uint32_t clkin_freq; // External clock input frequency in Hz + bool enable_clkout; // Enable clock output on the u.FL connector + uint32_t clkout_freq; // External clock output frequency in Hz + }; class Stream { public: @@ -58,11 +65,13 @@ class SoapyIcyRadio: public SoapySDR::Device class DMABuffer { public: + size_t index; // Index of the buffer in the pool SoapyIcyRadio *device; // Pointer to the parent device SoapyIcyRadio::Stream::Channel *parent; // Pointer to the parent channel void *virt; // Virtual (userspace) address pointing to the memory region where the DMA is going to write/read data size_t size; // Size of the buffer in bytes AXIDMAC::Transfer xfer; // DMA transfer + bool idle; // Whether this buffer is idle or the DMA is currently using it }; class Buffer @@ -107,9 +116,11 @@ class SoapyIcyRadio: public SoapySDR::Device std::vector dma_buffers; // Pool of DMA buffers std::vector buffers; // Pool of user buffers size_t next_user_buf; // Index of the next buffer that will be acquired by the user (queue tail) - size_t next_dma_buf; // Index of the next buffer that will be filled by the DMA (queue head) - uint64_t next_time; // Timestamp of the next buffer that will be filled by the DMA - bool next_time_valid; + uint64_t next_user_buf_time; // Expected timestamp of the next buffer that will be acquired by the user (used to check for overflows) + bool next_user_buf_time_valid; + size_t next_dma_user_buf; // Index of the next user buffer that will be filled/emptied by the DMA (queue head) + uint64_t next_dma_user_buf_time; // Timestamp of the next user buffer that will be filled/emptied by the DMA + bool next_dma_user_buf_time_valid; std::mutex mutex; }; @@ -135,8 +146,9 @@ class SoapyIcyRadio: public SoapySDR::Device std::mutex cur_buf_mutex; size_t cur_buf_handle; std::vector cur_buf_ptrs; - size_t cur_buf_rem; - uint64_t cur_buf_time; + size_t cur_buf_size; + size_t cur_buf_valid_size; + long long cur_buf_time; bool cur_buf_time_valid; }; @@ -214,16 +226,16 @@ class SoapyIcyRadio: public SoapySDR::Device /******************************************************************* * Gain API ******************************************************************/ - //std::vector listGains(const int direction, const size_t channel) const; - //bool hasGainMode(const int direction, const size_t channel) const; - //void setGainMode(const int direction, const size_t channel, const bool automatic); - //bool getGainMode(const int direction, const size_t channel) const; - //void setGain(const int direction, const size_t channel, const double value); - //void setGain(const int direction, const size_t channel, const std::string &name, const double value); - //double getGain(const int direction, const size_t channel) const; - //double getGain(const int direction, const size_t channel, const std::string &name) const; - //SoapySDR::Range getGainRange(const int direction, const size_t channel) const; - //SoapySDR::Range getGainRange(const int direction, const size_t channel, const std::string &name) const; + std::vector listGains(const int direction, const size_t channel) const; + bool hasGainMode(const int direction, const size_t channel) const; + void setGainMode(const int direction, const size_t channel, const bool automatic); + bool getGainMode(const int direction, const size_t channel) const; + void setGain(const int direction, const size_t channel, const double value); + void setGain(const int direction, const size_t channel, const std::string &name, const double value); + double getGain(const int direction, const size_t channel) const; + double getGain(const int direction, const size_t channel, const std::string &name) const; + SoapySDR::Range getGainRange(const int direction, const size_t channel) const; + SoapySDR::Range getGainRange(const int direction, const size_t channel, const std::string &name) const; /******************************************************************* * Frequency API @@ -242,7 +254,7 @@ class SoapyIcyRadio: public SoapySDR::Device ******************************************************************/ void setSampleRate(const int direction, const size_t channel, const double rate); double getSampleRate(const int direction, const size_t channel) const; - std::vector listSampleRates(const int direction, const size_t channel) const; + // std::vector listSampleRates(const int direction, const size_t channel) const; // Deprecated SoapySDR::RangeList getSampleRateRange(const int direction, const size_t channel) const; /******************************************************************* @@ -250,7 +262,7 @@ class SoapyIcyRadio: public SoapySDR::Device ******************************************************************/ void setBandwidth(const int direction, const size_t channel, const double bw); double getBandwidth(const int direction, const size_t channel) const; - std::vector listBandwidths(const int direction, const size_t channel) const; + // std::vector listBandwidths(const int direction, const size_t channel) const; // Deprecated SoapySDR::RangeList getBandwidthRange(const int direction, const size_t channel) const; /******************************************************************* @@ -269,13 +281,13 @@ class SoapyIcyRadio: public SoapySDR::Device /******************************************************************* * Time API ******************************************************************/ - //std::vector listTimeSources() const; - //void setTimeSource(const std::string &source); - //std::string getTimeSource() const; - //bool hasHardwareTime(const std::string &what = "") const; - //long long getHardwareTime(const std::string &what = "") const; - //void setHardwareTime(const long long timeNs, const std::string &what = ""); - //void setCommandTime(const long long timeNs, const std::string &what = ""); + std::vector listTimeSources() const; + void setTimeSource(const std::string &source); + std::string getTimeSource() const; + bool hasHardwareTime(const std::string &what = "") const; + long long getHardwareTime(const std::string &what = "") const; + void setHardwareTime(const long long timeNs, const std::string &what = ""); + // void setCommandTime(const long long timeNs, const std::string &what = ""); // Deprecated /******************************************************************* * Sensor API @@ -283,9 +295,9 @@ class SoapyIcyRadio: public SoapySDR::Device std::vector listSensors() const; SoapySDR::ArgInfo getSensorInfo(const std::string &key) const; std::string readSensor(const std::string &key) const; - //std::vector listSensors(const int direction, const size_t channel) const; - //SoapySDR::ArgInfo getSensorInfo(const int direction, const size_t channel, const std::string &key) const; - //std::string readSensor(const int direction, const size_t channel, const std::string &key) const; + std::vector listSensors(const int direction, const size_t channel) const; + SoapySDR::ArgInfo getSensorInfo(const int direction, const size_t channel, const std::string &key) const; + std::string readSensor(const int direction, const size_t channel, const std::string &key) const; /******************************************************************* * Register API @@ -343,6 +355,8 @@ class SoapyIcyRadio: public SoapySDR::Device //void* getNativeDeviceHandle() const; private: + void parseConfig(const SoapySDR::Kwargs &args); + void setupMemoryMaps(); void freeMemoryMaps(); @@ -408,6 +422,7 @@ class SoapyIcyRadio: public SoapySDR::Device private: int fd; // File descriptor + SoapyIcyRadio::Config config; // Memory maps MappedRegion *mm_axi_flash;