i2c: implement basic i2c bitbanging

This commit is contained in:
Harry Ho 2020-08-05 17:35:33 +08:00
parent e8ba73a8c7
commit c60230af25
3 changed files with 325 additions and 0 deletions

View File

@ -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<Microseconds>
}
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<bool, &'static str> {
// 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<u8, &'static str> {
// 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(())
}
}

View File

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

View File

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