i2c: implement basic i2c bitbanging
This commit is contained in:
parent
e8ba73a8c7
commit
c60230af25
229
libboard_zynq/src/i2c/mod.rs
Normal file
229
libboard_zynq/src/i2c/mod.rs
Normal 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(())
|
||||
}
|
||||
}
|
95
libboard_zynq/src/i2c/regs.rs
Normal file
95
libboard_zynq/src/i2c/regs.rs
Normal 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);
|
@ -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;
|
||||
|
Loading…
Reference in New Issue
Block a user