1
0
Fork 0

cxp_phys: low speed serial & GTX setup

downconn: add QPLL & GTX setup
downconn: add DRP to change linerate up to 12.5Gbps
downconn testing: add txuserclk config
upconn: add low speed serital setup
upconn & downconn: add linerate changer
This commit is contained in:
morgan 2024-10-15 10:31:35 +08:00
parent ab9fb82a8d
commit 9a4aa5a717
1 changed files with 491 additions and 0 deletions

View File

@ -0,0 +1,491 @@
use embedded_hal::prelude::_embedded_hal_blocking_delay_DelayUs;
use libboard_zynq::{println, timer::GlobalTimer};
use log::info;
use crate::pl::{csr, csr::CXP};
const CHANNEL_LEN: usize = csr::CXP_LEN;
#[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 setup(timer: &mut GlobalTimer) {
down_conn::setup(timer);
up_conn::setup();
change_linerate(CXP_SPEED::CXP_1);
}
pub fn change_linerate(speed: CXP_SPEED) {
info!("Changing all channels datarate to {:?}", speed);
down_conn::change_linerate(speed);
up_conn::change_linerate(speed);
}
mod up_conn {
use super::*;
pub fn setup() {
unsafe {
csr::cxp_phys::upconn_tx_enable_write(1);
}
}
pub fn change_linerate(speed: CXP_SPEED) {
unsafe {
match speed {
CXP_SPEED::CXP_1 | CXP_SPEED::CXP_2 | CXP_SPEED::CXP_3 | CXP_SPEED::CXP_5 | CXP_SPEED::CXP_6 => {
csr::cxp_phys::upconn_bitrate2x_enable_write(0);
}
CXP_SPEED::CXP_10 | CXP_SPEED::CXP_12 => {
csr::cxp_phys::upconn_bitrate2x_enable_write(1);
}
};
csr::cxp_phys::upconn_clk_reset_write(1);
}
}
}
mod down_conn {
use super::*;
pub fn setup(timer: &mut GlobalTimer) {
unsafe {
info!("turning on pmc loopback mode...");
for channel in 0..CHANNEL_LEN {
(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");
for channel in 0..CHANNEL_LEN {
// tx/rx setup
(CXP[channel].downconn_tx_start_init_write)(1);
(CXP[channel].downconn_rx_start_init_write)(1);
}
// DEBUG: printout
info!("waiting for tx & rx setup...");
timer.delay_us(50_000);
for channel in 0..CHANNEL_LEN {
info!(
"tx_phaligndone = {} | rx_phaligndone = {}",
(CXP[channel].downconn_txinit_phaligndone_read)(),
(CXP[channel].downconn_rxinit_phaligndone_read)(),
);
}
}
}
pub fn change_linerate(speed: CXP_SPEED) {
// DEBUG: DRP pll for TXUSRCLK = freq(linerate)/20
let settings = txusrclk::get_txusrclk_config(speed);
txusrclk::setup(settings);
change_qpll_fb_divider(speed);
change_gtx_divider(speed);
change_cdr_cfg(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");
for channel in 0..CHANNEL_LEN {
(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(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
};
for channel in 0..CHANNEL_LEN {
println!("channel {}, 0x88 = {:#06x}", channel, gtx_read(channel, 0x88));
gtx_write(channel, 0x88, div_reg);
println!("channel {}, 0x88 = {:#06x}", channel, gtx_read(channel, 0x88));
}
}
fn change_cdr_cfg(speed: CXP_SPEED) {
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
}
let cdr_cfg = match speed {
// when RXOUT_DIV = 8
CXP_SPEED::CXP_1 => CdrConfig {
cfg_reg0: 0x0020,
cfg_reg1: 0x1008,
cfg_reg2: 0x23FF,
cfg_reg3: 0x0000,
cfg_reg4: 0x0003,
},
// when RXOUT_DIV = 4
CXP_SPEED::CXP_2 | CXP_SPEED::CXP_5 => CdrConfig {
cfg_reg0: 0x0020,
cfg_reg1: 0x1010,
cfg_reg2: 0x23FF,
cfg_reg3: 0x0000,
cfg_reg4: 0x0003,
},
// when RXOUT_DIV= 2
CXP_SPEED::CXP_3 | CXP_SPEED::CXP_6 => CdrConfig {
cfg_reg0: 0x0020,
cfg_reg1: 0x1020,
cfg_reg2: 0x23FF,
cfg_reg3: 0x0000,
cfg_reg4: 0x0003,
},
// when RXOUT_DIV= 1
CXP_SPEED::CXP_10 | CXP_SPEED::CXP_12 => CdrConfig {
cfg_reg0: 0x0020,
cfg_reg1: 0x1040,
cfg_reg2: 0x23FF,
cfg_reg3: 0x0000,
cfg_reg4: 0x000B,
},
};
for channel in 0..CHANNEL_LEN {
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 {}
}
}
// DEBUG: remove this
pub mod txusrclk {
use super::*;
#[derive(Copy, Clone)]
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(settings: PLLSetting) {
for channel in 0..CHANNEL_LEN {
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);
info!("waiting for PLL of txusrclk to lock...");
while unsafe { (CXP[channel].downconn_txpll_locked_read)() == 0 } {}
info!("txusrclk locked :D");
}
}
}
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
}
}
}
}
}
}