From f1a4a2f97f5b6355b5bc66337aeab591473ae6f9 Mon Sep 17 00:00:00 2001 From: morgan Date: Thu, 4 Jan 2024 12:41:36 +0800 Subject: [PATCH] Si549 firmware io_expander: set CLK_SEL pin to output when si549 is used satman main: drive CLK_SEL to true when si549 is used satman main & si549: add main DCXO setup si549: add bit bang i2c si549: add si549 programming & main setup --- src/libboard_artiq/src/io_expander.rs | 6 +- src/libboard_artiq/src/lib.rs | 3 +- src/libboard_artiq/src/si549.rs | 270 ++++++++++++++++++++++++++ src/satman/src/main.rs | 9 + 4 files changed, 286 insertions(+), 2 deletions(-) create mode 100644 src/libboard_artiq/src/si549.rs diff --git a/src/libboard_artiq/src/io_expander.rs b/src/libboard_artiq/src/io_expander.rs index 5157805..db3e26b 100644 --- a/src/libboard_artiq/src/io_expander.rs +++ b/src/libboard_artiq/src/io_expander.rs @@ -19,11 +19,15 @@ const IODIR_OUT_SFP_LED: u8 = 0x40; const IODIR_OUT_SFP0_LED: u8 = 0x40; #[cfg(hw_rev = "v1.1")] const IODIR_OUT_SFP0_LED: u8 = 0x80; +#[cfg(has_si549)] +const IODIR_CLK_SEL: u8 = 0x80; // out +#[cfg(has_si5324)] +const IODIR_CLK_SEL: u8 = 0x00; // in //IO expander port direction const IODIR0: [u8; 2] = [ 0xFF & !IODIR_OUT_SFP_TX_DISABLE & !IODIR_OUT_SFP0_LED, - 0xFF & !IODIR_OUT_SFP_TX_DISABLE & !IODIR_OUT_SFP_LED, + 0xFF & !IODIR_OUT_SFP_TX_DISABLE & !IODIR_OUT_SFP_LED & !IODIR_CLK_SEL, ]; const IODIR1: [u8; 2] = [ diff --git a/src/libboard_artiq/src/lib.rs b/src/libboard_artiq/src/lib.rs index 0a7c286..4ee5a3c 100644 --- a/src/libboard_artiq/src/lib.rs +++ b/src/libboard_artiq/src/lib.rs @@ -35,7 +35,8 @@ pub mod drtio_eem; pub mod grabber; #[cfg(has_si5324)] pub mod si5324; - +#[cfg(has_si549)] +pub mod si549; use core::{cmp, str}; pub fn identifier_read(buf: &mut [u8]) -> &str { diff --git a/src/libboard_artiq/src/si549.rs b/src/libboard_artiq/src/si549.rs new file mode 100644 index 0000000..06b94e9 --- /dev/null +++ b/src/libboard_artiq/src/si549.rs @@ -0,0 +1,270 @@ +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; + +pub struct DividerConfig { + pub hsdiv: u16, + pub lsdiv: u8, + pub fbdiv: u64, +} + +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 { + 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(()) +} diff --git a/src/satman/src/main.rs b/src/satman/src/main.rs index 5cded72..744a7b6 100644 --- a/src/satman/src/main.rs +++ b/src/satman/src/main.rs @@ -29,6 +29,8 @@ use libboard_artiq::grabber; use libboard_artiq::io_expander; #[cfg(has_si5324)] use libboard_artiq::si5324; +#[cfg(has_si549)] +use libboard_artiq::si549; use libboard_artiq::{drtio_routing, drtioaux, drtioaux_proto::{MASTER_PAYLOAD_MAX_SIZE, SAT_PAYLOAD_MAX_SIZE}, identifier_read, logger, @@ -856,6 +858,11 @@ pub extern "C" fn main_core0() -> i32 { io_expander1 .init(&mut i2c) .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); @@ -867,6 +874,8 @@ pub extern "C" fn main_core0() -> i32 { #[cfg(has_si5324)] si5324::setup(&mut i2c, &SI5324_SETTINGS, si5324::Input::Ckin1, &mut timer).expect("cannot initialize Si5324"); + #[cfg(has_si549)] + si549::main_setup(&mut timer).expect("cannot initialize main Si549"); timer.delay_us(100_000); info!("Switching SYS clocks...");