diff --git a/firmware/src/i2c.rs b/firmware/src/i2c.rs new file mode 100644 index 0000000..c8dccfa --- /dev/null +++ b/firmware/src/i2c.rs @@ -0,0 +1,104 @@ +//! I2C driver for my amlib i2c interface implementation. +//! +//! See `gateware/i2c.py` for register information + + +/// TODO repr(C) a register bank, and add instances + +// Using the blocking API because the peripheral requires fairly tight timing to operate +// correctly, and I don't feel like writing the gateware to resolve that. +use embedded_hal::blocking::i2c::{Write, Read, SevenBitAddress, Transactional, Operation}; +use crate::{read_reg, write_reg}; +use core::arch::asm; + +// TODO I think there may be bus address semantics I'm not 100% on. +// There's a possiblity these addresses are wrong, and they need to be 4 bytes each +const CR: u32 = 0; +const SR: u32 = 1; +const DWR: u32 = 2; +const DRR: u32 = 3; + +#[derive(Clone, Copy, Debug)] +pub enum Error { + /// Device is busy for some reason + Busy, + /// I2C bus returned a NACK + Nack, +} + +pub struct AmlibI2c { + base_addr: u32, +} + +impl AmlibI2c { + pub fn new(base_addr: u32) -> Self { + AmlibI2c { base_addr } + } + + fn wait_while_busy(&self) { + unsafe { + while read_reg::(self.base_addr + SR) & 1 != 0 { + asm!("nop"); + } + } + } +} + +impl Write for AmlibI2c { + // TODO errors + type Error = Error; + + fn write(&mut self, address: SevenBitAddress, bytes: &[u8]) -> Result<(), Self::Error> { + unsafe { + if (read_reg::(self.base_addr + SR) & 1) != 0 { + return Err(Error::Busy); + } + + // START + write_reg(self.base_addr + CR, 0x01u32); + // Pre-load data w/ address (R/~W = 0) + write_reg(self.base_addr + DWR, (address << 1) as u32); + + self.wait_while_busy(); + + // Send address byte + write_reg(self.base_addr + CR, 0x04u32); + if read_reg::(self.base_addr + SR) & 0x02 != 0 { + return Err(Error::Nack); + } + + for byte in bytes { + // Write byte + write_reg(self.base_addr + DWR, *byte as u32); + self.wait_while_busy(); + // Send byte once done sending the last byte + write_reg(self.base_addr + CR, 0x04u32); + } + + self.wait_while_busy(); + // STOP + write_reg(self.base_addr + CR, 0x02u32); + + self.wait_while_busy(); + Ok(()) + } + } +} + +impl Read for AmlibI2c { + // TODO errors + type Error = Error; + + fn read(&mut self, address: SevenBitAddress, buffer: &mut [u8]) -> Result<(), Self::Error> { + Ok(()) + } +} + +impl Transactional for AmlibI2c { + type Error = Error; + + fn exec<'a>(&mut self, address: u8, operations: &mut [Operation<'a>]) + -> Result<(), Self::Error> { + Ok(()) + } +}