blob: 8b086fa8f1edb769754d8fbdb90c754969cccf48 [file]
// Licensed under the Apache-2.0 license
// SPDX-License-Identifier: Apache-2.0
//! I3C Controller
//!
//! Main hardware abstraction for I3C bus controller.
//!
//! # Lifecycle
//!
//! Two states, matching the SMC peripheral's `Uninitialized -> Ready`
//! precedent:
//!
//! | State | Entered by | Available operations |
//! |-------|-----------|----------------------|
//! | [`Uninitialized`] | [`I3cController::new`] | [`start()`](I3cController::start) |
//! | [`Ready`] | `start()` (IRQ trampoline claimed + hardware programmed) | bus operations |
//!
//! After `start()` the integration layer unmasks the NVIC line it owns (it
//! selected the bus, so it knows the matching platform interrupt line);
//! the driver never touches the NVIC.
//!
//! # ISR decoupling
//!
//! The ISR shares **no** `&mut` state with this controller: at `start()` the
//! driver parks an ISR-owned register handle plus the role flag in the
//! per-bus registry (`hardware::register_i3c_irq_handler`), and the ISR
//! communicates back exclusively through per-bus atomics and the global IBI
//! work rings (the SMC flag-and-defer model). The controller is therefore a
//! plain owned value — no pinning, no `'static` storage, no raw context
//! pointer.
//!
//! # Example
//!
//! ```rust,ignore
//! // === BOOT/INIT CODE (runs once) ===
//! // Platform init first (clocks, resets - not part of i3c_core)
//! scu.enable_i3c_clock(bus);
//! scu.deassert_i3c_reset(bus);
//!
//! let hw = unsafe { Ast1060I3c::new(bus, yield_fn) }.ok_or(...)?;
//! let mut ctrl = I3cController::new(hw, &mut config)
//! .start()?; // register ISR ctx (single-shot) + program hardware
//!
//! // Integration layer owns the NVIC line; unmask it now.
//! unsafe { NVIC::unmask(integration_owned_irq_line) };
//!
//! ctrl.priv_write(pid, &mut data)?;
//! ```
use core::marker::PhantomData;
use super::ccc;
use super::config::{DeviceEntry, I3cConfig, I3cTargetConfig};
use super::constants::I3C_BROADCAST_ADDR;
use super::error::I3cError;
use super::hardware::HardwareInterface;
use super::types::{DevKind, I3cIbi, I3cIbiType, I3cMsg};
use embedded_hal::i2c::SevenBitAddress;
// =============================================================================
// Lifecycle states
// =============================================================================
/// Initial state: nothing registered, no I/O done.
pub struct Uninitialized;
/// IRQ trampoline claimed and hardware programmed; bus operations available.
/// The integration layer unmasks the NVIC line it owns after entering this
/// state.
pub struct Ready;
// =============================================================================
// Controller shell
// =============================================================================
/// I3C controller: a plain owned value over the hardware driver, borrowing
/// the caller's configuration. No pinning or `'static` storage is required —
/// the ISR never holds a pointer into this object (see the module docs).
///
/// The configuration is **borrowed** (`&'c mut I3cConfig`), not owned: the
/// config embeds the device tables (~0.5 KiB), and the typestate transition
/// (`start(self) -> Self<Ready>`) moves the controller by value — owning the
/// config would transiently stack two copies inside one frame, which the
/// 2 KiB kernel bootstrap stack cannot afford. Borrowing keeps exactly one
/// config alive, wherever the caller placed it.
pub struct I3cController<'c, H: HardwareInterface, S = Uninitialized> {
hw: H,
config: &'c mut I3cConfig,
_state: PhantomData<S>,
}
impl<'c, H: HardwareInterface, S> I3cController<'c, H, S> {
/// Split-borrow helper for operations that drive `hw` with `config`.
#[inline]
fn parts(&mut self) -> (&mut H, &mut I3cConfig) {
(&mut self.hw, &mut *self.config)
}
/// Return this controller's bus number.
#[inline]
#[must_use]
pub fn bus_num(&self) -> u8 {
self.hw.bus_num()
}
}
impl<'c, H: HardwareInterface> I3cController<'c, H, Uninitialized> {
/// Bundle hardware and a borrowed configuration. No I/O, no registration.
#[must_use]
pub fn new(hw: H, config: &'c mut I3cConfig) -> Self {
Self {
hw,
config,
_state: PhantomData,
}
}
/// Bring the controller up: park this bus's ISR context in the registry
/// (single-shot per bus) and program the hardware.
///
/// The target/kernel owns the top-level interrupt vector; its ISR calls
/// [`dispatch_i3c_irq`](super::hardware::dispatch_i3c_irq), which services
/// the bus through the registered context. On return the device may
/// assert its IRQ line; nothing is delivered until the integration layer
/// unmasks the NVIC line it owns.
///
/// Returns [`I3cError::Busy`] if the bus's IRQ slot was already claimed by
/// another controller, or [`I3cError::Timeout`] if the hardware's initial
/// queue-reset poll timed out.
pub fn start(mut self) -> Result<I3cController<'c, H, Ready>, I3cError> {
let bus = self.hw.bus_num() as usize;
let ctx = self.hw.isr_ctx(self.config.is_secondary);
if !super::hardware::register_i3c_irq_handler(bus, ctx) {
return Err(I3cError::Busy);
}
if let Err(e) = self.hw.init(self.config) {
// Release the just-claimed slot, or every retry of `start()`
// would fail with `Busy` against a controller that never came up.
super::hardware::unregister_i3c_irq_handler(bus);
return Err(e);
}
// Memory barrier so init writes are visible before the integration
// layer unmasks the IRQ line.
cortex_m::asm::dmb();
Ok(I3cController {
hw: self.hw,
config: self.config,
_state: PhantomData,
})
}
}
// =============================================================================
// Bus operations (Ready)
// =============================================================================
impl<'c, H: HardwareInterface> I3cController<'c, H, Ready> {
// =========================================================================
// Device Management
// =========================================================================
/// Attach an I3C device to the bus
///
/// # Arguments
/// * `pid` - Provisional ID of the device
/// * `desired_da` - Desired dynamic address
/// * `slot` - DAT slot to use
pub fn attach_i3c_dev(&mut self, pid: u64, desired_da: u8, slot: u8) -> Result<(), I3cError> {
let (hw, config) = self.parts();
if desired_da == 0 || desired_da >= I3C_BROADCAST_ADDR {
return Err(I3cError::InvalidArgs);
}
// Bound the DAT slot: `by_pos` would silently ignore an out-of-range
// slot while the register facade aliases positions > 7 onto the last
// DAT register, corrupting whatever device lives there.
if usize::from(slot) >= super::constants::MAX_DEVICES_PER_BUS {
return Err(I3cError::InvalidArgs);
}
if config
.attached
.by_pos
.get(usize::from(slot))
.copied()
.flatten()
.is_some()
{
return Err(I3cError::DevAlreadyAttached);
}
let dev = DeviceEntry {
kind: DevKind::I3c,
pid: Some(pid),
static_addr: 0,
dyn_addr: desired_da,
desired_da,
bcr: 0,
dcr: 0,
maxrd: 0,
maxwr: 0,
mrl: 0,
mwl: 0,
max_ibi: 0,
ibi_en: false,
pos: Some(slot),
da_assigned: false,
};
let idx = config
.attached
.attach(dev)
.map_err(|_| I3cError::AddrInUse)?;
config
.attached
.map_pos(slot, u8::try_from(idx).map_err(|_| I3cError::InvalidArgs)?);
config.addrbook.mark_use(desired_da, true);
hw.attach_i3c_dev(slot.into(), desired_da)
.map_err(|_| I3cError::AddrInUse)
}
/// Detach an I3C device by DAT position
pub fn detach_i3c_dev(&mut self, pos: usize) {
let (hw, config) = self.parts();
// Release the dynamic address (parity with `detach_i3c_dev_by_idx`),
// or detaching by position would leak it in the address book forever.
let da = config
.attached
.by_pos
.get(pos)
.copied()
.flatten()
.and_then(|idx| config.attached.devices.get(usize::from(idx)))
.map(|dev| dev.dyn_addr);
if let Some(da) = da
&& da != 0
{
config.addrbook.mark_use(da, false);
}
config.attached.detach_by_pos(pos);
hw.detach_i3c_dev(pos);
}
/// Detach an I3C device by device index
pub fn detach_i3c_dev_by_idx(&mut self, dev_idx: usize) {
let (hw, config) = self.parts();
// `get` (not `[dev_idx]`) keeps this panic-free for the `no_panics`
// analysis; an out-of-range index is simply a no-op.
let Some(dev) = config.attached.devices.get(dev_idx) else {
return;
};
if dev.dyn_addr != 0 {
let dyn_addr = dev.dyn_addr;
config.addrbook.mark_use(dyn_addr, false);
}
let dev_pos = config.attached.devices.get(dev_idx).and_then(|dev| dev.pos);
if let Some(pos) = dev_pos {
hw.detach_i3c_dev(pos.into());
}
config.attached.detach(dev_idx);
}
// =========================================================================
// Bus Recovery
// =========================================================================
/// Recover the I3C bus from a stuck state
///
/// Performs bus recovery sequence:
/// 1. Enter software (bit-bang) mode
/// 2. Toggle SCL to clear stuck slaves
/// 3. Generate STOP condition
/// 4. Exit software mode
///
/// # Arguments
/// * `scl_toggles` - Number of SCL toggles (typically 9 to clear a stuck byte)
///
/// # When to Use
///
/// - Bus appears hung (transfers timing out)
/// - Device not responding after partial transfer
/// - After detecting SDA stuck low
///
/// # Example
///
/// ```rust,ignore
/// // Standard recovery with 9 SCL clocks
/// ctrl.recover_bus(9);
///
/// // More aggressive recovery
/// ctrl.recover_bus(18);
/// ```
pub fn recover_bus(&mut self, scl_toggles: u32) {
let (hw, _) = self.parts();
hw.enter_sw_mode();
hw.i3c_toggle_scl_in(scl_toggles);
hw.gen_internal_stop();
hw.exit_sw_mode();
}
/// Perform full bus recovery with controller reset
///
/// More aggressive recovery that also resets controller FIFOs:
/// 1. Bus recovery (SCL toggle + STOP)
/// 2. Reset TX/RX FIFOs
/// 3. Reset command queue
///
/// # Arguments
/// * `reset_mask` - Controller components to reset (use `RESET_CTRL_*` constants)
///
/// # Example
///
/// ```rust,ignore
/// use aspeed_rust::i3c_core::{RESET_CTRL_RX_FIFO, RESET_CTRL_TX_FIFO, RESET_CTRL_CMD_QUEUE};
///
/// // Full recovery with FIFO reset
/// let reset = RESET_CTRL_RX_FIFO | RESET_CTRL_TX_FIFO | RESET_CTRL_CMD_QUEUE;
/// ctrl.recover_bus_full(reset)?;
/// ```
///
/// # Errors
///
/// [`I3cError::Timeout`] if the controller reset bits did not self-clear —
/// the engine is wedged beyond what software recovery can fix.
pub fn recover_bus_full(&mut self, reset_mask: u32) -> Result<(), I3cError> {
self.recover_bus(8);
let (hw, _) = self.parts();
hw.reset_ctrl(reset_mask)
}
// =========================================================================
// Accessors
// =========================================================================
/// Allocate a dynamic address from `start_addr`.
#[inline]
pub fn alloc_dynamic_address_from(&mut self, start_addr: u8) -> Option<u8> {
let (_, config) = self.parts();
config.addrbook.alloc_from(start_addr)
}
/// Return the currently assigned target dynamic address, if any.
///
/// The address is assigned by the bus master and latched by the ISR into
/// the per-bus event block; the locally configured address (if any) is
/// the fallback.
#[inline]
#[must_use]
pub fn target_dynamic_address(&self) -> Option<u8> {
super::hardware::isr_events(self.hw.bus_num() as usize)
.dyn_addr()
.or_else(|| self.config.target_config.as_ref().and_then(|t| t.addr))
}
/// Set the device's IBI mandatory data byte and enable IBI delivery for `addr`.
pub fn enable_ibi(&mut self, addr: u8, mdb: u8) -> Result<(), I3cError> {
let (hw, config) = self.parts();
hw.set_ibi_mdb(mdb);
hw.ibi_enable(config, addr)
}
/// Disable IBI delivery for `addr` (DISEC + reject its SIRs).
pub fn disable_ibi(&mut self, addr: u8) -> Result<(), I3cError> {
let (hw, config) = self.parts();
hw.ibi_disable(config, addr)
}
/// Re-run the full hardware initialization on a live controller.
///
/// Recovery hammer for an engine wedged beyond what
/// [`recover_bus_full`](Self::recover_bus_full) can fix (the vendor C
/// driver's `target_rst_worker` equivalent). The ISR registration is left
/// untouched. Side effects: in target mode the dynamic address is dropped
/// (the bus master must re-run DAA) and SIRs are blocked until the next
/// DA assignment; in master mode the DAT slots of attached devices are
/// re-programmed from the bookkeeping (the bus targets keep their
/// addresses — only this controller was reset), but IBIs must be
/// re-enabled via [`enable_ibi`](Self::enable_ibi).
pub fn reinit(&mut self) -> Result<(), I3cError> {
let (hw, config) = self.parts();
hw.init(config)?;
for i in 0..config.attached.devices.len() {
let Some(dev) = config.attached.devices.get(i) else {
continue;
};
if let Some(pos) = dev.pos {
let _ = hw.attach_i3c_dev(pos.into(), dev.dyn_addr);
}
}
cortex_m::asm::dmb();
Ok(())
}
/// Issue a private read to `pid`, returning the number of received bytes.
pub fn priv_read(&mut self, pid: u64, out: &mut [u8]) -> Result<u32, I3cError> {
let (hw, config) = self.parts();
let actual_len = u32::try_from(out.len()).map_err(|_| I3cError::InvalidArgs)?;
let mut msgs = [I3cMsg {
buf: Some(out),
actual_len,
num_xfer: 0,
flags: super::constants::I3C_MSG_READ | super::constants::I3C_MSG_STOP,
hdr_mode: 0,
hdr_cmd_mode: 0,
}];
hw.priv_xfer(config, pid, &mut msgs)?;
Ok(msgs[0].actual_len)
}
/// Issue a private write to `pid`.
pub fn priv_write(&mut self, pid: u64, data: &mut [u8]) -> Result<(), I3cError> {
let (hw, config) = self.parts();
let actual_len = u32::try_from(data.len()).map_err(|_| I3cError::InvalidArgs)?;
let mut msgs = [I3cMsg {
buf: Some(data),
actual_len,
num_xfer: 0,
flags: super::constants::I3C_MSG_WRITE | super::constants::I3C_MSG_STOP,
hdr_mode: 0,
hdr_cmd_mode: 0,
}];
hw.priv_xfer(config, pid, &mut msgs)
}
/// Raise a hot-join request from the target side.
pub fn target_raise_hot_join(&mut self) -> Result<(), I3cError> {
let (hw, config) = self.parts();
hw.target_ibi_raise_hj(config)
}
// =========================================================================
// Master / Target operations (Delta D1)
// =========================================================================
//
// The reference exposed these through `proposed_traits::i3c_master::I3c`
// and the `proposed_traits` target traits (`aspeed-rust/src/i3c/hal_impl.rs`).
// That crate is unavailable in openprot and embedded-hal 1.0 defines no I3C
// trait, so — as the I2C port did for `proposed_traits::i2c_target` — the
// logic is preserved verbatim here as **inherent methods**. The only change
// is that `ErrorKind`-mapped errors become direct `I3cError` variants
// (`DynamicAddressConflict` -> `AddrInUse`, `InvalidCcc` -> `Invalid`).
/// Assign a dynamic address to the device at `static_address` via ENTDAA,
/// then read back PID/BCR and enable IBI. Returns the assigned address.
pub fn assign_dynamic_address(
&mut self,
static_address: SevenBitAddress,
) -> Result<SevenBitAddress, I3cError> {
let (hw, config) = self.parts();
let slot = config
.attached
.pos_of_addr(static_address)
.ok_or(I3cError::AddrInUse)?;
hw.do_entdaa(config, slot.into())
.map_err(|_| I3cError::AddrInUse)?;
let pid = ccc::ccc_getpid(hw, config, static_address).map_err(|_| I3cError::Invalid)?;
let dev_idx = config
.attached
.find_dev_idx_by_addr(static_address)
.ok_or(I3cError::Other)?;
let old_pid = config
.attached
.devices
.get(dev_idx)
.ok_or(I3cError::Other)?
.pid;
if let Some(op) = old_pid
&& pid != op
{
return Err(I3cError::Other);
}
let bcr = ccc::ccc_getbcr(hw, config, static_address).map_err(|_| I3cError::Invalid)?;
// DCR is informational — a device that NACKs GETDCR still works.
let dcr = ccc::ccc_getdcr(hw, config, static_address).unwrap_or(0);
{
let dev = config
.attached
.devices
.get_mut(dev_idx)
.ok_or(I3cError::Other)?;
dev.pid = Some(pid);
dev.bcr = bcr;
dev.dcr = dcr;
dev.da_assigned = true;
}
let dyn_addr: SevenBitAddress = config
.attached
.devices
.get(dev_idx)
.ok_or(I3cError::Other)?
.dyn_addr;
hw.ibi_enable(config, dyn_addr)
.map_err(|_| I3cError::Other)?;
Ok(dyn_addr)
}
/// Run dynamic address assignment for every attached I3C device.
///
/// Multi-device ENTDAA orchestration ported from the vendor C driver's
/// `aspeed_i3c_do_daa`. Walks the DAT slots that still need a verified
/// assignment, lets one device win each ENTDAA, reads its PID back and
/// corrects the two failure shapes the single-device
/// [`assign_dynamic_address`](Self::assign_dynamic_address) cannot:
///
/// - **Mis-assignment**: with several unassigned targets on the bus, any
/// of them may answer the ENTDAA issued for another device's slot (bus
/// arbitration picks the winner). The winner is moved to its own
/// expected address via SETNEWDA and the slot is retried for its
/// intended owner.
/// - **Unsolicited device**: a target whose PID matches no attached entry
/// is parked on a freshly allocated address so it stops answering
/// subsequent ENTDAAs.
///
/// Returns the number of devices verified in this run. Exits when ENTDAA
/// reports no more unassigned devices (NACK/timeout). IBIs are not
/// enabled here — call [`enable_ibi`](Self::enable_ibi) per device
/// afterwards.
pub fn bus_daa(&mut self) -> Result<u32, I3cError> {
let (hw, config) = self.parts();
let ndevs = config.attached.by_pos.len();
// DAT positions still needing a verified assignment.
let mut need: u32 = 0;
for idx in 0..config.attached.devices.len() {
let Some(dev) = config.attached.devices.get(idx) else {
continue;
};
if dev.kind == DevKind::I3c
&& dev.pid.is_some()
&& !dev.da_assigned
&& let Some(pos) = dev.pos
{
// pos < 8 enforced by attach_i3c_dev.
need |= 1u32 << pos;
}
}
let mut verified = 0u32;
let mut pos = 0usize;
// Hang guard only: every lap either clears a `need` bit, parks an
// unsolicited device (finite), or exits via the ENTDAA break below.
let mut budget = 8 * (ndevs as u32);
while need != 0 && budget != 0 {
budget -= 1;
if need & (1u32 << pos) == 0 {
pos = (pos + 1) % ndevs;
continue;
}
// The address the ENTDAA winner will latch: the DAT slot was
// programmed with its owner's desired address at attach time.
let Some(addr) = config
.attached
.by_pos
.get(pos)
.copied()
.flatten()
.and_then(|idx| config.attached.devices.get(usize::from(idx)))
.map(|d| d.desired_da)
else {
// Stale bit with no mapped device — drop it.
need &= !(1u32 << pos);
pos = (pos + 1) % ndevs;
continue;
};
if hw.do_entdaa(config, pos as u32).is_err() {
// NACK/timeout: nothing unassigned left on the bus.
break;
}
let Ok(pid) = ccc::ccc_getpid(hw, config, addr) else {
// Winner could not be identified; retry this slot next lap.
pos = (pos + 1) % ndevs;
continue;
};
let owner = config
.attached
.devices
.iter()
.position(|d| d.pid == Some(pid));
match owner {
Some(idx) => {
let expected = config
.attached
.devices
.get(idx)
.map_or(addr, |d| d.desired_da);
if expected == addr {
// The intended device answered its own slot.
let bcr = ccc::ccc_getbcr(hw, config, addr).unwrap_or(0);
let dcr = ccc::ccc_getdcr(hw, config, addr).unwrap_or(0);
if let Some(dev) = config.attached.devices.get_mut(idx) {
dev.bcr = bcr;
dev.dcr = dcr;
dev.da_assigned = true;
}
need &= !(1u32 << pos);
verified += 1;
} else if ccc::ccc_setnewda_bus_only(hw, config, addr, expected).is_ok() {
// Wrong device won this slot: it now sits on its own
// expected address (its own DAT slot already holds
// that address), so it is done...
let bcr = ccc::ccc_getbcr(hw, config, expected).unwrap_or(0);
let dcr = ccc::ccc_getdcr(hw, config, expected).unwrap_or(0);
if let Some(dev) = config.attached.devices.get_mut(idx) {
dev.bcr = bcr;
dev.dcr = dcr;
dev.da_assigned = true;
}
if let Some(own_pos) = config
.attached
.pos_of(idx)
.or_else(|| config.attached.devices.get(idx).and_then(|d| d.pos))
{
need &= !(1u32 << u32::from(own_pos));
}
verified += 1;
// ...and this slot's bit stays set so its intended
// owner gets the next ENTDAA here.
}
}
None => {
// Unknown PID: park it on a fresh address so it stops
// answering ENTDAA for slots it does not own.
let Some(park) = config.addrbook.alloc_from(8) else {
break;
};
config.addrbook.mark_use(park, true);
if ccc::ccc_setnewda_bus_only(hw, config, addr, park).is_err() {
config.addrbook.mark_use(park, false);
}
// Retry this slot without advancing.
continue;
}
}
pos = (pos + 1) % ndevs;
}
Ok(verified)
}
/// Acknowledge an IBI from `address` (validates the device is known).
pub fn acknowledge_ibi(&mut self, address: SevenBitAddress) -> Result<(), I3cError> {
let (_, config) = self.parts();
let dev_idx = config
.attached
.find_dev_idx_by_addr(address)
.ok_or(I3cError::Other)?;
// `get` (not `[dev_idx]`) keeps this panic-free for the `no_panics`
// analysis; `find_dev_idx_by_addr` already returns a valid index.
let dev = config
.attached
.devices
.get(dev_idx)
.ok_or(I3cError::Other)?;
if dev.pid.is_none() {
return Err(I3cError::Other);
}
Ok(())
}
/// Hot-join handler hook. Call [`assign_dynamic_address`](Self::assign_dynamic_address)
/// after receiving a hot-join IBI; nothing else is required here.
#[allow(clippy::unused_self)]
pub fn handle_hot_join(&mut self) -> Result<(), I3cError> {
Ok(())
}
/// Bus speed is fixed on the AST1060 controller; this is a no-op.
#[allow(clippy::unused_self)]
pub fn set_bus_speed(&mut self) -> Result<(), I3cError> {
Ok(())
}
/// The AST1060 controller does not support multi-master; this is a no-op.
#[allow(clippy::unused_self)]
pub fn request_mastership(&mut self) -> Result<(), I3cError> {
Ok(())
}
// --- Target (secondary) mode callbacks ---
/// Initialize target mode with `own_addr` (sets the static/target address).
pub fn target_init(&mut self, own_addr: u8) {
let (_, config) = self.parts();
if let Some(t) = config.target_config.as_mut() {
if t.addr.is_none() {
t.addr = Some(own_addr);
}
} else {
config.target_config =
Some(I3cTargetConfig::new(0, Some(own_addr), /* mdb */ 0xae));
}
}
/// Returns `true` if `addr` matches this target's assigned address.
#[must_use]
pub fn target_on_address_match(&self, addr: u8) -> bool {
self.target_dynamic_address() == Some(addr)
}
/// Record that the controller assigned this target a dynamic address; SIRs
/// are then permitted by software. Also syncs the ISR-latched address into
/// the thread-owned target config.
///
/// **Timing caveat (vendor C driver parity):** the C driver delays this
/// permission by one second after the DA assignment (`target_worker`),
/// because a controller that has not yet finished ENTDAA/DISEC sequencing
/// can be confused by an immediate SIR. This port has no timer, so the
/// caller owns that delay — wait ~1 s after the `TargetDaAssignment` work
/// item before calling this if the bus master is slow to settle.
pub fn target_on_dynamic_address_assigned(&mut self) {
let da = super::hardware::isr_events(self.hw.bus_num() as usize).dyn_addr();
if let (Some(da), Some(tc)) = (da, self.config.target_config.as_mut()) {
tc.addr = Some(da);
}
self.config.sir_allowed_by_sw = true;
}
/// This target always wants to raise IBIs when it has data.
#[must_use]
#[allow(clippy::unused_self)]
pub fn target_wants_ibi(&self) -> bool {
true
}
/// Build and submit the IBI payload `[mdb, crc8_ccitt(addr_rnw, mdb)]` for a
/// pending target read, returning the number of bytes made available.
pub fn target_get_ibi_payload(&mut self, buffer: &mut [u8]) -> Result<usize, I3cError> {
let (hw, config) = self.parts();
let (da, mdb) = match config.target_config.as_ref() {
Some(t) => (
match t.addr {
Some(da) => da,
None => return Ok(0),
},
t.mdb,
),
None => return Ok(0),
};
let addr_rnw = (da << 1) | 0x1;
let mut crc = crc8_ccitt(0, &[addr_rnw]);
crc = crc8_ccitt(crc, &[mdb]);
let payload = [mdb, crc];
let mut ibi = I3cIbi {
ibi_type: I3cIbiType::TargetIntr,
payload: Some(&payload),
};
let rc = hw.target_pending_read_notify(config, buffer, &mut ibi);
match rc {
Ok(()) => Ok(buffer.len() + payload.len()),
_ => Ok(0),
}
}
}
/// CRC-8 CCITT calculation (ported from `hal_impl.rs`).
#[inline]
fn crc8_ccitt(mut crc: u8, data: &[u8]) -> u8 {
for &b in data {
let mut x = crc ^ b;
for _ in 0..8 {
x = if (x & 0x80) != 0 {
(x << 1) ^ 0x07
} else {
x << 1
};
}
crc = x;
}
crc
}