287 lines
8.9 KiB
Rust
287 lines
8.9 KiB
Rust
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(())
|
|
}
|