1
0
Fork 0
artiq-zynq/src/libboard_artiq/src/si549.rs

779 lines
24 KiB
Rust

use embedded_hal::prelude::_embedded_hal_blocking_delay_DelayUs;
use libboard_zynq::timer::GlobalTimer;
use log::info;
use crate::pl::csr;
#[cfg(feature = "target_kasli_soc")]
const ADDRESS: u8 = 0x67;
#[derive(Clone, Copy)]
pub struct DividerConfig {
pub hsdiv: u16,
pub lsdiv: u8,
pub fbdiv: u64,
}
#[derive(Clone, Copy)]
pub struct FrequencySetting {
pub main: DividerConfig,
#[cfg(has_wrpll)]
pub helper: DividerConfig,
}
mod i2c {
use super::*;
#[derive(Clone, Copy)]
pub enum DCXO {
Main,
#[cfg(has_wrpll)]
Helper,
}
fn half_period(timer: &mut GlobalTimer) {
timer.delay_us(1)
}
fn sda_i(dcxo: DCXO) -> bool {
match dcxo {
DCXO::Main => unsafe { csr::main_dcxo::sda_in_read() == 1 },
#[cfg(has_wrpll)]
DCXO::Helper => unsafe { csr::helper_dcxo::sda_in_read() == 1 },
}
}
fn sda_oe(dcxo: DCXO, oe: bool) {
let val = if oe { 1 } else { 0 };
match dcxo {
DCXO::Main => unsafe { csr::main_dcxo::sda_oe_write(val) },
#[cfg(has_wrpll)]
DCXO::Helper => unsafe { csr::helper_dcxo::sda_oe_write(val) },
};
}
fn sda_o(dcxo: DCXO, o: bool) {
let val = if o { 1 } else { 0 };
match dcxo {
DCXO::Main => unsafe { csr::main_dcxo::sda_out_write(val) },
#[cfg(has_wrpll)]
DCXO::Helper => unsafe { csr::helper_dcxo::sda_out_write(val) },
};
}
fn scl_oe(dcxo: DCXO, oe: bool) {
let val = if oe { 1 } else { 0 };
match dcxo {
DCXO::Main => unsafe { csr::main_dcxo::scl_oe_write(val) },
#[cfg(has_wrpll)]
DCXO::Helper => unsafe { csr::helper_dcxo::scl_oe_write(val) },
};
}
fn scl_o(dcxo: DCXO, o: bool) {
let val = if o { 1 } else { 0 };
match dcxo {
DCXO::Main => unsafe { csr::main_dcxo::scl_out_write(val) },
#[cfg(has_wrpll)]
DCXO::Helper => unsafe { csr::helper_dcxo::scl_out_write(val) },
};
}
pub fn init(dcxo: DCXO, timer: &mut GlobalTimer) -> Result<(), &'static str> {
// Set SCL as output, and high level
scl_o(dcxo, true);
scl_oe(dcxo, true);
// Prepare a zero level on SDA so that sda_oe pulls it down
sda_o(dcxo, false);
// Release SDA
sda_oe(dcxo, false);
// Check the I2C bus is ready
half_period(timer);
half_period(timer);
if !sda_i(dcxo) {
// Try toggling SCL a few times
for _bit in 0..8 {
scl_o(dcxo, false);
half_period(timer);
scl_o(dcxo, true);
half_period(timer);
}
}
if !sda_i(dcxo) {
return Err("SDA is stuck low and doesn't get unstuck");
}
Ok(())
}
pub fn start(dcxo: DCXO, timer: &mut GlobalTimer) {
// Set SCL high then SDA low
scl_o(dcxo, true);
half_period(timer);
sda_oe(dcxo, true);
half_period(timer);
}
pub fn stop(dcxo: DCXO, timer: &mut GlobalTimer) {
// First, make sure SCL is low, so that the target releases the SDA line
scl_o(dcxo, false);
half_period(timer);
// Set SCL high then SDA high
sda_oe(dcxo, true);
scl_o(dcxo, true);
half_period(timer);
sda_oe(dcxo, false);
half_period(timer);
}
pub fn write(dcxo: DCXO, data: u8, timer: &mut GlobalTimer) -> bool {
// MSB first
for bit in (0..8).rev() {
// Set SCL low and set our bit on SDA
scl_o(dcxo, false);
sda_oe(dcxo, data & (1 << bit) == 0);
half_period(timer);
// Set SCL high ; data is shifted on the rising edge of SCL
scl_o(dcxo, true);
half_period(timer);
}
// Check ack
// Set SCL low, then release SDA so that the I2C target can respond
scl_o(dcxo, false);
half_period(timer);
sda_oe(dcxo, false);
// Set SCL high and check for ack
scl_o(dcxo, true);
half_period(timer);
// returns true if acked (I2C target pulled SDA low)
!sda_i(dcxo)
}
pub fn read(dcxo: DCXO, ack: bool, timer: &mut GlobalTimer) -> u8 {
// Set SCL low first, otherwise setting SDA as input may cause a transition
// on SDA with SCL high which will be interpreted as START/STOP condition.
scl_o(dcxo, false);
half_period(timer); // make sure SCL has settled low
sda_oe(dcxo, false);
let mut data: u8 = 0;
// MSB first
for bit in (0..8).rev() {
scl_o(dcxo, false);
half_period(timer);
// Set SCL high and shift data
scl_o(dcxo, true);
half_period(timer);
if sda_i(dcxo) {
data |= 1 << bit
}
}
// Send ack
// Set SCL low and pull SDA low when acking
scl_o(dcxo, false);
if ack {
sda_oe(dcxo, true)
}
half_period(timer);
// then set SCL high
scl_o(dcxo, true);
half_period(timer);
data
}
}
fn write(dcxo: i2c::DCXO, reg: u8, val: u8, timer: &mut GlobalTimer) -> Result<(), &'static str> {
i2c::start(dcxo, timer);
if !i2c::write(dcxo, ADDRESS << 1, timer) {
return Err("Si549 failed to ack write address");
}
if !i2c::write(dcxo, reg, timer) {
return Err("Si549 failed to ack register");
}
if !i2c::write(dcxo, val, timer) {
return Err("Si549 failed to ack value");
}
i2c::stop(dcxo, timer);
Ok(())
}
fn read(dcxo: i2c::DCXO, reg: u8, timer: &mut GlobalTimer) -> Result<u8, &'static str> {
i2c::start(dcxo, timer);
if !i2c::write(dcxo, ADDRESS << 1, timer) {
return Err("Si549 failed to ack write address");
}
if !i2c::write(dcxo, reg, timer) {
return Err("Si549 failed to ack register");
}
i2c::stop(dcxo, timer);
i2c::start(dcxo, timer);
if !i2c::write(dcxo, (ADDRESS << 1) | 1, timer) {
return Err("Si549 failed to ack read address");
}
let val = i2c::read(dcxo, false, timer);
i2c::stop(dcxo, timer);
Ok(val)
}
fn setup(dcxo: i2c::DCXO, config: DividerConfig, timer: &mut GlobalTimer) -> Result<(), &'static str> {
i2c::init(dcxo, timer)?;
write(dcxo, 255, 0x00, timer)?; // PAGE
write(dcxo, 69, 0x00, timer)?; // Disable FCAL override.
write(dcxo, 17, 0x00, timer)?; // Synchronously disable output
// The Si549 has no ID register, so we check that it responds correctly
// by writing values to a RAM-like register and reading them back.
for test_value in 0..255 {
write(dcxo, 23, test_value, timer)?;
let readback = read(dcxo, 23, timer)?;
if readback != test_value {
return Err("Si549 detection failed");
}
}
write(dcxo, 23, config.hsdiv as u8, timer)?;
write(dcxo, 24, (config.hsdiv >> 8) as u8 | (config.lsdiv << 4), timer)?;
write(dcxo, 26, config.fbdiv as u8, timer)?;
write(dcxo, 27, (config.fbdiv >> 8) as u8, timer)?;
write(dcxo, 28, (config.fbdiv >> 16) as u8, timer)?;
write(dcxo, 29, (config.fbdiv >> 24) as u8, timer)?;
write(dcxo, 30, (config.fbdiv >> 32) as u8, timer)?;
write(dcxo, 31, (config.fbdiv >> 40) as u8, timer)?;
write(dcxo, 7, 0x08, timer)?; // Start FCAL
timer.delay_us(30_000); // Internal FCAL VCO calibration
write(dcxo, 17, 0x01, timer)?; // Synchronously enable output
Ok(())
}
pub fn main_setup(timer: &mut GlobalTimer, settings: FrequencySetting) -> Result<(), &'static str> {
unsafe {
csr::main_dcxo::bitbang_enable_write(1);
csr::main_dcxo::i2c_address_write(ADDRESS);
}
setup(i2c::DCXO::Main, settings.main, timer)?;
// Si549 maximum settling time for large frequency change.
timer.delay_us(40_000);
unsafe {
csr::main_dcxo::bitbang_enable_write(0);
}
info!("Main Si549 started");
Ok(())
}
#[cfg(has_wrpll)]
pub mod wrpll {
use embedded_hal::blocking::delay::DelayMs;
use libcortex_a9::mutex::Mutex;
use super::*;
const TIMER_WIDTH: u32 = 24;
const COUNTER_DIV: u32 = 2;
const ADPLL_MAX: i32 = (950.0 / 0.0001164) as i32;
const KP: i32 = 6;
const KI: i32 = 2;
static BASE_ADPLL: Mutex<i32> = Mutex::new(0);
static H_INTEGRATOR: Mutex<i32> = Mutex::new(0);
static M_INTEGRATOR: Mutex<i32> = Mutex::new(0);
#[derive(Clone, Copy)]
pub enum FIQ {
RefTag,
MainTag,
}
mod tag_collector {
use super::*;
const BEATING_PERIOD: i32 = 0x8000;
const BEATING_HALFPERIOD: i32 = 0x4000;
static REF_TAG: Mutex<u32> = Mutex::new(0);
static REF_TAG_READY: Mutex<bool> = Mutex::new(false);
static MAIN_TAG: Mutex<u32> = Mutex::new(0);
static MAIN_TAG_READY: Mutex<bool> = Mutex::new(false);
pub fn reset() {
clear_phase_diff_ready();
*REF_TAG.lock() = 0;
*MAIN_TAG.lock() = 0;
}
pub fn clear_phase_diff_ready() {
*REF_TAG_READY.lock() = false;
*MAIN_TAG_READY.lock() = false;
}
pub fn collect_tags(interrupt: FIQ) {
match interrupt {
FIQ::RefTag => {
*REF_TAG.lock() = unsafe { csr::wrpll::ref_tag_read() };
*REF_TAG_READY.lock() = true;
}
FIQ::MainTag => {
*MAIN_TAG.lock() = unsafe { csr::wrpll::main_tag_read() };
*MAIN_TAG_READY.lock() = true;
}
}
}
pub fn phase_diff_ready() -> bool {
*REF_TAG_READY.lock() && *MAIN_TAG_READY.lock()
}
pub fn get_period_error() -> i32 {
// n * BEATING_PERIOD - REF_TAG(n) mod BEATING_PERIOD
let mut period_error = (*REF_TAG.lock()).overflowing_neg().0.rem_euclid(BEATING_PERIOD as u32) as i32;
// mapping tags from [0, 2π] -> [-π, π]
if period_error > BEATING_HALFPERIOD {
period_error -= BEATING_PERIOD
}
period_error
}
pub fn get_phase_error() -> i32 {
// MAIN_TAG(n) - REF_TAG(n) mod BEATING_PERIOD
let mut phase_error = (*MAIN_TAG.lock())
.overflowing_sub(*REF_TAG.lock())
.0
.rem_euclid(BEATING_PERIOD as u32) as i32;
// mapping tags from [0, 2π] -> [-π, π]
if phase_error > BEATING_HALFPERIOD {
phase_error -= BEATING_PERIOD
}
phase_error
}
}
pub fn helper_setup(timer: &mut GlobalTimer, settings: FrequencySetting) -> Result<(), &'static str> {
unsafe {
csr::wrpll::helper_reset_write(1);
csr::helper_dcxo::bitbang_enable_write(1);
csr::helper_dcxo::i2c_address_write(ADDRESS);
}
setup(i2c::DCXO::Helper, settings.helper, timer)?;
// Si549 maximum settling time for large frequency change.
timer.delay_us(40_000);
unsafe {
csr::wrpll::helper_reset_write(0);
csr::helper_dcxo::bitbang_enable_write(0);
}
info!("Helper Si549 started");
timer.delay_ms(5_000);
Ok(())
}
fn set_fiq(en: bool) {
let val = if en { 1 } else { 0 };
unsafe {
csr::wrpll::ref_tag_ev_enable_write(val);
csr::wrpll::main_tag_ev_enable_write(val);
}
}
/// set adpll using gateware i2c
/// Note: disable main/helper i2c bitbang before using this function
fn set_adpll(dcxo: i2c::DCXO, adpll: i32) -> Result<(), &'static str> {
if adpll.abs() > ADPLL_MAX {
return Err("adpll is too large");
}
match dcxo {
i2c::DCXO::Main => unsafe {
if csr::main_dcxo::bitbang_enable_read() == 1 {
return Err("Main si549 bitbang mode is active when using gateware i2c");
}
while csr::main_dcxo::adpll_busy_read() == 1 {}
csr::main_dcxo::i2c_address_write(ADDRESS);
csr::main_dcxo::adpll_write(adpll as u32);
csr::main_dcxo::adpll_stb_write(1);
csr::main_dcxo::adpll_stb_write(0);
if csr::main_dcxo::nack_read() == 1 {
return Err("Main si549 failed to ack adpll write");
}
},
i2c::DCXO::Helper => unsafe {
if csr::helper_dcxo::bitbang_enable_read() == 1 {
return Err("Helper si549 bitbang mode is active when using gateware i2c");
}
while csr::helper_dcxo::adpll_busy_read() == 1 {}
csr::helper_dcxo::i2c_address_write(ADDRESS);
csr::helper_dcxo::adpll_write(adpll as u32);
csr::helper_dcxo::adpll_stb_write(1);
csr::helper_dcxo::adpll_stb_write(0);
if csr::helper_dcxo::nack_read() == 1 {
return Err("Helper si549 failed to ack adpll write");
}
},
};
Ok(())
}
/// To get within capture range
fn set_base_adpll(timer: &mut GlobalTimer) -> Result<(), &'static str> {
let count2adpll =
|error: i32| (((error) as f64 * 1e6) / (0.0001164 * (1 << (TIMER_WIDTH - COUNTER_DIV)) as f64)) as i32;
let (ref_count, main_count) = get_freq_counts(timer);
let mut base_adpll_lock = BASE_ADPLL.lock();
*base_adpll_lock = count2adpll(ref_count as i32 - main_count as i32);
set_adpll(i2c::DCXO::Main, *base_adpll_lock)?;
set_adpll(i2c::DCXO::Helper, *base_adpll_lock)?;
Ok(())
}
pub fn get_freq_counts(timer: &mut GlobalTimer) -> (u32, u32) {
unsafe {
csr::wrpll::frequency_counter_update_en_write(1);
timer.delay_us(150_000); // 8ns << TIMER_WIDTH
csr::wrpll::frequency_counter_update_en_write(0);
#[cfg(wrpll_ref_clk = "GTX_CDR")]
let ref_count = csr::wrpll::frequency_counter_counter_rtio_rx0_read();
#[cfg(wrpll_ref_clk = "SMA_CLKIN")]
let ref_count = csr::wrpll::frequency_counter_counter_ref_read();
let main_count = csr::wrpll::frequency_counter_counter_sys_read();
(ref_count, main_count)
}
}
fn reset_plls() -> Result<(), &'static str> {
*H_INTEGRATOR.lock() = 0;
*M_INTEGRATOR.lock() = 0;
set_adpll(i2c::DCXO::Main, 0)?;
set_adpll(i2c::DCXO::Helper, 0)?;
Ok(())
}
fn clear_pending(interrupt: FIQ) {
match interrupt {
FIQ::RefTag => unsafe { csr::wrpll::ref_tag_ev_pending_write(1) },
FIQ::MainTag => unsafe { csr::wrpll::main_tag_ev_pending_write(1) },
};
}
fn is_pending(interrupt: FIQ) -> bool {
match interrupt {
FIQ::RefTag => unsafe { csr::wrpll::ref_tag_ev_pending_read() == 1 },
FIQ::MainTag => unsafe { csr::wrpll::main_tag_ev_pending_read() == 1 },
}
}
pub fn interrupt_handler() {
if is_pending(FIQ::RefTag) {
tag_collector::collect_tags(FIQ::RefTag);
clear_pending(FIQ::RefTag);
helper_pll().expect("failed to run helper DCXO PLL");
}
if is_pending(FIQ::MainTag) {
tag_collector::collect_tags(FIQ::MainTag);
clear_pending(FIQ::MainTag);
}
if tag_collector::phase_diff_ready() {
main_pll().expect("failed to run main DCXO PLL");
tag_collector::clear_phase_diff_ready();
}
}
fn helper_pll() -> Result<(), &'static str> {
// unsafe {
// use libboard_zynq::println;
// ERR_ARR[COUNTER] = tag_collector::get_period_error();
// if COUNTER == SIZE - 1 {
// for i in 0..SIZE {
// println!("{}", ERR_ARR[i])
// }
// COUNTER = 0;
// let mut timer = GlobalTimer::get();
// timer.delay_us(20_000_000);
// }
// COUNTER += 1;
// }
let period_err = tag_collector::get_period_error();
let mut integrator_lock = H_INTEGRATOR.lock();
*integrator_lock += period_err * KI;
let mut h_adpll = *BASE_ADPLL.lock() + period_err * KP + *integrator_lock;
h_adpll = h_adpll.clamp(-ADPLL_MAX, ADPLL_MAX);
set_adpll(i2c::DCXO::Helper, h_adpll)?;
Ok(())
}
const SIZE: usize = 2000;
static mut COUNTER: usize = 0;
static mut ERR_ARR: [i32; SIZE] = [0; SIZE];
static mut FIN_ADPLL: i32 = 0;
fn main_pll() -> Result<(), &'static str> {
// unsafe {
// use libboard_zynq::println;
// ERR_ARR[COUNTER] = tag_collector::get_phase_error();
// if COUNTER == SIZE - 1 {
// for i in 0..SIZE {
// println!("{}", ERR_ARR[i])
// }
// println!("{:>3} Zero crossing adpll = {:>5}", SIZE, FIN_ADPLL);
// COUNTER = 0;
// let mut timer = GlobalTimer::get();
// timer.delay_us(20_000_000);
// }
// COUNTER += 1;
// }
let phase_err = tag_collector::get_phase_error();
let mut integrator_lock = M_INTEGRATOR.lock();
*integrator_lock += phase_err * KI;
let mut m_adpll = *BASE_ADPLL.lock() + phase_err * KP + *integrator_lock;
m_adpll = m_adpll.clamp(-ADPLL_MAX, ADPLL_MAX);
set_adpll(i2c::DCXO::Main, m_adpll)?;
unsafe {
if ERR_ARR[COUNTER] == 0 {
FIN_ADPLL = m_adpll;
}
}
Ok(())
}
pub fn select_recovered_clock(rc: bool, timer: &mut GlobalTimer) {
set_fiq(false);
if rc {
tag_collector::reset();
reset_plls().expect("failed to reset main and helper PLL");
info!("warming up refclk...");
// refclk need a couple seconds for freq counter to read it properly
// timer.delay_us(20_000_000);
set_base_adpll(timer).expect("failed to set base adpll");
let ppm = 0.0;
*BASE_ADPLL.lock() += (ppm / 0.0001164) as i32;
timer.delay_us(200);
info!("KP = {}, KI = {}", KP, KI);
info!("adding {}ppm to main & helper base adpll ({})", ppm, *BASE_ADPLL.lock());
// clear gateware pending flag
clear_pending(FIQ::RefTag);
clear_pending(FIQ::MainTag);
unsafe {
info!("ref tag = {} ", csr::wrpll::ref_tag_read());
info!("main tag = {} ", csr::wrpll::main_tag_read());
}
// use nFIQ to avoid IRQ being disabled by mutex lock and mess up PLL
set_fiq(true);
info!("WRPLL interrupt enabled");
timer.delay_ms(500);
unsafe {
info!("main_tag_ev_enable_read() = {}", csr::wrpll::main_tag_ev_enable_read());
info!("main_tag_ev_status_read() = {}", csr::wrpll::main_tag_ev_status_read());
info!("ref_tag_ev_enable_read() = {}", csr::wrpll::ref_tag_ev_enable_read());
info!("ref_tag_ev_status_read() = {}", csr::wrpll::ref_tag_ev_status_read());
// loop {
// if is_pending(FIQ::RefTag) {
// info!("REF tag is pending")
// }
// if is_pending(FIQ::MainTag) {
// info!("Main tag is pending")
// }
// // info!("ref tag = {} ", csr::wrpll::ref_tag_read());
// // info!("main tag = {} ", csr::wrpll::main_tag_read());
// timer.delay_ms(500);
// }
}
}
}
}
#[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(timer: &mut GlobalTimer) {
unsafe {
csr::wrpll_refclk::mmcm_dclk_write(1);
timer.delay_us(1);
csr::wrpll_refclk::mmcm_dclk_write(0);
timer.delay_us(1);
}
}
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 }
}
fn read(timer: &mut GlobalTimer, address: u8) -> u16 {
set_addr(address);
set_enable(true);
// Set DADDR on the MMCM and assert DEN for one clock cycle
one_clock_cycle(timer);
set_enable(false);
while !drp_ready() {
// keep the clock signal until data is ready
one_clock_cycle(timer);
}
get_data()
}
fn write(timer: &mut GlobalTimer, 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(timer);
set_write_enable(false);
set_enable(false);
while !drp_ready() {
// keep the clock signal until write is finished
one_clock_cycle(timer);
}
}
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) -> Result<(), &'static str> {
// Based on "DRP State Machine" from XAPP888
// hold reset HIGH during mmcm config
reset(true);
write(timer, 0x08, settings.clkout0_reg1);
write(timer, 0x09, settings.clkout0_reg2);
write(timer, 0x14, settings.clkfbout_reg1);
write(timer, 0x15, settings.clkfbout_reg2);
write(timer, 0x16, settings.div_reg);
write(timer, 0x18, settings.lock_reg1);
write(timer, 0x19, settings.lock_reg2);
write(timer, 0x1A, settings.lock_reg3);
write(timer, 0x28, settings.power_reg);
write(timer, 0x4E, settings.filt_reg1);
write(timer, 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("failed to generate 125Mhz ref clock from SMA CLKIN");
}
// const TIMER_WIDTH: u32 = 24;
// const COUNTER_DIV: u32 = 2;
// let (ref_count, main_count) = wrpll::get_freq_counts(timer);
// let f_sys = 125e6;
// info!(
// "ref counter = {} freq = {}",
// ref_count,
// (ref_count as f64 * f_sys) as f64 / (1 << (TIMER_WIDTH - COUNTER_DIV)) as f64
// );
// info!(
// "Main counter = {} freq = {}",
// main_count,
// (main_count as f64 * f_sys) as f64 / (1 << (TIMER_WIDTH - COUNTER_DIV)) as f64
// );
Ok(())
}
}