diff --git a/src/libboard_artiq/src/cxp_downconn.rs b/src/libboard_artiq/src/cxp_downconn.rs new file mode 100644 index 0000000..2ae44b8 --- /dev/null +++ b/src/libboard_artiq/src/cxp_downconn.rs @@ -0,0 +1,534 @@ +use embedded_hal::prelude::_embedded_hal_blocking_delay_DelayUs; +use libboard_zynq::{println, timer::GlobalTimer}; +use log::info; + +// use log::info; +use crate::{cxp_proto, + pl::{csr, csr::CXP}}; + +#[derive(Clone, Copy, Debug)] +#[allow(non_camel_case_types)] +pub enum CXP_SPEED { + CXP_1, + CXP_2, + CXP_3, + CXP_5, + CXP_6, + CXP_10, + CXP_12, +} + +pub fn loopback_testing(channel: usize, timer: &mut GlobalTimer, speed: CXP_SPEED) { + println!("=============================================================================="); + cxp_gtx::change_linerate(channel, timer, speed); + + unsafe { + info!("waiting for tx&rx setup..."); + timer.delay_us(50_000); + info!( + "tx_phaligndone = {} | rx_phaligndone = {}", + (CXP[channel].downconn_txinit_phaligndone_read)(), + (CXP[channel].downconn_rxinit_phaligndone_read)(), + ); + + // enable txdata tranmission thought MGTXTXP, required by PMA loopback + (CXP[channel].downconn_txenable_write)(1); + + info!("waiting for rx to align..."); + while (CXP[channel].downconn_rx_ready_read)() != 1 {} + info!("rx ready!"); + + (CXP[channel].downconn_tx_stb_write)(1); + cxp_proto::downconn_send_test_packet(channel); + + cxp_proto::downconn_debug_send_trig_ack(channel); + + const DATA_MAXSIZE: usize = 253; + let data_size = 4; // no. of bytes + + let data: u32 = 0xDADA as u32; + let mut data_slice: [u8; DATA_MAXSIZE] = [0; DATA_MAXSIZE]; + data_slice[..4].clone_from_slice(&data.to_be_bytes()); + cxp_proto::downconn_debug_send( + channel, + &cxp_proto::UpConnPacket::Event { + conn_id: 0x1234_5678_u32, + packet_tag: 0x69_u8, + length: data_size + 3, + event_size: data_size, + namespace: 0x02_u8, + event_id: 0x00_6969u16, + timestamp: 0x1234_5678u64, + data: data_slice, + }, + ) + .expect("loopback gtx tx error"); + + timer.delay_us(1000); // wait packet has arrive at RX async fifo + (CXP[channel].downconn_tx_stb_write)(0); + + info!("trig ack = {}", (CXP[channel].downconn_trigger_ack_read)()); + (CXP[channel].downconn_trigger_ack_write)(1); + info!("after clr trig ack = {}", (CXP[channel].downconn_trigger_ack_read)()); + + info!("decoder error = {}", (CXP[channel].downconn_decoder_error_read)()); + info!("test error = {}", (CXP[channel].downconn_test_error_read)()); + info!("packet type = {:#06X}", (CXP[channel].downconn_packet_type_read)()); + + cxp_proto::receive(channel).expect("loopback gtx rx error"); + // cxp_proto::downconn_debug_mem_print(channel); + + // DEBUG: print loopback packets + const LEN: usize = 20; + let mut pak_arr: [u32; LEN] = [0; LEN]; + let mut k_arr: [u8; LEN] = [0; LEN]; + let mut i: usize = 0; + while (CXP[channel].downconn_debug_out_dout_valid_read)() == 1 { + pak_arr[i] = (CXP[channel].downconn_debug_out_dout_pak_read)(); + k_arr[i] = (CXP[channel].downconn_debug_out_kout_pak_read)(); + // println!("received {:#04X}", pak_arr[i]); + (CXP[channel].downconn_debug_out_inc_write)(1); + i += 1; + if i == LEN { + break; + } + } + cxp_proto::print_packetu32(&pak_arr, &k_arr); + } +} + +pub fn setup(timer: &mut GlobalTimer) { + // TODO: do a for loop for channel? + let channel: usize = 0; + unsafe { + info!("turning on pmc loopback mode..."); + (CXP[channel].downconn_loopback_mode_write)(0b010); // Near-End PMA Loopback + + // QPLL setup + csr::cxp_phys::downconn_qpll_reset_write(1); + info!("waiting for QPLL/CPLL to lock..."); + while csr::cxp_phys::downconn_qpll_locked_read() != 1 {} + info!("QPLL locked"); + + // tx/rx setup + (CXP[channel].downconn_tx_start_init_write)(1); + (CXP[channel].downconn_rx_start_init_write)(1); + + info!("waiting for tx & rx setup..."); + timer.delay_us(50_000); + info!( + "tx_phaligndone = {} | rx_phaligndone = {}", + (CXP[channel].downconn_txinit_phaligndone_read)(), + (CXP[channel].downconn_rxinit_phaligndone_read)(), + ); + } + + cxp_gtx::change_linerate(channel, timer, CXP_SPEED::CXP_1); +} + +pub mod cxp_gtx { + use super::*; + + struct CdrConfig { + pub cfg_reg0: u16, // addr = 0xA8 + pub cfg_reg1: u16, // addr = 0xA9 + pub cfg_reg2: u16, // addr = 0xAA + pub cfg_reg3: u16, // addr = 0xAB + pub cfg_reg4: u16, // addr = 0xAC + } + + pub fn change_linerate(channel: usize, timer: &mut GlobalTimer, speed: CXP_SPEED) { + info!("Changing datarate to {:?}", speed); + // DEBUG: DRP pll for TXUSRCLK = freq(linerate)/20 + let settings = txusrclk::get_txusrclk_config(speed); + txusrclk::setup(channel, timer, settings); + + change_qpll_fb_divider(speed); + change_gtx_divider(channel, speed); + change_cdr_cfg(channel, speed); + + unsafe { + csr::cxp_phys::downconn_qpll_reset_write(1); + info!("waiting for QPLL/CPLL to lock..."); + while csr::cxp_phys::downconn_qpll_locked_read() != 1 {} + info!("QPLL locked"); + } + + unsafe { + (CXP[channel].downconn_tx_restart_write)(1); + (CXP[channel].downconn_rx_restart_write)(1); + } + } + + fn change_qpll_fb_divider(speed: CXP_SPEED) { + let qpll_div_reg = match speed { + CXP_SPEED::CXP_1 | CXP_SPEED::CXP_2 | CXP_SPEED::CXP_5 | CXP_SPEED::CXP_10 => 0x0120, // FB_Divider = 80 + CXP_SPEED::CXP_3 | CXP_SPEED::CXP_6 | CXP_SPEED::CXP_12 => 0x0170, // FB_Divider = 100 + }; + + println!("0x36 = {:#06x}", qpll_read(0x36)); + qpll_write(0x36, qpll_div_reg); + println!("0x36 = {:#06x}", qpll_read(0x36)); + } + + fn change_gtx_divider(channel: usize, speed: CXP_SPEED) { + let div_reg = match speed { + CXP_SPEED::CXP_1 => 0x33, // RXOUT_DIV = 8 + CXP_SPEED::CXP_2 | CXP_SPEED::CXP_3 => 0x22, // RXOUT_DIV = 4 + CXP_SPEED::CXP_5 | CXP_SPEED::CXP_6 => 0x11, // RXOUT_DIV = 2 + CXP_SPEED::CXP_10 | CXP_SPEED::CXP_12 => 0x00, // RXOUT_DIV = 1 + }; + + println!("0x88 = {:#06x}", gtx_read(channel, 0x88)); + gtx_write(channel, 0x88, div_reg); + println!("0x88 = {:#06x}", gtx_read(channel, 0x88)); + } + + fn change_cdr_cfg(channel: usize, speed: CXP_SPEED) { + let cdr_cfg = match speed { + // when RXOUT_DIV = 8 + CXP_SPEED::CXP_1 => { + CdrConfig { + cfg_reg0: 0x0020, //0x0A8 + cfg_reg1: 0x1008, //0x0A9 + cfg_reg2: 0x23FF, //0x0AA + cfg_reg3: 0x0000, //0x0AB + cfg_reg4: 0x0003, //0x0AC + } + } + // when RXOUT_DIV = 4 + CXP_SPEED::CXP_2 | CXP_SPEED::CXP_5 => { + CdrConfig { + cfg_reg0: 0x0020, //0x0A8 + cfg_reg1: 0x1010, //0x0A9 + cfg_reg2: 0x23FF, //0x0AA + cfg_reg3: 0x0000, //0x0AB + cfg_reg4: 0x0003, //0x0AC + } + } + // when RXOUT_DIV= 2 + CXP_SPEED::CXP_3 | CXP_SPEED::CXP_6 => { + CdrConfig { + cfg_reg0: 0x0020, //0x0A8 + cfg_reg1: 0x1020, //0x0A9 + cfg_reg2: 0x23FF, //0x0AA + cfg_reg3: 0x0000, //0x0AB + cfg_reg4: 0x0003, //0x0AC + } + } + // when RXOUT_DIV= 1 + CXP_SPEED::CXP_10 | CXP_SPEED::CXP_12 => { + CdrConfig { + cfg_reg0: 0x0020, //0x0A8 + cfg_reg1: 0x1040, //0x0A9 + cfg_reg2: 0x23FF, //0x0AA + cfg_reg3: 0x0000, //0x0AB + cfg_reg4: 0x000B, //0x0AC + } + } + }; + + gtx_write(channel, 0x0A8, cdr_cfg.cfg_reg0); + gtx_write(channel, 0x0A9, cdr_cfg.cfg_reg1); + gtx_write(channel, 0x0AA, cdr_cfg.cfg_reg2); + gtx_write(channel, 0x0AB, cdr_cfg.cfg_reg3); + gtx_write(channel, 0x0AC, cdr_cfg.cfg_reg4); + } + + #[allow(dead_code)] + fn gtx_read(channel: usize, address: u16) -> u16 { + unsafe { + (CXP[channel].downconn_gtx_daddr_write)(address); + (CXP[channel].downconn_gtx_dread_write)(1); + while (CXP[channel].downconn_gtx_dready_read)() != 1 {} + (CXP[channel].downconn_gtx_dout_read)() + } + } + + fn gtx_write(channel: usize, address: u16, value: u16) { + unsafe { + (CXP[channel].downconn_gtx_daddr_write)(address); + (CXP[channel].downconn_gtx_din_write)(value); + (CXP[channel].downconn_gtx_din_stb_write)(1); + while (CXP[channel].downconn_gtx_dready_read)() != 1 {} + } + } + + #[allow(dead_code)] + fn qpll_read(address: u8) -> u16 { + unsafe { + csr::cxp_phys::downconn_qpll_daddr_write(address); + csr::cxp_phys::downconn_qpll_dread_write(1); + while csr::cxp_phys::downconn_qpll_dready_read() != 1 {} + csr::cxp_phys::downconn_qpll_dout_read() + } + } + + fn qpll_write(address: u8, value: u16) { + unsafe { + csr::cxp_phys::downconn_qpll_daddr_write(address); + csr::cxp_phys::downconn_qpll_din_write(value); + csr::cxp_phys::downconn_qpll_din_stb_write(1); + while csr::cxp_phys::downconn_qpll_dready_read() != 1 {} + } + } +} + +pub mod txusrclk { + use super::*; + + pub struct PLLSetting { + pub clkout0_reg1: u16, //0x08 + pub clkout0_reg2: u16, //0x09 + pub clkfbout_reg1: u16, //0x14 + pub clkfbout_reg2: u16, //0x15 + pub div_reg: u16, //0x16 + pub lock_reg1: u16, //0x18 + pub lock_reg2: u16, //0x19 + pub lock_reg3: u16, //0x1A + pub power_reg: u16, //0x28 + pub filt_reg1: u16, //0x4E + pub filt_reg2: u16, //0x4F + } + + fn one_clock_cycle(channel: usize) { + unsafe { + (CXP[channel].downconn_pll_dclk_write)(1); + (CXP[channel].downconn_pll_dclk_write)(0); + } + } + + fn set_addr(channel: usize, address: u8) { + unsafe { + (CXP[channel].downconn_pll_daddr_write)(address); + } + } + + fn set_data(channel: usize, value: u16) { + unsafe { + (CXP[channel].downconn_pll_din_write)(value); + } + } + + fn set_enable(channel: usize, en: bool) { + unsafe { + let val = if en { 1 } else { 0 }; + (CXP[channel].downconn_pll_den_write)(val); + } + } + + fn set_write_enable(channel: usize, en: bool) { + unsafe { + let val = if en { 1 } else { 0 }; + (CXP[channel].downconn_pll_dwen_write)(val); + } + } + + fn get_data(channel: usize) -> u16 { + unsafe { (CXP[channel].downconn_pll_dout_read)() } + } + + fn drp_ready(channel: usize) -> bool { + unsafe { (CXP[channel].downconn_pll_dready_read)() == 1 } + } + + #[allow(dead_code)] + fn read(channel: usize, address: u8) -> u16 { + set_addr(channel, address); + set_enable(channel, true); + // Set DADDR on the mmcm and assert DEN for one clock cycle + one_clock_cycle(channel); + + set_enable(channel, false); + while !drp_ready(channel) { + // keep the clock signal until data is ready + one_clock_cycle(channel); + } + get_data(channel) + } + + fn write(channel: usize, address: u8, value: u16) { + set_addr(channel, address); + set_data(channel, value); + set_write_enable(channel, true); + set_enable(channel, true); + // Set DADDR, DI on the mmcm and assert DWE, DEN for one clock cycle + one_clock_cycle(channel); + + set_write_enable(channel, false); + set_enable(channel, false); + while !drp_ready(channel) { + // keep the clock signal until write is finished + one_clock_cycle(channel); + } + } + + fn reset(channel: usize, rst: bool) { + unsafe { + let val = if rst { 1 } else { 0 }; + (CXP[channel].downconn_txpll_reset_write)(val) + } + } + + pub fn setup(channel: usize, timer: &mut GlobalTimer, settings: PLLSetting) { + if false { + info!("0x08 = {:#06x}", read(channel, 0x08)); + info!("0x09 = {:#06x}", read(channel, 0x09)); + info!("0x14 = {:#06x}", read(channel, 0x14)); + info!("0x15 = {:#06x}", read(channel, 0x15)); + info!("0x16 = {:#06x}", read(channel, 0x16)); + info!("0x18 = {:#06x}", read(channel, 0x18)); + info!("0x19 = {:#06x}", read(channel, 0x19)); + info!("0x1A = {:#06x}", read(channel, 0x1A)); + info!("0x28 = {:#06x}", read(channel, 0x28)); + info!("0x4E = {:#06x}", read(channel, 0x4E)); + info!("0x4F = {:#06x}", read(channel, 0x4F)); + } else { + // Based on "DRP State Machine" from XAPP888 + // hold reset HIGH during pll config + reset(channel, true); + write(channel, 0x08, settings.clkout0_reg1); + write(channel, 0x09, settings.clkout0_reg2); + write(channel, 0x14, settings.clkfbout_reg1); + write(channel, 0x15, settings.clkfbout_reg2); + write(channel, 0x16, settings.div_reg); + write(channel, 0x18, settings.lock_reg1); + write(channel, 0x19, settings.lock_reg2); + write(channel, 0x1A, settings.lock_reg3); + write(channel, 0x28, settings.power_reg); + write(channel, 0x4E, settings.filt_reg1); + write(channel, 0x4F, settings.filt_reg2); + reset(channel, false); + + // wait for the pll to lock + timer.delay_us(100); + + let locked = unsafe { (CXP[channel].downconn_txpll_locked_read)() == 1 }; + info!("txusrclk locked = {}", locked); + } + } + + pub fn get_txusrclk_config(speed: CXP_SPEED) -> PLLSetting { + match speed { + CXP_SPEED::CXP_1 => { + // CLKFBOUT_MULT = 8, DIVCLK_DIVIDE = 1 , CLKOUT0_DIVIDE = 32 + // TXUSRCLK=62.5MHz + PLLSetting { + clkout0_reg1: 0x1410, //0x08 + clkout0_reg2: 0x0000, //0x09 + clkfbout_reg1: 0x1104, //0x14 + clkfbout_reg2: 0x0000, //0x15 + div_reg: 0x1041, //0x16 + lock_reg1: 0x03e8, //0x18 + lock_reg2: 0x5801, //0x19 + lock_reg3: 0xdbe9, //0x1A + power_reg: 0x0000, //0x28 + filt_reg1: 0x9808, //0x4E + filt_reg2: 0x9100, //0x4F + } + } + CXP_SPEED::CXP_2 => { + // CLKFBOUT_MULT = 8, DIVCLK_DIVIDE = 1 , CLKOUT0_DIVIDE = 16 + // TXUSRCLK=62.5MHz + PLLSetting { + clkout0_reg1: 0x1208, //0x08 + clkout0_reg2: 0x0000, //0x09 + clkfbout_reg1: 0x1104, //0x14 + clkfbout_reg2: 0x0000, //0x15 + div_reg: 0x1041, //0x16 + lock_reg1: 0x03e8, //0x18 + lock_reg2: 0x5801, //0x19 + lock_reg3: 0xdbe9, //0x1A + power_reg: 0x0000, //0x28 + filt_reg1: 0x9808, //0x4E + filt_reg2: 0x9100, //0x4F + } + } + CXP_SPEED::CXP_3 => { + // CLKFBOUT_MULT = 10, DIVCLK_DIVIDE = 1 , CLKOUT0_DIVIDE = 16 + // TXUSRCLK=78.125MHz + PLLSetting { + clkout0_reg1: 0x1208, //0x08 + clkout0_reg2: 0x0000, //0x09 + clkfbout_reg1: 0x1145, //0x14 + clkfbout_reg2: 0x0000, //0x15 + div_reg: 0x1041, //0x16 + lock_reg1: 0x03e8, //0x18 + lock_reg2: 0x7001, //0x19 + lock_reg3: 0xf3e9, //0x1A + power_reg: 0x0000, //0x28 + filt_reg1: 0x9908, //0x4E + filt_reg2: 0x1900, //0x4F + } + } + CXP_SPEED::CXP_5 => { + // CLKFBOUT_MULT = 8, DIVCLK_DIVIDE = 1 , CLKOUT0_DIVIDE = 8 + // TXUSRCLK=125MHz + PLLSetting { + clkout0_reg1: 0x1104, //0x08 + clkout0_reg2: 0x0000, //0x09 + clkfbout_reg1: 0x1104, //0x14 + clkfbout_reg2: 0x0000, //0x15 + div_reg: 0x1041, //0x16 + lock_reg1: 0x03e8, //0x18 + lock_reg2: 0x5801, //0x19 + lock_reg3: 0xdbe9, //0x1A + power_reg: 0x0000, //0x28 + filt_reg1: 0x9808, //0x4E + filt_reg2: 0x9100, //0x4F + } + } + CXP_SPEED::CXP_6 => { + // CLKFBOUT_MULT = 10, DIVCLK_DIVIDE = 1 , CLKOUT0_DIVIDE = 8 + // TXUSRCLK=156.25MHz + PLLSetting { + clkout0_reg1: 0x1104, //0x08 + clkout0_reg2: 0x0000, //0x09 + clkfbout_reg1: 0x1145, //0x14 + clkfbout_reg2: 0x0000, //0x15 + div_reg: 0x1041, //0x16 + lock_reg1: 0x03e8, //0x18 + lock_reg2: 0x7001, //0x19 + lock_reg3: 0xf3e9, //0x1A + power_reg: 0x0000, //0x28 + filt_reg1: 0x9908, //0x4E + filt_reg2: 0x1900, //0x4F + } + } + CXP_SPEED::CXP_10 => { + // CLKFBOUT_MULT = 8, DIVCLK_DIVIDE = 1 , CLKOUT0_DIVIDE = 4 + // TXUSRCLK=250MHz + PLLSetting { + clkout0_reg1: 0x1082, //0x08 + clkout0_reg2: 0x0000, //0x09 + clkfbout_reg1: 0x1104, //0x14 + clkfbout_reg2: 0x0000, //0x15 + div_reg: 0x1041, //0x16 + lock_reg1: 0x03e8, //0x18 + lock_reg2: 0x5801, //0x19 + lock_reg3: 0xdbe9, //0x1A + power_reg: 0x0000, //0x28 + filt_reg1: 0x9808, //0x4E + filt_reg2: 0x9100, //0x4F + } + } + CXP_SPEED::CXP_12 => { + // CLKFBOUT_MULT = 10, DIVCLK_DIVIDE = 1 , CLKOUT0_DIVIDE = 4 + // TXUSRCLK=312.5MHz + PLLSetting { + clkout0_reg1: 0x1082, //0x08 + clkout0_reg2: 0x0000, //0x09 + clkfbout_reg1: 0x1145, //0x14 + clkfbout_reg2: 0x0000, //0x15 + div_reg: 0x1041, //0x16 + lock_reg1: 0x03e8, //0x18 + lock_reg2: 0x7001, //0x19 + lock_reg3: 0xf3e9, //0x1A + power_reg: 0x0000, //0x28 + filt_reg1: 0x9908, //0x4E + filt_reg2: 0x1900, //0x4F + } + } + } + } +} diff --git a/src/libboard_artiq/src/lib.rs b/src/libboard_artiq/src/lib.rs index 81cac5d..3868efd 100644 --- a/src/libboard_artiq/src/lib.rs +++ b/src/libboard_artiq/src/lib.rs @@ -42,6 +42,8 @@ pub mod si5324; pub mod si549; use core::{cmp, str}; +#[cfg(has_cxp_phys)] +pub mod cxp_downconn; #[cfg(has_cxp_phys)] pub mod cxp_upconn;