1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316
|
//
// Copyright 2010-2016 Ettus Research LLC
// Copyright 2018 Ettus Research, a National Instruments Company
//
// SPDX-License-Identifier: GPL-3.0-or-later
//
#include "x300_dac_ctrl.hpp"
#include "x300_regs.hpp"
#include <uhd/exception.hpp>
#include <uhd/types/time_spec.hpp>
#include <uhd/utils/log.hpp>
#include <uhd/utils/safe_call.hpp>
#include <boost/format.hpp>
#include <chrono>
#include <thread>
#define X300_DAC_FRONTEND_SYNC_FAILURE_FATAL
using namespace uhd;
#define write_ad9146_reg(addr, data) \
_iface->write_spi(_slaveno, spi_config_t::EDGE_RISE, ((addr) << 8) | (data), 16)
#define read_ad9146_reg(addr) \
(_iface->read_spi(_slaveno, spi_config_t::EDGE_RISE, ((addr) << 8) | (1 << 15), 16) \
& 0xff)
x300_dac_ctrl::~x300_dac_ctrl(void)
{
/* NOP */
}
/*!
* A X300 codec control specific to the ad9146 ic.
*/
class x300_dac_ctrl_impl : public x300_dac_ctrl
{
public:
x300_dac_ctrl_impl(
uhd::spi_iface::sptr iface, const size_t slaveno, const double refclk)
: _iface(iface), _slaveno(static_cast<int>(slaveno)), _refclk(refclk)
{
// Power up all DAC subsystems
write_ad9146_reg(0x01, 0x10); // Up: I DAC, Q DAC, Receiver, Voltage Ref, Clocks
write_ad9146_reg(
0x02, 0x00); // No extended delays. Up: Voltage Ref, PLL, DAC, FIFO, Filters
reset();
}
~x300_dac_ctrl_impl(void) override
{
UHD_SAFE_CALL(
// Power down all DAC subsystems
write_ad9146_reg(
0x01, 0xEF); // Down: I DAC, Q DAC, Receiver, Voltage Ref, Clocks
write_ad9146_reg(0x02,
0x1F); // No extended delays. Down: Voltage Ref, PLL, DAC, FIFO, Filters
)
}
void reset() override
{
// ADI recommendations:
//- soft reset the chip before configuration
//- put the chip in sleep mode during configuration and wake it up when done
//- configure synchronization settings when sleeping
_soft_reset();
_sleep_mode(true);
_init();
// We run backend sync regardless of whether we need to sync multiple DACs
// because we use the internal DAC FIFO to meet system synchronous timing
// and we need to guarantee that the FIFO is not empty.
_backend_sync();
_sleep_mode(false);
}
void sync() override
{
try {
// Just return if PLL is locked and backend is synchronized
_check_pll();
_check_dac_sync();
return;
} catch (...) {
}
std::string err_str;
// Try 3 times to sync before giving up
for (size_t retries = 0; retries < 3; retries++) {
try {
_sleep_mode(true);
_init();
_backend_sync();
_sleep_mode(false);
return;
} catch (const uhd::runtime_error& e) {
err_str = e.what();
}
}
throw uhd::runtime_error(err_str);
}
void verify_sync() override
{
_check_pll();
_check_dac_sync();
#ifdef X300_DAC_FRONTEND_SYNC_FAILURE_FATAL
_check_frontend_sync(true);
#else
_check_frontend_sync(false);
#endif
}
//
// Setup all non-synchronization related DAC parameters
//
void _init()
{
write_ad9146_reg(0x1e, 0x01); // Datasheet: "Set 1 for proper operation"
write_ad9146_reg(0x06, 0xFF); // Clear all event flags
// Calculate N0 to be VCO friendly.
// Aim for VCO between 1 and 2GHz, assert otherwise.
const int N1 = 4;
int N0_val, N0;
for (N0_val = 0; N0_val < 3; N0_val++) {
N0 = (1 << N0_val); // 1, 2, 4
if ((_refclk * N0 * N1) >= 1e9)
break;
}
UHD_ASSERT_THROW((_refclk * N0 * N1) >= 1e9);
UHD_ASSERT_THROW((_refclk * N0 * N1) <= 2e9);
// Start PLL
write_ad9146_reg(0x06, 0xC0); // Clear PLL event flags
write_ad9146_reg(0x0C, 0xD1); // Narrow PLL loop filter, Midrange charge pump.
write_ad9146_reg(0x0D, 0xD1 | (N0_val << 2)); // N1=4, N2=16, N0 as calculated
write_ad9146_reg(0x0A, 0xCF); // Auto init VCO band training as per datasheet
write_ad9146_reg(0x0A, 0xA0); // See above.
_check_pll();
// Configure digital interface settings
// Bypass DCI delay. We center the clock edge in the data
// valid window in the FPGA by phase shifting the DCI going
// to the DAC.
write_ad9146_reg(0x16, 0x04);
// 2's comp, I first, byte wide interface
write_ad9146_reg(0x03, 0x00);
// FPGA wants I,Q in the sample word:
// - First transaction goes into low bits
// - Second transaction goes into high bits
// therefore, we want Q to go first (bit 6 == 1)
write_ad9146_reg(0x03, (1 << 6)); // 2s comp, i first, byte mode
// Configure interpolation filters
write_ad9146_reg(0x1C, 0x00); // Configure HB1
write_ad9146_reg(0x1D, 0x00); // Configure HB2
write_ad9146_reg(0x1B, 0xE4); // Bypass: Modulator, InvSinc, IQ Bal
// Disable sync mode by default (may get turned on later)
write_ad9146_reg(0x10, 0x40); // Disable SYNC mode.
}
//
// Attempt to synchronize AD9146's
//
void _backend_sync(void)
{
write_ad9146_reg(0x10, 0x40); // Disable SYNC mode to reset state machines.
// SYNC Settings:
//- SYNC = Enabled
//- Data Rate Mode: Synchronize at the rate at which data is consumed and not at
// the granularity of the FIFO
//- Falling edge sync: For the X300, DACCLK is generated using RefClk. Within the
// DAC, the RefClk is sampled by DACCLK to sync interpolation
// stages across multiple DACs. To ensure that we capture the
// RefClk when it is not transitioning, we sample on the
// falling edge of DACCLK
//- Averaging = MAX
write_ad9146_reg(
0x10, 0xC7); // Enable SYNC mode. Falling edge sync. Averaging set to 128.
// Wait for backend SYNC state machine to lock before proceeding. This guarantees
// that the inputs and output of the FIFO have synchronized clocks
_check_dac_sync();
// FIFO write pointer offset
// One of ADI's requirements to use data-rate synchronization in PLL mode is to
// meet setup and hold times for RefClk -> DCI clock which we *do not* currently
// meet in the FPGA. The DCI clock reaches a full RefClk cycle later which results
// in the FIFO popping before the first push. This results in a steady-state FIFO
// fullness of pointer - 1. To reach the optimal FIFO fullness of 4 we set the
// pointer to 5.
// FIXME: At some point we should meet timing on this interface
write_ad9146_reg(0x17, 0x05);
// We are requesting a soft FIFO align just to put the FIFO
// in a known state. The FRAME will actually sync the
// FIFO correctly when a stream is created
write_ad9146_reg(0x18, 0x02); // Request soft FIFO align
write_ad9146_reg(0x18, 0x00); // (See above)
}
//
// Check for PLL lock. Fatal if not locked within timeout
//
void _check_pll()
{
// Clear PLL event flags
write_ad9146_reg(0x06, 0xC0);
// Verify PLL is Locked. 1 sec timeout.
// NOTE: Data sheet inconsistent about which pins give PLL lock status. FIXME!
const auto exit_time = std::chrono::steady_clock::now() + std::chrono::seconds(1);
while (true) {
const size_t reg_e = read_ad9146_reg(0x0E); // PLL Status (Expect bit 7 = 1)
const size_t reg_6 =
read_ad9146_reg(0x06); // Event Flags (Expect bit 7 = 0 and bit 6 = 1)
if ((((reg_e >> 7) & 0x1) == 0x1) && (((reg_6 >> 6) & 0x3) == 0x1))
break;
if (exit_time < std::chrono::steady_clock::now())
throw uhd::runtime_error(
"x300_dac_ctrl: timeout waiting for DAC PLL to lock");
if (reg_6 & (1 << 7)) // Lock lost?
write_ad9146_reg(0x06, 0xC0); // Clear PLL event flags
std::this_thread::sleep_for(std::chrono::milliseconds(10));
}
}
//
// Check for DAC sync. Fatal if not synced within timeout
//
void _check_dac_sync()
{
// Clear Sync event flags
write_ad9146_reg(0x06, 0x30);
write_ad9146_reg(0x12, 0x00);
const auto exit_time = std::chrono::steady_clock::now() + std::chrono::seconds(1);
while (true) {
std::this_thread::sleep_for(
std::chrono::milliseconds(1)); // wait for sync to complete
const size_t reg_12 =
read_ad9146_reg(0x12); // Sync Status (Expect bit 7 = 0, bit 6 = 1)
const size_t reg_6 =
read_ad9146_reg(0x06); // Event Flags (Expect bit 5 = 0 and bit 4 = 1)
if ((((reg_12 >> 6) & 0x3) == 0x1) && (((reg_6 >> 4) & 0x3) == 0x1))
break;
if (exit_time < std::chrono::steady_clock::now())
throw uhd::runtime_error(
"x300_dac_ctrl: timeout waiting for backend synchronization");
if (reg_6 & (1 << 5))
write_ad9146_reg(0x06, 0x30); // Clear Sync event flags
#ifdef X300_DAC_RETRY_BACKEND_SYNC
if (reg_12 & (1 << 7)) { // Sync acquired and lost?
write_ad9146_reg(0x10,
0xC7); // Enable SYNC mode. Falling edge sync. Averaging set to 128.
write_ad9146_reg(0x12, 0x00); // Clear Sync event flags
}
#endif
}
}
//
// Check FIFO thermometer.
//
void _check_frontend_sync(bool failure_is_fatal)
{
// Register 0x19 has a thermometer indicator of the FIFO depth
const size_t reg_19 = read_ad9146_reg(0x19);
if ((reg_19 & 0xFF) != 0xF) {
std::string msg(
(boost::format(
"x300_dac_ctrl: front-end sync failed. unexpected FIFO depth [0x%x]")
% (reg_19 & 0xFF))
.str());
if (failure_is_fatal) {
throw uhd::runtime_error(msg);
} else {
UHD_LOGGER_WARNING("X300") << msg;
}
}
}
void _sleep_mode(bool sleep)
{
uint8_t sleep_val = sleep ? (1 << 7) : 0x00;
// Set sleep word and default fullscale value
write_ad9146_reg(0x41, sleep_val | 0x01); // I DAC
write_ad9146_reg(0x45, sleep_val | 0x01); // Q DAC
}
void _soft_reset()
{
write_ad9146_reg(0x00, 0x20); // Take DAC into reset.
write_ad9146_reg(0x00, 0x80); // Enable SPI reads and come out of reset
}
private:
uhd::spi_iface::sptr _iface;
const int _slaveno;
const double _refclk;
};
/***********************************************************************
* Public make function for the DAC control
**********************************************************************/
x300_dac_ctrl::sptr x300_dac_ctrl::make(
uhd::spi_iface::sptr iface, const size_t slaveno, const double clock_rate)
{
return sptr(new x300_dac_ctrl_impl(iface, slaveno, clock_rate));
}
|