use core::future::poll_fn;
use core::task::Poll;
use embassy_hal_internal::{Peri, PeripheralType};
use embassy_sync::waitqueue::AtomicWaker;
use pac::peripherals::i3c::{I3C, I3C_MWDATAB, I3C_MWDATABE};
use crate::hal::timeout::Timeout;
use crate::pac;
const REQUEST_START: u8 = 1;
const REQUEST_STOP: u8 = 2;
const REQUEST_DAA: u8 = 4;
const TYPE_I3C: u8 = 0;
const TYPE_I2C: u8 = 1;
const MASTER_ON: u8 = 1;
const MAX_READ_CHUNK: usize = 256;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum BusMode {
#[default]
I3cSdr,
I2c,
}
impl BusMode {
const fn type_bits(self) -> u8 {
match self {
Self::I3cSdr => TYPE_I3C,
Self::I2c => TYPE_I2C,
}
}
}
pub enum Operation<'a> {
Read(&'a mut [u8]),
Write(&'a [u8]),
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub struct DeviceInfo {
pub manufacturer_id: u16,
pub part_number: u32,
pub bcr: u8,
pub dcr: u8,
pub dynamic_address: u8,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ConfigError {
InvalidFrequency,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[non_exhaustive]
pub enum Error {
Nack,
Underrun,
WriteAbort,
Terminated,
Fifo,
Protocol,
Timeout,
InvalidArgument,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Config {
pub clock_hz: u32,
pub push_pull_hz: u32,
pub open_drain_hz: u32,
pub i2c_hz: u32,
}
impl Config {
pub const fn new(clock_hz: u32) -> Self {
Self {
clock_hz,
push_pull_hz: 4_000_000,
open_drain_hz: 1_000_000,
i2c_hz: 400_000,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
struct Baud {
pp: u8,
od: u8,
i2c: u8,
}
fn baud(config: Config) -> Result<Baud, ConfigError> {
if config.clock_hz == 0
|| config.push_pull_hz == 0
|| config.open_drain_hz == 0
|| config.i2c_hz == 0
|| config.push_pull_hz > config.clock_hz / 2
{
return Err(ConfigError::InvalidFrequency);
}
let pp_div = config
.clock_hz
.div_ceil(config.push_pull_hz.saturating_mul(2));
if !(1..=16).contains(&pp_div) {
return Err(ConfigError::InvalidFrequency);
}
let pp = pp_div - 1;
let actual_pp = config.clock_hz / (2 * (pp + 1));
if config.open_drain_hz > actual_pp {
return Err(ConfigError::InvalidFrequency);
}
let od_div = actual_pp
.saturating_mul(2)
.div_ceil(config.open_drain_hz)
.max(2);
if od_div > 257 {
return Err(ConfigError::InvalidFrequency);
}
let od = od_div - 2;
let od_rate = actual_pp.saturating_mul(2) / od_div;
if config.i2c_hz > od_rate {
return Err(ConfigError::InvalidFrequency);
}
let i2c_div = od_rate.div_ceil(config.i2c_hz).clamp(1, 16);
Ok(Baud {
pp: pp as u8,
od: od as u8,
i2c: (i2c_div - 1) as u8,
})
}
pub struct State {
waker: AtomicWaker,
}
impl State {
pub(crate) const fn new() -> Self {
Self {
waker: AtomicWaker::new(),
}
}
fn on_interrupt(&self, r: I3C) {
let pending = r.MINTMASKED().read().0;
if pending != 0 {
r.MINTCLR()
.write_value(pac::peripherals::i3c::I3C_MINTCLR(pending));
self.waker.wake();
}
}
}
pub(crate) trait SealedInstance {
fn regs() -> I3C;
fn state() -> &'static State;
const LPCG: Option<u32>;
}
#[allow(private_bounds)]
pub trait Instance: SealedInstance + PeripheralType {
const IRQ: Option<u16>;
}
macro_rules! impl_i3c {
($instance:ident, $base:expr, $irq:expr) => {
impl crate::i3c::SealedInstance for crate::peripherals::$instance {
const LPCG: ::core::option::Option<u32> = crate::clock::lpcg(stringify!($instance));
#[inline]
fn regs() -> crate::pac::peripherals::i3c::I3C {
unsafe { crate::pac::peripherals::i3c::I3C::from_ptr($base as *mut ()) }
}
#[inline]
fn state() -> &'static crate::i3c::State {
static STATE: crate::i3c::State = crate::i3c::State::new();
&STATE
}
}
impl crate::i3c::Instance for crate::peripherals::$instance {
const IRQ: ::core::option::Option<u16> = $irq;
}
};
}
pub(crate) use impl_i3c;
pub fn on_interrupt<T: Instance>() {
T::state().on_interrupt(T::regs());
}
pub struct I3c {
regs: I3C,
state: &'static State,
timeout: Timeout,
}
impl I3c {
pub fn new<T: Instance>(peri: Peri<'static, T>, config: Config) -> Result<Self, ConfigError> {
let b = baud(config)?;
let _ = peri;
crate::clock::ungate(T::LPCG);
let regs = T::regs();
regs.MCONFIG().write(|w| w.set_MSTENA(0));
regs.MDATACTRL().write(|w| {
w.set_FLUSHTB(true);
w.set_FLUSHFB(true);
w.set_UNLOCK(true);
w.set_TXTRIG(0);
w.set_RXTRIG(0);
});
regs.MCONFIG().write(|w| {
w.set_PPBAUD(b.pp);
w.set_ODBAUD(b.od);
w.set_I2CBAUD(b.i2c);
w.set_ODHPP(true);
w.set_DISTO(false);
w.set_MSTENA(MASTER_ON);
});
Ok(Self {
regs,
state: T::state(),
timeout: Timeout::DEFAULT,
})
}
pub fn set_timeout(&mut self, timeout: Timeout) {
self.timeout = timeout;
}
fn wait_blocking<T>(&self, mut ready: impl FnMut(&I3C) -> Option<T>) -> Result<T, Error> {
let mut deadline = self.timeout.start();
loop {
self.check_error()?;
if let Some(value) = ready(&self.regs) {
return Ok(value);
}
deadline.tick().map_err(|_| Error::Timeout)?;
}
}
fn clear_status(&self) {
self.regs
.MSTATUS()
.write_value(pac::peripherals::i3c::I3C_MSTATUS(
(1 << 8) | (1 << 9) | (1 << 10) | (1 << 15),
));
self.regs
.MERRWARN()
.write_value(pac::peripherals::i3c::I3C_MERRWARN(u32::MAX));
}
fn check_error(&self) -> Result<(), Error> {
let e = self.regs.MERRWARN().read();
if e.NACK() {
return Err(Error::Nack);
}
if e.URUN() {
return Err(Error::Underrun);
}
if e.WRABT() {
return Err(Error::WriteAbort);
}
if e.TERM() {
return Err(Error::Terminated);
}
if e.OREAD() || e.OWRITE() {
return Err(Error::Fifo);
}
if e.MSGERR() || e.INVREQ() || e.HPAR() || e.HCRC() {
return Err(Error::Protocol);
}
if e.TIMEOUT() {
return Err(Error::Timeout);
}
Ok(())
}
fn start(&self, address: u8, read: bool, len: usize, mode: BusMode) -> Result<(), Error> {
if address >= 0x80 || len > MAX_READ_CHUNK {
return Err(Error::InvalidArgument);
}
self.regs.MCTRL().write(|w| {
w.set_REQUEST(REQUEST_START);
w.set_TYPE(mode.type_bits());
w.set_DIR(read);
w.set_ADDR(address);
w.set_RDTERM((len & 0xff) as u8);
});
self.wait_control()
}
fn wait_control(&self) -> Result<(), Error> {
self.wait_blocking(|r| r.MSTATUS().read().MCTRLDONE().then_some(()))?;
self.regs.MSTATUS().write(|w| w.set_MCTRLDONE(true));
Ok(())
}
fn wait_tx(&self) -> Result<(), Error> {
self.wait_blocking(|r| r.MSTATUS().read().TXNOTFULL().then_some(()))
}
fn wait_rx(&self) -> Result<u8, Error> {
self.wait_blocking(|r| {
r.MSTATUS()
.read()
.RXPEND()
.then(|| r.MRDATAB().read().VALUE())
})
}
fn wait_complete(&self) -> Result<(), Error> {
self.wait_blocking(|r| r.MSTATUS().read().COMPLETE().then_some(()))?;
self.regs.MSTATUS().write(|w| w.set_COMPLETE(true));
Ok(())
}
fn stop(&self, mode: BusMode) -> Result<(), Error> {
self.regs
.MCONFIG()
.modify(|w| w.set_ODSTOP(mode == BusMode::I2c));
self.regs.MCTRL().write(|w| {
w.set_REQUEST(REQUEST_STOP);
w.set_TYPE(mode.type_bits());
});
self.wait_control()
}
fn write_part(&self, address: u8, bytes: &[u8], mode: BusMode) -> Result<(), Error> {
self.start(address, false, 0, mode)?;
if let Some((last, rest)) = bytes.split_last() {
for &byte in rest {
self.wait_tx()?;
self.regs
.MWDATAB()
.write_value(I3C_MWDATAB(u32::from(byte)));
}
self.wait_tx()?;
self.regs
.MWDATABE()
.write_value(I3C_MWDATABE(u32::from(*last)));
self.wait_complete()?;
}
Ok(())
}
fn read_part(&self, address: u8, bytes: &mut [u8], mode: BusMode) -> Result<(), Error> {
if bytes.is_empty() {
return Err(Error::InvalidArgument);
}
for chunk in bytes.chunks_mut(MAX_READ_CHUNK) {
self.start(address, true, chunk.len(), mode)?;
for byte in chunk {
*byte = self.wait_rx()?;
}
}
Ok(())
}
pub fn blocking_write(
&mut self,
address: u8,
bytes: &[u8],
mode: BusMode,
) -> Result<(), Error> {
self.clear_status();
let guard = RecoverGuard::new(self.regs, mode);
self.write_part(address, bytes, mode)?;
self.stop(mode)?;
guard.defuse();
Ok(())
}
pub fn blocking_read(
&mut self,
address: u8,
bytes: &mut [u8],
mode: BusMode,
) -> Result<(), Error> {
self.clear_status();
let guard = RecoverGuard::new(self.regs, mode);
self.read_part(address, bytes, mode)?;
self.stop(mode)?;
guard.defuse();
Ok(())
}
pub fn blocking_write_read(
&mut self,
address: u8,
write: &[u8],
read: &mut [u8],
mode: BusMode,
) -> Result<(), Error> {
self.clear_status();
let guard = RecoverGuard::new(self.regs, mode);
self.write_part(address, write, mode)?;
self.read_part(address, read, mode)?;
self.stop(mode)?;
guard.defuse();
Ok(())
}
pub fn blocking_transaction(
&mut self,
address: u8,
operations: &mut [Operation<'_>],
mode: BusMode,
) -> Result<(), Error> {
if operations.is_empty() {
return Ok(());
}
self.clear_status();
let guard = RecoverGuard::new(self.regs, mode);
for operation in operations {
match operation {
Operation::Read(bytes) => self.read_part(address, bytes, mode)?,
Operation::Write(bytes) => self.write_part(address, bytes, mode)?,
}
}
self.stop(mode)?;
guard.defuse();
Ok(())
}
pub fn broadcast_ccc(&mut self, command: u8) -> Result<(), Error> {
self.blocking_write(0x7e, &[command], BusMode::I3cSdr)
}
pub fn reset_daa(&mut self) -> Result<(), Error> {
self.broadcast_ccc(0x06)
}
pub fn daa(&mut self, devices: &mut [DeviceInfo], first_address: u8) -> Result<usize, Error> {
if first_address == 0
|| first_address >= 0x7e
|| first_address as usize + devices.len() >= 0x7e
{
return Err(Error::InvalidArgument);
}
self.clear_status();
self.regs.MCTRL().write(|w| w.set_REQUEST(REQUEST_DAA));
let mut assigned = 0;
for device in devices {
let complete = self.wait_blocking(|r| {
let status = r.MSTATUS().read();
if status.COMPLETE() {
return Some(true);
}
(status.MCTRLDONE() && status.STATE() == 5).then_some(false)
})?;
if complete {
return Ok(assigned);
}
let mut payload = [0u8; 8];
for byte in &mut payload {
*byte = self.wait_blocking(|r| {
(!r.MDATACTRL().read().RXEMPTY()).then(|| r.MRDATAB().read().VALUE())
})?;
}
let address = first_address + assigned as u8;
*device = DeviceInfo {
manufacturer_id: u16::from_be_bytes([payload[0], payload[1]]) >> 1,
part_number: u32::from_be_bytes([payload[2], payload[3], payload[4], payload[5]]),
bcr: payload[6],
dcr: payload[7],
dynamic_address: address,
};
self.regs
.MWDATAB()
.write_value(I3C_MWDATAB(u32::from(address)));
self.regs.MCTRL().write(|w| w.set_REQUEST(REQUEST_DAA));
assigned += 1;
}
Ok(assigned)
}
async fn wait_async(
&self,
mask: u32,
ready: impl Fn(&Self) -> Option<Result<(), Error>>,
) -> Result<(), Error> {
poll_fn(|cx| {
if let Some(result) = ready(self) {
return Poll::Ready(result);
}
self.state.waker.register(cx.waker());
self.regs
.MINTSET()
.write_value(pac::peripherals::i3c::I3C_MINTSET(mask));
if let Some(result) = ready(self) {
return Poll::Ready(result);
}
Poll::Pending
})
.await
}
async fn start_async(
&self,
address: u8,
read: bool,
len: usize,
mode: BusMode,
) -> Result<(), Error> {
if address >= 0x80 || len > MAX_READ_CHUNK {
return Err(Error::InvalidArgument);
}
self.regs.MCTRL().write(|w| {
w.set_REQUEST(REQUEST_START);
w.set_TYPE(mode.type_bits());
w.set_DIR(read);
w.set_ADDR(address);
w.set_RDTERM((len & 0xff) as u8);
});
self.wait_async((1 << 9) | (1 << 15), |s| match s.check_error() {
Err(e) => Some(Err(e)),
Ok(()) if s.regs.MSTATUS().read().MCTRLDONE() => Some(Ok(())),
Ok(()) => None,
})
.await
}
async fn write_part_async(
&self,
address: u8,
bytes: &[u8],
mode: BusMode,
) -> Result<(), Error> {
self.start_async(address, false, 0, mode).await?;
if let Some((last, rest)) = bytes.split_last() {
for &byte in rest {
self.wait_async((1 << 12) | (1 << 15), |s| match s.check_error() {
Err(e) => Some(Err(e)),
Ok(()) if s.regs.MSTATUS().read().TXNOTFULL() => Some(Ok(())),
Ok(()) => None,
})
.await?;
self.regs
.MWDATAB()
.write_value(I3C_MWDATAB(u32::from(byte)));
}
self.wait_async((1 << 12) | (1 << 15), |s| match s.check_error() {
Err(e) => Some(Err(e)),
Ok(()) if s.regs.MSTATUS().read().TXNOTFULL() => Some(Ok(())),
Ok(()) => None,
})
.await?;
self.regs
.MWDATABE()
.write_value(I3C_MWDATABE(u32::from(*last)));
self.wait_async((1 << 10) | (1 << 15), |s| match s.check_error() {
Err(e) => Some(Err(e)),
Ok(()) if s.regs.MSTATUS().read().COMPLETE() => Some(Ok(())),
Ok(()) => None,
})
.await?;
}
Ok(())
}
async fn read_part_async(
&self,
address: u8,
bytes: &mut [u8],
mode: BusMode,
) -> Result<(), Error> {
if bytes.is_empty() {
return Err(Error::InvalidArgument);
}
for chunk in bytes.chunks_mut(MAX_READ_CHUNK) {
self.start_async(address, true, chunk.len(), mode).await?;
for byte in chunk {
self.wait_async((1 << 11) | (1 << 15), |s| match s.check_error() {
Err(e) => Some(Err(e)),
Ok(()) if s.regs.MSTATUS().read().RXPEND() => Some(Ok(())),
Ok(()) => None,
})
.await?;
*byte = self.regs.MRDATAB().read().VALUE();
}
}
Ok(())
}
async fn stop_async(&self, mode: BusMode) -> Result<(), Error> {
self.regs
.MCONFIG()
.modify(|w| w.set_ODSTOP(mode == BusMode::I2c));
self.regs.MCTRL().write(|w| {
w.set_REQUEST(REQUEST_STOP);
w.set_TYPE(mode.type_bits());
});
self.wait_async((1 << 9) | (1 << 15), |s| match s.check_error() {
Err(e) => Some(Err(e)),
Ok(()) if s.regs.MSTATUS().read().MCTRLDONE() => Some(Ok(())),
Ok(()) => None,
})
.await
}
pub async fn write(&mut self, address: u8, bytes: &[u8], mode: BusMode) -> Result<(), Error> {
self.clear_status();
let guard = RecoverGuard::new(self.regs, mode);
self.write_part_async(address, bytes, mode).await?;
self.stop_async(mode).await?;
guard.defuse();
Ok(())
}
pub async fn read(
&mut self,
address: u8,
bytes: &mut [u8],
mode: BusMode,
) -> Result<(), Error> {
self.clear_status();
let guard = RecoverGuard::new(self.regs, mode);
self.read_part_async(address, bytes, mode).await?;
self.stop_async(mode).await?;
guard.defuse();
Ok(())
}
pub async fn write_read(
&mut self,
address: u8,
write: &[u8],
read: &mut [u8],
mode: BusMode,
) -> Result<(), Error> {
self.clear_status();
let guard = RecoverGuard::new(self.regs, mode);
self.write_part_async(address, write, mode).await?;
self.read_part_async(address, read, mode).await?;
self.stop_async(mode).await?;
guard.defuse();
Ok(())
}
pub async fn transaction(
&mut self,
address: u8,
operations: &mut [Operation<'_>],
mode: BusMode,
) -> Result<(), Error> {
if operations.is_empty() {
return Ok(());
}
self.clear_status();
let guard = RecoverGuard::new(self.regs, mode);
for operation in operations {
match operation {
Operation::Read(bytes) => self.read_part_async(address, bytes, mode).await?,
Operation::Write(bytes) => self.write_part_async(address, bytes, mode).await?,
}
}
self.stop_async(mode).await?;
guard.defuse();
Ok(())
}
}
const RECOVERY_SPINS: u32 = 200_000;
fn recover(r: I3C, mode: BusMode) {
r.MINTCLR()
.write_value(pac::peripherals::i3c::I3C_MINTCLR(u32::MAX));
r.MDATACTRL().modify(|w| {
w.set_FLUSHTB(true);
w.set_FLUSHFB(true);
});
let clear = || {
r.MSTATUS().write_value(pac::peripherals::i3c::I3C_MSTATUS(
(1 << 8) | (1 << 9) | (1 << 10) | (1 << 15),
));
r.MERRWARN()
.write_value(pac::peripherals::i3c::I3C_MERRWARN(u32::MAX));
};
clear();
if r.MSTATUS().read().STATE() != 0 {
r.MCONFIG().modify(|w| w.set_ODSTOP(mode == BusMode::I2c));
r.MCTRL().write(|w| {
w.set_REQUEST(REQUEST_STOP);
w.set_TYPE(mode.type_bits());
});
let mut spins = RECOVERY_SPINS;
while spins > 0 && !r.MSTATUS().read().MCTRLDONE() {
spins -= 1;
}
}
clear();
}
struct RecoverGuard {
regs: I3C,
mode: BusMode,
armed: bool,
}
impl RecoverGuard {
fn new(regs: I3C, mode: BusMode) -> Self {
Self {
regs,
mode,
armed: true,
}
}
fn defuse(mut self) {
self.armed = false;
}
}
impl Drop for RecoverGuard {
fn drop(&mut self) {
if self.armed {
recover(self.regs, self.mode);
}
}
}
#[cfg(test)]
mod tests {
use super::{Config, baud};
#[test]
fn common_clock_plan_fits() {
let b = baud(Config::new(24_000_000)).unwrap();
assert!(b.pp <= 15);
}
#[test]
fn impossible_push_pull_rate_is_rejected() {
let mut config = Config::new(1_000_000);
config.push_pull_hz = 10_000_000;
assert!(baud(config).is_err());
}
#[test]
fn open_drain_cannot_exceed_push_pull_clock() {
let mut config = Config::new(24_000_000);
config.open_drain_hz = 20_000_000;
assert!(baud(config).is_err());
}
}