blob: 4a6d48f829b5eefeed6e2f8c2424922f796d8c03 [file]
// Licensed under the Apache-2.0 license
//! AST1060 I2C controller implementation
use core::cell::UnsafeCell;
use core::marker::PhantomData;
use super::timing::configure_timing;
use super::types::{I2cConfig, I2cXferMode};
use super::{constants, error::I2cError};
use ast1060_pac::{i2c::RegisterBlock, i2cbuff::RegisterBlock as BuffRegisterBlock};
/// Main I2C hardware abstraction.
///
/// Wraps a raw `*const RegisterBlock` and `*const BuffRegisterBlock` pair
/// for one AST1060 I2C controller. Mirrors the [`Usart`](super::super::uart::Usart)
/// pattern: the constructor is `unsafe`; ownership/coordination is the
/// caller's responsibility.
///
/// `Y` is the caller-supplied yield closure. [`wait_completion`] calls
/// it as `(yield_ns)(100_000)` between status polls so the runtime can
/// hand the CPU back to a scheduler instead of busy-looping. For a
/// pure busy-wait, pass `|_| core::hint::spin_loop()`.
#[allow(clippy::struct_excessive_bools)]
pub struct Ast1060I2c<'a, Y: FnMut(u32)> {
regs: *const RegisterBlock,
buff_regs: *const BuffRegisterBlock,
/// Transfer mode (visible to other modules for optimization decisions)
pub(crate) xfer_mode: I2cXferMode,
multi_master: bool,
smbus_alert: bool,
#[allow(dead_code)]
bus_recover: bool,
// Transfer state (visible to transfer/master modules)
/// Current device address being communicated with
pub(crate) current_addr: u8,
/// Current transfer length
pub(crate) current_len: u32,
/// Bytes transferred so far
pub(crate) current_xfer_cnt: u32,
/// Completion flag for synchronous operations
pub(crate) completion: bool,
/// DMA buffer for DMA mode (non-cached SRAM, caller-owned)
pub(crate) dma_buf: Option<&'a mut [u8]>,
/// Cooperative yield invoked between status polls in
/// [`wait_completion`]. Argument is the suggested wait window in
/// nanoseconds.
pub(crate) yield_ns: Y,
/// Makes `Ast1060I2c` `!Sync` so the raw register pointers can't be
/// shared across threads without explicit synchronization.
_not_sync: PhantomData<UnsafeCell<()>>,
}
impl<'a, Y: FnMut(u32)> Ast1060I2c<'a, Y> {
/// Create a new I2C instance and initialize hardware.
///
/// Performs full hardware init (controller reset, multi-master,
/// timing, interrupts). Use [`from_initialized`] when the bus has
/// already been brought up by application code or a previous
/// `new()` call.
///
/// # Safety
///
/// - `regs` and `buff_regs` must be valid, non-null pointers to the
/// AST1060 I2C register block and its companion buffer block for
/// the **same** controller instance.
/// - The pointed register blocks must remain valid for the lifetime
/// of this `Ast1060I2c`.
/// - Caller must enforce global ownership/coordination so concurrent
/// mutable access does not occur through other code paths.
pub unsafe fn new(
regs: *const RegisterBlock,
buff_regs: *const BuffRegisterBlock,
config: &I2cConfig,
yield_ns: Y,
) -> Result<Self, I2cError> {
let mut i2c = unsafe { Self::from_initialized(regs, buff_regs, config, yield_ns) };
i2c.init_hardware(&config)?;
Ok(i2c)
}
/// Create I2C instance from pre-initialized hardware (NO hardware init).
///
/// Lightweight constructor that wraps register pointers without
/// writing to hardware. Use when:
/// - Hardware was already initialized by app `main.rs` before kernel start
/// - Hardware was initialized by a previous `new()` call
/// - You want to avoid redundant re-initialization overhead
///
/// # Safety
///
/// Same contract as [`new`]. Additionally, the caller must ensure
/// hardware is already configured: I2C global registers (I2CG0C,
/// I2CG10) are set (call [`super::global::init_i2c_global`] ONCE in
/// the app before use); controller is enabled (I2CC00); timing is
/// configured; pin mux is configured.
#[must_use]
pub unsafe fn from_initialized(
regs: *const RegisterBlock,
buff_regs: *const BuffRegisterBlock,
config: &I2cConfig,
yield_ns: Y,
) -> Self {
Self {
regs,
buff_regs,
xfer_mode: config.xfer_mode,
multi_master: config.multi_master,
smbus_alert: config.smbus_alert,
bus_recover: true,
current_addr: 0,
current_len: 0,
current_xfer_cnt: 0,
completion: false,
dma_buf: None,
yield_ns,
_not_sync: PhantomData,
}
}
/// Create I2C instance with DMA mode support.
///
/// Like [`new`] but also attaches a DMA buffer for use when
/// `xfer_mode == I2cXferMode::DmaMode`. The buffer must reside in
/// non-cached SRAM (e.g. `#[link_section = ".ram_nc"]`) so that the
/// DMA engine and CPU see the same data without cache maintenance.
///
/// # Safety
///
/// Same contract as [`new`]. Additionally `dma_buf` must remain valid
/// for the lifetime of this `Ast1060I2c` and must be in a memory
/// region the DMA engine can address coherently with the CPU.
pub unsafe fn new_with_dma(
regs: *const RegisterBlock,
buff_regs: *const BuffRegisterBlock,
config: &I2cConfig,
dma_buf: &'a mut [u8],
yield_ns: Y,
) -> Result<Self, I2cError> {
let mut i2c = unsafe { Self::from_initialized(regs, buff_regs, config, yield_ns) };
i2c.dma_buf = Some(dma_buf);
i2c.init_hardware(&config)?;
Ok(i2c)
}
/// Create I2C instance from pre-initialized hardware with DMA buffer
/// (NO hardware init).
///
/// Like [`from_initialized`] but attaches a DMA buffer for use when
/// `xfer_mode == I2cXferMode::DmaMode`. Use this per-operation after
/// the bus has already been initialized via [`new_with_dma`], to
/// avoid the overhead of re-running hardware initialization.
///
/// The buffer must reside in non-cached SRAM (e.g. `#[link_section = ".ram_nc"]`).
///
/// # Safety
///
/// Same contract as [`from_initialized`] and [`new_with_dma`].
#[must_use]
pub unsafe fn from_initialized_with_dma(
regs: *const RegisterBlock,
buff_regs: *const BuffRegisterBlock,
config: &I2cConfig,
dma_buf: &'a mut [u8],
yield_ns: Y,
) -> Self {
let mut i2c = unsafe { Self::from_initialized(regs, buff_regs, config, yield_ns) };
i2c.dma_buf = Some(dma_buf);
i2c
}
/// Get access to registers (visible to other modules)
#[inline]
pub(crate) fn regs(&self) -> &RegisterBlock {
// SAFETY: `Ast1060I2c` construction is `unsafe`, so the caller
// upholds pointer validity, non-nullness, and ownership.
unsafe { &*self.regs }
}
/// Get access to buffer registers (visible to other modules)
#[inline]
pub(crate) fn buff_regs(&self) -> &BuffRegisterBlock {
// SAFETY: see `regs`.
unsafe { &*self.buff_regs }
}
/// Initialize hardware
#[inline(never)]
pub fn init_hardware(&mut self, config: &I2cConfig) -> Result<(), I2cError> {
// PRECONDITION: I2C global registers must be initialized by app before use.
// See: super::global::init_i2c_global().
// Reset I2C controller
unsafe {
self.regs().i2cc00().write(|w| w.bits(0));
}
// Configure multi-master
if !self.multi_master {
self.regs()
.i2cc00()
.modify(|_, w| w.dis_multimaster_capability_for_master_fn_only().set_bit());
}
// Enable master function and bus auto-release
self.regs().i2cc00().modify(|_, w| {
w.enbl_bus_autorelease_when_scllow_sdalow_or_slave_mode_inactive_timeout()
.set_bit()
.enbl_master_fn()
.set_bit()
});
// Configure timing
configure_timing(self.regs(), config)?;
// Clear all interrupts
unsafe {
self.regs().i2cm14().write(|w| w.bits(0xffff_ffff));
}
// Enable interrupts for packet mode
self.regs().i2cm10().modify(|_, w| {
w.enbl_pkt_cmd_done_int()
.set_bit()
.enbl_bus_recover_done_int()
.set_bit()
});
if self.smbus_alert {
self.regs()
.i2cm10()
.modify(|_, w| w.enbl_smbus_dev_alert_int().set_bit());
}
Ok(())
}
/// Enable interrupts
pub fn enable_interrupts(&mut self, mask: u32) {
unsafe {
self.regs().i2cm10().write(|w| w.bits(mask));
}
}
/// Clear interrupts
pub fn clear_interrupts(&mut self, mask: u32) {
unsafe {
self.regs().i2cm14().write(|w| w.bits(mask));
}
}
/// Check if bus is busy
///
/// Checks if any I2C transfer is currently active by examining status register bits.
#[must_use]
pub fn is_bus_busy(&self) -> bool {
let status = self.regs().i2cm14().read().bits();
// Bus is busy if any transfer command bits are set
(status
& (constants::AST_I2CM_TX_CMD
| constants::AST_I2CM_RX_CMD
| constants::AST_I2CM_START_CMD))
!= 0
}
/// Wait for completion with timeout (visible to master/transfer modules).
/// Calls the caller-supplied `yield_ns` closure between status polls.
pub(crate) fn wait_completion(&mut self, timeout_us: u32) -> Result<(), I2cError> {
let mut timeout = timeout_us;
self.completion = false;
while timeout > 0 && !self.completion {
self.handle_interrupt()?;
timeout = timeout.saturating_sub(1);
(self.yield_ns)(100_000);
}
if self.completion {
Ok(())
} else {
Err(I2cError::Timeout)
}
}
}