Kasli-soc: add WRPLL clock recovery #282
|
@ -22,7 +22,7 @@ pub mod drtioaux;
|
|||
pub mod drtioaux_async;
|
||||
pub mod drtioaux_proto;
|
||||
pub mod fiq;
|
||||
#[cfg(all(feature = "target_kasli_soc", has_drtio))]
|
||||
#[cfg(feature = "target_kasli_soc")]
|
||||
pub mod io_expander;
|
||||
pub mod logger;
|
||||
#[cfg(has_drtio)]
|
||||
|
|
|
@ -673,3 +673,144 @@ pub mod wrpll {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(has_wrpll_refclk)]
|
||||
pub mod wrpll_refclk {
|
||||
use super::*;
|
||||
|
||||
pub struct MmcmSetting {
|
||||
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() {
|
||||
unsafe {
|
||||
csr::wrpll_refclk::mmcm_dclk_write(1);
|
||||
csr::wrpll_refclk::mmcm_dclk_write(0);
|
||||
}
|
||||
}
|
||||
|
||||
fn set_addr(address: u8) {
|
||||
unsafe {
|
||||
csr::wrpll_refclk::mmcm_daddr_write(address);
|
||||
}
|
||||
}
|
||||
|
||||
fn set_data(value: u16) {
|
||||
unsafe {
|
||||
csr::wrpll_refclk::mmcm_din_write(value);
|
||||
}
|
||||
}
|
||||
|
||||
fn set_enable(en: bool) {
|
||||
unsafe {
|
||||
let val = if en { 1 } else { 0 };
|
||||
csr::wrpll_refclk::mmcm_den_write(val);
|
||||
}
|
||||
}
|
||||
|
||||
fn set_write_enable(en: bool) {
|
||||
unsafe {
|
||||
let val = if en { 1 } else { 0 };
|
||||
csr::wrpll_refclk::mmcm_dwen_write(val);
|
||||
}
|
||||
}
|
||||
|
||||
fn get_data() -> u16 {
|
||||
unsafe { csr::wrpll_refclk::mmcm_dout_read() }
|
||||
}
|
||||
|
||||
fn drp_ready() -> bool {
|
||||
unsafe { csr::wrpll_refclk::mmcm_dready_read() == 1 }
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
fn read(address: u8) -> u16 {
|
||||
set_addr(address);
|
||||
set_enable(true);
|
||||
// Set DADDR on the mmcm and assert DEN for one clock cycle
|
||||
one_clock_cycle();
|
||||
|
||||
set_enable(false);
|
||||
while !drp_ready() {
|
||||
// keep the clock signal until data is ready
|
||||
one_clock_cycle();
|
||||
}
|
||||
get_data()
|
||||
}
|
||||
|
||||
fn write(address: u8, value: u16) {
|
||||
set_addr(address);
|
||||
set_data(value);
|
||||
set_write_enable(true);
|
||||
set_enable(true);
|
||||
// Set DADDR, DI on the mmcm and assert DWE, DEN for one clock cycle
|
||||
one_clock_cycle();
|
||||
|
||||
set_write_enable(false);
|
||||
set_enable(false);
|
||||
while !drp_ready() {
|
||||
// keep the clock signal until write is finished
|
||||
one_clock_cycle();
|
||||
}
|
||||
}
|
||||
|
||||
fn reset(rst: bool) {
|
||||
unsafe {
|
||||
let val = if rst { 1 } else { 0 };
|
||||
csr::wrpll_refclk::mmcm_reset_write(val)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn setup(timer: &mut GlobalTimer, settings: MmcmSetting, mmcm_bypass: bool) -> Result<(), &'static str> {
|
||||
unsafe {
|
||||
csr::wrpll_refclk::refclk_reset_write(1);
|
||||
}
|
||||
|
||||
if mmcm_bypass {
|
||||
info!("Bypassing mmcm");
|
||||
unsafe {
|
||||
csr::wrpll_refclk::mmcm_bypass_write(1);
|
||||
}
|
||||
} else {
|
||||
// Based on "DRP State Machine" from XAPP888
|
||||
// hold reset HIGH during mmcm config
|
||||
reset(true);
|
||||
write(0x08, settings.clkout0_reg1);
|
||||
write(0x09, settings.clkout0_reg2);
|
||||
write(0x14, settings.clkfbout_reg1);
|
||||
write(0x15, settings.clkfbout_reg2);
|
||||
write(0x16, settings.div_reg);
|
||||
write(0x18, settings.lock_reg1);
|
||||
write(0x19, settings.lock_reg2);
|
||||
write(0x1A, settings.lock_reg3);
|
||||
write(0x28, settings.power_reg);
|
||||
write(0x4E, settings.filt_reg1);
|
||||
write(0x4F, settings.filt_reg2);
|
||||
reset(false);
|
||||
|
||||
// wait for the mmcm to lock
|
||||
timer.delay_us(100);
|
||||
sb10q
commented
MHz MHz
|
||||
|
||||
let locked = unsafe { csr::wrpll_refclk::mmcm_locked_read() == 1 };
|
||||
if !locked {
|
||||
return Err("mmcm failed to generate 125MHz ref clock from SMA CLKIN");
|
||||
}
|
||||
}
|
||||
|
||||
unsafe {
|
||||
csr::wrpll_refclk::refclk_reset_write(0);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
|
|
@ -8,14 +8,14 @@
|
|||
#[macro_use]
|
||||
extern crate alloc;
|
||||
|
||||
#[cfg(all(feature = "target_kasli_soc", has_drtio))]
|
||||
#[cfg(all(feature = "target_kasli_soc", has_virtual_leds))]
|
||||
use core::cell::RefCell;
|
||||
|
||||
use ksupport;
|
||||
use libasync::task;
|
||||
#[cfg(has_drtio_eem)]
|
||||
use libboard_artiq::drtio_eem;
|
||||
#[cfg(all(feature = "target_kasli_soc", has_drtio))]
|
||||
#[cfg(feature = "target_kasli_soc")]
|
||||
use libboard_artiq::io_expander;
|
||||
use libboard_artiq::{identifier_read, logger, pl};
|
||||
use libboard_zynq::{gic, mpcore, timer::GlobalTimer};
|
||||
|
@ -43,7 +43,7 @@ extern "C" {
|
|||
static __exceptions_start: u32;
|
||||
}
|
||||
|
||||
#[cfg(all(feature = "target_kasli_soc", has_drtio))]
|
||||
#[cfg(all(feature = "target_kasli_soc", has_virtual_leds))]
|
||||
async fn io_expanders_service(
|
||||
i2c_bus: RefCell<&mut libboard_zynq::i2c::I2c>,
|
||||
io_expander0: RefCell<io_expander::IoExpander>,
|
||||
|
@ -101,7 +101,7 @@ pub fn main_core0() {
|
|||
info!("gateware ident: {}", identifier_read(&mut [0; 64]));
|
||||
|
||||
ksupport::i2c::init();
|
||||
#[cfg(all(feature = "target_kasli_soc", has_drtio))]
|
||||
#[cfg(feature = "target_kasli_soc")]
|
||||
{
|
||||
let i2c_bus = unsafe { (ksupport::i2c::I2C_BUS).as_mut().unwrap() };
|
||||
let mut io_expander0 = io_expander::IoExpander::new(i2c_bus, 0).unwrap();
|
||||
|
@ -112,6 +112,11 @@ pub fn main_core0() {
|
|||
io_expander1
|
||||
.init(i2c_bus)
|
||||
.expect("I2C I/O expander #1 initialization failed");
|
||||
|
||||
// Drive CLK_SEL to true
|
||||
#[cfg(has_si549)]
|
||||
io_expander0.set(1, 7, true);
|
||||
|
||||
// Drive TX_DISABLE to false on SFP0..3
|
||||
io_expander0.set(0, 1, false);
|
||||
io_expander1.set(0, 1, false);
|
||||
|
@ -119,6 +124,7 @@ pub fn main_core0() {
|
|||
io_expander1.set(1, 1, false);
|
||||
io_expander0.service(i2c_bus).unwrap();
|
||||
io_expander1.service(i2c_bus).unwrap();
|
||||
#[cfg(has_virtual_leds)]
|
||||
task::spawn(io_expanders_service(
|
||||
RefCell::new(i2c_bus),
|
||||
RefCell::new(io_expander0),
|
||||
|
|
|
@ -4,6 +4,8 @@ use ksupport::i2c;
|
|||
use libboard_artiq::pl;
|
||||
#[cfg(has_si5324)]
|
||||
use libboard_artiq::si5324;
|
||||
#[cfg(has_si549)]
|
||||
use libboard_artiq::si549;
|
||||
#[cfg(has_si5324)]
|
||||
use libboard_zynq::i2c::I2c;
|
||||
use libboard_zynq::timer::GlobalTimer;
|
||||
|
@ -260,6 +262,150 @@ fn setup_si5324(i2c: &mut I2c, timer: &mut GlobalTimer, clk: RtioClock) {
|
|||
si5324::setup(i2c, &si5324_settings, si5324_ref_input, timer).expect("cannot initialize Si5324");
|
||||
}
|
||||
|
||||
#[cfg(all(has_si549, has_wrpll))]
|
||||
fn wrpll_setup(timer: &mut GlobalTimer, clk: RtioClock, si549_settings: &si549::FrequencySetting) {
|
||||
// register values are directly copied from preconfigured mmcm
|
||||
let (mmcm_setting, mmcm_bypass) = match clk {
|
||||
RtioClock::Ext0_Synth0_10to125 => (
|
||||
si549::wrpll_refclk::MmcmSetting {
|
||||
// CLKFBOUT_MULT = 62.5, DIVCLK_DIVIDE = 1 , CLKOUT0_DIVIDE = 5
|
||||
clkout0_reg1: 0x1083,
|
||||
clkout0_reg2: 0x0080,
|
||||
clkfbout_reg1: 0x179e,
|
||||
clkfbout_reg2: 0x4c00,
|
||||
div_reg: 0x1041,
|
||||
lock_reg1: 0x00fa,
|
||||
lock_reg2: 0x7c01,
|
||||
lock_reg3: 0xffe9,
|
||||
power_reg: 0x9900,
|
||||
filt_reg1: 0x0808,
|
||||
filt_reg2: 0x0800,
|
||||
},
|
||||
false,
|
||||
),
|
||||
RtioClock::Ext0_Synth0_80to125 => (
|
||||
si549::wrpll_refclk::MmcmSetting {
|
||||
// CLKFBOUT_MULT = 15.625, DIVCLK_DIVIDE = 1 , CLKOUT0_DIVIDE = 10
|
||||
clkout0_reg1: 0x1145,
|
||||
clkout0_reg2: 0x0000,
|
||||
clkfbout_reg1: 0x11c7,
|
||||
clkfbout_reg2: 0x5880,
|
||||
div_reg: 0x1041,
|
||||
lock_reg1: 0x028a,
|
||||
lock_reg2: 0x7c01,
|
||||
lock_reg3: 0xffe9,
|
||||
power_reg: 0x9900,
|
||||
filt_reg1: 0x0808,
|
||||
filt_reg2: 0x9800,
|
||||
},
|
||||
false,
|
||||
),
|
||||
RtioClock::Ext0_Synth0_100to125 => (
|
||||
si549::wrpll_refclk::MmcmSetting {
|
||||
// CLKFBOUT_MULT = 12.5, DIVCLK_DIVIDE = 1 , CLKOUT0_DIVIDE = 10
|
||||
clkout0_reg1: 0x1145,
|
||||
clkout0_reg2: 0x0000,
|
||||
clkfbout_reg1: 0x1145,
|
||||
clkfbout_reg2: 0x4c00,
|
||||
div_reg: 0x1041,
|
||||
lock_reg1: 0x0339,
|
||||
lock_reg2: 0x7c01,
|
||||
lock_reg3: 0xffe9,
|
||||
power_reg: 0x9900,
|
||||
filt_reg1: 0x0808,
|
||||
filt_reg2: 0x9800,
|
||||
},
|
||||
false,
|
||||
),
|
||||
RtioClock::Ext0_Synth0_125to125 => (
|
||||
si549::wrpll_refclk::MmcmSetting {
|
||||
// CLKFBOUT_MULT = 10, DIVCLK_DIVIDE = 1 , CLKOUT0_DIVIDE = 10
|
||||
clkout0_reg1: 0x1145,
|
||||
clkout0_reg2: 0x0000,
|
||||
clkfbout_reg1: 0x1145,
|
||||
clkfbout_reg2: 0x0000,
|
||||
div_reg: 0x1041,
|
||||
lock_reg1: 0x03e8,
|
||||
lock_reg2: 0x7001,
|
||||
lock_reg3: 0xf3e9,
|
||||
power_reg: 0x0100,
|
||||
filt_reg1: 0x0808,
|
||||
filt_reg2: 0x1100,
|
||||
},
|
||||
true,
|
||||
),
|
||||
_ => unreachable!(),
|
||||
};
|
||||
|
||||
si549::helper_setup(timer, &si549_settings).expect("cannot initialize helper Si549");
|
||||
si549::wrpll_refclk::setup(timer, mmcm_setting, mmcm_bypass).expect("cannot initialize ref clk for wrpll");
|
||||
si549::wrpll::select_recovered_clock(true, timer);
|
||||
}
|
||||
|
||||
#[cfg(has_si549)]
|
||||
fn get_si549_setting(clk: RtioClock) -> si549::FrequencySetting {
|
||||
match clk {
|
||||
RtioClock::Ext0_Synth0_10to125 => {
|
||||
info!("using 10MHz reference to make 125MHz RTIO clock with WRPLL");
|
||||
}
|
||||
RtioClock::Ext0_Synth0_80to125 => {
|
||||
info!("using 80MHz reference to make 125MHz RTIO clock with WRPLL");
|
||||
}
|
||||
RtioClock::Ext0_Synth0_100to125 => {
|
||||
info!("using 100MHz reference to make 125MHz RTIO clock with WRPLL");
|
||||
}
|
||||
RtioClock::Ext0_Synth0_125to125 => {
|
||||
info!("using 125MHz reference to make 125MHz RTIO clock with WRPLL");
|
||||
}
|
||||
RtioClock::Int_100 => {
|
||||
info!("using internal 100MHz RTIO clock");
|
||||
}
|
||||
RtioClock::Int_125 => {
|
||||
info!("using internal 125MHz RTIO clock");
|
||||
}
|
||||
_ => {
|
||||
warn!(
|
||||
"rtio_clock setting '{:?}' is unsupported. Falling back to default internal 125MHz RTIO clock.",
|
||||
clk
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
match clk {
|
||||
RtioClock::Int_100 => {
|
||||
si549::FrequencySetting {
|
||||
main: si549::DividerConfig {
|
||||
hsdiv: 0x06C,
|
||||
lsdiv: 0,
|
||||
fbdiv: 0x046C5F49797,
|
||||
},
|
||||
helper: si549::DividerConfig {
|
||||
// 100MHz*32767/32768
|
||||
hsdiv: 0x06C,
|
||||
lsdiv: 0,
|
||||
fbdiv: 0x046C5670BBD,
|
||||
},
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
// Everything else use 125MHz
|
||||
si549::FrequencySetting {
|
||||
main: si549::DividerConfig {
|
||||
hsdiv: 0x058,
|
||||
lsdiv: 0,
|
||||
fbdiv: 0x04815791F25,
|
||||
},
|
||||
helper: si549::DividerConfig {
|
||||
// 125MHz*32767/32768
|
||||
hsdiv: 0x058,
|
||||
lsdiv: 0,
|
||||
fbdiv: 0x04814E8F442,
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn init(timer: &mut GlobalTimer, cfg: &Config) {
|
||||
let clk = get_rtio_clock_cfg(cfg);
|
||||
#[cfg(has_si5324)]
|
||||
|
@ -274,9 +420,29 @@ pub fn init(timer: &mut GlobalTimer, cfg: &Config) {
|
|||
}
|
||||
}
|
||||
|
||||
#[cfg(has_si549)]
|
||||
let si549_settings = get_si549_setting(clk);
|
||||
|
||||
#[cfg(has_si549)]
|
||||
si549::main_setup(timer, &si549_settings).expect("cannot initialize main Si549");
|
||||
|
||||
#[cfg(has_drtio)]
|
||||
init_drtio(timer);
|
||||
|
||||
#[cfg(not(has_drtio))]
|
||||
init_rtio(timer);
|
||||
|
||||
#[cfg(all(has_si549, has_wrpll))]
|
||||
{
|
||||
// SYS CLK switch will reset CSRs that are used by WRPLL
|
||||
match clk {
|
||||
RtioClock::Ext0_Synth0_10to125
|
||||
| RtioClock::Ext0_Synth0_80to125
|
||||
| RtioClock::Ext0_Synth0_100to125
|
||||
| RtioClock::Ext0_Synth0_125to125 => {
|
||||
wrpll_setup(timer, clk, &si549_settings);
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue
Maximum DCLK frequency is 200MHz as per https://www.mouser.com/datasheet/2/903/ds191_XC7Z030_XC7Z045_data_sheet-1923769.pdf
You can remove the timer and delays.