From e592a695371e247678de675770a5beea7eaa69d6 Mon Sep 17 00:00:00 2001 From: morgan Date: Wed, 20 Dec 2023 15:56:49 +0800 Subject: [PATCH] Si549 firmware satman main: gate CLK_SEL to true & main dcxo setup behind has_si549 cfg io_expander: config CLK_SEL port direction with has_si549 cfg si549: add bit bang i2c, si549 programming si549: add main setup --- src/libboard_artiq/src/io_expander.rs | 8 +- src/libboard_artiq/src/lib.rs | 3 +- src/libboard_artiq/src/si549.rs | 286 ++++++++++++++++++++++++++ src/satman/src/main.rs | 9 + 4 files changed, 304 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..e063a06 100644 --- a/src/libboard_artiq/src/io_expander.rs +++ b/src/libboard_artiq/src/io_expander.rs @@ -12,6 +12,11 @@ struct Registers { gpiob: u8, // Output Port 1 } +#[cfg(has_si549)] +const USE_SI549: u8 = 0xFF; +#[cfg(has_si5324)] +const USE_SI549: u8 = 0x00; + //IO expanders pins const IODIR_OUT_SFP_TX_DISABLE: u8 = 0x02; const IODIR_OUT_SFP_LED: u8 = 0x40; @@ -19,11 +24,12 @@ 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; +const IODIR_OUT_CLK_SEL: u8 = 0x80; //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_OUT_CLK_SEL & USE_SI549), ]; 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..da9f778 --- /dev/null +++ b/src/libboard_artiq/src/si549.rs @@ -0,0 +1,286 @@ +use core::result::Result::Ok; + +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; + +mod i2c { + use super::*; + + #[derive(Debug, Clone, Copy)] + pub enum DCXO { + Main, + #[cfg(has_wrpll)] + Helper, + } + + fn half_period(timer: &mut GlobalTimer) { + timer.delay_us(1) + } + const SDA_MASK: u8 = 2; + const SCL_MASK: u8 = 1; + + fn sda_i(dcxo: DCXO) -> bool { + let reg = match dcxo { + DCXO::Main => unsafe { csr::main_dcxo::gpio_in_read() }, + #[cfg(has_wrpll)] + DCXO::Helper => unsafe { csr::helper_dcxo::gpio_in_read() }, + }; + reg & SDA_MASK != 0 + } + + fn sda_oe(dcxo: DCXO, oe: bool) { + let reg = match dcxo { + DCXO::Main => unsafe { csr::main_dcxo::gpio_oe_read() }, + #[cfg(has_wrpll)] + DCXO::Helper => unsafe { csr::helper_dcxo::gpio_oe_read() }, + }; + let reg = if oe { reg | SDA_MASK } else { reg & !SDA_MASK }; + match dcxo { + DCXO::Main => unsafe { csr::main_dcxo::gpio_oe_write(reg) }, + #[cfg(has_wrpll)] + DCXO::Helper => unsafe { csr::helper_dcxo::gpio_oe_write(reg) }, + } + } + + fn sda_o(dcxo: DCXO, o: bool) { + let reg = match dcxo { + DCXO::Main => unsafe { csr::main_dcxo::gpio_out_read() }, + #[cfg(has_wrpll)] + DCXO::Helper => unsafe { csr::helper_dcxo::gpio_out_read() }, + }; + let reg = if o { reg | SDA_MASK } else { reg & !SDA_MASK }; + match dcxo { + DCXO::Main => unsafe { csr::main_dcxo::gpio_out_write(reg) }, + #[cfg(has_wrpll)] + DCXO::Helper => unsafe { csr::helper_dcxo::gpio_out_write(reg) }, + } + } + + fn scl_oe(dcxo: DCXO, oe: bool) { + let reg = match dcxo { + DCXO::Main => unsafe { csr::main_dcxo::gpio_oe_read() }, + #[cfg(has_wrpll)] + DCXO::Helper => unsafe { csr::helper_dcxo::gpio_oe_read() }, + }; + let reg = if oe { reg | SCL_MASK } else { reg & !SCL_MASK }; + match dcxo { + DCXO::Main => unsafe { csr::main_dcxo::gpio_oe_write(reg) }, + #[cfg(has_wrpll)] + DCXO::Helper => unsafe { csr::helper_dcxo::gpio_oe_write(reg) }, + } + } + + fn scl_o(dcxo: DCXO, o: bool) { + let reg = match dcxo { + DCXO::Main => unsafe { csr::main_dcxo::gpio_out_read() }, + #[cfg(has_wrpll)] + DCXO::Helper => unsafe { csr::helper_dcxo::gpio_out_read() }, + }; + let reg = if o { reg | SCL_MASK } else { reg & !SCL_MASK }; + match dcxo { + DCXO::Main => unsafe { csr::main_dcxo::gpio_out_write(reg) }, + #[cfg(has_wrpll)] + DCXO::Helper => unsafe { csr::helper_dcxo::gpio_out_write(reg) }, + } + } + + 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 program(dcxo: i2c::DCXO, hsdiv: u16, lsdiv: u8, fbdiv: u64, 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, hsdiv as u8, timer)?; + write(dcxo, 24, (hsdiv >> 8) as u8 | (lsdiv << 4), timer)?; + write(dcxo, 26, fbdiv as u8, timer)?; + write(dcxo, 27, (fbdiv >> 8) as u8, timer)?; + write(dcxo, 28, (fbdiv >> 16) as u8, timer)?; + write(dcxo, 29, (fbdiv >> 24) as u8, timer)?; + write(dcxo, 30, (fbdiv >> 32) as u8, timer)?; + write(dcxo, 31, (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) -> Result<(), &'static str> { + unsafe { + csr::main_dcxo::bitbang_enable_write(1); + csr::main_dcxo::i2c_address_write(ADDRESS); + } + + #[cfg(rtio_frequency = "125.0")] + let (m_hsdiv, m_lsdiv, m_fbdiv) = (0x058, 0, 0x04815791F25); + + program(i2c::DCXO::Main, m_hsdiv, m_lsdiv, m_fbdiv, 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 80d53cd..0f83d38 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, @@ -762,6 +764,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); @@ -773,6 +780,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...");