From c60230af2504a236150cb011ec8c9e233a4320c8 Mon Sep 17 00:00:00 2001 From: Harry Ho Date: Wed, 5 Aug 2020 17:35:33 +0800 Subject: [PATCH] i2c: implement basic i2c bitbanging --- libboard_zynq/src/i2c/mod.rs | 229 ++++++++++++++++++++++++++++++++++ libboard_zynq/src/i2c/regs.rs | 95 ++++++++++++++ libboard_zynq/src/lib.rs | 1 + 3 files changed, 325 insertions(+) create mode 100644 libboard_zynq/src/i2c/mod.rs create mode 100644 libboard_zynq/src/i2c/regs.rs diff --git a/libboard_zynq/src/i2c/mod.rs b/libboard_zynq/src/i2c/mod.rs new file mode 100644 index 0000000..e10ec80 --- /dev/null +++ b/libboard_zynq/src/i2c/mod.rs @@ -0,0 +1,229 @@ +//! I2C Bit-banging Controller + +mod regs; +use log::{error, info, warn}; +use crate::{print, println}; +use super::clocks::Clocks; +use super::slcr; +use super::time::Microseconds; +use embedded_hal::timer::CountDown; +use libregister::{RegisterR, RegisterRW, RegisterW}; + +const INVALID_BUS: &'static str = "Invalid I2C bus"; + +pub struct I2C { + regs: regs::RegisterBlock, + count_down: super::timer::global::CountDown +} + +impl I2C { + #[cfg(feature = "target_zc706")] + pub fn i2c() -> Self { + // Route I2C 0 SCL / SDA Signals to MIO Pins 50 / 51 + slcr::RegisterBlock::unlocked(|slcr| { + // SCL + slcr.mio_pin_50.write( + slcr::MioPin50::zeroed() + .l3_sel(0b000) // GPIO 50 + .io_type(slcr::IoBufferType::Lvcmos25) + .pullup(true) + ); + // SDA + slcr.mio_pin_51.write( + slcr::MioPin51::zeroed() + .l3_sel(0b00) // GPIO 51 + .io_type(slcr::IoBufferType::Lvcmos25) + .pullup(true) + ); + }); + + Self::ctor_common() + } + + fn ctor_common() -> Self { + // Setup register block + let clocks = Clocks::get(); + let mut self_ = Self { + regs: unsafe { regs::RegisterBlock::new() }, + count_down: unsafe { super::timer::GlobalTimer::get() }.countdown() + }; + + // Setup GPIO output mask + self_.regs.gpio_output_mask.modify(|_, w| { + w.scl_m(true).sda_m(true) + }); + + self_.init(); + self_ + } + + /// Delay for I2C operations, simple wrapper for nb. + fn delay(&mut self, us: u64) { + self.count_down.start(Microseconds(us)); + nb::block!(self.count_down.wait()).unwrap(); + } + + fn half_period(&mut self) { self.delay(100) } + + fn sda_i(&mut self) -> bool { + self.regs.gpio_input.read().sda() + } + + fn scl_i(&mut self) -> bool { + self.regs.gpio_input.read().scl() + } + + fn sda_oe(&mut self, oe: bool) { + self.regs.gpio_output_enable.modify(|_, w| { + w.sda(oe) + }) + } + + fn sda_o(&mut self, o: bool) { + self.regs.gpio_output_mask.modify(|_, w| { + w.sda_o(o) + }) + } + + fn scl_oe(&mut self, oe: bool) { + self.regs.gpio_output_enable.modify(|_, w| { + w.scl(oe) + }) + } + + fn scl_o(&mut self, o: bool) { + self.regs.gpio_output_mask.modify(|_, w| { + w.scl_o(o) + }) + } + + pub fn init(&mut self) -> Result<(), &'static str> { + self.scl_oe(false); + self.sda_oe(false); + self.scl_o(false); + self.sda_o(false); + + // Check the I2C bus is ready + self.half_period(); + self.half_period(); + if !self.sda_i() { + // Try toggling SCL a few times + for _bit in 0..8 { + self.scl_oe(true); + self.half_period(); + self.scl_oe(false); + self.half_period(); + } + } + + if !self.sda_i() { + return Err("SDA is stuck low and doesn't get unstuck"); + } + if !self.scl_i() { + return Err("SCL is stuck low and doesn't get unstuck"); + } + // postcondition: SCL and SDA high + Ok(()) + } + + pub fn start(&mut self) -> Result<(), &'static str> { + // precondition: SCL and SDA high + if !self.scl_i() { + return Err("SCL is stuck low and doesn't get unstuck"); + } + if !self.sda_i() { + return Err("SDA arbitration lost"); + } + self.sda_oe(true); + self.half_period(); + self.scl_oe(true); + // postcondition: SCL and SDA low + Ok(()) + } + + pub fn restart(&mut self) -> Result<(), &'static str> { + // precondition SCL and SDA low + self.sda_oe(false); + self.half_period(); + self.scl_oe(false); + self.half_period(); + self.start()?; + // postcondition: SCL and SDA low + Ok(()) + } + + pub fn stop(&mut self) -> Result<(), &'static str> { + // precondition: SCL and SDA low + self.half_period(); + self.scl_oe(false); + self.half_period(); + self.sda_oe(false); + self.half_period(); + if !self.sda_i() { + return Err("SDA arbitration lost"); + } + // postcondition: SCL and SDA high + Ok(()) + } + + pub fn write(&mut self, data: u8) -> Result { + // precondition: SCL and SDA low + // MSB first + for bit in (0..8).rev() { + self.sda_oe(data & (1 << bit) == 0); + self.half_period(); + self.scl_o(false); + self.half_period(); + self.scl_oe(true); + } + self.sda_oe(false); + self.half_period(); + self.scl_oe(false); + self.half_period(); + // Read ack/nack + let ack = !self.sda_i(); + self.scl_oe(true); + self.sda_oe(true); + // postcondition: SCL and SDA low + + Ok(ack) + } + + pub fn read(&mut self, ack: bool) -> Result { + // precondition: SCL and SDA low + self.sda_oe(false); + + let mut data: u8 = 0; + + // MSB first + for bit in (0..8).rev() { + self.half_period(); + self.scl_oe(false); + self.half_period(); + if self.sda_i() { data |= 1 << bit } + self.scl_oe(true); + } + // Send ack/nack + self.sda_oe(ack); + self.half_period(); + self.scl_oe(false); + self.half_period(); + self.scl_oe(true); + self.sda_oe(true); + // postcondition: SCL and SDA low + + Ok(data) + } + + pub fn pca9548_select(&mut self, address: u8, channels: u8) -> Result<(), &'static str> { + self.start()?; + if !self.write(address << 1)? { + return Err("PCA9548 failed to ack write address") + } + if !self.write(channels)? { + return Err("PCA9548 failed to ack control word") + } + self.stop()?; + Ok(()) + } +} diff --git a/libboard_zynq/src/i2c/regs.rs b/libboard_zynq/src/i2c/regs.rs new file mode 100644 index 0000000..b7b211a --- /dev/null +++ b/libboard_zynq/src/i2c/regs.rs @@ -0,0 +1,95 @@ +use volatile_register::{RO, WO, RW}; + +use libregister::{register, register_bit, register_bits}; + +// With reference to: +// +// artiq:artiq/gateware/targets/kasli.py: +// self.submodules.i2c = gpio.GPIOTristate([i2c.scl, i2c.sda]) +// +// misoc:misoc/cores/gpio.py: +// class GPIOTristate(Module, AutoCSR): +// def __init__(self, signals, reset_out=0, reset_oe=0): +// l = len(signals) +// self._in = CSRStatus(l) +// self._out = CSRStorage(l, reset=reset_out) +// self._oe = CSRStorage(l, reset=reset_oe) +// +// Hence, using GPIOs as SCL and SDA GPIOs respectively. +// +// Current compatibility: +// zc706: GPIO 50, 51 == SCL, SDA + +#[repr(C)] +pub struct RegisterBlock { + pub gpio_output_mask: &'static mut GPIOOutputMask, + pub gpio_input: &'static mut GPIOInput, + pub gpio_output_enable: &'static mut GPIOOutputEnable, +} + +impl RegisterBlock { + pub unsafe fn new() -> Self { + Self { + gpio_output_mask: GPIOOutputMask::new(), + gpio_input: GPIOInput::new(), + gpio_output_enable: GPIOOutputEnable::new() + } + } +} + +impl GPIOOutputMask { + #[cfg(feature = "target_zc706")] + pub unsafe fn new() -> &'static mut Self { + &mut *(0xE000A00C as *mut _) + } +} + +impl GPIOInput { + #[cfg(feature = "target_zc706")] + pub unsafe fn new() -> &'static mut Self { + &mut *(0xE000A064 as *mut _) + } +} + +impl GPIOOutputEnable { + #[cfg(feature = "target_zc706")] + pub unsafe fn new() -> &'static mut Self { + &mut *(0xE000A248 as *mut _) + } +} + +// MASK_DATA_1_MSW: +// Maskable output data for MIO[53:48] +register!(gpio_output_mask, GPIOOutputMask, RW, u32); +// Output for SCL +#[cfg(feature = "target_zc706")] +register_bit!(gpio_output_mask, scl_o, 2); +// Output for SDA +#[cfg(feature = "target_zc706")] +register_bit!(gpio_output_mask, sda_o, 3); +// Mask for SCL; set to 1 to write to output +#[cfg(feature = "target_zc706")] +register_bit!(gpio_output_mask, scl_m, 18); +// Mask for SDA; set to 1 to write to output +#[cfg(feature = "target_zc706")] +register_bit!(gpio_output_mask, sda_m, 19); + +// DATA_1_RO: +// Input data for MIO[53:32] +register!(gpio_input, GPIOInput, RO, u32); +// Input for SCL +#[cfg(feature = "target_zc706")] +register_bit!(gpio_input, scl, 8); +// Input for SDA +#[cfg(feature = "target_zc706")] +register_bit!(gpio_input, sda, 9); + +// OEN_1: +// Output enable for MIO[53:32] +register!(gpio_output_enable, GPIOOutputEnable, RW, u32); +// Output enable for SCL +#[cfg(feature = "target_zc706")] +register_bit!(gpio_output_enable, scl, 8); +// Output enable for SDA +#[cfg(feature = "target_zc706")] +register_bit!(gpio_output_enable, sda, 9); diff --git a/libboard_zynq/src/lib.rs b/libboard_zynq/src/lib.rs index 43494cc..8c6daf4 100644 --- a/libboard_zynq/src/lib.rs +++ b/libboard_zynq/src/lib.rs @@ -20,5 +20,6 @@ pub mod flash; pub mod time; pub mod timer; pub mod sdio; +pub mod i2c; pub mod logger; pub mod ps7_init;