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
morgan 2023-12-20 15:56:49 +08:00
parent 0cd1ac9204
commit e592a69537
4 changed files with 304 additions and 2 deletions

View File

@ -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] = [

View File

@ -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 {

View File

@ -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<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 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(())
}

View File

@ -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...");