From 14fa038118884f807c4680c128a12b0148ca7fd5 Mon Sep 17 00:00:00 2001 From: morgan Date: Mon, 11 Mar 2024 14:46:38 +0800 Subject: [PATCH] Firmware: Runtime WRPLL runtime: drive CLK_SEL to true when si549 is used runtime & libboard_artiq: allow standalone to use io_expander si549: add bit bang mmcm dynamic configuration si549: add frequency counter for refclk rtio_clocking & si549: add 125Mhz wrpll refclk setup --- src/libboard_artiq/src/lib.rs | 2 +- src/libboard_artiq/src/si549.rs | 141 ++++++++++++++++++++++++++ src/runtime/src/main.rs | 14 ++- src/runtime/src/rtio_clocking.rs | 166 +++++++++++++++++++++++++++++++ 4 files changed, 318 insertions(+), 5 deletions(-) diff --git a/src/libboard_artiq/src/lib.rs b/src/libboard_artiq/src/lib.rs index e9b9460..70c4cb6 100644 --- a/src/libboard_artiq/src/lib.rs +++ b/src/libboard_artiq/src/lib.rs @@ -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)] diff --git a/src/libboard_artiq/src/si549.rs b/src/libboard_artiq/src/si549.rs index f92f547..05f269f 100644 --- a/src/libboard_artiq/src/si549.rs +++ b/src/libboard_artiq/src/si549.rs @@ -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); + + 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(()) + } +} diff --git a/src/runtime/src/main.rs b/src/runtime/src/main.rs index e8b71a8..dd24e25 100644 --- a/src/runtime/src/main.rs +++ b/src/runtime/src/main.rs @@ -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, @@ -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), diff --git a/src/runtime/src/rtio_clocking.rs b/src/runtime/src/rtio_clocking.rs index e6cf632..fc906c1 100644 --- a/src/runtime/src/rtio_clocking.rs +++ b/src/runtime/src/rtio_clocking.rs @@ -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); + } + _ => {} + } + } }