aboutsummaryrefslogtreecommitdiffstats
path: root/host/lib/usrp/x300/x300_dac_ctrl.cpp
blob: f951a44a2ad7586c06544f633c9ac7dac0de3af6 (plain)
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
317
//
// 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 <uhdlib/utils/system_time.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)
    {
        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()
    {
        // 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()
    {
        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()
    {
        _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 time_spec_t exit_time = uhd::get_system_time() + time_spec_t(1.0);
        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 < uhd::get_system_time())
                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 time_spec_t exit_time = uhd::get_system_time() + time_spec_t(1.0);
        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 < uhd::get_system_time())
                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));
}