i2c: implement basic i2c bitbanging
This commit is contained in:
parent
e8ba73a8c7
commit
c60230af25
|
@ -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(())
|
||||||
|
}
|
||||||
|
}
|
|
@ -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);
|
|
@ -20,5 +20,6 @@ pub mod flash;
|
||||||
pub mod time;
|
pub mod time;
|
||||||
pub mod timer;
|
pub mod timer;
|
||||||
pub mod sdio;
|
pub mod sdio;
|
||||||
|
pub mod i2c;
|
||||||
pub mod logger;
|
pub mod logger;
|
||||||
pub mod ps7_init;
|
pub mod ps7_init;
|
||||||
|
|
Loading…
Reference in New Issue