// // Copyright 2010-2013 Ettus Research LLC // // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License // along with this program. If not, see . // #include "x300_dac_ctrl.hpp" #include "x300_regs.hpp" #include #include #include #include #include #include #include //sleep 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) /*! * 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(slaveno) { write_ad9146_reg(0x00, 0x20); // Take DAC into reset. write_ad9146_reg(0x00, 0x80); // Enable SPI reads and come out of reset write_ad9146_reg(0x1e, 0x01); // Data path config - set for proper operation // Calculate N0 to be VCO friendly. // Aim for VCO between 1 and 2GHz, assert otherwise. // const int N1 = 4; 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(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(0x0D, 0x90 | (N0_val << 2)); // N1=2, N2=8, N0 as calculated write_ad9146_reg(0x0A, 0xCF); // Auto init VCO band training as per datasheet write_ad9146_reg(0x0A, 0xA0); // See above. // Verify PLL is Locked. 1 sec timeout. // NOTE: Data sheet inconsistant about which pins give PLL lock status. FIXME! const time_spec_t exit_time = time_spec_t::get_system_time() + time_spec_t(1.0); while (true) { const size_t reg_e = read_ad9146_reg(0x0E); /* Expect bit 7 = 1 */ if ((exit_time < time_spec_t::get_system_time()) && ((reg_e & (1 << 7)) == 0)) throw uhd::runtime_error("x300_dac_ctrl: timeout waiting for DAC PLL to lock"); else if ((reg_e & ((1 << 7) | (1 << 6))) != 0) break; boost::this_thread::sleep(boost::posix_time::milliseconds(10)); } /* Skew DCI signal to find stable data eye */ //write_ad9146_reg(0x16, 0x04); //Disable delay in DCI //write_ad9146_reg(0x16, 0x00); //165ps delay in DCI //write_ad9146_reg(0x16, 0x01); //375ps delay in DCI write_ad9146_reg(0x16, 0x02); //615ps delay in DCI //write_ad9146_reg(0x16, 0x03); //720ps delay in DCI write_ad9146_reg(0x03, 0x00); // 2's comp, I first, byte wide interface //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 write_ad9146_reg(0x10, 0x48); // Disable SYNC mode. write_ad9146_reg(0x17, 0x04); // FIFO write pointer offset write_ad9146_reg(0x18, 0x02); // Request soft FIFO align write_ad9146_reg(0x18, 0x00); // (See above) write_ad9146_reg(0x1B, 0xE4); // Bypass: Modulator, InvSinc, IQ Bal /* Configure interpolation filters */ write_ad9146_reg(0x1C, 0x00); // Configure HB1 write_ad9146_reg(0x1D, 0x00); // Configure HB2 } ~x300_dac_ctrl_impl(void) { UHD_SAFE_CALL ( write_ad9146_reg(0x1, 0xf); //total power down write_ad9146_reg(0x2, 0xf); //total power down ) } void arm_dac_sync(void) { // // Attempt to synchronize AD9146's // write_ad9146_reg(0x10, 0xCF); // Enable SYNC mode. Sync Averaging set to 128. const time_spec_t exit_time = time_spec_t::get_system_time() + time_spec_t(1.0); while (true) { const size_t reg_12 = read_ad9146_reg(0x12); /* Expect bit 7 = 0, bit 6 = 1 */ if ((exit_time < time_spec_t::get_system_time()) && (((reg_12 & (1 << 6)) == 0) || ((reg_12 & (1 << 7)) != 0))) throw uhd::runtime_error("x300_dac_ctrl: timeout waiting for backend synchronization"); else if (((reg_12 & (1 << 6)) != 0) && ((reg_12 & (1 << 7)) == 0)) break; boost::this_thread::sleep(boost::posix_time::milliseconds(10)); } } private: uhd::spi_iface::sptr _iface; const size_t _slaveno; }; /*********************************************************************** * 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)); }