forked from M-Labs/zynq-rs
230 lines
6.0 KiB
Rust
230 lines
6.0 KiB
Rust
//! 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(())
|
|
}
|
|
}
|