2017-11-10 17:56:45 +08:00
|
|
|
mod clock_mux {
|
2018-05-15 01:54:29 +08:00
|
|
|
use board_misoc::csr;
|
2017-11-10 17:56:45 +08:00
|
|
|
|
|
|
|
const CLK_SRC_EXT_SEL : u8 = 1 << 0;
|
|
|
|
const REF_CLK_SRC_SEL : u8 = 1 << 1;
|
|
|
|
const DAC_CLK_SRC_SEL : u8 = 1 << 2;
|
2018-06-13 02:00:12 +08:00
|
|
|
const REF_LO_CLK_SEL : u8 = 1 << 3;
|
2017-11-10 17:56:45 +08:00
|
|
|
|
2017-11-19 01:08:31 +08:00
|
|
|
pub fn init() {
|
2017-11-10 17:56:45 +08:00
|
|
|
unsafe {
|
|
|
|
csr::clock_mux::out_write(
|
|
|
|
1*CLK_SRC_EXT_SEL | // use ext clk from sma
|
2017-11-30 12:06:54 +08:00
|
|
|
1*REF_CLK_SRC_SEL |
|
2018-06-13 02:00:12 +08:00
|
|
|
1*DAC_CLK_SRC_SEL |
|
|
|
|
0*REF_LO_CLK_SEL);
|
2017-11-10 17:56:45 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2017-10-04 01:42:57 +08:00
|
|
|
|
2017-09-06 10:46:02 +08:00
|
|
|
mod hmc830 {
|
2018-05-15 01:54:29 +08:00
|
|
|
use board_misoc::{csr, clock};
|
2017-09-06 10:46:02 +08:00
|
|
|
|
|
|
|
fn spi_setup() {
|
|
|
|
unsafe {
|
2018-02-23 17:50:49 +08:00
|
|
|
while csr::converter_spi::idle_read() == 0 {}
|
|
|
|
csr::converter_spi::offline_write(0);
|
|
|
|
csr::converter_spi::end_write(1);
|
2017-11-06 19:08:28 +08:00
|
|
|
csr::converter_spi::cs_polarity_write(0b0001);
|
2017-09-06 10:46:02 +08:00
|
|
|
csr::converter_spi::clk_polarity_write(0);
|
|
|
|
csr::converter_spi::clk_phase_write(0);
|
|
|
|
csr::converter_spi::lsb_first_write(0);
|
|
|
|
csr::converter_spi::half_duplex_write(0);
|
2018-05-16 22:46:14 +08:00
|
|
|
csr::converter_spi::length_write(32 - 1);
|
2018-02-23 17:50:49 +08:00
|
|
|
csr::converter_spi::div_write(16 - 2);
|
2017-09-06 10:46:02 +08:00
|
|
|
csr::converter_spi::cs_write(1 << csr::CONFIG_CONVERTER_SPI_HMC830_CS);
|
2018-05-16 22:46:14 +08:00
|
|
|
}
|
|
|
|
}
|
2018-02-23 17:50:49 +08:00
|
|
|
|
2018-05-16 22:46:14 +08:00
|
|
|
pub fn select_spi_mode() {
|
|
|
|
spi_setup();
|
|
|
|
unsafe {
|
|
|
|
// rising egde on CS since cs_polarity still 0
|
|
|
|
// selects "HMC Mode"
|
2018-02-23 21:22:47 +08:00
|
|
|
// do a dummy cycle with cs still high to clear CS
|
2018-02-23 17:50:49 +08:00
|
|
|
csr::converter_spi::length_write(0);
|
|
|
|
csr::converter_spi::data_write(0);
|
|
|
|
while csr::converter_spi::writable_read() == 0 {}
|
2018-02-23 21:22:47 +08:00
|
|
|
csr::converter_spi::length_write(32 - 1);
|
2017-09-06 10:46:02 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
fn write(addr: u8, data: u32) {
|
2018-02-23 21:22:47 +08:00
|
|
|
let val = ((addr as u32) << 24) | data;
|
2017-09-06 10:46:02 +08:00
|
|
|
unsafe {
|
2018-02-23 17:50:49 +08:00
|
|
|
while csr::converter_spi::writable_read() == 0 {}
|
2018-02-23 21:22:47 +08:00
|
|
|
csr::converter_spi::data_write(val << 1); // last clk cycle loads data
|
2017-09-06 10:46:02 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
fn read(addr: u8) -> u32 {
|
2018-02-23 21:22:47 +08:00
|
|
|
// SDO (miso/read bits) is technically CPHA=1, while SDI is CPHA=0
|
|
|
|
// trust that the 8.2ns+0.2ns/pF provide enough hold time on top of
|
|
|
|
// the SPI round trip delay and stick with CPHA=0
|
|
|
|
write((1 << 6) | addr, 0);
|
2017-09-06 10:46:02 +08:00
|
|
|
unsafe {
|
2018-03-01 18:37:33 +08:00
|
|
|
while csr::converter_spi::writable_read() == 0 {}
|
2018-02-23 17:50:49 +08:00
|
|
|
csr::converter_spi::data_read() & 0xffffff
|
2017-09-06 10:46:02 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-05-12 06:06:49 +08:00
|
|
|
pub fn detect() -> Result<(), &'static str> {
|
2017-09-06 10:46:02 +08:00
|
|
|
spi_setup();
|
2017-10-04 01:42:57 +08:00
|
|
|
let id = read(0x00);
|
2017-09-06 10:46:02 +08:00
|
|
|
if id != 0xa7975 {
|
|
|
|
error!("invalid HMC830 ID: 0x{:08x}", id);
|
|
|
|
return Err("invalid HMC830 identification");
|
2017-11-06 19:08:28 +08:00
|
|
|
} else {
|
|
|
|
info!("HMC830 found");
|
2017-09-06 10:46:02 +08:00
|
|
|
}
|
2018-05-12 06:06:49 +08:00
|
|
|
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
2018-06-12 19:37:17 +08:00
|
|
|
pub fn init() {
|
|
|
|
// Configure HMC830 for integer-N operation
|
|
|
|
// See "PLLs with integrated VCO- RF Applications Product & Operating
|
|
|
|
// Guide"
|
2018-05-12 06:06:49 +08:00
|
|
|
spi_setup();
|
2018-06-12 19:37:17 +08:00
|
|
|
info!("loading HMC830 configuration...");
|
|
|
|
|
|
|
|
write(0x0, 0x20); // software reset
|
|
|
|
write(0x0, 0x00); // normal operation
|
|
|
|
write(0x6, 0x307ca); // integer-N mode (NB data sheet table 5.8 not self-consistent)
|
|
|
|
write(0x7, 0x4d); // digital lock detect, 1/2 cycle window (6.5ns window)
|
|
|
|
write(0x9, 0x2850); // charge pump: 1.6mA, no offset
|
|
|
|
write(0xa, 0x2045); // for wideband devices like the HMC830
|
|
|
|
write(0xb, 0x7c061); // for HMC830
|
|
|
|
|
|
|
|
// VCO subsystem registers
|
|
|
|
// NB software reset does not seem to reset these registers, so always
|
|
|
|
// program them all!
|
|
|
|
write(0x5, 0xf88); // 1: defaults
|
|
|
|
write(0x5, 0x6010); // 2: mute output until output divider set
|
|
|
|
write(0x5, 0x2818); // 3: wideband PLL defaults
|
|
|
|
write(0x5, 0x60a0); // 4: HMC830 magic value
|
|
|
|
write(0x5, 0x1628); // 5: HMC830 magic value
|
|
|
|
write(0x5, 0x7fb0); // 6: HMC830 magic value
|
|
|
|
write(0x5, 0x0); // ready for VCO auto-cal
|
|
|
|
|
|
|
|
info!(" ...done");
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn set_dividers(r_div: u32, n_div: u32, m_div: u32, out_div: u32) {
|
|
|
|
// VCO frequency: f_vco = (f_ref/r_div)*(n_int + n_frac/2**24)
|
|
|
|
// VCO frequency range [1.5GHz, 3GHz]
|
|
|
|
// Output frequency: f_out = f_vco/out_div
|
|
|
|
// Max PFD frequency: 125MHz for integer-N, 100MHz for fractional
|
|
|
|
// (mode B)
|
|
|
|
// Max reference frequency: 350MHz, however f_ref >= 200MHz requires
|
|
|
|
// setting 0x08[21]=1
|
|
|
|
//
|
|
|
|
// :param r_div: reference divider [1, 16383]
|
|
|
|
// :param n_div: VCO divider, integer part. Integer-N mode: [16, 2**19-1]
|
|
|
|
// fractional mode: [20, 2**19-4]
|
|
|
|
// :param m_div: VCO divider, fractional part [0, 2**24-1]
|
|
|
|
// :param out_div: output divider [1, 62] (0 mutes output)
|
|
|
|
info!("setting HMC830 dividers...");
|
|
|
|
write(0x5, 0x6010 + (out_div << 7) + (((out_div <= 2) as u32) << 15));
|
|
|
|
write(0x5, 0x0); // ready for VCO auto-cal
|
|
|
|
write(0x2, r_div);
|
|
|
|
write(0x4, m_div);
|
|
|
|
write(0x3, n_div);
|
|
|
|
|
2018-05-16 23:15:02 +08:00
|
|
|
info!(" ...done");
|
2018-06-12 19:37:17 +08:00
|
|
|
}
|
2017-10-04 01:42:57 +08:00
|
|
|
|
2018-06-12 19:37:17 +08:00
|
|
|
pub fn check_locked() -> Result<(), &'static str> {
|
|
|
|
info!("waiting for HMC830 lock...");
|
2017-10-04 01:42:57 +08:00
|
|
|
let t = clock::get_ms();
|
2017-11-10 17:56:45 +08:00
|
|
|
while read(0x12) & 0x02 == 0 {
|
|
|
|
if clock::get_ms() > t + 2000 {
|
2018-06-12 19:37:17 +08:00
|
|
|
error!("lock timeout. Register dump:");
|
2018-01-20 08:19:31 +08:00
|
|
|
for addr in 0x00..0x14 {
|
|
|
|
// These registers don't exist (in the data sheet at least)
|
|
|
|
if addr == 0x0d || addr == 0x0e { continue; }
|
2018-05-16 23:15:02 +08:00
|
|
|
error!(" [0x{:02x}] = 0x{:04x}", addr, read(addr));
|
2018-01-20 08:19:31 +08:00
|
|
|
}
|
2018-05-16 23:15:02 +08:00
|
|
|
return Err("lock timeout");
|
2017-11-10 17:56:45 +08:00
|
|
|
}
|
|
|
|
}
|
2018-05-16 23:15:02 +08:00
|
|
|
info!(" ...locked");
|
2017-10-04 01:42:57 +08:00
|
|
|
|
2017-09-06 10:46:02 +08:00
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-04-27 19:20:20 +08:00
|
|
|
pub mod hmc7043 {
|
2018-06-19 23:48:48 +08:00
|
|
|
use board_misoc::{csr, clock};
|
2018-02-23 17:50:49 +08:00
|
|
|
|
2018-06-12 19:37:17 +08:00
|
|
|
// All frequencies assume 1.2GHz HMC830 output
|
2018-06-27 17:36:13 +08:00
|
|
|
pub const DAC_CLK_DIV: u16 = 2; // 600MHz
|
|
|
|
pub const FPGA_CLK_DIV: u16 = 8; // 150MHz
|
|
|
|
pub const SYSREF_DIV: u16 = 128; // 9.375MHz
|
|
|
|
pub const HMC_SYSREF_DIV: u16 = SYSREF_DIV*8; // 1.171875MHz (must be <= 4MHz)
|
2018-02-17 12:07:11 +08:00
|
|
|
|
2018-06-21 10:11:52 +08:00
|
|
|
// enabled, divider, output config
|
2018-06-27 17:36:13 +08:00
|
|
|
const OUTPUT_CONFIG: [(bool, u16, u8); 14] = [
|
2018-06-21 10:11:52 +08:00
|
|
|
(true, DAC_CLK_DIV, 0x08), // 0: DAC2_CLK
|
|
|
|
(true, SYSREF_DIV, 0x08), // 1: DAC2_SYSREF
|
|
|
|
(true, DAC_CLK_DIV, 0x08), // 2: DAC1_CLK
|
|
|
|
(true, SYSREF_DIV, 0x08), // 3: DAC1_SYSREF
|
|
|
|
(false, 0, 0x08), // 4: ADC2_CLK
|
|
|
|
(false, 0, 0x08), // 5: ADC2_SYSREF
|
2018-06-21 22:32:41 +08:00
|
|
|
(true, FPGA_CLK_DIV, 0x08), // 6: GTP_CLK2
|
2018-06-21 10:11:52 +08:00
|
|
|
(true, SYSREF_DIV, 0x10), // 7: FPGA_DAC_SYSREF, LVDS
|
|
|
|
(true, FPGA_CLK_DIV, 0x08), // 8: GTP_CLK1
|
|
|
|
(false, 0, 0x10), // 9: AMC_MASTER_AUX_CLK
|
|
|
|
(false, 0, 0x10), // 10: RTM_MASTER_AUX_CLK
|
2018-06-27 21:46:55 +08:00
|
|
|
(true, FPGA_CLK_DIV, 0x10), // 11: FPGA_ADC_SYSREF, LVDS -- repurposed for siphaser
|
2018-06-21 10:11:52 +08:00
|
|
|
(false, 0, 0x08), // 12: ADC1_CLK
|
|
|
|
(false, 0, 0x08), // 13: ADC1_SYSREF
|
2018-06-20 17:02:54 +08:00
|
|
|
];
|
2017-09-06 10:46:02 +08:00
|
|
|
|
|
|
|
fn spi_setup() {
|
|
|
|
unsafe {
|
2018-02-23 17:50:49 +08:00
|
|
|
while csr::converter_spi::idle_read() == 0 {}
|
|
|
|
csr::converter_spi::offline_write(0);
|
|
|
|
csr::converter_spi::end_write(1);
|
2017-11-06 19:08:28 +08:00
|
|
|
csr::converter_spi::cs_polarity_write(0b0001);
|
2017-09-06 10:46:02 +08:00
|
|
|
csr::converter_spi::clk_polarity_write(0);
|
|
|
|
csr::converter_spi::clk_phase_write(0);
|
|
|
|
csr::converter_spi::lsb_first_write(0);
|
2018-02-23 17:50:49 +08:00
|
|
|
csr::converter_spi::half_duplex_write(0); // change mid-transaction for reads
|
|
|
|
csr::converter_spi::length_write(24 - 1);
|
|
|
|
csr::converter_spi::div_write(16 - 2);
|
2017-09-06 10:46:02 +08:00
|
|
|
csr::converter_spi::cs_write(1 << csr::CONFIG_CONVERTER_SPI_HMC7043_CS);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-07-11 19:45:24 +08:00
|
|
|
fn spi_wait_idle() {
|
|
|
|
unsafe {
|
|
|
|
while csr::converter_spi::idle_read() == 0 {}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-09-06 10:46:02 +08:00
|
|
|
fn write(addr: u16, data: u8) {
|
|
|
|
let cmd = (0 << 15) | addr;
|
|
|
|
let val = ((cmd as u32) << 8) | data as u32;
|
|
|
|
unsafe {
|
2018-02-23 17:50:49 +08:00
|
|
|
while csr::converter_spi::writable_read() == 0 {}
|
|
|
|
csr::converter_spi::data_write(val << 8);
|
2017-09-06 10:46:02 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
fn read(addr: u16) -> u8 {
|
2017-11-06 19:08:28 +08:00
|
|
|
let cmd = (1 << 15) | addr;
|
2018-02-23 17:50:49 +08:00
|
|
|
let val = cmd as u32;
|
2017-09-06 10:46:02 +08:00
|
|
|
unsafe {
|
2018-02-23 17:50:49 +08:00
|
|
|
while csr::converter_spi::writable_read() == 0 {}
|
|
|
|
csr::converter_spi::end_write(0);
|
|
|
|
csr::converter_spi::length_write(16 - 1);
|
|
|
|
csr::converter_spi::data_write(val << 16);
|
|
|
|
while csr::converter_spi::writable_read() == 0 {}
|
|
|
|
csr::converter_spi::end_write(1);
|
|
|
|
csr::converter_spi::half_duplex_write(1);
|
|
|
|
csr::converter_spi::length_write(8 - 1);
|
|
|
|
csr::converter_spi::data_write(0);
|
|
|
|
while csr::converter_spi::writable_read() == 0 {}
|
|
|
|
csr::converter_spi::half_duplex_write(0);
|
|
|
|
csr::converter_spi::length_write(24 - 1);
|
|
|
|
csr::converter_spi::data_read() as u8
|
2017-09-06 10:46:02 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-06-19 23:49:17 +08:00
|
|
|
pub const CHIP_ID: u32 = 0xf17904;
|
|
|
|
|
|
|
|
pub fn get_id() -> u32 {
|
2017-09-06 10:46:02 +08:00
|
|
|
spi_setup();
|
2018-06-19 23:49:17 +08:00
|
|
|
(read(0x78) as u32) << 16 | (read(0x79) as u32) << 8 | read(0x7a) as u32
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn detect() -> Result<(), &'static str> {
|
|
|
|
let id = get_id();
|
|
|
|
if id != CHIP_ID {
|
2017-09-06 10:46:02 +08:00
|
|
|
error!("invalid HMC7043 ID: 0x{:08x}", id);
|
|
|
|
return Err("invalid HMC7043 identification");
|
2017-11-06 19:08:28 +08:00
|
|
|
} else {
|
|
|
|
info!("HMC7043 found");
|
2017-09-06 10:46:02 +08:00
|
|
|
}
|
2018-05-12 06:06:49 +08:00
|
|
|
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
2018-06-12 20:10:26 +08:00
|
|
|
pub fn enable() {
|
2018-06-19 23:48:48 +08:00
|
|
|
info!("enabling HMC7043");
|
2018-06-04 20:59:08 +08:00
|
|
|
|
|
|
|
unsafe {
|
2018-06-05 20:41:48 +08:00
|
|
|
csr::hmc7043_reset::out_write(0);
|
2018-06-04 20:59:08 +08:00
|
|
|
}
|
2018-06-19 23:48:48 +08:00
|
|
|
clock::spin_us(10_000);
|
2018-06-04 20:59:08 +08:00
|
|
|
|
2018-05-12 06:06:49 +08:00
|
|
|
spi_setup();
|
2018-06-04 20:59:08 +08:00
|
|
|
write(0x0, 0x1); // Software reset
|
|
|
|
write(0x0, 0x0); // Normal operation
|
|
|
|
write(0x1, 0x48); // mute all outputs
|
2018-05-12 06:06:49 +08:00
|
|
|
}
|
|
|
|
|
2018-07-11 19:45:24 +08:00
|
|
|
/* Read an HMC7043 internal status bit through the GPO interface.
|
|
|
|
* This method is required to work around bugs in the register interface.
|
|
|
|
*/
|
|
|
|
fn gpo_indirect_read(mux_setting: u8) -> bool {
|
|
|
|
write(0x50, (mux_setting << 2) | 0x3);
|
|
|
|
spi_wait_idle();
|
|
|
|
unsafe {
|
|
|
|
csr::hmc7043_gpo::in_read() == 1
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-06-12 20:10:26 +08:00
|
|
|
pub fn init() {
|
2018-05-12 06:06:49 +08:00
|
|
|
spi_setup();
|
2018-05-16 22:28:09 +08:00
|
|
|
info!("loading configuration...");
|
2018-02-05 20:40:17 +08:00
|
|
|
|
2018-07-11 19:07:15 +08:00
|
|
|
write(0x3, 0x14); // Disable the REFSYNCIN reseeder
|
|
|
|
write(0xA, 0x06); // Disable the REFSYNCIN input buffer
|
2018-02-17 12:07:11 +08:00
|
|
|
write(0xB, 0x07); // Enable the CLKIN input as LVPECL
|
|
|
|
write(0x9F, 0x4d); // Unexplained high-performance mode
|
|
|
|
write(0xA0, 0xdf); // Unexplained high-performance mode
|
|
|
|
|
|
|
|
// Enable required output groups
|
|
|
|
write(0x4, (1 << 0) |
|
|
|
|
(1 << 1) |
|
|
|
|
(1 << 3) |
|
2018-07-03 20:15:43 +08:00
|
|
|
(1 << 4) |
|
|
|
|
(1 << 5));
|
2018-02-17 12:07:11 +08:00
|
|
|
|
2018-06-05 19:00:58 +08:00
|
|
|
write(0x5c, (HMC_SYSREF_DIV & 0xff) as u8); // Set SYSREF timer divider
|
|
|
|
write(0x5d, ((HMC_SYSREF_DIV & 0x0f) >> 8) as u8);
|
|
|
|
|
2018-06-21 10:44:26 +08:00
|
|
|
for channel in 0..OUTPUT_CONFIG.len() {
|
2018-02-17 12:07:11 +08:00
|
|
|
let channel_base = 0xc8 + 0x0a*(channel as u16);
|
2018-06-21 10:11:52 +08:00
|
|
|
let (enabled, divider, outcfg) = OUTPUT_CONFIG[channel];
|
2018-02-17 12:07:11 +08:00
|
|
|
|
|
|
|
if enabled {
|
2018-06-21 10:44:26 +08:00
|
|
|
if channel % 2 == 0 {
|
2018-06-21 15:54:30 +08:00
|
|
|
// DCLK channel: enable high-performance mode
|
2018-06-21 10:44:26 +08:00
|
|
|
write(channel_base, 0xd1);
|
|
|
|
} else {
|
2018-06-21 15:54:30 +08:00
|
|
|
// SYSREF channel: disable hi-perf mode, enable slip
|
|
|
|
write(channel_base, 0x71);
|
2018-06-21 10:44:26 +08:00
|
|
|
}
|
|
|
|
} else {
|
|
|
|
write(channel_base, 0x10);
|
2018-02-17 12:07:11 +08:00
|
|
|
}
|
2018-06-05 19:02:26 +08:00
|
|
|
write(channel_base + 0x1, (divider & 0xff) as u8);
|
|
|
|
write(channel_base + 0x2, ((divider & 0x0f) >> 8) as u8);
|
2018-02-05 20:40:17 +08:00
|
|
|
|
2018-06-21 10:44:26 +08:00
|
|
|
// bypass analog phase shift on DCLK channels to reduce noise
|
|
|
|
if channel % 2 == 0 {
|
|
|
|
if divider != 0 {
|
|
|
|
write(channel_base + 0x7, 0x00); // enable divider
|
|
|
|
} else {
|
|
|
|
write(channel_base + 0x7, 0x03); // bypass divider for lowest noise
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
write(channel_base + 0x7, 0x01);
|
2018-06-12 19:56:04 +08:00
|
|
|
}
|
2018-02-05 20:40:17 +08:00
|
|
|
|
2018-06-15 22:24:31 +08:00
|
|
|
write(channel_base + 0x8, outcfg)
|
2018-02-17 12:07:11 +08:00
|
|
|
}
|
2018-01-20 00:17:58 +08:00
|
|
|
|
2018-06-05 19:00:58 +08:00
|
|
|
write(0x1, 0x4a); // Reset dividers and FSMs
|
|
|
|
write(0x1, 0x48);
|
|
|
|
write(0x1, 0xc8); // Synchronize dividers
|
2018-06-20 17:02:54 +08:00
|
|
|
write(0x1, 0x40); // Unmute, high-performance/low-noise mode
|
2018-06-05 19:00:58 +08:00
|
|
|
|
2018-05-16 23:15:02 +08:00
|
|
|
info!(" ...done");
|
2017-09-06 10:46:02 +08:00
|
|
|
}
|
2018-04-27 19:20:20 +08:00
|
|
|
|
2018-07-11 19:45:24 +08:00
|
|
|
pub fn check_phased() -> Result<(), &'static str> {
|
|
|
|
if !gpo_indirect_read(3) {
|
|
|
|
return Err("GPO reported phases did not align");
|
|
|
|
}
|
|
|
|
// Should be the same as the GPO read
|
|
|
|
let sysref_fsm_status = read(0x91);
|
|
|
|
if sysref_fsm_status != 0x2 {
|
|
|
|
error!("Bad SYSREF FSM status: {:02x}", sysref_fsm_status);
|
|
|
|
return Err("Bad SYSREF FSM status");
|
|
|
|
}
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
2018-06-21 16:23:41 +08:00
|
|
|
pub fn sysref_offset_dac(dacno: u8, phase_offset: u16) {
|
2018-04-27 20:32:03 +08:00
|
|
|
/* Analog delay resolution: 25ps
|
|
|
|
* Digital delay resolution: 1/2 input clock cycle = 416ps for 1.2GHz
|
|
|
|
* 16*25ps = 400ps: limit analog delay to 16 steps instead of 32.
|
|
|
|
*/
|
2018-06-21 16:23:41 +08:00
|
|
|
let analog_delay = (phase_offset % 17) as u8;
|
|
|
|
let digital_delay = (phase_offset / 17) as u8;
|
2018-06-21 10:44:26 +08:00
|
|
|
spi_setup();
|
2018-04-27 19:20:20 +08:00
|
|
|
if dacno == 0 {
|
2018-06-20 17:40:48 +08:00
|
|
|
write(0x00d5, analog_delay);
|
|
|
|
write(0x00d6, digital_delay);
|
2018-04-27 19:20:20 +08:00
|
|
|
} else if dacno == 1 {
|
2018-06-20 17:40:48 +08:00
|
|
|
write(0x00e9, analog_delay);
|
|
|
|
write(0x00ea, digital_delay);
|
2018-04-27 19:20:20 +08:00
|
|
|
} else {
|
|
|
|
unimplemented!();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-06-21 16:23:41 +08:00
|
|
|
fn sysref_offset_fpga(phase_offset: u16) {
|
|
|
|
let analog_delay = (phase_offset % 17) as u8;
|
|
|
|
let digital_delay = (phase_offset / 17) as u8;
|
2018-06-21 15:54:30 +08:00
|
|
|
spi_setup();
|
|
|
|
write(0x0111, analog_delay);
|
|
|
|
write(0x0112, digital_delay);
|
|
|
|
}
|
|
|
|
|
|
|
|
fn sysref_slip() {
|
|
|
|
spi_setup();
|
|
|
|
write(0x0002, 0x02);
|
|
|
|
write(0x0002, 0x00);
|
|
|
|
}
|
|
|
|
|
|
|
|
fn sysref_sample() -> bool {
|
|
|
|
unsafe { csr::sysref_sampler::sample_result_read() == 1 }
|
|
|
|
}
|
|
|
|
|
2018-06-27 17:36:13 +08:00
|
|
|
pub fn sysref_rtio_align(phase_offset: u16, expected_align: u16) {
|
2018-06-21 15:54:30 +08:00
|
|
|
info!("aligning SYSREF with RTIO...");
|
|
|
|
|
|
|
|
let mut slips0 = 0;
|
|
|
|
let mut slips1 = 0;
|
|
|
|
// meet setup/hold (assuming FPGA timing margins are OK)
|
2018-06-21 16:23:41 +08:00
|
|
|
sysref_offset_fpga(phase_offset);
|
2018-06-21 15:54:30 +08:00
|
|
|
// if we are already in the 1 zone, get out of it
|
|
|
|
while sysref_sample() {
|
|
|
|
sysref_slip();
|
|
|
|
slips0 += 1;
|
2018-06-27 17:32:56 +08:00
|
|
|
if slips0 > 1024 {
|
|
|
|
error!(" failed to reach 1->0 transition");
|
|
|
|
break;
|
|
|
|
}
|
2018-06-21 15:54:30 +08:00
|
|
|
}
|
|
|
|
// get to the edge of the 0->1 transition (our final setpoint)
|
|
|
|
while !sysref_sample() {
|
|
|
|
sysref_slip();
|
|
|
|
slips1 += 1;
|
2018-06-27 17:32:56 +08:00
|
|
|
if slips1 > 1024 {
|
|
|
|
error!(" failed to reach 0->1 transition");
|
|
|
|
break;
|
|
|
|
}
|
2018-06-21 15:54:30 +08:00
|
|
|
}
|
2018-06-27 17:36:13 +08:00
|
|
|
info!(" ...done ({}/{} slips)", slips0, slips1);
|
|
|
|
if (slips0 + slips1) % expected_align != 0 {
|
|
|
|
error!(" unexpected slip alignment");
|
|
|
|
}
|
2018-06-21 15:54:30 +08:00
|
|
|
|
2018-06-27 17:35:26 +08:00
|
|
|
let mut margin_minus = None;
|
2018-06-21 15:54:30 +08:00
|
|
|
for d in 0..phase_offset {
|
2018-06-21 16:23:41 +08:00
|
|
|
sysref_offset_fpga(phase_offset - d);
|
2018-06-21 15:54:30 +08:00
|
|
|
if !sysref_sample() {
|
2018-06-27 17:35:26 +08:00
|
|
|
margin_minus = Some(d);
|
2018-06-21 15:54:30 +08:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
// meet setup/hold
|
2018-06-21 16:23:41 +08:00
|
|
|
sysref_offset_fpga(phase_offset);
|
2018-06-21 15:54:30 +08:00
|
|
|
|
2018-06-27 17:35:26 +08:00
|
|
|
if margin_minus.is_some() {
|
|
|
|
let margin_minus = margin_minus.unwrap();
|
2018-06-21 22:26:49 +08:00
|
|
|
// one phase slip (period of the 1.2GHz input clock)
|
|
|
|
let period = 2*17; // approximate: 2 digital coarse delay steps
|
2018-06-27 17:35:26 +08:00
|
|
|
let margin_plus = if period > margin_minus { period - margin_minus } else { 0 };
|
|
|
|
info!(" margins at FPGA: -{} +{}", margin_minus, margin_plus);
|
2018-06-21 22:26:49 +08:00
|
|
|
if margin_minus < 10 || margin_plus < 10 {
|
2018-06-21 15:54:30 +08:00
|
|
|
error!("SYSREF margin at FPGA is too small");
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
error!("unable to determine SYSREF margin at FPGA");
|
|
|
|
}
|
|
|
|
}
|
2017-09-06 10:46:02 +08:00
|
|
|
}
|
2017-09-05 21:46:03 +08:00
|
|
|
|
2017-08-31 13:35:47 +08:00
|
|
|
pub fn init() -> Result<(), &'static str> {
|
2017-11-10 17:56:45 +08:00
|
|
|
clock_mux::init();
|
2018-05-16 22:46:14 +08:00
|
|
|
/* do not use other SPI devices before HMC830 SPI mode selection */
|
|
|
|
hmc830::select_spi_mode();
|
2018-05-12 06:06:49 +08:00
|
|
|
hmc830::detect()?;
|
2018-06-12 19:37:17 +08:00
|
|
|
hmc830::init();
|
2018-06-19 13:47:32 +08:00
|
|
|
|
|
|
|
// 1.2GHz out
|
|
|
|
#[cfg(hmc830_ref = "100")]
|
|
|
|
hmc830::set_dividers(1, 24, 0, 2);
|
|
|
|
#[cfg(hmc830_ref = "150")]
|
|
|
|
hmc830::set_dividers(2, 32, 0, 2);
|
|
|
|
|
2018-06-12 19:37:17 +08:00
|
|
|
hmc830::check_locked()?;
|
2018-06-04 20:59:08 +08:00
|
|
|
|
2018-06-19 23:49:17 +08:00
|
|
|
if hmc7043::get_id() == hmc7043::CHIP_ID {
|
|
|
|
error!("HMC7043 detected while in reset (board rework missing?)");
|
|
|
|
}
|
2018-06-12 20:10:26 +08:00
|
|
|
hmc7043::enable();
|
2018-06-04 20:59:08 +08:00
|
|
|
hmc7043::detect()?;
|
2018-06-12 20:10:26 +08:00
|
|
|
hmc7043::init();
|
2018-07-11 19:45:24 +08:00
|
|
|
hmc7043::check_phased()?;
|
2018-06-12 20:10:26 +08:00
|
|
|
|
|
|
|
Ok(())
|
2017-08-31 13:35:47 +08:00
|
|
|
}
|