use embedded_hal::i2c::{Operation, SevenBitAddress};
use rivet::sync::Signal;
const I2CMSA: usize = 0x000;
const I2CMCS: usize = 0x004;
const I2CMDR: usize = 0x008;
const I2CMTPR: usize = 0x00C;
const I2CMIMR: usize = 0x010;
const I2CMICR: usize = 0x01C;
const I2CMCR: usize = 0x020;
const CS_RUN: u32 = 1 << 0;
const CS_START: u32 = 1 << 1;
const CS_STOP: u32 = 1 << 2;
const CS_ACK: u32 = 1 << 3;
const CS_BUSY: u32 = 1 << 0;
const CS_ERROR: u32 = 1 << 1;
const CR_MFE: u32 = 1 << 4;
const IMR_IM: u32 = 1 << 0;
const ICR_IC: u32 = 1 << 0;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Nak;
impl embedded_hal::i2c::Error for Nak {
fn kind(&self) -> embedded_hal::i2c::ErrorKind {
embedded_hal::i2c::ErrorKind::NoAcknowledge(
embedded_hal::i2c::NoAcknowledgeSource::Unknown,
)
}
}
pub struct StellarisI2c {
base: usize,
sig: &'static Signal,
}
impl StellarisI2c {
pub const unsafe fn new(base: usize, sig: &'static Signal) -> Self {
Self { base, sig }
}
fn reg(&self, offset: usize) -> *mut u32 {
(self.base + offset) as *mut u32
}
pub fn init(&self) {
unsafe {
core::ptr::write_volatile(self.reg(I2CMCR), CR_MFE);
core::ptr::write_volatile(self.reg(I2CMTPR), 7);
core::ptr::write_volatile(self.reg(I2CMIMR), IMR_IM);
}
}
fn set_address(&self, address: u8, read: bool) {
unsafe {
let val = ((address as u32) << 1) | u32::from(read);
core::ptr::write_volatile(self.reg(I2CMSA), val);
}
}
fn mcs(&self) -> u32 {
unsafe { core::ptr::read_volatile(self.reg(I2CMCS)) }
}
fn write_dr(&self, byte: u8) {
unsafe { core::ptr::write_volatile(self.reg(I2CMDR), byte as u32) };
}
fn read_dr(&self) -> u8 {
unsafe { core::ptr::read_volatile(self.reg(I2CMDR)) as u8 }
}
fn run_sync(&self, cmd: u32) -> Result<(), Nak> {
unsafe { core::ptr::write_volatile(self.reg(I2CMCS), cmd) };
while self.mcs() & CS_BUSY != 0 {
core::hint::spin_loop();
}
if self.mcs() & CS_ERROR != 0 {
Err(Nak)
} else {
Ok(())
}
}
async fn run_async(&self, cmd: u32) -> Result<(), Nak> {
self.sig.reset();
unsafe { core::ptr::write_volatile(self.reg(I2CMCS), cmd) };
if self.mcs() & CS_BUSY == 0 {
let status = self.mcs();
self.sig.try_take();
return if status & CS_ERROR != 0 { Err(Nak) } else { Ok(()) };
}
self.sig.wait().await;
if self.mcs() & CS_ERROR != 0 {
Err(Nak)
} else {
Ok(())
}
}
}
pub fn isr_ack(base: usize, sig: &Signal) {
unsafe {
core::ptr::write_volatile((base + I2CMICR) as *mut u32, ICR_IC);
}
sig.signal();
}
#[macro_export]
macro_rules! stellaris_i2c_instance {
($sig_name:ident, $isr_name:ident, base = $base:expr) => {
static $sig_name: ::rivet::sync::Signal = ::rivet::sync::Signal::new();
fn $isr_name() {
$crate::stellaris_i2c::isr_ack($base, &$sig_name);
}
};
}
macro_rules! impl_transaction {
($self:ident, $address:ident, $operations:ident, $run:ident $(. $await_kw:ident)?) => {{
let op_count = $operations.len();
for (i, op) in $operations.iter_mut().enumerate() {
let is_last_op = i + 1 == op_count;
match op {
Operation::Write(bytes) => {
$self.set_address($address, false);
let n = bytes.len();
for (j, &b) in bytes.iter().enumerate() {
$self.write_dr(b);
let is_first = j == 0;
let is_last = j + 1 == n;
let mut cmd = CS_RUN;
if is_first {
cmd |= CS_START;
}
if is_last {
cmd |= CS_STOP;
}
$self.$run(cmd)$(.$await_kw)??;
}
}
Operation::Read(bytes) => {
$self.set_address($address, true);
let n = bytes.len();
for (j, slot) in bytes.iter_mut().enumerate() {
let is_first = j == 0;
let is_last = j + 1 == n;
let mut cmd = CS_RUN;
if is_first {
cmd |= CS_START;
}
if is_last {
cmd |= CS_STOP;
} else {
cmd |= CS_ACK;
}
$self.$run(cmd)$(.$await_kw)??;
*slot = $self.read_dr();
}
}
}
let _ = is_last_op;
}
Ok(())
}};
}
impl embedded_hal::i2c::ErrorType for StellarisI2c {
type Error = Nak;
}
impl embedded_hal::i2c::I2c<SevenBitAddress> for StellarisI2c {
fn transaction(
&mut self,
address: SevenBitAddress,
operations: &mut [Operation<'_>],
) -> Result<(), Self::Error> {
impl_transaction!(self, address, operations, run_sync)
}
}
impl embedded_hal_async::i2c::I2c<SevenBitAddress> for StellarisI2c {
async fn transaction(
&mut self,
address: SevenBitAddress,
operations: &mut [Operation<'_>],
) -> Result<(), Self::Error> {
impl_transaction!(self, address, operations, run_async.await)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[allow(dead_code)]
fn generic_sync<I: embedded_hal::i2c::I2c>(i2c: &mut I, addr: u8, buf: &mut [u8]) {
let _ = i2c.read(addr, buf);
}
#[allow(dead_code)]
async fn generic_async<I: embedded_hal_async::i2c::I2c>(i2c: &mut I, addr: u8, buf: &mut [u8]) {
let _ = i2c.read(addr, buf).await;
}
#[test]
fn type_checks() {
let _ = generic_sync::<StellarisI2c>;
let _ = generic_async::<StellarisI2c>;
}
}