blob: 98e639ed70c60cf02dd938375a7d4fcb088d4a37 [file]
// Licensed under the Apache-2.0 license
// SPDX-License-Identifier: Apache-2.0
//! I3C Hardware Interface
//!
//! Defines the hardware abstraction traits and IRQ handling infrastructure.
//!
//! # Trait Hierarchy
//!
//! The hardware interface is split into focused sub-traits:
//!
//! ```text
//! HardwareInterface (supertrait)
//! ├── HardwareCore - Init, IRQ, enable/disable
//! ├── HardwareClock - Clock configuration
//! ├── HardwareFifo - FIFO operations
//! ├── HardwareTransfer - Transfers, CCC, device management
//! ├── HardwareRecovery - SW mode, bus recovery
//! └── HardwareTarget - Target mode operations
//! ```
//!
//! # Platform Initialization
//!
//! SCU operations (clock enable, reset control) are **not** part of these traits.
//! They should be performed by the platform/board layer before creating the
//! I3C controller.
use core::cell::UnsafeCell;
use critical_section::Mutex;
use super::ccc::{CccPayload, ccc_events_set};
use super::config::{I3C_MIN_CORE_CLK_SDR, I3cConfig};
use super::constants::{
CM_TFR_STS_MASTER_HALT, CM_TFR_STS_TARGET_HALT, COMMAND_ATTR_ADDR_ASSGN_CMD,
COMMAND_ATTR_SLAVE_DATA_CMD, COMMAND_ATTR_XFER_ARG, COMMAND_ATTR_XFER_CMD,
COMMAND_PORT_ARG_DATA_LEN, COMMAND_PORT_ARG_DB, COMMAND_PORT_ATTR, COMMAND_PORT_CMD,
COMMAND_PORT_CP, COMMAND_PORT_DBP, COMMAND_PORT_DEV_COUNT, COMMAND_PORT_DEV_INDEX,
COMMAND_PORT_READ_TRANSFER, COMMAND_PORT_ROC, COMMAND_PORT_SPEED, COMMAND_PORT_TID,
COMMAND_PORT_TOC, DEV_ADDR_TABLE_IBI_MDB, DEV_ADDR_TABLE_IBI_PEC, DEV_ADDR_TABLE_SIR_REJECT,
I3C_AST10X0_MIPI_MANUF_ID, I3C_BCR_IBI_PAYLOAD_HAS_DATA_BYTE, I3C_BUS_FREE_TIMING_RESET,
I3C_BUS_I2C_FM_TF_MAX_NS, I3C_BUS_I2C_FM_THIGH_MIN_NS, I3C_BUS_I2C_FM_TLOW_MIN_NS,
I3C_BUS_I2C_FM_TR_MAX_NS, I3C_BUS_I2C_FMP_TF_MAX_NS, I3C_BUS_I2C_FMP_THIGH_MIN_NS,
I3C_BUS_I2C_FMP_TLOW_MIN_NS, I3C_BUS_I2C_FMP_TR_MAX_NS, I3C_BUS_I2C_STD_TF_MAX_NS,
I3C_BUS_I2C_STD_THIGH_MIN_NS, I3C_BUS_I2C_STD_TLOW_MIN_NS, I3C_BUS_I2C_STD_TR_MAX_NS,
I3C_BUS_THIGH_MAX_NS, I3C_CCC_DEVCTRL, I3C_CCC_ENTDAA, I3C_CCC_EVT_INTR, I3C_CCC_SETHID,
I3C_CTRL_POLL_DELAY_NS, I3C_DEFAULT_STATIC_ADDR, I3C_GLOBAL_RESET_DEASSERT_MASK,
I3C_IBI_DATA_THRESHOLD_MAX, I3C_INIT_POLL_DELAY_NS, I3C_INTR_STATUS_ALL_BITS, I3C_MSG_READ,
I3C_OP_TIMEOUT_US, I3C_POLL_MAX_ITERS, I3CG_REG1_SCL_IN_SW_MODE_EN,
I3CG_REG1_SCL_IN_SW_MODE_VAL, I3CG_REG1_SDA_IN_SW_MODE_EN, I3CG_REG1_SDA_IN_SW_MODE_VAL,
IBIQ_STATUS_IBI_DATA_LEN, IBIQ_STATUS_IBI_DATA_LEN_SHIFT, IBIQ_STATUS_IBI_ID,
IBIQ_STATUS_IBI_ID_SHIFT, INTR_CCC_UPDATED_STAT, INTR_DYN_ADDR_ASSGN_STAT, INTR_IBI_THLD_STAT,
INTR_RESP_READY_STAT, INTR_TRANSFER_ABORT_STAT, INTR_TRANSFER_ERR_STAT, MAX_CMDS, NSEC_PER_SEC,
RESET_CTRL_ALL, RESET_CTRL_QUEUES, RESET_CTRL_XFER_QUEUES, RESPONSE_ERROR_IBA_NACK,
RESPONSE_PORT_DATA_LEN_MASK, RESPONSE_PORT_DATA_LEN_SHIFT, RESPONSE_PORT_ERR_STATUS_MASK,
RESPONSE_PORT_ERR_STATUS_SHIFT, RESPONSE_PORT_TID_MASK, RESPONSE_PORT_TID_SHIFT,
SDA_TX_HOLD_MASK, SDA_TX_HOLD_MAX, SDA_TX_HOLD_MIN, SLV_DCR_MASK, SLV_EVENT_CTRL_SIR_EN, bit,
field_get, field_prep,
};
use super::error::I3cError as I3cDrvError;
use super::error::I3cError;
use super::ibi as ibi_workq;
use super::types::{I3cCmd, I3cIbi, I3cMsg, I3cXfer, SpeedI3c, Tid};
use super::registers::I3cRegisters;
use core::sync::atomic::Ordering;
// =============================================================================
// IRQ Handler Infrastructure
// =============================================================================
#[derive(Clone, Copy)]
struct Handler {
func: fn(usize),
ctx: usize,
}
// `UnsafeCell` (not `RefCell`) for the same reason as `IBI_RINGS` in `ibi.rs`:
// mutual exclusion comes from the critical section, and the access helpers
// below are leaf functions (no caller code runs while the reference is live),
// so the `RefCell` runtime borrow flag would only add a reachable panic path
// that the `no_panics` analysis must reject.
static BUS_HANDLERS: [Mutex<UnsafeCell<Option<Handler>>>; 4] = [
Mutex::new(UnsafeCell::new(None)),
Mutex::new(UnsafeCell::new(None)),
Mutex::new(UnsafeCell::new(None)),
Mutex::new(UnsafeCell::new(None)),
];
/// Register an IRQ handler for an I3C bus.
///
/// Single-shot per bus: the first registration claims the slot for the
/// program's lifetime, mirroring the one-controller-per-physical-bus contract
/// of [`Ast1060I3c::new`]. Returns `false` (and leaves the existing handler in
/// place) if `bus` is out of range or the slot is already claimed.
///
/// # Arguments
/// * `bus` - Bus index (0-3)
/// * `func` - Handler function
/// * `ctx` - Context value passed to handler
#[must_use]
pub fn register_i3c_irq_handler(bus: usize, func: fn(usize), ctx: usize) -> bool {
let Some(slot) = BUS_HANDLERS.get(bus) else {
return false;
};
critical_section::with(|cs| {
// SAFETY: the critical section excludes ISR/thread concurrency, and
// the `&mut` never escapes this leaf function, so this is the only
// live reference to the slot.
let handler: &mut Option<Handler> = unsafe { &mut *slot.borrow(cs).get() };
if handler.is_some() {
return false;
}
*handler = Some(Handler { func, ctx });
true
})
}
/// NVIC interrupt line for an I3C bus, if the bus exists.
///
/// The driver does not touch the NVIC (Delta D6): the kernel/integration layer
/// owns the top-level vector *and* the line mask. After registering a handler
/// and initializing the hardware, the integration layer uses this mapping to
/// unmask (and, on teardown, mask) the line it owns.
#[must_use]
pub const fn i3c_bus_interrupt(bus: u8) -> Option<ast1060_pac::Interrupt> {
match bus {
0 => Some(ast1060_pac::Interrupt::i3c),
1 => Some(ast1060_pac::Interrupt::i3c1),
2 => Some(ast1060_pac::Interrupt::i3c2),
3 => Some(ast1060_pac::Interrupt::i3c3),
_ => None,
}
}
/// Dispatch IRQ for a specific bus
///
/// Called by the actual IRQ entry points (defined elsewhere to avoid symbol conflicts).
#[inline]
pub fn dispatch_i3c_irq(bus: usize) {
// Copy handler out of critical section to avoid blocking IRQs during handler
let handler = critical_section::with(|cs| {
// SAFETY: the critical section excludes the writer
// (`register_i3c_irq_handler`); `Handler` is `Copy`, so the value is
// copied out and no reference escapes.
BUS_HANDLERS.get(bus).and_then(|m| unsafe { *m.borrow(cs).get() })
});
if let Some(h) = handler {
(h.func)(h.ctx);
}
}
// IRQ entry points - defined in src/i3c/ module to avoid symbol conflicts.
// Use register_i3c_irq_handler() to register handlers that will be called
// from those entry points.
// =============================================================================
// Sub-trait: Core Operations
// =============================================================================
/// Core hardware operations: init, IRQ, enable/disable
pub trait HardwareCore {
/// Initialize the I3C controller hardware
fn init(&mut self, config: &mut I3cConfig);
/// Get the bus number for this instance
fn bus_num(&self) -> u8;
/// Enable the I3C controller
fn i3c_enable(&mut self, config: &I3cConfig);
/// Disable the I3C controller
fn i3c_disable(&mut self, is_secondary: bool);
/// Set the controller role (primary/secondary)
fn set_role(&mut self, is_secondary: bool);
/// Main ISR handler
fn i3c_aspeed_isr(&mut self, config: &mut I3cConfig);
}
// =============================================================================
// Sub-trait: Clock Configuration
// =============================================================================
/// Clock and timing configuration
pub trait HardwareClock {
/// Initialize clock timing parameters
///
/// Implementations should use `config.core_clk_hz` if set, falling back
/// to [`get_clock_rate()`](Self::get_clock_rate) for auto-detection.
fn init_clock(&mut self, config: &mut I3cConfig);
/// Calculate I2C clock dividers for given SCL frequency
fn calc_i2c_clk(&mut self, fscl_hz: u32) -> (u32, u32);
/// Initialize the PID (Provisional ID) for this controller
fn init_pid(&mut self, config: &mut I3cConfig);
}
// =============================================================================
// Sub-trait: FIFO Operations
// =============================================================================
/// FIFO read/write operations
pub trait HardwareFifo {
/// Write to TX FIFO
fn wr_tx_fifo(&mut self, bytes: &[u8]);
/// Read `out.len()` bytes from the RX FIFO
fn rd_rx_fifo(&mut self, out: &mut [u8]);
/// Read `out.len()` bytes from the IBI FIFO
fn rd_ibi_fifo(&mut self, out: &mut [u8]);
}
// =============================================================================
// Sub-trait: Transfer Operations
// =============================================================================
/// Transfer, CCC, and device management operations
pub trait HardwareTransfer {
/// Set the IBI Mandatory Data Byte
fn set_ibi_mdb(&mut self, mdb: u8);
/// Exit halt state
fn exit_halt(&mut self, config: &mut I3cConfig);
/// Enter halt state
fn enter_halt(&mut self, by_sw: bool, config: &mut I3cConfig);
/// Reset controller components (FIFOs, queues, etc.)
fn reset_ctrl(&mut self, reset: u32);
/// Enable IBI for a device
fn ibi_enable(&mut self, config: &mut I3cConfig, addr: u8) -> Result<(), I3cError>;
/// Start a transfer
fn start_xfer(&mut self, config: &mut I3cConfig, xfer: &mut I3cXfer);
/// End a transfer
fn end_xfer(&mut self, config: &mut I3cConfig);
/// Get DAT position for an address
fn get_addr_pos(&mut self, config: &I3cConfig, addr: u8) -> Option<u8>;
/// Detach a device by DAT position
fn detach_i3c_dev(&mut self, pos: usize);
/// Attach a device to a DAT position
fn attach_i3c_dev(&mut self, pos: usize, addr: u8) -> Result<(), I3cError>;
/// Execute a CCC
fn do_ccc(&mut self, config: &mut I3cConfig, ccc: &mut CccPayload) -> Result<(), I3cError>;
/// Execute ENTDAA (Enter Dynamic Address Assignment)
fn do_entdaa(&mut self, config: &mut I3cConfig, index: u32) -> Result<(), I3cError>;
/// Build commands for private transfer
fn priv_xfer_build_cmds<'a>(
&mut self,
cmds: &mut [I3cCmd<'a>],
msgs: &mut [I3cMsg<'a>],
pos: u8,
) -> Result<(), I3cError>;
/// Execute a private transfer
fn priv_xfer(
&mut self,
config: &mut I3cConfig,
pid: u64,
msgs: &mut [I3cMsg],
) -> Result<(), I3cError>;
/// Handle IBI SIR (Slave Interrupt Request)
fn handle_ibi_sir(&mut self, config: &mut I3cConfig, addr: u8, len: usize);
/// Handle all pending IBIs
fn handle_ibis(&mut self, config: &mut I3cConfig);
}
// =============================================================================
// Sub-trait: Recovery / Software Mode
// =============================================================================
/// Software mode and bus recovery operations
pub trait HardwareRecovery {
/// Enter software mode for manual bus control
fn enter_sw_mode(&mut self);
/// Exit software mode
fn exit_sw_mode(&mut self);
/// Toggle SCL line in software mode
fn i3c_toggle_scl_in(&mut self, count: u32);
/// Generate an internal STOP condition
fn gen_internal_stop(&mut self);
/// Calculate even parity for a byte
fn even_parity(byte: u8) -> bool;
}
// =============================================================================
// Sub-trait: Target Mode Operations
// =============================================================================
/// Target (secondary) mode operations
pub trait HardwareTarget {
/// Write data to target TX buffer
fn target_tx_write(&mut self, buf: &[u8]);
/// Raise a Hot-Join IBI (target mode)
fn target_ibi_raise_hj(&self, config: &mut I3cConfig) -> Result<(), I3cError>;
/// Handle response ready in target mode
fn target_handle_response_ready(&mut self, config: &mut I3cConfig);
/// Notify pending read in target mode
fn target_pending_read_notify(
&mut self,
config: &mut I3cConfig,
buf: &[u8],
notifier: &mut I3cIbi,
) -> Result<(), I3cError>;
/// Handle CCC update in target mode
fn target_handle_ccc_update(&mut self, config: &mut I3cConfig);
}
// =============================================================================
// Supertrait: Full Hardware Interface
// =============================================================================
/// Complete hardware abstraction for I3C controllers
///
/// This is a supertrait combining all sub-traits. Implementors must provide
/// all operations.
///
/// # Sub-traits
///
/// - [`HardwareCore`] - Init, IRQ, enable/disable
/// - [`HardwareClock`] - Clock configuration
/// - [`HardwareFifo`] - FIFO operations
/// - [`HardwareTransfer`] - Transfers, CCC, device management
/// - [`HardwareRecovery`] - SW mode, bus recovery
/// - [`HardwareTarget`] - Target mode operations
pub trait HardwareInterface:
HardwareCore + HardwareClock + HardwareFifo + HardwareTransfer + HardwareRecovery + HardwareTarget
{
}
// Blanket implementation: any type implementing all sub-traits implements HardwareInterface
impl<T> HardwareInterface for T where
T: HardwareCore
+ HardwareClock
+ HardwareFifo
+ HardwareTransfer
+ HardwareRecovery
+ HardwareTarget
{
}
/// I3C bus 0 interrupt handler - call this from your ISR
#[inline]
pub fn i3c_irq_handler() {
dispatch_i3c_irq(0);
}
/// I3C bus 1 interrupt handler - call this from your ISR
#[inline]
pub fn i3c1_irq_handler() {
dispatch_i3c_irq(1);
}
/// I3C bus 2 interrupt handler - call this from your ISR
#[inline]
pub fn i3c2_irq_handler() {
dispatch_i3c_irq(2);
}
/// I3C bus 3 interrupt handler - call this from your ISR
#[inline]
pub fn i3c3_irq_handler() {
dispatch_i3c_irq(3);
}
// Delta D6: the reference's `#[cfg(feature = "isr-handlers")] #[no_mangle]
// extern "C" fn i3c{,1,2,3}()` symbol exports are dropped here. openprot is the
// kernel-integration target: the kernel owns the interrupt vector and calls
// `dispatch_i3c_irq(bus)` (via the `i3c*_irq_handler` helpers above), which is
// exactly the case the reference gated those exports OFF for. Carrying a
// never-enabled `isr-handlers` feature would only risk a symbol clash with the
// kernel ISR and an `unexpected_cfgs` lint, with no observable difference in
// the deployed (feature-off) build.
/// Concrete AST1060 I3C hardware implementation: the per-bus
/// [`I3cRegisters`] façade (Delta D3 — all MMIO `unsafe` confined there)
/// plus a Cooperative-Yield wait policy (Delta D2).
///
/// One driver type manages any of the bus instances — the bus is selected at
/// **runtime** in [`new`](Self::new), so several controllers (one per bus)
/// share this single type. `Y` is the caller-injected yield closure invoked
/// between completion polls (see [`super::types::Completion::wait_for_us`]);
/// pass `|_| core::hint::spin_loop()` for a bare-metal busy-wait.
///
/// Not `Copy`/`Clone`: this value owns the (also non-`Copy`) registers
/// wrapper, so bus exclusivity follows from ownership.
pub struct Ast1060I3c<Y: FnMut(u32)> {
regs: I3cRegisters,
/// Cooperative yield hook invoked between status polls. Argument is the
/// suggested wait window in nanoseconds (advisory). Private so external
/// code cannot swap the wait policy out from under an active driver.
yield_fn: Y,
}
impl<Y: FnMut(u32)> Ast1060I3c<Y> {
/// Create the I3C hardware driver for `bus` (0..=3). Returns `None` if
/// `bus` is out of range.
///
/// # Safety
///
/// Delegates the [`I3cRegisters::new`] contract — the entire MMIO
/// `unsafe` perimeter:
/// - the AST1060 PAC singleton pointers are valid for the program's
/// lifetime (they are on AST1060 hardware);
/// - access to the returned instance is serialized by the caller (the
/// device is `!Sync`); only one `Ast1060I3c` per physical bus may be
/// active at a time.
pub unsafe fn new(bus: u8, yield_fn: Y) -> Option<Self> {
// SAFETY: forwarded — see this function's contract above.
let regs = unsafe { I3cRegisters::new(bus) }?;
Some(Self { regs, yield_fn })
}
/// Bus index this driver was constructed for.
#[inline]
fn bus(&self) -> u8 {
self.regs.bus()
}
}
/// Debug logging is dropped in the openprot port (Delta D4): the reference's
/// `Logger`/`heapless::String` path is removed. This no-op still evaluates the
/// format arguments (via `format_args!`) so the surrounding `let reg = …`
/// bindings stay "used", but performs no formatting or I/O. The leading
/// `$logger` fragment is captured and ignored (never expanded), so the absent
/// `logger` field is never referenced.
macro_rules! i3c_debug {
($logger:expr, $($arg:tt)*) => {{
let _ = format_args!($($arg)*);
}};
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum PollError {
Timeout,
}
/// Bounded poll loop (Cooperative-Yield Bounded-Poll Device, Delta D2).
///
/// The reference took a `&mut D: DelayNs`; here the wait policy is the
/// caller-injected, type-erased `yield_fn`, invoked once per non-completing
/// poll with an advisory wait window (`delay_ns`). Exhausting `max_iters`
/// returns a typed [`PollError::Timeout`] — never an unbounded spin.
pub fn poll_with_timeout<F, C>(
mut read_reg: F,
mut condition: C,
yield_fn: &mut dyn FnMut(u32),
delay_ns: u32,
max_iters: u32,
) -> Result<u32, PollError>
where
F: FnMut() -> u32,
C: FnMut(u32) -> bool,
{
for _ in 0..max_iters {
let val = read_reg();
if condition(val) {
return Ok(val);
}
yield_fn(delay_ns);
}
Err(PollError::Timeout)
}
impl<Y: FnMut(u32)> Ast1060I3c<Y> {
fn toggle_scl_in(&mut self, count: u32) {
for _ in 0..count {
self.regs.i3cg_reg1_clear_bits(I3CG_REG1_SCL_IN_SW_MODE_VAL);
self.regs.i3cg_reg1_set_bits(I3CG_REG1_SCL_IN_SW_MODE_VAL);
}
}
fn gen_internal_stop(&mut self) {
self.regs.i3cg_reg1_clear_bits(I3CG_REG1_SCL_IN_SW_MODE_VAL);
self.regs.i3cg_reg1_clear_bits(I3CG_REG1_SDA_IN_SW_MODE_VAL);
self.regs.i3cg_reg1_set_bits(I3CG_REG1_SCL_IN_SW_MODE_VAL);
self.regs.i3cg_reg1_set_bits(I3CG_REG1_SDA_IN_SW_MODE_VAL);
}
fn enter_sw_mode(&mut self) {
i3c_debug!(self.logger, "enter sw mode");
let mut reg = self.regs.i3cg_read_reg1();
reg |= I3CG_REG1_SCL_IN_SW_MODE_VAL | I3CG_REG1_SDA_IN_SW_MODE_VAL;
self.regs.i3cg_reg1_overwrite(reg);
reg |= I3CG_REG1_SCL_IN_SW_MODE_EN | I3CG_REG1_SDA_IN_SW_MODE_EN;
self.regs.i3cg_reg1_overwrite(reg);
}
fn exit_sw_mode(&mut self) {
let mut reg = self.regs.i3cg_read_reg1();
reg &= !(I3CG_REG1_SCL_IN_SW_MODE_EN | I3CG_REG1_SDA_IN_SW_MODE_EN);
self.regs.i3cg_reg1_overwrite(reg);
}
}
impl<Y: FnMut(u32)> HardwareCore for Ast1060I3c<Y> {
fn init(&mut self, config: &mut I3cConfig) {
i3c_debug!(self.logger, "i3c init");
self.regs
.global_reset_deassert(I3C_GLOBAL_RESET_DEASSERT_MASK);
self.regs.i3cg_program_reg1(I3C_DEFAULT_STATIC_ADDR);
let reg = self.regs.i3cg_read_reg1();
i3c_debug!(self.logger, "i3cg_reg1: {:#x}", reg);
self.regs.i3cg_write_reg0(0x0);
let reg = self.regs.i3cg_read_reg0();
i3c_debug!(self.logger, "i3cg_reg0: {:#x}", reg);
self.regs.core_reset_assert();
self.regs.clock_on();
self.regs.core_reset_deassert();
self.i3c_disable(config.is_secondary);
i3c_debug!(
self.logger,
"bus num: {}, is_secondary: {}",
self.bus(),
config.is_secondary
);
self.regs.assert_all_queue_resets();
let regs = &self.regs;
let _ = poll_with_timeout(
|| regs.read_reset_ctrl(),
|val| val == 0,
&mut self.yield_fn,
I3C_INIT_POLL_DELAY_NS,
I3C_POLL_MAX_ITERS,
);
self.set_role(config.is_secondary);
self.init_clock(config);
self.regs.clear_intr_status(I3C_INTR_STATUS_ALL_BITS);
if config.is_secondary {
self.regs.enable_target_irqs();
} else {
self.regs.enable_master_irqs();
}
config.sir_allowed_by_sw = false;
self.regs
.set_ibi_data_threshold(I3C_IBI_DATA_THRESHOLD_MAX);
self.regs.set_rx_buf_threshold(0);
self.init_pid(config);
config.maxdevs = self.regs.dat_depth();
config.free_pos = if config.maxdevs == 32 {
u32::MAX
} else {
(1u32 << config.maxdevs) - 1
};
config.need_da = 0;
for i in 0..(config.maxdevs) {
self.regs.dat_set_reject(i.into());
}
self.regs.write_mr_reject(I3C_INTR_STATUS_ALL_BITS);
self.regs.write_sir_reject(I3C_INTR_STATUS_ALL_BITS);
self.regs.set_hot_join_nack(true);
if config.is_secondary {
self.regs.program_secondary_static_addr(9);
} else {
self.regs.program_primary_dynamic_addr(8);
}
self.i3c_enable(config);
i3c_debug!(self.logger, "i3c enabled");
if !config.is_secondary {
self.regs.enable_ibi_thld_irq();
}
self.regs.set_hot_join_nack(false);
i3c_debug!(self.logger, "i3c init done");
// Safety: Ensure memory barrier and init completion before interrupts are enabled by the caller
core::sync::atomic::compiler_fence(Ordering::SeqCst);
}
fn bus_num(&self) -> u8 {
self.bus()
}
fn i3c_disable(&mut self, is_secondary: bool) {
i3c_debug!(self.logger, "i3c disable");
if !self.regs.controller_enabled() {
return;
}
if is_secondary {
self.enter_sw_mode();
}
self.regs.disable_controller();
if is_secondary {
self.toggle_scl_in(8);
self.gen_internal_stop();
self.exit_sw_mode();
}
}
fn i3c_enable(&mut self, config: &I3cConfig) {
i3c_debug!(self.logger, "i3c enable");
if config.is_secondary {
i3c_debug!(self.logger, "i3c enable as secondary");
self.regs.write_slv_event_ctrl(0);
self.enter_sw_mode();
self.regs.enable_controller_secondary();
let wait_cnt = self.regs.ibi_free_cycles();
let wait_ns = wait_cnt * config.core_period;
(self.yield_fn)(wait_ns * 100_u32);
self.toggle_scl_in(8);
if self.regs.controller_enabled() {
self.gen_internal_stop();
}
self.exit_sw_mode();
} else {
self.regs.enable_controller_primary();
}
}
fn set_role(&mut self, is_secondary: bool) {
self.regs.set_dev_op_mode(u8::from(is_secondary));
}
fn i3c_aspeed_isr(&mut self, config: &mut I3cConfig) {
let status = self.regs.read_intr_status();
i3c_debug!(self.logger, "[ISR] 0x{:08x}", status);
if status == 0 {
return;
}
if config.is_secondary {
if status & INTR_DYN_ADDR_ASSGN_STAT != 0 {
let da = self.regs.dynamic_addr();
if let Some(tc) = &mut config.target_config {
tc.addr = Some(da);
}
let _ = ibi_workq::i3c_ibi_work_enqueue_target_da_assignment(self.bus().into());
}
if (status & INTR_RESP_READY_STAT) != 0 {
self.target_handle_response_ready(config);
}
if (status & INTR_CCC_UPDATED_STAT) != 0 {
self.target_handle_ccc_update(config);
}
} else {
if (status & (INTR_RESP_READY_STAT | INTR_TRANSFER_ERR_STAT | INTR_TRANSFER_ABORT_STAT))
!= 0
{
self.end_xfer(config);
}
if (status & INTR_IBI_THLD_STAT) != 0 {
self.handle_ibis(config);
}
}
self.regs.clear_intr_status(status);
}
}
impl<Y: FnMut(u32)> HardwareClock for Ast1060I3c<Y> {
fn init_clock(&mut self, config: &mut I3cConfig) {
// `unwrap_or` + `.max(1)` (not `.expect()` / raw divides) keep this
// panic-free for the `no_panics` analysis: a missing/zero core clock
// cannot trigger an `expect` panic or a divide-by-zero. For a valid
// config the values are unchanged. `period` is a local clamped `>= 1`
// so the compiler proves every `div_ceil(period)` divisor non-zero.
let clk_rate = config.core_clk_hz.unwrap_or(I3C_MIN_CORE_CLK_SDR).max(1);
i3c_debug!(self.logger, "i3c clock rate: {} Hz", clk_rate);
config.core_period = (NSEC_PER_SEC).div_ceil(clk_rate);
let period = config.core_period.max(1);
let ns_to_cnt_u8 = |ns: u32| -> u8 { u8::try_from(ns.div_ceil(period)).unwrap_or(u8::MAX) };
let ns_to_cnt_u16 =
|ns: u32| -> u16 { u16::try_from(ns.div_ceil(period)).unwrap_or(u16::MAX) };
// I2C FM
let (fm_hi_ns, fm_lo_ns) = self.calc_i2c_clk(config.i2c_scl_hz);
self.regs
.set_i2c_fm_timing(ns_to_cnt_u16(fm_hi_ns), ns_to_cnt_u16(fm_lo_ns));
// I2C FMP
let (i2c_fmp_hi_ns, i2c_fmp_lo_ns) = self.calc_i2c_clk(1_000_000);
self.regs
.set_i2c_fmp_timing(ns_to_cnt_u8(i2c_fmp_hi_ns), ns_to_cnt_u16(i2c_fmp_lo_ns));
// I3C OD
let (od_hi_ns, od_lo_ns) =
if config.i3c_od_scl_hi_period_ns != 0 && config.i3c_od_scl_lo_period_ns != 0 {
(
config.i3c_od_scl_hi_period_ns,
config.i3c_od_scl_lo_period_ns,
)
} else {
(i2c_fmp_hi_ns, i2c_fmp_lo_ns)
};
self.regs
.set_od_timing(ns_to_cnt_u8(od_hi_ns), ns_to_cnt_u8(od_lo_ns));
// I3C PP
let (i3c_pp_hi_ns, i3c_pp_lo_ns) =
if config.i3c_pp_scl_hi_period_ns != 0 && config.i3c_pp_scl_lo_period_ns != 0 {
(
config.i3c_pp_scl_hi_period_ns,
config.i3c_pp_scl_lo_period_ns,
)
} else {
let total_ns = NSEC_PER_SEC.div_ceil(config.i3c_scl_hz.max(1));
let hi_ns = core::cmp::min(I3C_BUS_THIGH_MAX_NS, total_ns.saturating_sub(1));
let lo_ns = total_ns.saturating_sub(hi_ns).max(1);
(hi_ns, lo_ns)
};
self.regs
.set_pp_timing(ns_to_cnt_u8(i3c_pp_hi_ns), ns_to_cnt_u8(i3c_pp_lo_ns));
// SDA TX hold time (`period` is the clamped, provably-non-zero divisor)
let hold_steps = (config.sda_tx_hold_ns)
.div_ceil(period)
.clamp(SDA_TX_HOLD_MIN, SDA_TX_HOLD_MAX);
let mut reg = self.regs.read_sda_hold();
reg = (reg & !SDA_TX_HOLD_MASK) | ((hold_steps & 0x7) << 16);
self.regs.write_sda_hold(reg);
// BUS_FREE_TIMING
self.regs.write_bus_free_timing(I3C_BUS_FREE_TIMING_RESET);
}
fn calc_i2c_clk(&mut self, fscl_hz: u32) -> (u32, u32) {
use core::cmp::max;
// `.max(1)` on both the SCL frequency and the resulting period keeps the
// downstream `div_ceil(period_ns)` divisors provably non-zero (panic-free
// for the `no_panics` analysis); a valid `fscl_hz` is unaffected.
let period_ns: u32 = (1_000_000_000u32).div_ceil(fscl_hz.max(1)).max(1);
let (lo_min, hi_min): (u32, u32) = if fscl_hz <= 100_000 {
(
(I3C_BUS_I2C_STD_TLOW_MIN_NS + I3C_BUS_I2C_STD_TF_MAX_NS).div_ceil(period_ns),
(I3C_BUS_I2C_STD_THIGH_MIN_NS + I3C_BUS_I2C_STD_TR_MAX_NS).div_ceil(period_ns),
)
} else if fscl_hz <= 400_000 {
(
(I3C_BUS_I2C_FM_TLOW_MIN_NS + I3C_BUS_I2C_FM_TF_MAX_NS).div_ceil(period_ns),
(I3C_BUS_I2C_FM_THIGH_MIN_NS + I3C_BUS_I2C_FM_TR_MAX_NS).div_ceil(period_ns),
)
} else {
(
(I3C_BUS_I2C_FMP_TLOW_MIN_NS + I3C_BUS_I2C_FMP_TF_MAX_NS).div_ceil(period_ns),
(I3C_BUS_I2C_FMP_THIGH_MIN_NS + I3C_BUS_I2C_FMP_TR_MAX_NS).div_ceil(period_ns),
)
};
let leftover = period_ns.saturating_sub(lo_min + hi_min);
let lo = lo_min + leftover / 2;
let hi = max(period_ns.saturating_sub(lo), hi_min);
(hi, lo)
}
fn init_pid(&mut self, config: &mut I3cConfig) {
let bus = self.bus();
self.regs.set_pid_mfg_id(I3C_AST10X0_MIPI_MANUF_ID);
let rev_id: u32 = self.regs.hw_rev_id();
let mut reg: u32 = rev_id << 16 | u32::from(bus) << 12;
reg |= 0xa000_0000;
self.regs.write_slv_pid_value(reg);
let mut reg: u32 = self.regs.read_slv_char_ctrl();
reg &= !SLV_DCR_MASK;
reg |= (config.dcr << 8) | 0x66;
self.regs.write_slv_char_ctrl(reg);
}
}
impl<Y: FnMut(u32)> HardwareFifo for Ast1060I3c<Y> {
fn wr_tx_fifo(&mut self, bytes: &[u8]) {
self.regs.tx_fifo_write(bytes);
}
fn rd_rx_fifo(&mut self, out: &mut [u8]) {
self.regs.rx_fifo_read(out);
}
fn rd_ibi_fifo(&mut self, out: &mut [u8]) {
self.regs.ibi_fifo_read(out);
}
}
impl<Y: FnMut(u32)> HardwareRecovery for Ast1060I3c<Y> {
fn enter_sw_mode(&mut self) {
self.enter_sw_mode();
}
fn exit_sw_mode(&mut self) {
self.exit_sw_mode();
}
fn i3c_toggle_scl_in(&mut self, count: u32) {
self.toggle_scl_in(count);
}
fn gen_internal_stop(&mut self) {
self.gen_internal_stop();
}
fn even_parity(byte: u8) -> bool {
let mut parity = false;
let mut b = byte;
while b != 0 {
parity = !parity;
b &= b - 1;
}
!parity
}
}
impl<Y: FnMut(u32)> HardwareTransfer for Ast1060I3c<Y> {
fn set_ibi_mdb(&mut self, mdb: u8) {
self.regs.set_ibi_mdb(mdb);
}
fn exit_halt(&mut self, config: &mut I3cConfig) {
let state = self.regs.xfer_status();
let expected = if config.is_secondary {
CM_TFR_STS_TARGET_HALT
} else {
CM_TFR_STS_MASTER_HALT
};
if state != expected {
return;
}
self.regs.resume();
let regs = &self.regs;
let rc = poll_with_timeout(
|| u32::from(regs.xfer_status()),
|val| val != u32::from(expected),
&mut self.yield_fn,
I3C_CTRL_POLL_DELAY_NS,
I3C_POLL_MAX_ITERS,
);
if rc.is_err() {
i3c_debug!(self.logger, "exit_halt: timeout");
}
}
fn enter_halt(&mut self, by_sw: bool, config: &mut I3cConfig) {
let expected = if config.is_secondary {
CM_TFR_STS_TARGET_HALT
} else {
CM_TFR_STS_MASTER_HALT
};
if by_sw {
self.regs.abort();
}
let regs = &self.regs;
let rc = poll_with_timeout(
|| u32::from(regs.xfer_status()),
|val| val == u32::from(expected),
&mut self.yield_fn,
I3C_CTRL_POLL_DELAY_NS,
I3C_POLL_MAX_ITERS,
);
if rc.is_err() {
i3c_debug!(self.logger, "enter_halt: timeout");
}
}
fn reset_ctrl(&mut self, reset: u32) {
let reg = reset & RESET_CTRL_ALL;
if reg == 0 {
return;
}
self.regs.write_reset_ctrl(reg);
let regs = &self.regs;
let rc = poll_with_timeout(
|| regs.read_reset_ctrl(),
|val| val == 0,
&mut self.yield_fn,
I3C_CTRL_POLL_DELAY_NS,
I3C_POLL_MAX_ITERS,
);
if rc.is_err() {
i3c_debug!(self.logger, "reset_ctrl: timeout");
}
}
fn ibi_enable(&mut self, config: &mut I3cConfig, addr: u8) -> Result<(), I3cDrvError> {
let dev_idx = config
.attached
.find_dev_idx_by_addr(addr)
.ok_or(I3cDrvError::NoSuchDev)?;
i3c_debug!(self.logger, "ibi_enable: dev_idx={}", dev_idx);
// `get(dev_idx)` (not `[dev_idx]`) keeps this path panic-free for the
// `no_panics` analysis; `find_dev_idx_by_addr` already returns a valid
// index.
let pos_opt = config
.attached
.pos_of(dev_idx)
.or_else(|| config.attached.devices.get(dev_idx).and_then(|d| d.pos));
let pos: u8 = pos_opt.ok_or(I3cDrvError::NoDatPos)?;
i3c_debug!(self.logger, "ibi_enable: pos={}", pos);
let dev = config
.attached
.devices
.get(dev_idx)
.ok_or(I3cDrvError::NoSuchDev)?;
let tgt_bcr: u32 = u32::from(dev.bcr);
let mut reg = self.regs.dat_read(pos.into());
reg &= !DEV_ADDR_TABLE_SIR_REJECT;
if tgt_bcr & I3C_BCR_IBI_PAYLOAD_HAS_DATA_BYTE != 0 {
reg |= DEV_ADDR_TABLE_IBI_MDB | DEV_ADDR_TABLE_IBI_PEC;
}
self.regs.dat_write_raw(pos.into(), reg);
let mut sir_reject = self.regs.read_sir_reject();
sir_reject &= !bit(pos.into());
self.regs.write_sir_reject(sir_reject);
self.regs.enable_ibi_thld_irq();
let events = I3C_CCC_EVT_INTR;
// ccc_events_set requires HardwareTransfer trait bound on Self.
// We are inside HardwareTransfer impl for Ast1060I3c.
// Rust might have trouble inferring if Self: HardwareTransfer is not fully established yet?
// But Ast1060I3c implements HardwareTransfer (this block).
// However, ccc_events_set takes `&mut impl HardwareInterface`.
// Ast1060I3c implements HardwareInterface (blanket impl over all sub-traits).
// So this call should be valid.
let _ = ccc_events_set(self, config, dev.dyn_addr, true, events);
i3c_debug!(self.logger, "i3cd030 (SIR reject) = {:#x}", sir_reject);
i3c_debug!(
self.logger,
"i3cd040 (IBI thld) = {:#x}",
self.regs.read_intr_status_en()
);
i3c_debug!(
self.logger,
"i3cd044 (IBI thld sig) = {:#x}",
self.regs.read_intr_signal_en()
);
i3c_debug!(
self.logger,
"i3cd280 dat_addr[{}] = {:#x}",
pos,
self.regs.dat_read(pos.into())
);
i3c_debug!(self.logger, "ibi_enable done");
Ok(())
}
fn start_xfer(&mut self, config: &mut I3cConfig, xfer: &mut I3cXfer) {
let prev = config
.curr_xfer
.swap(core::ptr::from_mut(xfer).cast::<()>(), Ordering::AcqRel);
if !prev.is_null() {
i3c_debug!(self.logger, "start_xfer: previous xfer still in flight");
}
xfer.ret = -1;
xfer.done.reset();
for cmd in xfer.cmds.iter() {
if let Some(tx) = cmd.tx {
let take = tx.len().min(cmd.tx_len as usize);
if take > 0 {
i3c_debug!(self.logger, "start_xfer: write {} bytes", take);
self.wr_tx_fifo(&tx[..take]);
}
}
}
self.regs
.set_resp_buf_threshold(u8::try_from(xfer.cmds.len().saturating_sub(1)).unwrap_or(0));
for cmd in xfer.cmds.iter() {
i3c_debug!(
self.logger,
"start_xfer: cmd: cmd_hi={:#x}, cmd_lo={:#x}",
cmd.cmd_hi,
cmd.cmd_lo
);
self.regs.push_cmd(cmd.cmd_hi);
self.regs.push_cmd(cmd.cmd_lo);
}
}
fn end_xfer(&mut self, config: &mut I3cConfig) {
let p = config
.curr_xfer
.swap(core::ptr::null_mut(), Ordering::AcqRel);
if p.is_null() {
// Drain the response queue to prevent interrupt loops if no xfer is active
let nresp = self.regs.resp_buf_level();
for _ in 0..nresp {
let _ = self.regs.pop_response();
}
return;
}
// SAFETY: `curr_xfer` is published by `start_xfer` from a unique
// `&mut I3cXfer`. The ISR path here and the timeout cleanup paths both
// compete via `swap(null, AcqRel)`; only the side that observes a
// non-null pointer may reconstruct and use it, while the loser sees
// null and performs no dereference. This target runs the handoff on a
// single core, and the owning thread waits for completion or timeout
// before dropping the stack-owned `xfer`, so the pointee outlives this
// exclusive ownership transfer.
let xfer: &mut I3cXfer = unsafe { &mut *(p.cast::<I3cXfer>()) };
let nresp = self.regs.resp_buf_level();
for _ in 0..nresp {
let resp = self.regs.pop_response();
let tid = field_get(resp, RESPONSE_PORT_TID_MASK, RESPONSE_PORT_TID_SHIFT) as usize;
let rx_len = field_get(
resp,
RESPONSE_PORT_DATA_LEN_MASK,
RESPONSE_PORT_DATA_LEN_SHIFT,
) as usize;
let err = field_get(
resp,
RESPONSE_PORT_ERR_STATUS_MASK,
RESPONSE_PORT_ERR_STATUS_SHIFT,
);
i3c_debug!(
self.logger,
"end_xfer: tid={}, rx_len={}, err={}",
tid,
rx_len,
err
);
if tid >= xfer.cmds.len() {
if rx_len > 0 {
self.regs.rx_fifo_drain(rx_len);
}
continue;
}
// `get_mut` (not `[tid]`) keeps the scatter path panic-free for the
// `no_panics` analysis; `tid < len` is already guaranteed above.
let Some(cmd) = xfer.cmds.get_mut(tid) else {
continue;
};
cmd.rx_len = u32::try_from(rx_len).unwrap_or(0);
cmd.ret = i32::try_from(err).unwrap_or(-1);
if rx_len == 0 {
continue;
}
if err == 0 {
// `get_mut(..rx_len)` guards a malformed hardware length that
// would otherwise panic on `rx_buf[..rx_len]`; on mismatch the
// bytes are drained instead.
if let Some(dst) = cmd.rx.as_deref_mut().and_then(|b| b.get_mut(..rx_len)) {
self.regs.rx_fifo_read(dst);
} else {
self.regs.rx_fifo_drain(rx_len);
}
} else if rx_len > 0 {
self.regs.rx_fifo_drain(rx_len);
}
}
let mut ret = 0;
for i in 0..nresp {
if let Some(c) = xfer.cmds.get(i)
&& c.ret != 0
{
ret = c.ret;
}
}
if ret != 0 {
self.enter_halt(false, config);
self.reset_ctrl(RESET_CTRL_QUEUES);
self.exit_halt(config);
}
xfer.ret = ret;
xfer.done.complete();
}
fn get_addr_pos(&mut self, config: &I3cConfig, addr: u8) -> Option<u8> {
config
.addrs
.iter()
.take(config.maxdevs as usize)
.position(|&a| a == addr)
.and_then(|i| u8::try_from(i).ok())
}
fn detach_i3c_dev(&mut self, pos: usize) {
self.regs.dat_set_reject(pos);
}
fn attach_i3c_dev(&mut self, pos: usize, addr: u8) -> Result<(), I3cDrvError> {
let mut da_with_parity = addr;
if Self::even_parity(addr) {
da_with_parity |= 1 << 7;
}
self.regs.dat_program_addr(pos, da_with_parity);
Ok(())
}
#[allow(clippy::too_many_lines)]
fn do_ccc(
&mut self,
config: &mut I3cConfig,
payload: &mut CccPayload<'_, '_>,
) -> Result<(), I3cDrvError> {
let mut cmds = [I3cCmd {
cmd_lo: 0,
cmd_hi: 0,
tx: None,
rx: None,
tx_len: 0,
rx_len: 0,
ret: 0,
}];
let mut pos = 0;
let mut rnw: bool = false;
let mut is_broadcast = false;
let (id, data_len) = {
let Some(ccc) = payload.ccc.as_ref() else {
return Err(I3cDrvError::Invalid);
};
(ccc.id, ccc.data.as_deref().map_or(0, <[u8]>::len))
};
let dbp_is_direct = id > 0x7F;
let db: u8 = if dbp_is_direct && data_len > 0 {
payload
.ccc
.as_ref()
.and_then(|c| c.data.as_deref())
.map_or(0, |d| d[0])
} else {
0
};
{
let cmd = &mut cmds[0];
if id <= 0x7F {
is_broadcast = true;
if data_len > 0
&& let Some(d) = payload.ccc.as_ref().and_then(|c| c.data.as_deref())
{
cmd.tx = Some(d);
cmd.tx_len = u32::try_from(data_len).map_err(|_| I3cDrvError::Invalid)?;
}
} else {
let Some(tgt_addr) = payload
.targets
.as_ref()
.and_then(|ts| ts.first())
.map(|t| t.addr)
else {
return Err(I3cDrvError::Invalid);
};
let pos_ops = config.attached.pos_of_addr(tgt_addr);
i3c_debug!(
self.logger,
"do_ccc: tgt_addr=0x{:02x}, pos_ops={:?}",
tgt_addr,
pos_ops
);
pos = match pos_ops {
Some(p) => p,
None => return Err(I3cDrvError::Invalid),
};
i3c_debug!(
self.logger,
"do_ccc: tgt_addr=0x{:02x}, pos={}",
tgt_addr,
pos
);
let Some(tp) = payload.targets.as_deref_mut().and_then(|ts| ts.first_mut()) else {
return Err(I3cDrvError::Invalid);
};
rnw = tp.rnw;
if rnw {
let len = tp.data.as_deref().map_or(0, <[u8]>::len);
if len == 0 {
return Err(I3cDrvError::Invalid);
}
cmd.rx_len = u32::try_from(len).map_err(|_| I3cDrvError::Invalid)?;
cmd.rx = tp.data.as_deref_mut();
} else {
let (d_opt, len) = match tp.data.as_deref() {
Some(d) => (Some(d), d.len()),
None => (None, 0),
};
cmd.tx = d_opt;
cmd.tx_len = u32::try_from(len).map_err(|_| I3cDrvError::Invalid)?;
tp.num_xfer = len;
}
}
}
let cmd = &mut cmds[0];
cmd.cmd_hi = field_prep(COMMAND_PORT_ATTR, COMMAND_ATTR_XFER_ARG);
if dbp_is_direct && data_len > 0 {
cmd.cmd_lo |= COMMAND_PORT_DBP;
cmd.cmd_hi |= field_prep(COMMAND_PORT_ARG_DB, db.into());
}
if rnw {
cmd.cmd_hi |= field_prep(COMMAND_PORT_ARG_DATA_LEN, cmd.rx_len);
} else {
cmd.cmd_hi |= field_prep(COMMAND_PORT_ARG_DATA_LEN, cmd.tx_len);
}
cmd.cmd_lo |= field_prep(COMMAND_PORT_ATTR, COMMAND_ATTR_XFER_CMD)
| field_prep(COMMAND_PORT_CMD, id.into())
| field_prep(COMMAND_PORT_READ_TRANSFER, u32::from(rnw))
| COMMAND_PORT_CP
| COMMAND_PORT_ROC
| COMMAND_PORT_TOC;
if !is_broadcast {
cmd.cmd_lo |= field_prep(COMMAND_PORT_DEV_INDEX, u32::from(pos));
}
if id == I3C_CCC_SETHID || id == I3C_CCC_DEVCTRL {
cmd.cmd_lo |= field_prep(COMMAND_PORT_SPEED, SpeedI3c::I2cFmAsI3c as u32);
}
let mut xfer = I3cXfer::new(&mut cmds[..]);
self.start_xfer(config, &mut xfer);
if !xfer.done.wait_for_us(I3C_OP_TIMEOUT_US, &mut self.yield_fn) {
self.enter_halt(true, config);
self.reset_ctrl(RESET_CTRL_XFER_QUEUES);
self.exit_halt(config);
let _ = config
.curr_xfer
.swap(core::ptr::null_mut(), Ordering::AcqRel);
}
let ret = xfer.ret;
if ret == i32::try_from(RESPONSE_ERROR_IBA_NACK).map_err(|_| I3cDrvError::Invalid)? {
return Ok(());
}
if is_broadcast && let Some(ccc_rw) = payload.ccc.as_mut() {
let num_xfer = ccc_rw.data.as_deref().map(<[u8]>::len);
if let Some(n) = num_xfer {
ccc_rw.num_xfer = n;
}
}
match ret {
0 => Ok(()),
_ => Err(I3cDrvError::Invalid),
}
}
fn do_entdaa(&mut self, config: &mut I3cConfig, pos: u32) -> Result<(), I3cDrvError> {
i3c_debug!(self.logger, "do_entdaa: pos={}", pos);
let cmd = I3cCmd {
cmd_lo: field_prep(COMMAND_PORT_ATTR, COMMAND_ATTR_ADDR_ASSGN_CMD)
| field_prep(COMMAND_PORT_CMD, u32::from(I3C_CCC_ENTDAA))
| field_prep(COMMAND_PORT_DEV_COUNT, 1)
| field_prep(COMMAND_PORT_DEV_INDEX, pos)
| COMMAND_PORT_ROC
| COMMAND_PORT_TOC,
cmd_hi: field_prep(COMMAND_PORT_ATTR, COMMAND_ATTR_XFER_ARG),
tx: None,
rx: None,
tx_len: 0,
rx_len: 0,
ret: 0,
};
i3c_debug!(
self.logger,
"do_entdaa: cmd_lo=0x{:08x}, cmd_hi=0x{:08x}",
cmd.cmd_lo,
cmd.cmd_hi
);
let mut cmds = [cmd];
let mut xfer = I3cXfer::new(&mut cmds[..]);
xfer.ret = -1;
self.start_xfer(config, &mut xfer);
if !xfer.done.wait_for_us(I3C_OP_TIMEOUT_US, &mut self.yield_fn) {
self.enter_halt(true, config);
self.reset_ctrl(RESET_CTRL_XFER_QUEUES);
self.exit_halt(config);
let _ = config
.curr_xfer
.swap(core::ptr::null_mut(), Ordering::AcqRel);
return Err(I3cDrvError::Invalid);
}
i3c_debug!(self.logger, "do_entdaa: xfer done");
match xfer.ret {
0 => Ok(()),
_ => Err(I3cDrvError::Invalid),
}
}
fn priv_xfer_build_cmds<'a>(
&mut self,
cmds: &mut [I3cCmd<'a>],
msgs: &mut [I3cMsg<'a>],
pos: u8,
) -> Result<(), I3cDrvError> {
let cmds_len = cmds.len();
if cmds_len != msgs.len() {
return Err(I3cDrvError::Invalid);
}
// Zip (not parallel `cmds[i]`/`msgs[i]` indexing) so the build loop is
// panic-free for the `no_panics` analysis; lengths are equal (checked).
for (i, (cmd, m)) in cmds.iter_mut().zip(msgs.iter_mut()).enumerate() {
let (is_read, ptr, len) = {
let is_read = (m.flags & I3C_MSG_READ) != 0;
if is_read {
let buf = match m.buf.as_deref_mut() {
Some(b) if !b.is_empty() => b,
_ => return Err(I3cDrvError::Invalid),
};
(true, buf.as_mut_ptr(), buf.len())
} else {
let buf = match m.buf.as_deref() {
Some(b) if !b.is_empty() => b,
_ => return Err(I3cDrvError::Invalid),
};
m.num_xfer = u32::try_from(buf.len()).map_err(|_| I3cDrvError::Invalid)?;
(false, buf.as_ptr().cast_mut(), buf.len())
}
};
*cmd = I3cCmd {
cmd_hi: field_prep(COMMAND_PORT_ATTR, COMMAND_ATTR_XFER_ARG)
| field_prep(
COMMAND_PORT_ARG_DATA_LEN,
u32::try_from(len).map_err(|_| I3cDrvError::Invalid)?,
),
cmd_lo: field_prep(
COMMAND_PORT_TID,
u32::try_from(i).map_err(|_| I3cDrvError::Invalid)?,
) | field_prep(COMMAND_PORT_DEV_INDEX, u32::from(pos))
| COMMAND_PORT_ROC,
tx: None,
rx: None,
tx_len: 0,
rx_len: 0,
ret: 0,
};
if is_read {
let rx_slice: &'a mut [u8] = unsafe { core::slice::from_raw_parts_mut(ptr, len) };
cmd.rx = Some(rx_slice);
cmd.rx_len = u32::try_from(len).map_err(|_| I3cDrvError::Invalid)?;
cmd.cmd_lo |= COMMAND_PORT_READ_TRANSFER;
} else {
let tx_slice: &'a [u8] =
unsafe { core::slice::from_raw_parts(ptr.cast_const(), len) };
cmd.tx = Some(tx_slice);
cmd.tx_len = u32::try_from(len).map_err(|_| I3cDrvError::Invalid)?;
}
let is_last = i + 1 == cmds_len;
if is_last {
cmd.cmd_lo |= COMMAND_PORT_TOC;
}
}
Ok(())
}
fn priv_xfer(
&mut self,
config: &mut I3cConfig,
pid: u64,
msgs: &mut [I3cMsg],
) -> Result<(), I3cDrvError> {
let pos_opt = config.attached.pos_of_pid(pid);
let pos: u8 = pos_opt.ok_or(I3cDrvError::NoDatPos)?;
if msgs.len() == 1 {
let mut cmd = I3cCmd::new();
let cmds = core::slice::from_mut(&mut cmd);
self.priv_xfer_build_cmds(cmds, msgs, pos)?;
let mut xfer = I3cXfer::new(cmds);
self.start_xfer(config, &mut xfer);
if !xfer.done.wait_for_us(I3C_OP_TIMEOUT_US, &mut self.yield_fn) {
self.enter_halt(true, config);
self.reset_ctrl(RESET_CTRL_XFER_QUEUES);
self.exit_halt(config);
let _ = config
.curr_xfer
.swap(core::ptr::null_mut(), Ordering::AcqRel);
return Err(I3cDrvError::Timeout);
}
if let Some(m) = msgs.first_mut()
&& (m.flags & I3C_MSG_READ) != 0
{
m.actual_len = xfer.cmds.first().map_or(0, |c| c.rx_len);
}
return match xfer.ret {
0 => Ok(()),
_ => Err(I3cDrvError::Timeout),
};
}
let mut cmds: heapless::Vec<I3cCmd, MAX_CMDS> = heapless::Vec::new();
for _ in 0..msgs.len() {
// `?` (not `.unwrap()`) keeps this panic-free; > MAX_CMDS msgs is a
// typed error, not a panic.
cmds.push(I3cCmd {
cmd_lo: 0,
cmd_hi: 0,
tx: None,
rx: None,
tx_len: 0,
rx_len: 0,
ret: 0,
})
.map_err(|_| I3cDrvError::TooManyMsgs)?;
}
let ret = self.priv_xfer_build_cmds(cmds.as_mut_slice(), msgs, pos);
match ret {
Ok(()) => {}
Err(e) => return Err(e),
}
let mut xfer = I3cXfer::new(cmds.as_mut_slice());
self.start_xfer(config, &mut xfer);
if !xfer.done.wait_for_us(I3C_OP_TIMEOUT_US, &mut self.yield_fn) {
self.enter_halt(true, config);
self.reset_ctrl(RESET_CTRL_XFER_QUEUES);
self.exit_halt(config);
let _ = config
.curr_xfer
.swap(core::ptr::null_mut(), Ordering::AcqRel);
return Err(I3cDrvError::Timeout);
}
for (i, m) in msgs.iter_mut().enumerate() {
if (m.flags & I3C_MSG_READ) != 0
&& let Some(c) = xfer.cmds.get(i)
{
m.actual_len = c.rx_len;
}
}
match xfer.ret {
0 => Ok(()),
_ => Err(I3cDrvError::Timeout),
}
}
fn handle_ibi_sir(&mut self, config: &mut I3cConfig, addr: u8, len: usize) {
i3c_debug!(self.logger, "handle_ibi_sir: addr=0x{:02x}", addr);
let pos = config.attached.pos_of_addr(addr);
if pos.is_none() {
i3c_debug!(
self.logger,
"handle_ibi_sir: no such addr in attached devices"
);
self.regs.ibi_fifo_drain(len);
}
let mut ibi_buf: [u8; 2] = [0u8; 2];
let take = core::cmp::min(len, ibi_buf.len());
self.rd_ibi_fifo(&mut ibi_buf[..take]);
let bus = self.bus() as usize;
let _ = ibi_workq::i3c_ibi_work_enqueue_target_irq(bus, addr, &ibi_buf[..take]);
}
fn handle_ibis(&mut self, config: &mut I3cConfig) {
let nibis = self.regs.ibi_status_count();
i3c_debug!(self.logger, "Number of IBIs: {}", nibis);
if nibis == 0 {
return;
}
for _ in 0..nibis {
let reg = self.regs.ibi_fifo_pop();
let ibi_id = field_get(reg, IBIQ_STATUS_IBI_ID, IBIQ_STATUS_IBI_ID_SHIFT);
let ibi_data_len = field_get(
reg,
IBIQ_STATUS_IBI_DATA_LEN,
IBIQ_STATUS_IBI_DATA_LEN_SHIFT,
) as usize;
let ibi_addr = (ibi_id >> 1) & 0x7F;
let rnw = (ibi_id & 1) != 0;
i3c_debug!(
self.logger,
"IBI: addr=0x{:02x}, rnw={}, len={}",
ibi_addr,
rnw,
ibi_data_len
);
if ibi_addr != 2 && rnw {
// sirq
self.handle_ibi_sir(config, ibi_addr as u8, ibi_data_len);
} else if ibi_addr == 2 && !rnw {
// hot-join
let bus = self.bus() as usize;
i3c_debug!(self.logger, "Hot-join IBI");
let _ = ibi_workq::i3c_ibi_work_enqueue_hotjoin(bus);
} else {
// normal ibi
i3c_debug!(self.logger, "Normal IBI");
self.regs.ibi_fifo_drain(ibi_data_len);
}
}
}
}
impl<Y: FnMut(u32)> HardwareTarget for Ast1060I3c<Y> {
fn target_tx_write(&mut self, buf: &[u8]) {
self.wr_tx_fifo(buf);
let cmd = field_prep(COMMAND_PORT_ATTR, COMMAND_ATTR_SLAVE_DATA_CMD)
| field_prep(
COMMAND_PORT_ARG_DATA_LEN,
u32::try_from(buf.len()).map_or(0, |v| v),
)
| field_prep(COMMAND_PORT_TID, Tid::TargetRdData as u32);
self.regs.push_cmd(cmd);
}
fn target_ibi_raise_hj(&self, config: &mut I3cConfig) -> Result<(), I3cDrvError> {
if !config.is_secondary {
return Err(I3cDrvError::Invalid);
}
if !self.regs.hj_capable() {
return Err(I3cDrvError::Invalid);
}
if self.regs.dynamic_addr_valid() {
return Err(I3cDrvError::Access);
}
self.regs.write_slv_event_ctrl(8); // set HJ request
Ok(())
}
fn target_handle_response_ready(&mut self, config: &mut I3cConfig) {
let nresp = self.regs.resp_buf_level();
for _ in 0..nresp {
let resp = self.regs.pop_response();
let tid = field_get(resp, RESPONSE_PORT_TID_MASK, RESPONSE_PORT_TID_SHIFT) as usize;
let rx_len = field_get(
resp,
RESPONSE_PORT_DATA_LEN_MASK,
RESPONSE_PORT_DATA_LEN_SHIFT,
) as usize;
let err = field_get(
resp,
RESPONSE_PORT_ERR_STATUS_MASK,
RESPONSE_PORT_ERR_STATUS_SHIFT,
);
i3c_debug!(
self.logger,
"Response: tid={}, rx_len={}, err={}",
tid,
rx_len,
err
);
if err != 0 {
self.enter_halt(false, config);
self.reset_ctrl(RESET_CTRL_QUEUES);
self.exit_halt(config);
continue;
}
if rx_len != 0 {
let mut buf: [u8; 256] = [0u8; 256];
// Bound `rx_len` (a raw hardware field) to the buffer via
// `get_mut`: this ISR runs in handler mode, so an oversized
// length must not panic (same hardening as `end_xfer`).
let n = rx_len.min(buf.len());
if let Some(dst) = buf.get_mut(..n) {
self.rd_rx_fifo(dst);
}
let _ = ibi_workq::i3c_ibi_work_enqueue_target_master_write(
self.bus().into(),
buf.get(..n).unwrap_or(&[]),
);
i3c_debug!(
self.logger,
"[MASTER ==> TARGET] TARGET READ: {:02x?}",
buf.get(..n).unwrap_or(&[])
);
}
if tid == Tid::TargetIbi as usize {
config.target_ibi_done.complete();
}
if tid == Tid::TargetRdData as usize {
config.target_data_done.complete();
}
}
}
fn target_pending_read_notify(
&mut self,
config: &mut I3cConfig,
buf: &[u8],
notifier: &mut I3cIbi,
) -> Result<(), I3cDrvError> {
let reg = self.regs.read_slv_event_ctrl();
if !(config.sir_allowed_by_sw && (reg & SLV_EVENT_CTRL_SIR_EN != 0)) {
return Err(I3cDrvError::Access);
}
let Some(mdb) = notifier.first_byte() else {
return Err(I3cDrvError::Invalid);
};
self.set_ibi_mdb(mdb);
if let Some(p) = notifier.payload
&& !p.is_empty()
{
self.wr_tx_fifo(p);
}
let payload_len = u32::try_from(notifier.payload.map_or(0, <[u8]>::len))
.map_err(|_| I3cDrvError::Invalid)?;
let cmd: u32 = field_prep(COMMAND_PORT_ATTR, COMMAND_ATTR_SLAVE_DATA_CMD)
| field_prep(COMMAND_PORT_ARG_DATA_LEN, payload_len)
| field_prep(COMMAND_PORT_TID, Tid::TargetIbi as u32);
self.regs.push_cmd(cmd);
config.target_ibi_done.reset();
self.regs.set_resp_buf_threshold(0);
self.target_tx_write(buf);
config.target_data_done.reset();
self.regs.raise_sir();
if !config
.target_ibi_done
.wait_for_us(I3C_OP_TIMEOUT_US, &mut self.yield_fn)
{
i3c_debug!(self.logger, "SIR timeout! Reset I3C controller");
self.enter_halt(false, config);
self.reset_ctrl(RESET_CTRL_QUEUES);
self.exit_halt(config);
return Err(I3cDrvError::IoError);
}
if !config
.target_data_done
.wait_for_us(I3C_OP_TIMEOUT_US, &mut self.yield_fn)
{
i3c_debug!(self.logger, "wait master read timeout! Reset queues");
self.i3c_disable(config.is_secondary);
self.reset_ctrl(RESET_CTRL_QUEUES);
self.i3c_enable(config);
return Err(I3cDrvError::Timeout);
}
Ok(())
}
fn target_handle_ccc_update(&mut self, config: &mut I3cConfig) {
let event = self.regs.read_slv_event_ctrl();
self.regs.write_slv_event_ctrl(event);
i3c_debug!(self.logger, "CCC update event: 0x{:08x}", event);
let reg = self.regs.xfer_status();
if reg == CM_TFR_STS_TARGET_HALT {
self.enter_halt(true, config);
self.exit_halt(config);
}
}
}