| // 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); |
| } |
| } |
| } |