diff --git a/Cargo.lock b/Cargo.lock index ed851a6..8852ba4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -65,6 +65,7 @@ dependencies = [ "ast1060-pac", "cortex-m", "cortex-m-rt", + "critical-section", "embedded-hal 1.0.0", "embedded-hal 1.0.0-alpha.1", "embedded-io", @@ -175,6 +176,7 @@ checksum = "8ec610d8f49840a5b376c69663b6369e71f4b34484b9b2eb29fb918d92516cb9" dependencies = [ "bare-metal", "bitfield", + "critical-section", "embedded-hal 0.2.7", "volatile-register", ] @@ -200,6 +202,12 @@ dependencies = [ "syn 1.0.109", ] +[[package]] +name = "critical-section" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "790eea4361631c5e7d22598ecd5723ff611904e3344ce8720784c93e3d83d40b" + [[package]] name = "embedded-hal" version = "0.2.7" diff --git a/Cargo.toml b/Cargo.toml index 8cf4097..bb64742 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -18,6 +18,8 @@ edition = "2021" default = [] std = [] i2c_target = [] +i3c_master = [] +i3c_target = [] test-rsa = [] test-ecdsa = [] test-hmac = [] @@ -37,11 +39,10 @@ hex-literal = "0.4" heapless = "0.8.0" nb = "1.1.0" paste = "1.0" - +critical-section = "1.2" +cortex-m = { version = "0.7.5", features = ["critical-section-single-core"] } openprot-hal-blocking = { git="https://github.com/OpenPRoT/openprot" } zerocopy = { version = "0.8.25", features = ["derive"] } - -cortex-m = { version = "0.7.5" } cortex-m-rt = { version = "0.6.5", features = ["device"] } panic-halt = "1.0.0" diff --git a/src/i3c/ast1060_i3c.rs b/src/i3c/ast1060_i3c.rs new file mode 100644 index 0000000..233b875 --- /dev/null +++ b/src/i3c/ast1060_i3c.rs @@ -0,0 +1,2138 @@ +// Licensed under the Apache-2.0 license + +use crate::common::{DummyDelay, Logger}; +use crate::i3c::ccc::{ + ccc_events_set, CccPayload, I3C_CCC_DEVCTRL, I3C_CCC_ENTDAA, I3C_CCC_EVT_INTR, I3C_CCC_SETHID, +}; +use crate::i3c::i3c_config::{Completion, I3cConfig}; +use crate::i3c::ibi_workq; +use core::cell::RefCell; +use core::marker::PhantomData; +use core::ptr::read_volatile; +use core::sync::atomic::Ordering; +use cortex_m::peripheral::NVIC; +use critical_section::Mutex; +use embedded_hal::delay::DelayNs; + +pub const I3C_MSG_WRITE: u8 = 0x0; +pub const I3C_MSG_READ: u8 = 0x1; +pub const I3C_MSG_STOP: u8 = 0x2; + +pub const I3C_BUS_I2C_STD_TLOW_MIN_NS: u32 = 4_700; +pub const I3C_BUS_I2C_STD_THIGH_MIN_NS: u32 = 4_000; +pub const I3C_BUS_I2C_STD_TR_MAX_NS: u32 = 1_000; +pub const I3C_BUS_I2C_STD_TF_MAX_NS: u32 = 300; + +pub const I3C_BUS_I2C_FM_TLOW_MIN_NS: u32 = 1_300; +pub const I3C_BUS_I2C_FM_THIGH_MIN_NS: u32 = 600; +pub const I3C_BUS_I2C_FM_TR_MAX_NS: u32 = 300; +pub const I3C_BUS_I2C_FM_TF_MAX_NS: u32 = 300; + +pub const I3C_BUS_I2C_FMP_TLOW_MIN_NS: u32 = 500; +pub const I3C_BUS_I2C_FMP_THIGH_MIN_NS: u32 = 260; +pub const I3C_BUS_I2C_FMP_TR_MAX_NS: u32 = 120; +pub const I3C_BUS_I2C_FMP_TF_MAX_NS: u32 = 120; + +pub const I3C_BUS_THIGH_MAX_NS: u32 = 41; + +pub const NSEC_PER_SEC: u32 = 1_000_000_000; +pub const SDA_TX_HOLD_MIN: u32 = 0b001; +pub const SDA_TX_HOLD_MAX: u32 = 0b111; +pub const SDA_TX_HOLD_MASK: u32 = 0x0007_0000; // bits 18:16 + +pub const SLV_DCR_MASK: u32 = 0x0000_ff00; +pub const SLV_EVENT_CTRL: u32 = 0x38; +pub const SLV_EVENT_CTRL_MWL_UPD: u32 = bit(7); +pub const SLV_EVENT_CTRL_MRL_UPD: u32 = bit(6); +pub const SLV_EVENT_CTRL_HJ_REQ: u32 = bit(3); +pub const SLV_EVENT_CTRL_SIR_EN: u32 = bit(0); + +pub const I3CG_REG1_SCL_IN_SW_MODE_VAL: u32 = bit(23); +pub const I3CG_REG1_SDA_IN_SW_MODE_VAL: u32 = bit(27); +pub const I3CG_REG1_SCL_IN_SW_MODE_EN: u32 = bit(28); +pub const I3CG_REG1_SDA_IN_SW_MODE_EN: u32 = bit(29); + +pub const CM_TFR_STS_MASTER_HALT: u8 = 0xf; +pub const CM_TFR_STS_TARGET_HALT: u8 = 0x6; + +pub const COMMAND_QUEUE_PORT: u32 = 0x0c; + +// --- single-bit flags --- +pub const COMMAND_PORT_PEC: u32 = bit(31); +pub const COMMAND_PORT_TOC: u32 = bit(30); +pub const COMMAND_PORT_READ_TRANSFER: u32 = bit(28); +pub const COMMAND_PORT_SDAP: u32 = bit(27); +pub const COMMAND_PORT_ROC: u32 = bit(26); +pub const COMMAND_PORT_DBP: u32 = bit(25); +pub const COMMAND_PORT_CP: u32 = bit(15); +pub const COMMAND_PORT_SPEED: u32 = bits(23, 21); +pub const COMMAND_PORT_DEV_INDEX: u32 = bits(20, 16); +pub const COMMAND_PORT_CMD: u32 = bits(14, 7); +pub const COMMAND_PORT_TID: u32 = bits(6, 3); +pub const COMMAND_PORT_ARG_DB: u32 = bits(15, 8); +pub const COMMAND_PORT_ARG_DATA_LEN: u32 = bits(31, 16); +pub const COMMAND_PORT_ATTR: u32 = bits(2, 0); +pub const COMMAND_PORT_DEV_COUNT: u32 = bits(25, 21); + +pub const TID_TARGET_IBI: u32 = 0x1; +pub const TID_TARGET_RD_DATA: u32 = 0x2; +pub const TID_TARGET_MASTER_WR: u32 = 0x8; +pub const TID_TARGET_MASTER_DEF: u32 = 0xf; + +pub const COMMAND_ATTR_XFER_CMD: u32 = 0; +pub const COMMAND_ATTR_XFER_ARG: u32 = 1; +pub const COMMAND_ATTR_SHORT_ARG: u32 = 2; +pub const COMMAND_ATTR_ADDR_ASSGN_CMD: u32 = 3; +pub const COMMAND_ATTR_SLAVE_DATA_CMD: u32 = 0; + +pub const DEV_ADDR_TABLE_LEGACY_I2C_DEV: u32 = bit(31); +pub const DEV_ADDR_TABLE_DYNAMIC_ADDR: u32 = bits(23, 16); +pub const DEV_ADDR_TABLE_MR_REJECT: u32 = bit(14); +pub const DEV_ADDR_TABLE_SIR_REJECT: u32 = bit(13); +pub const DEV_ADDR_TABLE_IBI_MDB: u32 = bit(12); +pub const DEV_ADDR_TABLE_IBI_PEC: u32 = bit(11); +pub const DEV_ADDR_TABLE_STATIC_ADDR: u32 = bits(6, 0); + +pub const IBI_QUEUE_STATUS: u32 = 0x18; +pub const IBIQ_STATUS_IBI_ID: u32 = bits(15, 8); +pub const IBIQ_STATUS_IBI_ID_SHIFT: u32 = 8; +pub const IBIQ_STATUS_IBI_DATA_LEN: u32 = bits(7, 0); +pub const IBIQ_STATUS_IBI_DATA_LEN_SHIFT: u32 = 0; + +pub const RESET_CTRL_IBI_QUEUE: u32 = bit(5); +pub const RESET_CTRL_RX_FIFO: u32 = bit(4); +pub const RESET_CTRL_TX_FIFO: u32 = bit(3); +pub const RESET_CTRL_RESP_QUEUE: u32 = bit(2); +pub const RESET_CTRL_CMD_QUEUE: u32 = bit(1); +pub const RESET_CTRL_SOFT: u32 = bit(0); + +pub const RESET_CTRL_ALL: u32 = RESET_CTRL_IBI_QUEUE + | RESET_CTRL_RX_FIFO + | RESET_CTRL_TX_FIFO + | RESET_CTRL_RESP_QUEUE + | RESET_CTRL_CMD_QUEUE + | RESET_CTRL_SOFT; + +pub const RESET_CTRL_QUEUES: u32 = RESET_CTRL_IBI_QUEUE + | RESET_CTRL_RX_FIFO + | RESET_CTRL_TX_FIFO + | RESET_CTRL_RESP_QUEUE + | RESET_CTRL_CMD_QUEUE; + +pub const RESET_CTRL_XFER_QUEUES: u32 = + RESET_CTRL_RX_FIFO | RESET_CTRL_TX_FIFO | RESET_CTRL_RESP_QUEUE | RESET_CTRL_CMD_QUEUE; + +pub const RESPONSE_QUEUE_PORT: u32 = 0x10; +pub const RESPONSE_PORT_ERR_STATUS_SHIFT: u32 = 28; +pub const RESPONSE_PORT_ERR_STATUS_MASK: u32 = genmask(31, 28); +pub const RESPONSE_PORT_TID_SHIFT: u32 = 24; +pub const RESPONSE_PORT_TID_MASK: u32 = genmask(27, 24); +pub const RESPONSE_PORT_DATA_LEN_SHIFT: u32 = 0; +pub const RESPONSE_PORT_DATA_LEN_MASK: u32 = genmask(15, 0); + +pub const RESPONSE_NO_ERROR: u32 = 0; +pub const RESPONSE_ERROR_CRC: u32 = 1; +pub const RESPONSE_ERROR_PARITY: u32 = 2; +pub const RESPONSE_ERROR_FRAME: u32 = 3; +pub const RESPONSE_ERROR_IBA_NACK: u32 = 4; +pub const RESPONSE_ERROR_ADDRESS_NACK: u32 = 5; +pub const RESPONSE_ERROR_OVER_UNDER_FLOW: u32 = 6; +pub const RESPONSE_ERROR_TRANSF_ABORT: u32 = 8; +pub const RESPONSE_ERROR_I2C_W_NACK_ERR: u32 = 9; +pub const RESPONSE_ERROR_EARLY_TERMINATE: u32 = 10; +pub const RESPONSE_ERROR_PEC_ERR: u32 = 12; + +pub const INTR_STATUS: u32 = 0x3c; +pub const INTR_STATUS_EN: u32 = 0x40; +pub const INTR_SIGNAL_EN: u32 = 0x44; +pub const INTR_FORCE: u32 = 0x48; + +pub const INTR_BUSOWNER_UPDATE_STAT: u32 = bit(13); +pub const INTR_IBI_UPDATED_STAT: u32 = bit(12); +pub const INTR_READ_REQ_RECV_STAT: u32 = bit(11); +pub const INTR_DEFSLV_STAT: u32 = bit(10); +pub const INTR_TRANSFER_ERR_STAT: u32 = bit(9); +pub const INTR_DYN_ADDR_ASSGN_STAT: u32 = bit(8); +pub const INTR_CCC_UPDATED_STAT: u32 = bit(6); +pub const INTR_TRANSFER_ABORT_STAT: u32 = bit(5); +pub const INTR_RESP_READY_STAT: u32 = bit(4); +pub const INTR_CMD_QUEUE_READY_STAT: u32 = bit(3); +pub const INTR_IBI_THLD_STAT: u32 = bit(2); +pub const INTR_RX_THLD_STAT: u32 = bit(1); +pub const INTR_TX_THLD_STAT: u32 = bit(0); +pub const I3C_BCR_IBI_PAYLOAD_HAS_DATA_BYTE: u32 = bit(2); + +#[must_use] +pub const fn bit(n: u32) -> u32 { + 1 << n +} +#[must_use] +pub const fn bits(h: u32, l: u32) -> u32 { + ((1u32 << (h - l + 1)) - 1) << l +} +#[must_use] +pub const fn field_prep(mask: u32, val: u32) -> u32 { + (val << mask.trailing_zeros()) & mask +} +const fn field_get(val: u32, mask: u32, shift: u32) -> u32 { + (val & mask) >> shift +} +const fn genmask(msb: u32, lsb: u32) -> u32 { + let width = msb - lsb + 1; + if width >= 32 { + u32::MAX + } else { + ((1u32 << width) - 1) << lsb + } +} + +const MAX_CMDS: usize = 32; + +#[derive(Debug)] +pub enum I3cDrvError { + NoDatPos, + NoMsgs, + TooManyMsgs, + InvalidArgs, + Timeout, + NoSuchDev, + Access, + IoError, + Invalid, +} + +#[derive(Clone, Copy)] +struct Handler { + func: fn(usize), + ctx: usize, +} + +static BUS_HANDLERS: [Mutex>>; 4] = [ + Mutex::new(RefCell::new(None)), + Mutex::new(RefCell::new(None)), + Mutex::new(RefCell::new(None)), + Mutex::new(RefCell::new(None)), +]; + +pub fn register_i3c_irq_handler(bus: usize, func: fn(usize), ctx: usize) { + assert!(bus < 4); + critical_section::with(|cs| { + *BUS_HANDLERS[bus].borrow(cs).borrow_mut() = Some(Handler { func, ctx }); + }); +} + +#[inline] +fn dispatch_irq(bus: usize) { + critical_section::with(|cs| { + if let Some(h) = *BUS_HANDLERS[bus].borrow(cs).borrow() { + (h.func)(h.ctx); + } + }); +} + +#[no_mangle] +pub extern "C" fn i3c() { + dispatch_irq(0); +} +#[no_mangle] +pub extern "C" fn i3c1() { + dispatch_irq(1); +} +#[no_mangle] +pub extern "C" fn i3c2() { + dispatch_irq(2); +} +#[no_mangle] +pub extern "C" fn i3c3() { + dispatch_irq(3); +} + +#[repr(u32)] +pub enum SpeedI3c { + Sdr0 = 0x0, + Sdr1 = 0x1, + Sdr2 = 0x2, + Sdr3 = 0x3, + Sdr4 = 0x4, + HdrTs = 0x5, + HdrDdr = 0x6, + I2cFmAsI3c = 0x7, +} + +#[repr(u32)] +pub enum SpeedI2c { + Fm = 0x0, + Fmp = 0x1, +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub enum Tid { + TargetIbi = 0x1, + TargetRdData = 0x2, + TargetMasterWr = 0x8, + TargetMasterDef = 0xF, +} + +pub enum I3cStatus { + Ok, + Timeout, + Busy, + Pending, + Invalid, +} + +#[derive(Debug)] +pub struct I3cCmd<'a> { + pub cmd_lo: u32, + pub cmd_hi: u32, + pub tx: Option<&'a [u8]>, + pub rx: Option<&'a mut [u8]>, + pub tx_len: u32, + pub rx_len: u32, + pub ret: i32, +} + +pub struct I3cMsg<'a> { + pub buf: Option<&'a mut [u8]>, + pub actual_len: u32, + pub num_xfer: u32, + pub flags: u8, + pub hdr_mode: u8, + pub hdr_cmd_mode: u8, +} + +pub struct I3cXfer<'cmds, 'buf> { + pub cmds: &'cmds mut [I3cCmd<'buf>], + pub ret: i32, + pub done: Completion, +} + +impl<'cmds, 'buf> I3cXfer<'cmds, 'buf> { + pub fn new(cmds: &'cmds mut [I3cCmd<'buf>]) -> Self { + Self { + cmds, + ret: 0, + done: Completion::new(), + } + } + + pub fn ncmds(&self) -> usize { + self.cmds.len() + } +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub struct I3cPid(pub u64); + +impl I3cPid { + #[must_use] + pub const fn manuf_id(self) -> u16 { + ((self.0 >> 33) & 0x1FFF) as u16 + } + #[must_use] + pub const fn has_random_lower32(self) -> bool { + (self.0 & (1u64 << 32)) != 0 + } +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub struct I3cDeviceId { + pub pid: I3cPid, +} + +impl I3cDeviceId { + #[must_use] + pub const fn new(pid: u64) -> Self { + Self { pid: I3cPid(pid) } + } +} + +pub const I3C_BROADCAST_ADDR: u8 = 0x7E; +pub const I3C_MAX_ADDR: u8 = 0x7F; + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub enum I3cIbiType { + TargetIntr, + ControllerRoleRequest, + HotJoin, + WorkqueueCb, +} + +#[derive(Clone, Copy, Debug)] +pub struct I3cIbi<'a> { + pub ibi_type: I3cIbiType, + pub payload: Option<&'a [u8]>, +} + +impl<'a> I3cIbi<'a> { + #[inline] + #[must_use] + pub fn payload_len(&self) -> u8 { + self.payload.map_or(0, |p| { + u8::try_from(p.len().min(u8::MAX as usize)).unwrap_or(u8::MAX) + }) + } + + #[must_use] + pub fn first_byte(&self) -> Option { + self.payload.and_then(|p| p.first().copied()) + } +} + +pub trait HardwareInterface { + fn init(&mut self, config: &mut I3cConfig); + fn bus_num(&self) -> u8; + fn enable_irq(&mut self); + fn disable_irq(&mut self); + fn i3c_enable(&mut self, config: &I3cConfig); + fn i3c_disable(&mut self, is_secondary: bool); + fn core_reset_assert(&mut self, bus: u8); + fn core_reset_deassert(&mut self, bus: u8); + fn global_reset_assert(&mut self); + fn global_reset_deassert(&mut self); + fn clock_on(&mut self, bus: u8); + fn set_role(&mut self, is_secondary: bool); + fn init_clock(&mut self, config: &mut I3cConfig); + fn get_clock_rate(&self) -> u32; + fn calc_i2c_clk(&mut self, fscl_hz: u32) -> (u32, u32); + fn init_pid(&mut self, config: &mut I3cConfig, bus: u8); + fn enter_sw_mode(&mut self); + fn exit_sw_mode(&mut self); + fn i3c_toggle_scl_in(&mut self, count: u32); + fn gen_internal_stop(&mut self); + fn even_parity(byte: u8) -> bool; + fn set_ibi_mdb(&mut self, mdb: u8); + fn exit_halt(&mut self, config: &mut I3cConfig); + fn enter_halt(&mut self, by_sw: bool, config: &mut I3cConfig); + fn reset_ctrl(&mut self, reset: u32); + fn wr_tx_fifo(&mut self, bytes: &[u8]); + fn rd_fifo(&mut self, read_word: F, out: &mut [u8]) + where + F: FnMut() -> u32; + fn drain_fifo(&mut self, read_word: F, len: usize) + where + F: FnMut() -> u32; + fn rd_rx_fifo(&mut self, out: &mut [u8]); + fn rd_ibi_fifo(&mut self, out: &mut [u8]); + fn ibi_enable(&mut self, config: &mut I3cConfig, addr: u8) -> Result<(), I3cDrvError>; + fn start_xfer(&mut self, config: &mut I3cConfig, xfer: &mut I3cXfer); + fn end_xfer(&mut self, config: &mut I3cConfig); + fn get_addr_pos(&mut self, config: &I3cConfig, addr: u8) -> Option; + fn detach_i3c_dev(&mut self, pos: usize); + fn attach_i3c_dev(&mut self, pos: usize, addr: u8) -> Result<(), I3cDrvError>; + fn do_ccc(&mut self, config: &mut I3cConfig, ccc: &mut CccPayload) -> Result<(), I3cDrvError>; + fn do_entdaa(&mut self, config: &mut I3cConfig, index: u32) -> Result<(), I3cDrvError>; + fn priv_xfer_build_cmds<'a>( + &mut self, + cmds: &mut [I3cCmd<'a>], + msgs: &mut [I3cMsg<'a>], + pos: u8, + ) -> Result<(), I3cDrvError>; + fn priv_xfer( + &mut self, + config: &mut I3cConfig, + pid: u64, + msgs: &mut [I3cMsg], + ) -> Result<(), I3cDrvError>; + fn target_tx_write(&mut self, buf: &[u8]); + fn handle_ibi_sir(&mut self, config: &mut I3cConfig, addr: u8, len: usize); + fn handle_ibis(&mut self, config: &mut I3cConfig); + fn i3c_aspeed_isr(&mut self, config: &mut I3cConfig); + // target apis + fn target_ibi_raise_hj(&self, config: &mut I3cConfig) -> Result<(), I3cDrvError>; + fn target_handle_response_ready(&mut self, config: &mut I3cConfig); + fn target_pending_read_notify( + &mut self, + config: &mut I3cConfig, + buf: &[u8], + notifier: &mut I3cIbi, + ) -> Result<(), I3cDrvError>; + fn target_handle_ccc_update(&mut self, config: &mut I3cConfig); +} + +pub trait Instance { + fn ptr() -> *const ast1060_pac::i3c::RegisterBlock; + fn ptr_global() -> *const ast1060_pac::i3cglobal::RegisterBlock; + fn scu() -> *const ast1060_pac::scu::RegisterBlock; + const BUS_NUM: u8; +} + +macro_rules! macro_i3c { + ($I3cx: ident, $x: literal) => { + impl Instance for ast1060_pac::$I3cx { + fn ptr() -> *const ast1060_pac::i3c::RegisterBlock { + ast1060_pac::$I3cx::ptr() + } + + fn ptr_global() -> *const ast1060_pac::i3cglobal::RegisterBlock { + ast1060_pac::I3cglobal::ptr() + } + + fn scu() -> *const ast1060_pac::scu::RegisterBlock { + ast1060_pac::Scu::ptr() + } + const BUS_NUM: u8 = $x; + } + }; +} + +macro_i3c!(I3c, 0); +macro_i3c!(I3c1, 1); +macro_i3c!(I3c2, 2); +macro_i3c!(I3c3, 3); + +pub struct Ast1060I3c { + pub i3c: &'static ast1060_pac::i3c::RegisterBlock, + pub i3cg: &'static ast1060_pac::i3cglobal::RegisterBlock, + pub scu: &'static ast1060_pac::scu::RegisterBlock, + pub logger: L, + _marker: PhantomData, +} + +impl Ast1060I3c { + pub fn new(logger: L) -> Self { + let i3c = unsafe { &*I3C::ptr() }; + let i3cg = unsafe { &*I3C::ptr_global() }; + let scu = unsafe { &*I3C::scu() }; + Self { + i3c, + i3cg, + scu, + logger, + _marker: PhantomData, + } + } +} + +macro_rules! i3c_debug { + ($logger:expr, $($arg:tt)*) => {{ + use core::fmt::Write as _; + let mut buf: heapless::String<128> = heapless::String::new(); + let _ = write!(&mut buf, $($arg)*); + $logger.debug(buf.as_str()); + }}; +} + +#[allow(unused_macros)] +macro_rules! read_i3cg_reg0 { + ($self:expr, $bus:expr) => {{ + match $bus { + 0 => $self.i3cg.i3c010().read().bits(), + 1 => $self.i3cg.i3c020().read().bits(), + 2 => $self.i3cg.i3c030().read().bits(), + 3 => $self.i3cg.i3c040().read().bits(), + _ => panic!("invalid I3C bus index: {}", $bus), + } + }}; +} + +macro_rules! read_i3cg_reg1 { + ($self:expr, $bus:expr) => {{ + match $bus { + 0 => $self.i3cg.i3c014().read().bits(), + 1 => $self.i3cg.i3c024().read().bits(), + 2 => $self.i3cg.i3c034().read().bits(), + 3 => $self.i3cg.i3c044().read().bits(), + _ => panic!("invalid I3C bus index: {}", $bus), + } + }}; +} + +macro_rules! write_i3cg_reg0 { + ($self:expr, $bus:expr, |$w:ident| $body:expr) => {{ + match $bus { + 0 => $self.i3cg.i3c010().write(|$w| $body), + 1 => $self.i3cg.i3c020().write(|$w| $body), + 2 => $self.i3cg.i3c030().write(|$w| $body), + 3 => $self.i3cg.i3c040().write(|$w| $body), + _ => panic!("invalid I3C bus index: {}", $bus), + } + }}; +} + +macro_rules! write_i3cg_reg1 { + ($self:expr, $bus:expr, |$w:ident| $body:expr) => {{ + match $bus { + 0 => $self.i3cg.i3c014().write(|$w| $body), + 1 => $self.i3cg.i3c024().write(|$w| $body), + 2 => $self.i3cg.i3c034().write(|$w| $body), + 3 => $self.i3cg.i3c044().write(|$w| $body), + _ => panic!("invalid I3C bus index: {}", $bus), + } + }}; +} + +#[allow(unused_macros)] +macro_rules! modify_i3cg_reg0 { + ($self:expr, $bus:expr, |$r:ident, $w:ident| $body:expr) => {{ + match $bus { + 0 => $self.i3cg.i3c010().modify(|$r, $w| $body), + 1 => $self.i3cg.i3c020().modify(|$r, $w| $body), + 2 => $self.i3cg.i3c030().modify(|$r, $w| $body), + 3 => $self.i3cg.i3c040().modify(|$r, $w| $body), + _ => panic!("invalid I3C bus index: {}", $bus), + } + }}; +} + +macro_rules! modify_i3cg_reg1 { + ($self:expr, $bus:expr, |$r:ident, $w:ident| $body:expr) => {{ + match $bus { + 0 => $self.i3cg.i3c014().modify(|$r, $w| $body), + 1 => $self.i3cg.i3c024().modify(|$r, $w| $body), + 2 => $self.i3cg.i3c034().modify(|$r, $w| $body), + 3 => $self.i3cg.i3c044().modify(|$r, $w| $body), + _ => panic!("invalid I3C bus index: {}", $bus), + } + }}; +} + +macro_rules! i3c_dat_read { + ($self:expr, $pos:expr) => {{ + match ($pos) { + 0 => $self.i3c.i3cd280().read().bits(), + 1 => $self.i3c.i3cd284().read().bits(), + 2 => $self.i3c.i3cd288().read().bits(), + 3 => $self.i3c.i3cd28c().read().bits(), + 4 => $self.i3c.i3cd290().read().bits(), + 5 => $self.i3c.i3cd294().read().bits(), + 6 => $self.i3c.i3cd298().read().bits(), + 7 => $self.i3c.i3cd29c().read().bits(), + _ => 0, + } + }}; +} + +macro_rules! i3c_dat_write { + ($self:expr, $pos:expr, |$w:ident| $body:expr) => {{ + match ($pos) { + 0 => { + $self.i3c.i3cd280().write(|$w| $body); + } + 1 => { + $self.i3c.i3cd284().write(|$w| $body); + } + 2 => { + $self.i3c.i3cd288().write(|$w| $body); + } + 3 => { + $self.i3c.i3cd28c().write(|$w| $body); + } + 4 => { + $self.i3c.i3cd290().write(|$w| $body); + } + 5 => { + $self.i3c.i3cd294().write(|$w| $body); + } + 6 => { + $self.i3c.i3cd298().write(|$w| $body); + } + 7 => { + $self.i3c.i3cd29c().write(|$w| $body); + } + _ => { /* ignore */ } + } + }}; +} + +#[allow(unused_macros)] +macro_rules! i3c_dat_modify { + ($self:expr, $pos:expr, |$r:ident, $w:ident| $body:expr) => {{ + match ($pos) { + 0 => { + $self.i3c.i3cd280().modify(|$r, $w| $body); + } + 1 => { + $self.i3c.i3cd284().modify(|$r, $w| $body); + } + 2 => { + $self.i3c.i3cd288().modify(|$r, $w| $body); + } + 3 => { + $self.i3c.i3cd28c().modify(|$r, $w| $body); + } + 4 => { + $self.i3c.i3cd290().modify(|$r, $w| $body); + } + 5 => { + $self.i3c.i3cd294().modify(|$r, $w| $body); + } + 6 => { + $self.i3c.i3cd298().modify(|$r, $w| $body); + } + 7 => { + $self.i3c.i3cd29c().modify(|$r, $w| $body); + } + _ => { /* ignore */ } + } + }}; +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum PollError { + Timeout, +} + +pub fn poll_with_timeout( + mut read_reg: F, + mut condition: C, + delay: &mut D, + delay_ns: u32, + max_iters: u32, +) -> Result +where + F: FnMut() -> u32, + C: FnMut(u32) -> bool, + D: embedded_hal::delay::DelayNs, +{ + for _ in 0..max_iters { + let val = read_reg(); + if condition(val) { + return Ok(val); + } + delay.delay_ns(delay_ns); + } + Err(PollError::Timeout) +} + +impl HardwareInterface for Ast1060I3c { + #[allow(clippy::too_many_lines)] + fn init(&mut self, config: &mut I3cConfig) { + i3c_debug!(self.logger, "i3c init"); + + // Global reset is shared, so just need to deassert it + self.global_reset_deassert(); + + write_i3cg_reg1!(self, I3C::BUS_NUM, |w| unsafe { + w.actmode() + .bits(1) + .instid() + .bits(I3C::BUS_NUM) + .staticaddr() + .bits(0x74) + }); + let reg = read_i3cg_reg1!(self, I3C::BUS_NUM); + i3c_debug!(self.logger, "i3cg_reg1: {:#x}", reg); + + write_i3cg_reg0!(self, I3C::BUS_NUM, |w| unsafe { w.bits(0x0) }); + let reg = read_i3cg_reg0!(self, I3C::BUS_NUM); + i3c_debug!(self.logger, "i3cg_reg0: {:#x}", reg); + + let mut delay = DummyDelay {}; + self.core_reset_assert(I3C::BUS_NUM); + self.clock_on(I3C::BUS_NUM); + self.core_reset_deassert(I3C::BUS_NUM); + self.i3c_disable(config.is_secondary); + unsafe { + let scu090: u32 = 0x7e6e_2090; + + let reg: u32 = read_volatile(scu090 as *const u32); + i3c_debug!(self.logger, "scu090: {:#x}", reg); + + let scu050: u32 = 0x7e6e_2050; + + let reg: u32 = read_volatile(scu050 as *const u32); + i3c_debug!(self.logger, "scu050: {:#x}", reg); + } + + i3c_debug!( + self.logger, + "bus num: {}, is_secondary: {}", + I3C::BUS_NUM, + config.is_secondary + ); + // Reset controller + self.i3c.i3cd034().write(|w| { + w.ibiqueue_sw_rst() + .set_bit() + .rx_buffer_sw_rst() + .set_bit() + .tx_buffer_sw_rst() + .set_bit() + .response_queue_sw_rst() + .set_bit() + .cmd_queue_sw_rst() + .set_bit() + .core_sw_rst() + .set_bit() + }); + + let _ = poll_with_timeout( + || self.i3c.i3cd034().read().bits(), + |val| val == 0, + &mut delay, + 100_000, + 1_000_000, + ); + + self.set_role(config.is_secondary); + self.init_clock(config); + // init interrupt mask + self.i3c.i3cd03c().write(|w| unsafe { w.bits(0xffff_ffff) }); + if config.is_secondary { + self.i3c.i3cd040().write(|w| { + w.transfererrstaten() + .set_bit() + .respreadystatintren() + .set_bit() + .cccupdatedstaten() + .set_bit() + .dynaddrassgnstaten() + .set_bit() + .ibiupdatedstaten() + .set_bit() + .readreqrecvstaten() + .set_bit() + }); + + self.i3c.i3cd044().write(|w| { + w.transfererrsignalen() + .set_bit() + .respreadysignalintren() + .set_bit() + .cccupdatedsignalen() + .set_bit() + .dynaddrassgnsignalen() + .set_bit() + .ibiupdatedsignalen() + .set_bit() + .readreqrecvsignalen() + .set_bit() + }); + } else { + self.i3c.i3cd040().write(|w| { + w.transfererrstaten() + .set_bit() + .respreadystatintren() + .set_bit() + }); + + self.i3c.i3cd044().write(|w| { + w.transfererrsignalen() + .set_bit() + .respreadysignalintren() + .set_bit() + }); + } + + config.sir_allowed_by_sw = false; + + // Init hardware queues + self.i3c + .i3cd01c() + .write(|w| unsafe { w.ibidata_threshold_value().bits(31) }); + + self.i3c + .i3cd020() + .modify(|_, w| unsafe { w.rx_buffer_threshold_value().bits(0) }); + + // Init PID and DCR for target/secondary mode + self.init_pid(config, I3C::BUS_NUM); + + // Get max device and DAT start addr + config.maxdevs = self.i3c.i3cd05c().read().devaddrtabledepth().bits(); + config.free_pos = if config.maxdevs == 32 { + u32::MAX + } else { + (1u32 << config.maxdevs) - 1 + }; + config.need_da = 0; + + // Init DAT + for i in 0..(config.maxdevs) { + i3c_dat_write!(self, i, |w| { + w.sirreject().set_bit().mrreject().set_bit() + }); + } + + self.i3c.i3cd02c().write(|w| unsafe { w.bits(0xffff_ffff) }); + self.i3c.i3cd030().write(|w| unsafe { w.bits(0xffff_ffff) }); + self.i3c + .i3cd000() + .modify(|_, w| w.hot_join_ack_nack_ctrl().set_bit()); + + if config.is_secondary { + self.i3c + .i3cd004() + .write(|w| unsafe { w.dev_static_addr().bits(9).static_addr_valid().set_bit() }); + } else { + self.i3c + .i3cd004() + .write(|w| unsafe { w.dev_dynamic_addr().bits(8).dynamic_addr_valid().set_bit() }); + } + + self.i3c_enable(config); + + i3c_debug!(self.logger, "i3c enabled"); + // Enable hot-join + if !config.is_secondary { + self.i3c + .i3cd040() + .modify(|_, w| w.ibithldstaten().set_bit()); + self.i3c + .i3cd044() + .modify(|_, w| w.ibithldsignalen().set_bit()); + } + self.i3c + .i3cd000() + .modify(|_, w| w.hot_join_ack_nack_ctrl().clear_bit()); + i3c_debug!(self.logger, "i3c init done"); + i3c_debug!( + self.logger, + "i3c i3cd000: {:#x}", + self.i3c.i3cd000().read().bits() + ); + } + + fn bus_num(&self) -> u8 { + I3C::BUS_NUM + } + + fn enable_irq(&mut self) { + unsafe { + match I3C::BUS_NUM { + 0 => NVIC::unmask(ast1060_pac::Interrupt::i3c), + 1 => NVIC::unmask(ast1060_pac::Interrupt::i3c1), + 2 => NVIC::unmask(ast1060_pac::Interrupt::i3c2), + 3 => NVIC::unmask(ast1060_pac::Interrupt::i3c3), + _ => {} + } + } + } + + fn disable_irq(&mut self) { + match I3C::BUS_NUM { + 0 => NVIC::mask(ast1060_pac::Interrupt::i3c), + 1 => NVIC::mask(ast1060_pac::Interrupt::i3c1), + 2 => NVIC::mask(ast1060_pac::Interrupt::i3c2), + 3 => NVIC::mask(ast1060_pac::Interrupt::i3c3), + _ => {} + } + } + + fn i3c_disable(&mut self, is_secondary: bool) { + i3c_debug!(self.logger, "i3c disable"); + if self.i3c.i3cd000().read().enbl_i3cctrl().bit_is_clear() { + return; + } + + if is_secondary { + // enter sw mode + self.enter_sw_mode(); + } + self.i3c + .i3cd000() + .modify(|_, w| w.enbl_i3cctrl().clear_bit()); + + if is_secondary { + self.i3c_toggle_scl_in(8); + self.gen_internal_stop(); + self.exit_sw_mode(); + } + } + + fn core_reset_assert(&mut self, bus: u8) { + match bus { + 0 => self.scu.scu050().modify(|_, w| w.rst_i3c0ctrl().set_bit()), + 1 => self.scu.scu050().modify(|_, w| w.rst_i3c1ctrl().set_bit()), + 2 => self.scu.scu050().modify(|_, w| w.rst_i3c2ctrl().set_bit()), + 3 => self.scu.scu050().modify(|_, w| w.rst_i3c3ctrl().set_bit()), + _ => panic!("invalid I3C bus index: {bus}"), + }; + } + + fn core_reset_deassert(&mut self, bus: u8) { + let mask = 1u32 << (8 + u32::from(bus)); + self.scu + .scu054() + .modify(|_, w| unsafe { w.scu050sys_rst_ctrl_clear_reg2().bits(mask) }); + } + + fn global_reset_assert(&mut self) { + self.scu + .scu050() + .modify(|_, w| w.rst_i3cregdmactrl().set_bit()); + } + + fn global_reset_deassert(&mut self) { + self.scu + .scu054() + .modify(|_, w| unsafe { w.scu050sys_rst_ctrl_clear_reg2().bits(0x80) }); + } + + fn clock_on(&mut self, bus: u8) { + let mask = 1u32 << (8 + u32::from(bus)); + self.scu + .scu094() + .modify(|_, w| unsafe { w.scu090clk_stop_ctrl_clear_reg_set2().bits(mask) }); + } + + fn set_role(&mut self, is_secondary: bool) { + if is_secondary { + self.i3c + .i3cd0b0() + .modify(|_, w| unsafe { w.dev_op_mode().bits(1) }); + } else { + self.i3c + .i3cd0b0() + .modify(|_, w| unsafe { w.dev_op_mode().bits(0) }); + } + } + + fn init_clock(&mut self, config: &mut I3cConfig) { + let clk_rate = self.get_clock_rate(); + i3c_debug!(self.logger, "i3c clock rate: {} Hz", clk_rate); + config.core_period = (NSEC_PER_SEC).div_ceil(clk_rate); + + let ns_to_cnt_u8 = + |ns: u32| -> u8 { u8::try_from(ns.div_ceil(config.core_period)).unwrap_or(u8::MAX) }; + let ns_to_cnt_u16 = + |ns: u32| -> u16 { u16::try_from(ns.div_ceil(config.core_period)).unwrap_or(u16::MAX) }; + + // I2C FM + let (fm_hi_ns, fm_lo_ns) = self.calc_i2c_clk(config.i2c_scl_hz); + self.i3c.i3cd0bc().write(|w| unsafe { + w.i2cfmhcnt() + .bits(ns_to_cnt_u16(fm_hi_ns)) + .i2cfmlcnt() + .bits(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.i3c.i3cd0c0().write(|w| unsafe { + w.i2cfmphcnt() + .bits(ns_to_cnt_u8(i2c_fmp_hi_ns)) + .i2cfmplcnt() + .bits(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.i3c.i3cd0b4().write(|w| unsafe { + w.i3codhcnt() + .bits(ns_to_cnt_u8(od_hi_ns)) + .i3codlcnt() + .bits(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.i3c.i3cd0b8().write(|w| unsafe { + w.i3cpphcnt() + .bits(ns_to_cnt_u8(i3c_pp_hi_ns)) + .i3cpplcnt() + .bits(ns_to_cnt_u8(i3c_pp_lo_ns)) + }); + + // SDA TX hold time + let hold_steps = (config.sda_tx_hold_ns) + .div_ceil(config.core_period) + .clamp(SDA_TX_HOLD_MIN, SDA_TX_HOLD_MAX); + let mut reg = self.i3c.i3cd0d0().read().bits(); + reg = (reg & !SDA_TX_HOLD_MASK) | ((hold_steps & 0x7) << 16); + self.i3c.i3cd0d0().write(|w| unsafe { w.bits(reg) }); + + // BUS_FREE_TIMING + self.i3c.i3cd0d4().write(|w| unsafe { w.bits(0xffff_007c) }); + } + + fn get_clock_rate(&self) -> u32 { + 200_000_000 + } + + fn calc_i2c_clk(&mut self, fscl_hz: u32) -> (u32, u32) { + use core::cmp::max; + + debug_assert!(fscl_hz > 0); + let period_ns: u32 = (1_000_000_000u32).div_ceil(fscl_hz); + + 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 as u32, lo) + } + + fn init_pid(&mut self, config: &mut I3cConfig, bus: u8) { + self.i3c + .i3cd070() + .write(|w| unsafe { w.slvmipimfgid().bits(0x3f6).slvpiddcr().clear_bit() }); + + let rev_id: u32 = self.scu.scu004().read().hw_rev_id().bits().into(); + let mut reg: u32 = rev_id << 16 | u32::from(bus) << 12; + reg |= 0xa000_0000; + self.i3c.i3cd074().write(|w| unsafe { w.bits(reg) }); + let mut reg: u32 = self.i3c.i3cd078().read().bits(); + reg &= !SLV_DCR_MASK; + reg |= (config.dcr << 8) | 0x66; + self.i3c.i3cd078().write(|w| unsafe { w.bits(reg) }); + } + + fn enter_sw_mode(&mut self) { + i3c_debug!(self.logger, "enter sw mode"); + let bus = I3C::BUS_NUM; + let mut reg = read_i3cg_reg1!(self, bus); + reg |= I3CG_REG1_SCL_IN_SW_MODE_VAL | I3CG_REG1_SDA_IN_SW_MODE_VAL; + modify_i3cg_reg1!(self, bus, |_r, w| unsafe { w.bits(reg) }); + reg |= I3CG_REG1_SCL_IN_SW_MODE_EN | I3CG_REG1_SDA_IN_SW_MODE_EN; + modify_i3cg_reg1!(self, bus, |_r, w| unsafe { w.bits(reg) }); + } + + fn exit_sw_mode(&mut self) { + let bus = I3C::BUS_NUM; + let mut reg = read_i3cg_reg1!(self, bus); + reg &= !(I3CG_REG1_SCL_IN_SW_MODE_EN | I3CG_REG1_SDA_IN_SW_MODE_EN); + modify_i3cg_reg1!(self, bus, |_r, w| unsafe { w.bits(reg) }); + } + + 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.i3c.i3cd038().write(|w| unsafe { w.bits(0) }); + self.enter_sw_mode(); + // Enable hot-join + self.i3c.i3cd000().modify(|_, w| { + w.enbl_adaption_of_i2ci3cmode() + .clear_bit() + .ibipayloaden() + .set_bit() + .enbl_i3cctrl() + .set_bit() + }); + let wait_cnt = &self.i3c.i3cd0d4().read().i3cibifree().bits(); + let wait_ns = u32::from(*wait_cnt) * config.core_period; + let mut delay = DummyDelay {}; + delay.delay_ns(wait_ns * 100_u32); + self.i3c_toggle_scl_in(8); + if self.i3c.i3cd000().read().enbl_i3cctrl().bit_is_set() { + self.gen_internal_stop(); + } + self.exit_sw_mode(); + } else { + self.i3c.i3cd000().modify(|_, w| { + w.i3cbroadcast_addr_include() + .set_bit() + .enbl_i3cctrl() + .set_bit() + }); + } + } + + fn i3c_toggle_scl_in(&mut self, count: u32) { + let bus = I3C::BUS_NUM; + for _ in 0..count { + modify_i3cg_reg1!(self, bus, |r, w| unsafe { + w.bits(r.bits() & !I3CG_REG1_SCL_IN_SW_MODE_VAL) + }); + modify_i3cg_reg1!(self, bus, |r, w| unsafe { + w.bits(r.bits() | I3CG_REG1_SCL_IN_SW_MODE_VAL) + }); + } + } + + fn gen_internal_stop(&mut self) { + let bus = I3C::BUS_NUM; + modify_i3cg_reg1!(self, bus, |r, w| unsafe { + w.bits(r.bits() & !I3CG_REG1_SCL_IN_SW_MODE_VAL) + }); + modify_i3cg_reg1!(self, bus, |r, w| unsafe { + w.bits(r.bits() & !I3CG_REG1_SDA_IN_SW_MODE_VAL) + }); + modify_i3cg_reg1!(self, bus, |r, w| unsafe { + w.bits(r.bits() | I3CG_REG1_SCL_IN_SW_MODE_VAL) + }); + modify_i3cg_reg1!(self, bus, |r, w| unsafe { + w.bits(r.bits() | I3CG_REG1_SDA_IN_SW_MODE_VAL) + }); + } + + fn even_parity(byte: u8) -> bool { + let mut parity = false; + let mut b = byte; + + while b != 0 { + parity = !parity; + b &= b - 1; + } + + !parity + } + + fn set_ibi_mdb(&mut self, mdb: u8) { + self.i3c + .i3cd000() + .modify(|_, w| unsafe { w.mdb().bits(mdb) }); + } + + fn exit_halt(&mut self, config: &mut I3cConfig) { + let state = self.i3c.i3cd054().read().cmtfrstatus().bits(); + let expected = if config.is_secondary { + CM_TFR_STS_TARGET_HALT + } else { + CM_TFR_STS_MASTER_HALT + }; + + if state != expected { + return; + } + + self.i3c.i3cd000().modify(|_, w| w.i3cresume().set_bit()); + + let rc = poll_with_timeout( + || u32::from(self.i3c.i3cd054().read().cmtfrstatus().bits()), + |val| val != u32::from(expected), + &mut DummyDelay {}, + 10000, + 1_000_000, + ); + + 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.i3c.i3cd000().modify(|_, w| w.i3cabort().set_bit()); + } + + let rc = poll_with_timeout( + || u32::from(self.i3c.i3cd054().read().cmtfrstatus().bits()), + |val| val == u32::from(expected), + &mut DummyDelay {}, + 10000, + 1_000_000, + ); + + 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.i3c.i3cd034().write(|w| unsafe { w.bits(reg) }); + let rc = poll_with_timeout( + || self.i3c.i3cd034().read().bits(), + |val| val == 0, + &mut DummyDelay {}, + 10_000, + 1_000_000, + ); + + if rc.is_err() { + i3c_debug!(self.logger, "reset_ctrl: timeout"); + } + } + + fn wr_tx_fifo(&mut self, bytes: &[u8]) { + let mut chunks = bytes.chunks_exact(4); + for chunk in &mut chunks { + let word = u32::from_le_bytes([chunk[0], chunk[1], chunk[2], chunk[3]]); + self.i3c + .i3cd014() + .write(|w| unsafe { w.tx_data_port().bits(word) }); + } + + let rem = chunks.remainder(); + if !rem.is_empty() { + let mut tmp = [0u8; 4]; + tmp[..rem.len()].copy_from_slice(rem); + let word = u32::from_le_bytes(tmp); + self.i3c + .i3cd014() + .write(|w| unsafe { w.tx_data_port().bits(word) }); + } + } + + fn rd_fifo(&mut self, mut read_word: F, out: &mut [u8]) + where + F: FnMut() -> u32, + { + let mut chunks = out.chunks_exact_mut(4); + for chunk in &mut chunks { + let val = read_word(); + chunk.copy_from_slice(&val.to_le_bytes()); + } + + let rem = chunks.into_remainder(); + if !rem.is_empty() { + let val = read_word(); + let bytes = val.to_le_bytes(); + rem.copy_from_slice(&bytes[..rem.len()]); + } + } + + fn rd_rx_fifo(&mut self, out: &mut [u8]) { + self.rd_fifo(|| self.i3c.i3cd014().read().rx_data_port().bits(), out); + } + + fn rd_ibi_fifo(&mut self, out: &mut [u8]) { + self.rd_fifo(|| self.i3c.i3cd018().read().bits(), out); + } + + 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); + let pos_opt = config + .attached + .pos_of(dev_idx) + .or(config.attached.devices[dev_idx].pos); + + let pos: u8 = pos_opt.ok_or(I3cDrvError::NoDatPos)?; + i3c_debug!(self.logger, "ibi_enable: pos={}", pos); + let dev = &config.attached.devices[dev_idx]; + let tgt_bcr: u32 = u32::from(dev.bcr); + let mut reg = i3c_dat_read!(self, u32::from(pos)); + 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; + } + + i3c_dat_write!(self, pos, |w| unsafe { w.bits(reg) }); + + let mut sir_reject = self.i3c.i3cd030().read().bits(); + sir_reject &= !bit(pos.into()); + self.i3c.i3cd030().write(|w| unsafe { w.bits(sir_reject) }); + + self.i3c + .i3cd040() + .modify(|_, w| w.ibithldstaten().set_bit()); + + self.i3c + .i3cd044() + .modify(|_, w| w.ibithldsignalen().set_bit()); + + let events = I3C_CCC_EVT_INTR; + 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.i3c.i3cd040().read().bits() + ); + i3c_debug!( + self.logger, + "i3cd044 (IBI thld sig) = {:#x}", + self.i3c.i3cd044().read().bits() + ); + i3c_debug!( + self.logger, + "i3cd280 dat_addr[{}] = {:#x}", + pos, + i3c_dat_read!(self, u32::from(pos)) + ); + 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.i3c.i3cd01c().modify(|_, w| unsafe { + w.response_buffer_threshold_value() + .bits(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.i3c.i3cd00c().write(|w| unsafe { w.bits(cmd.cmd_hi) }); + self.i3c.i3cd00c().write(|w| unsafe { w.bits(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() { + i3c_debug!(self.logger, "end_xfer: no current xfer"); + return; + } + let xfer: &mut I3cXfer = unsafe { &mut *(p.cast::()) }; + + let nresp = self.i3c.i3cd04c().read().respbufblr().bits() as usize; + + for _ in 0..nresp { + let resp = self.i3c.i3cd010().read().bits(); + + 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.drain_fifo(|| self.i3c.i3cd014().read().rx_data_port().bits(), rx_len); + } + continue; + } + + let cmd = &mut xfer.cmds[tid]; + 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 { + if let Some(rx_buf) = cmd.rx.as_deref_mut() { + self.rd_rx_fifo(&mut rx_buf[..rx_len]); + } + } + } + let mut ret = 0; + for i in 0..nresp { + let r = xfer.cmds[i].ret; + if r != 0 { + ret = r; + } + } + + 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 { + 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) { + i3c_dat_write!(self, pos, |w| { + w.sirreject().set_bit().mrreject().set_bit() + }); + } + + 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; + } + + i3c_dat_write!(self, pos, |w| unsafe { + w.sirreject() + .set_bit() + .mrreject() + .set_bit() + .devdynamicaddr() + .bits(da_with_parity) + }); + + Ok(()) + } + + #[allow(clippy::too_many_lines)] + fn do_ccc( + &mut self, + config: &mut I3cConfig, + payload: &mut CccPayload<'_, '_>, + ) -> Result<(), I3cDrvError> { + // init i3c_cmd to all 0 + 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 { + // -------- Broadcast CCC -------- + is_broadcast = true; + + if data_len > 0 { + if 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); + }; + // -------- Direct CCC -------- + 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); + + let mut delay = DummyDelay {}; + if !xfer.done.wait_for_us(1_000_000_000, &mut delay) { + 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 { + if let Some(ccc_rw) = payload.ccc.as_mut() { + if let Some(d) = ccc_rw.data.as_deref() { + ccc_rw.num_xfer = d.len(); + } + } + } + + 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); + + let mut delay = DummyDelay {}; + + if !xfer.done.wait_for_us(1_000_000_000, &mut delay) { + 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); + } + + for i in 0..cmds_len { + let (is_read, ptr, len) = { + let m = &mut msgs[i]; + 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()) + } + }; + + let cmd = &mut cmds[i]; + *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)?; + + let mut cmds: heapless::Vec = heapless::Vec::new(); + for _ in 0..msgs.len() { + cmds.push(I3cCmd { + cmd_lo: 0, + cmd_hi: 0, + tx: None, + rx: None, + tx_len: 0, + rx_len: 0, + ret: 0, + }) + .unwrap(); + } + + 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); + + let mut delay = DummyDelay {}; + if !xfer.done.wait_for_us(1_000_000_000, &mut delay) { + 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 { + m.actual_len = xfer.cmds[i].rx_len; + } + } + + match xfer.ret { + 0 => Ok(()), + _ => Err(I3cDrvError::Timeout), + } + } + + 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.i3c.i3cd00c().write(|w| unsafe { w.bits(cmd) }); + } + + fn drain_fifo(&mut self, mut read_word: F, len: usize) + where + F: FnMut() -> u32, + { + let nwords = (len + 3) >> 2; + for _ in 0..nwords { + let _ = read_word(); + } + } + + 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.drain_fifo(|| self.i3c.i3cd018().read().bits(), 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 = I3C::BUS_NUM as usize; + ibi_workq::i3c_ibi_work_enqueue_target_irq(bus, addr, &ibi_buf[..take]); + } + + fn handle_ibis(&mut self, config: &mut I3cConfig) { + let nibis = self.i3c.i3cd04c().read().ibistatuscnt().bits(); + + i3c_debug!(self.logger, "Number of IBIs: {}", nibis); + if nibis == 0 { + return; + } + + for _ in 0..nibis { + // i3c_debug!(self.logger, "i3cd018 (IBI Queue status) = {:#x}", self.i3c.i3cd018().read().bits()); + let reg = self.i3c.i3cd018().read().bits(); + + 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 = I3C::BUS_NUM as usize; + i3c_debug!(self.logger, "Hot-join IBI"); + ibi_workq::i3c_ibi_work_enqueue_hotjoin(bus); + } else { + // normal ibi + i3c_debug!(self.logger, "Normal IBI"); + self.drain_fifo(|| self.i3c.i3cd018().read().bits(), ibi_data_len); + } + } + } + + fn i3c_aspeed_isr(&mut self, config: &mut I3cConfig) { + self.disable_irq(); + let status = self.i3c.i3cd03c().read().bits(); + i3c_debug!(self.logger, "[ISR] 0x{:08x}", status); + if status == 0 { + self.enable_irq(); + return; + } + + if config.is_secondary { + if status & INTR_DYN_ADDR_ASSGN_STAT != 0 { + let da = self.i3c.i3cd004().read().dev_dynamic_addr().bits(); + if let Some(tc) = &mut config.target_config { + tc.addr = Some(da); + } + ibi_workq::i3c_ibi_work_enqueue_target_da_assignment(I3C::BUS_NUM.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.i3c.i3cd03c().write(|w| unsafe { w.bits(status) }); + self.enable_irq(); + } + + fn target_ibi_raise_hj(&self, config: &mut I3cConfig) -> Result<(), I3cDrvError> { + if !config.is_secondary { + return Err(I3cDrvError::Invalid); + } + let hj_support = self.i3c.i3cd008().read().slvhjcap().bit(); + if !hj_support { + return Err(I3cDrvError::Invalid); + } + + let addr_valid = self.i3c.i3cd004().read().dynamic_addr_valid().bit(); + if addr_valid { + return Err(I3cDrvError::Access); + } + + self.i3c.i3cd038().write(|w| unsafe { w.bits(8) }); // set HJ request + + Ok(()) + } + + fn target_handle_response_ready(&mut self, config: &mut I3cConfig) { + let nresp = self.i3c.i3cd04c().read().respbufblr().bits(); + + for _ in 0..nresp { + let resp = self.i3c.i3cd010().read().bits(); + + 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]; + self.rd_rx_fifo(&mut buf[..rx_len]); + i3c_debug!( + self.logger, + "[MASTER ==> TARGET] TARGET READ: {:02x?}", + &buf[..rx_len] + ); + } + + 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.i3c.i3cd038().read().bits(); + 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 { + if !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.i3c.i3cd00c().write(|w| unsafe { w.bits(cmd) }); + + config.target_ibi_done.reset(); + + self.i3c + .i3cd01c() + .modify(|_, w| unsafe { w.response_buffer_threshold_value().bits(0) }); + + self.target_tx_write(buf); + config.target_data_done.reset(); + + self.i3c.i3cd08c().write(|w| w.sir().set_bit()); + + let mut delay = DummyDelay {}; + + if !config + .target_ibi_done + .wait_for_us(1_000_000_000, &mut delay) + { + 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(1_000_000_000, &mut delay) + { + 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.i3c.i3cd038().read().bits(); + self.i3c.i3cd038().write(|w| unsafe { w.bits(event) }); + i3c_debug!(self.logger, "CCC update event: 0x{:08x}", event); + let reg = self.i3c.i3cd054().read().cmtfrstatus().bits(); + if reg == CM_TFR_STS_TARGET_HALT { + self.enter_halt(true, config); + self.exit_halt(config); + } + } +} diff --git a/src/i3c/ccc.rs b/src/i3c/ccc.rs new file mode 100644 index 0000000..0372dfb --- /dev/null +++ b/src/i3c/ccc.rs @@ -0,0 +1,442 @@ +// Licensed under the Apache-2.0 license + +use super::ast1060_i3c::HardwareInterface; +use super::i3c_config::I3cConfig; + +pub const I3C_CCC_RSTDAA: u8 = 0x6; +pub const I3C_CCC_ENTDAA: u8 = 0x7; +pub const I3C_CCC_SETHID: u8 = 0x61; +pub const I3C_CCC_DEVCTRL: u8 = 0x62; +pub const I3C_CCC_SETDASA: u8 = 0x87; +pub const I3C_CCC_SETNEWDA: u8 = 0x88; +pub const I3C_CCC_GETPID: u8 = 0x8d; +pub const I3C_CCC_GETBCR: u8 = 0x8e; +pub const I3C_CCC_GETSTATUS: u8 = 0x90; + +pub const I3C_CCC_EVT_INTR: u8 = 1 << 0; +pub const I3C_CCC_EVT_CR: u8 = 1 << 1; +pub const I3C_CCC_EVT_HJ: u8 = 1 << 3; +pub const I3C_CCC_EVT_ALL: u8 = I3C_CCC_EVT_INTR | I3C_CCC_EVT_CR | I3C_CCC_EVT_HJ; + +#[derive(Debug)] +pub enum CccError { + InvalidParam, + NotFound, + NoFreeSlot, + Invalid, +} + +#[derive(Debug)] +pub struct CccTargetPayload<'a> { + /// Target 7‑bit dynamic address (left‑aligned; driver decides if LSB is R/W). + pub addr: u8, + /// `false` = write, `true` = read. + pub rnw: bool, + /// Data buffer for write (source) or read (destination). + pub data: Option<&'a mut [u8]>, + /// Actual bytes transferred (driver fills on return). + pub num_xfer: usize, +} + +#[derive(Debug)] +pub struct Ccc<'a> { + pub id: u8, + /// Optional CCC data immediately following the CCC byte. + pub data: Option<&'a mut [u8]>, + /// Actual bytes transferred (driver fills on return). + pub num_xfer: usize, +} + +/// One CCC transaction description. +#[derive(Debug)] +pub struct CccPayload<'a, 'b> { + pub ccc: Option>, + /// Optional list of direct‑CCC target payloads. + pub targets: Option<&'b mut [CccTargetPayload<'a>]>, +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum CccRstActDefByte { + CccRstActNoReset = 0x0, + CccRstActPeriphralOnly = 0x1, + CccRstActResetWholeTarget = 0x2, + CccRstActDebugNetworkAdapter = 0x3, + CccRstActVirtualTargetDetect = 0x4, +} + +impl CccRstActDefByte { + #[inline] + fn as_byte(self) -> u8 { + self as u8 + } +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub enum GetStatusFormat { + Fmt1, + Fmt2(GetStatusDefByte), +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub enum GetStatusDefByte { + /// 0x00 - TGTSTAT + TgtStat, + /// 0x91 - PRECR + Precr, +} + +impl GetStatusDefByte { + #[inline] + fn as_byte(self) -> u8 { + match self { + GetStatusDefByte::TgtStat => 0x00, + GetStatusDefByte::Precr => 0x91, + } + } +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub enum GetStatusResp { + Fmt1 { + status: u16, + }, + Fmt2 { + kind: GetStatusDefByte, + raw_u16: u16, + }, +} + +const fn i3c_ccc_enec(broadcast: bool) -> u8 { + if broadcast { + 0x00 + } else { + 0x80 + } +} + +const fn i3c_ccc_disec(broadcast: bool) -> u8 { + if broadcast { + 0x01 + } else { + 0x81 + } +} + +const fn i3c_ccc_rstact(broadcast: bool) -> u8 { + if broadcast { + 0x2a + } else { + 0x9a + } +} + +pub fn ccc_events_all_set( + hw: &mut H, + config: &mut I3cConfig, + enable: bool, + events: u8, +) -> Result<(), CccError> +where + H: HardwareInterface, +{ + let id = if enable { + i3c_ccc_enec(true) + } else { + i3c_ccc_disec(true) + }; + let rc = hw.do_ccc( + config, + &mut CccPayload { + ccc: Some(Ccc { + id, + data: Some(&mut [events]), + num_xfer: 0, + }), + targets: None, + }, + ); + + match rc { + Ok(()) => Ok(()), + _ => Err(CccError::Invalid), + } +} + +pub fn ccc_events_set( + hw: &mut H, + config: &mut I3cConfig, + da: u8, + enable: bool, + events: u8, +) -> Result<(), CccError> +where + H: HardwareInterface, +{ + if da == 0 { + return Err(CccError::InvalidParam); + } + + let mut ev_buf = [events]; + let tgt = CccTargetPayload { + addr: da, + rnw: false, + data: Some(&mut ev_buf[..]), + num_xfer: 0, + }; + + let mut tgts = [tgt]; + let ccc_id = if enable { + i3c_ccc_enec(false) + } else { + i3c_ccc_disec(false) + }; + let ccc = Ccc { + id: ccc_id, + data: None, + num_xfer: 0, + }; + + let mut payload = CccPayload { + ccc: Some(ccc), + targets: Some(&mut tgts[..]), + }; + + let rc = hw.do_ccc(config, &mut payload); + match rc { + Ok(()) => Ok(()), + _ => Err(CccError::Invalid), + } +} + +pub fn ccc_rstact_all( + hw: &mut H, + config: &mut I3cConfig, + action: CccRstActDefByte, +) -> Result<(), CccError> +where + H: HardwareInterface, +{ + let mut db = [action.as_byte()]; + let ccc = Ccc { + id: i3c_ccc_rstact(true), + data: Some(&mut db[..]), + num_xfer: 0, + }; + let mut payload = CccPayload { + ccc: Some(ccc), + targets: None, + }; + + let rc = hw.do_ccc(config, &mut payload); + match rc { + Ok(()) => Ok(()), + _ => Err(CccError::Invalid), + } +} + +pub fn ccc_getbcr(hw: &mut H, config: &mut I3cConfig, dyn_addr: u8) -> Result +where + H: HardwareInterface, +{ + if dyn_addr == 0 { + return Err(CccError::InvalidParam); + } + + let mut bcr_buf = [0u8; 1]; + + let tgt = CccTargetPayload { + addr: dyn_addr, + rnw: true, + data: Some(&mut bcr_buf[..]), + num_xfer: 0, + }; + let mut tgts = [tgt]; + + let ccc = Ccc { + id: I3C_CCC_GETBCR, + data: None, + num_xfer: 0, + }; + let mut payload = CccPayload { + ccc: Some(ccc), + targets: Some(&mut tgts[..]), + }; + + let rc = hw.do_ccc(config, &mut payload); + match rc { + Ok(()) => Ok(bcr_buf[0]), + _ => Err(CccError::Invalid), + } +} + +pub fn ccc_setnewda( + hw: &mut H, + config: &mut I3cConfig, + curr_da: u8, + new_da: u8, +) -> Result<(), CccError> +where + H: HardwareInterface, +{ + if curr_da == 0 || new_da == 0 { + return Err(CccError::InvalidParam); + } + + let pos = config.attached.pos_of_addr(curr_da); + if pos.is_none() { + return Err(CccError::NotFound); + } + + if !config.addrbook.is_free(new_da) { + return Err(CccError::NoFreeSlot); + } + let mut new_dyn_addr = (new_da & 0x7F) << 1; + let tgt = CccTargetPayload { + addr: curr_da, + rnw: false, + data: Some(core::slice::from_mut(&mut new_dyn_addr)), + num_xfer: 0, + }; + let mut tgts = [tgt]; + let ccc = Ccc { + id: I3C_CCC_SETNEWDA, + data: None, + num_xfer: 0, + }; + let mut payload = CccPayload { + ccc: Some(ccc), + targets: Some(&mut tgts[..]), + }; + + let rc = hw.do_ccc(config, &mut payload); + match rc { + Ok(()) => Ok(()), + _ => Err(CccError::Invalid), + } +} + +fn bytes_to_pid(bytes: &[u8]) -> u64 { + bytes + .iter() + .take(6) + .fold(0u64, |acc, &b| (acc << 8) | u64::from(b)) +} + +pub fn ccc_getpid(hw: &mut H, config: &mut I3cConfig, dyn_addr: u8) -> Result +where + H: HardwareInterface, +{ + let mut pid_buf = [0u8; 6]; + + let tgt = CccTargetPayload { + addr: dyn_addr, + rnw: true, + data: Some(&mut pid_buf[..]), + num_xfer: 0, + }; + let mut tgts = [tgt]; + + let ccc = Ccc { + id: I3C_CCC_GETPID, + data: None, + num_xfer: 0, + }; + let mut payload = CccPayload { + ccc: Some(ccc), + targets: Some(&mut tgts[..]), + }; + + let rc = hw.do_ccc(config, &mut payload); + match rc { + Ok(()) => Ok(bytes_to_pid(&pid_buf)), + _ => Err(CccError::Invalid), + } +} + +pub fn ccc_getstatus( + hw: &mut H, + config: &mut I3cConfig, + da: u8, + fmt: GetStatusFormat, +) -> Result +where + H: HardwareInterface, +{ + let mut data_buf = [0u8; 2]; + + let mut defbyte_buf = [0u8; 1]; + + let tgt = CccTargetPayload { + addr: da, + rnw: true, + data: Some(&mut data_buf[..]), + num_xfer: 0, + }; + + let mut ccc = Ccc { + id: I3C_CCC_GETSTATUS, + data: None, + num_xfer: 0, + }; + + let kind_opt = match fmt { + GetStatusFormat::Fmt1 => None, + GetStatusFormat::Fmt2(kind) => { + defbyte_buf[0] = kind.as_byte(); + ccc.data = Some(&mut defbyte_buf[..]); + Some(kind) + } + }; + + let mut targets_arr = [tgt]; + let mut payload = CccPayload { + ccc: Some(ccc), + targets: Some(&mut targets_arr[..]), + }; + + let rc = hw.do_ccc(config, &mut payload); + match rc { + Ok(()) => { + let val = u16::from_be_bytes(data_buf); + + let resp = match kind_opt { + None => GetStatusResp::Fmt1 { status: val }, + Some(kind) => GetStatusResp::Fmt2 { kind, raw_u16: val }, + }; + Ok(resp) + } + + _ => Err(CccError::Invalid), + } +} + +pub fn ccc_getstatus_fmt1(hw: &mut H, config: &mut I3cConfig, da: u8) -> Result +where + H: HardwareInterface, +{ + match ccc_getstatus(hw, config, da, GetStatusFormat::Fmt1) { + Ok(GetStatusResp::Fmt1 { status }) => Ok(status), + _ => Err(CccError::Invalid), + } +} + +pub fn ccc_rstdaa_all(hw: &mut H, config: &mut I3cConfig) -> Result<(), CccError> +where + H: HardwareInterface, +{ + let rc = hw.do_ccc( + config, + &mut CccPayload { + ccc: Some(Ccc { + id: I3C_CCC_RSTDAA, + data: None, + num_xfer: 0, + }), + targets: None, + }, + ); + match rc { + Ok(()) => Ok(()), + _ => Err(CccError::Invalid), + } +} diff --git a/src/i3c/i3c_config.rs b/src/i3c/i3c_config.rs new file mode 100644 index 0000000..fd0cd66 --- /dev/null +++ b/src/i3c/i3c_config.rs @@ -0,0 +1,380 @@ +// Licensed under the Apache-2.0 license + +use core::marker::PhantomData; +use core::sync::atomic::{AtomicBool, AtomicPtr, Ordering}; +use embedded_hal::delay::DelayNs; +use heapless::Vec; + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum I3cConfigError { + AddrInUse, + AddrExhausted, + NoFreeSlot, + DevNotFound, + DevAlreadyAttached, + InvalidParam, + Other, +} + +#[derive(Default)] +pub struct CommonState { + _phantom: PhantomData<()>, +} + +#[derive(Default)] +pub struct CommonCfg { + _phantom: PhantomData<()>, +} + +#[derive(Clone, Copy)] +pub struct ResetSpec { + pub id: u32, + pub active_high: bool, +} + +pub struct I3cTargetConfig { + pub flags: u8, + pub addr: Option, + pub mdb: u8, +} + +impl I3cTargetConfig { + #[must_use] + pub const fn new(flags: u8, addr: Option, mdb: u8) -> Self { + Self { flags, addr, mdb } + } +} + +pub struct AddrBook { + pub in_use: [bool; 128], + pub reserved: [bool; 128], +} + +impl Default for AddrBook { + fn default() -> Self { + Self::new() + } +} + +impl AddrBook { + #[must_use] + pub const fn new() -> Self { + Self { + in_use: [false; 128], + reserved: [false; 128], + } + } + + #[inline] + #[must_use] + pub fn is_free(&self, a: u8) -> bool { + !self.in_use[a as usize] && !self.reserved[a as usize] + } + + pub fn reserve_defaults(&mut self) { + for a in 0usize..=7 { + self.reserved[a] = true; + } + self.reserved[0x7E_usize] = true; + for i in 0..=7 { + let alt = 0x7E ^ (1u8 << i); + if alt <= 0x7E { + self.reserved[alt as usize] = true; + } + } + } + + pub fn alloc_from(&mut self, start: u8) -> Option { + let mut a = start.max(8); + while a < 0x7F { + if self.is_free(a) { + // self.in_use[a as usize] = true; + return Some(a); + } + a += 1; + } + None + } + + #[inline] + pub fn mark_use(&mut self, a: u8, used: bool) { + if a != 0 { + self.in_use[a as usize] = used; + } + } +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub enum DevKind { + I3c, + I2c, +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub struct DeviceEntry { + pub kind: DevKind, + pub pid: Option, + pub static_addr: u8, + pub dyn_addr: u8, + pub desired_da: u8, + pub bcr: u8, + pub dcr: u8, + pub maxrd: u8, + pub maxwr: u8, + pub mrl: u16, + pub mwl: u16, + pub max_ibi: u8, + pub ibi_en: bool, + pub pos: Option, +} + +pub struct Attached { + pub devices: Vec, + pub by_pos: [Option; 8], +} + +impl Default for Attached { + fn default() -> Self { + Self::new() + } +} + +impl Attached { + #[must_use] + pub const fn new() -> Self { + Self { + devices: heapless::Vec::new(), + by_pos: [None; 8], + } + } + + pub fn attach(&mut self, dev: DeviceEntry) -> Result { + let idx = self.devices.len(); + self.devices + .push(dev) + .map_err(|_| I3cConfigError::NoFreeSlot)?; + Ok(idx) + } + + pub fn detach(&mut self, dev_idx: usize) { + if dev_idx >= self.devices.len() { + return; + } + + if let Some(pos) = self.devices[dev_idx].pos { + if let Some(p) = self.by_pos.get_mut(pos as usize) { + *p = None; + } + } + + self.devices.remove(dev_idx); + for bp in &mut self.by_pos { + if let Some(idx) = *bp { + let idx_usize = idx as usize; + if idx_usize > dev_idx { + *bp = Some(u8::try_from(idx_usize - 1).expect("idx too large")); + } + } + } + } + + pub fn detach_by_pos(&mut self, pos: usize) { + if let Some(Some(dev_idx)) = self.by_pos.get(pos) { + self.detach(*dev_idx as usize); + } + } + #[must_use] + pub fn pos_of(&self, dev_idx: usize) -> Option { + self.by_pos + .iter() + .position(|&v| v == Some(u8::try_from(dev_idx).expect("dev_idx too large"))) + .and_then(|p| u8::try_from(p).ok()) + } + #[must_use] + pub fn find_dev_idx_by_addr(&self, da: u8) -> Option { + self.devices.iter().position(|d| d.dyn_addr == da) + } + #[must_use] + pub fn pos_of_addr(&self, da: u8) -> Option { + let dev_idx = self.devices.iter().position(|d| d.dyn_addr == da)?; + self.pos_of(dev_idx) + } + #[must_use] + pub fn pos_of_pid(&self, pid: u64) -> Option { + let dev_idx = self.devices.iter().position(|d| d.pid == Some(pid))?; + self.pos_of(dev_idx) + } + + #[inline] + pub fn map_pos(&mut self, pos: u8, idx: u8) { + self.by_pos[pos as usize] = Some(idx); + } + + #[inline] + pub fn unmap_pos(&mut self, pos: u8) { + self.by_pos[pos as usize] = None; + } +} + +pub struct Completion { + done: AtomicBool, +} + +impl Default for Completion { + fn default() -> Self { + Self::new() + } +} + +impl Completion { + #[must_use] + pub const fn new() -> Self { + Self { + done: AtomicBool::new(false), + } + } + + #[inline] + pub fn reset(&self) { + self.done.store(false, Ordering::Release); + } + + #[inline] + pub fn complete(&self) { + self.done.store(true, Ordering::Release); + + cortex_m::asm::sev(); + } + + #[inline] + pub fn is_completed(&self) -> bool { + self.done.load(Ordering::Acquire) + } + + pub fn wait_for_us(&self, timeout_us: u32, delay: &mut D) -> bool { + let mut left = timeout_us; + while !self.is_completed() { + if left == 0 { + return false; + } + delay.delay_us(1); + left -= 1; + } + true + } +} + +pub struct I3cConfig { + // Optional: your own “common” higher-level state + pub common: CommonState, + + pub target_config: Option, + pub addrbook: AddrBook, + pub attached: Attached, + + // Concurrency + pub curr_xfer: AtomicPtr<()>, + + // Timing/phy params (ns) + pub core_period: u32, + pub i2c_scl_hz: u32, + pub i3c_scl_hz: u32, + pub i3c_pp_scl_hi_period_ns: u32, + pub i3c_pp_scl_lo_period_ns: u32, + pub i3c_od_scl_hi_period_ns: u32, + pub i3c_od_scl_lo_period_ns: u32, + pub sda_tx_hold_ns: u32, + pub is_secondary: bool, + + // Tables/indices + pub maxdevs: u16, + pub free_pos: u32, + pub need_da: u32, + + pub addrs: [u8; 8], + pub dcr: u32, + + // Target-mode data + pub sir_allowed_by_sw: bool, + pub target_ibi_done: Completion, + pub target_data_done: Completion, +} + +impl Default for I3cConfig { + fn default() -> Self { + Self::new() + } +} + +impl I3cConfig { + #[must_use] + pub fn new() -> Self { + Self { + common: CommonState::default(), + target_config: None, + addrbook: AddrBook::new(), + attached: Attached::new(), + curr_xfer: AtomicPtr::new(core::ptr::null_mut()), + core_period: 0, + i2c_scl_hz: 0, + i3c_scl_hz: 0, + i3c_pp_scl_hi_period_ns: 0, + i3c_pp_scl_lo_period_ns: 0, + i3c_od_scl_hi_period_ns: 0, + i3c_od_scl_lo_period_ns: 0, + sda_tx_hold_ns: 0, + is_secondary: false, + maxdevs: 8, + free_pos: 0, + need_da: 0, + addrs: [0; 8], + dcr: 0, + sir_allowed_by_sw: false, + target_ibi_done: Completion::new(), + target_data_done: Completion::new(), + } + } + + pub fn init_runtime_fields(&mut self) { + self.addrbook = AddrBook::new(); + self.addrbook.reserve_defaults(); + self.attached = Attached::new(); + } + pub fn pick_initial_da(&mut self, static_addr: u8, desired: u8) -> Option { + if desired != 0 && self.addrbook.is_free(desired) { + self.addrbook.mark_use(desired, true); + return Some(desired); + } + if static_addr != 0 && self.addrbook.is_free(static_addr) { + self.addrbook.mark_use(static_addr, true); + return Some(static_addr); + } + self.addrbook.alloc_from(8) + } + + pub fn reassign_da(&mut self, from: u8, to: u8) -> Result<(), I3cConfigError> { + if from == to { + return Ok(()); + } + if !self.addrbook.is_free(to) { + return Err(I3cConfigError::AddrInUse); + } + + self.addrbook.mark_use(from, false); + self.addrbook.mark_use(to, true); + + if let Some((i, mut e)) = self + .attached + .devices + .iter() + .enumerate() + .find_map(|(i, d)| (d.dyn_addr == from).then_some((i, *d))) + { + e.dyn_addr = to; + self.attached.devices[i] = e; + Ok(()) + } else { + Err(I3cConfigError::DevNotFound) + } + } +} diff --git a/src/i3c/i3c_controller.rs b/src/i3c/i3c_controller.rs new file mode 100644 index 0000000..0cc37ed --- /dev/null +++ b/src/i3c/i3c_controller.rs @@ -0,0 +1,99 @@ +// Licensed under the Apache-2.0 license + +use crate::common::Logger; +use crate::i3c::ast1060_i3c::register_i3c_irq_handler; +use crate::i3c::ast1060_i3c::HardwareInterface; +use crate::i3c::i3c_config::{DevKind, DeviceEntry, I3cConfig}; + +const I3C_BROADCAST_ADDR: u8 = 0x7E; + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum AttachError { + OutOfRange, + AddrInUse, +} + +pub struct I3cController { + pub hw: H, + pub config: I3cConfig, + pub logger: L, +} + +impl I3cController { + pub fn init(&mut self) { + let ctx = core::ptr::from_mut::(self) as usize; + let bus = self.hw.bus_num() as usize; + register_i3c_irq_handler(bus, Self::irq_trampoline, ctx); + + self.hw.enable_irq(); + self.hw.init(&mut self.config); + } + + fn irq_trampoline(ctx: usize) { + let ctrl: &mut Self = unsafe { &mut *(ctx as *mut Self) }; + ctrl.hw.i3c_aspeed_isr(&mut ctrl.config); + } + + pub fn attach_i3c_dev( + &mut self, + pid: u64, + desired_da: u8, + slot: u8, + ) -> Result<(), AttachError> { + if desired_da == 0 || desired_da >= I3C_BROADCAST_ADDR { + return Err(AttachError::OutOfRange); + } + + let dev = DeviceEntry { + kind: DevKind::I3c, + pid: Some(pid), + static_addr: 0, + dyn_addr: desired_da, + desired_da, + bcr: 0, + dcr: 0, + maxrd: 0, + maxwr: 0, + mrl: 0, + mwl: 0, + max_ibi: 0, + ibi_en: false, + pos: Some(slot), + }; + + let idx = self + .config + .attached + .attach(dev) + .map_err(|_| AttachError::AddrInUse)?; + self.config.attached.map_pos( + slot, + u8::try_from(idx).map_err(|_| AttachError::OutOfRange)?, + ); + self.config.addrbook.mark_use(desired_da, true); + let rc = self.hw.attach_i3c_dev(slot.into(), desired_da); + match rc { + Ok(()) => Ok(()), + Err(_) => Err(AttachError::AddrInUse), + } + } + + pub fn detach_i3c_dev(&mut self, pos: usize) { + self.config.attached.detach_by_pos(pos); + self.hw.detach_i3c_dev(pos); + } + + pub fn detach_i3c_dev_by_idx(&mut self, dev_idx: usize) { + let dev = &self.config.attached.devices[dev_idx]; + + if dev.dyn_addr != 0 { + self.config.addrbook.mark_use(dev.dyn_addr, false); + } + + if let Some(pos) = dev.pos { + self.hw.detach_i3c_dev(pos.into()); + } + + self.config.attached.detach(dev_idx); + } +} diff --git a/src/i3c/i3c_master.rs b/src/i3c/i3c_master.rs new file mode 100644 index 0000000..d0854d4 --- /dev/null +++ b/src/i3c/i3c_master.rs @@ -0,0 +1,142 @@ +// Licensed under the Apache-2.0 license + +use proposed_traits::i3c_master::{Error, ErrorKind, ErrorType, I3c, I3cSpeed}; + +use embedded_hal::i2c::SevenBitAddress; + +use crate::common::Logger; +use crate::i3c::ast1060_i3c::HardwareInterface; +use crate::i3c::ccc; +use crate::i3c::i3c_controller::I3cController; + +#[derive(Debug, Copy, Clone, Eq, PartialEq)] +pub struct I3cMasterError(pub ErrorKind); + +impl Error for I3cMasterError { + #[inline] + fn kind(&self) -> ErrorKind { + self.0 + } +} + +impl From for I3cMasterError { + #[inline] + fn from(k: ErrorKind) -> Self { + I3cMasterError(k) + } +} + +impl ErrorType for I3cController { + type Error = I3cMasterError; +} + +impl I3c for I3cController { + fn assign_dynamic_address( + &mut self, + static_address: SevenBitAddress, + ) -> Result { + let slot = self + .config + .attached + .pos_of_addr(static_address) + .ok_or(I3cMasterError(ErrorKind::DynamicAddressConflict))?; + + let rc = self.hw.do_entdaa(&mut self.config, slot.into()); + match rc { + Ok(()) => {} + Err(_) => { + return Err(I3cMasterError(ErrorKind::DynamicAddressConflict)); + } + } + + let pid = { + ccc::ccc_getpid(&mut self.hw, &mut self.config, static_address) + .map_err(|_| I3cMasterError(ErrorKind::InvalidCcc))? + }; + + let dev_idx = self + .config + .attached + .find_dev_idx_by_addr(static_address) + .ok_or(I3cMasterError(ErrorKind::Other))?; + + let old_pid = { + self.config + .attached + .devices + .get(dev_idx) + .ok_or(I3cMasterError(ErrorKind::Other))? + .pid + }; + if let Some(op) = old_pid { + if pid != op { + return Err(I3cMasterError(ErrorKind::Other)); + } + } + + let bcr = ccc::ccc_getbcr(&mut self.hw, &mut self.config, static_address) + .map_err(|_| I3cMasterError(ErrorKind::InvalidCcc))?; + + { + let dev = self + .config + .attached + .devices + .get_mut(dev_idx) + .ok_or(I3cMasterError(ErrorKind::Other))?; + + dev.pid = Some(pid); + dev.bcr = bcr; + } + + let dyn_addr: SevenBitAddress = { + let dev = self + .config + .attached + .devices + .get(dev_idx) + .ok_or(I3cMasterError(ErrorKind::Other))?; + + dev.dyn_addr + }; + + let ret = self.hw.ibi_enable(&mut self.config, dyn_addr); + match ret { + Ok(()) => {} + _ => { + return Err(I3cMasterError(ErrorKind::Other)); + } + } + + Ok(dyn_addr) + } + + fn acknowledge_ibi(&mut self, address: SevenBitAddress) -> Result<(), Self::Error> { + let dev_idx = self + .config + .attached + .find_dev_idx_by_addr(address) + .ok_or(I3cMasterError(ErrorKind::Other))?; + + if self.config.attached.devices[dev_idx].pid.is_none() { + return Err(I3cMasterError(ErrorKind::Other)); + } + + Ok(()) + } + + fn handle_hot_join(&mut self) -> Result<(), Self::Error> { + // only need to call assign_dynamic_address after receiving hot-join IBI + Ok(()) + } + + fn set_bus_speed(&mut self, _speed: I3cSpeed) -> Result<(), Self::Error> { + // ast1060 i3c c driver doesn't support changing bus speed + Ok(()) + } + + fn request_mastership(&mut self) -> Result<(), Self::Error> { + // ast1060 controller doesn't support multi-master + Ok(()) + } +} diff --git a/src/i3c/i3c_target.rs b/src/i3c/i3c_target.rs new file mode 100644 index 0000000..a55bb86 --- /dev/null +++ b/src/i3c/i3c_target.rs @@ -0,0 +1,108 @@ +// Licensed under the Apache-2.0 license + +use core::convert::Infallible; + +use embedded_hal::i2c::ErrorType as HalI2cErrorType; +use proposed_traits::i2c_target::I2CCoreTarget; +use proposed_traits::i3c_target::{DynamicAddressable, I3CCoreTarget, IBICapable}; + +use crate::i3c::ast1060_i3c::{HardwareInterface, I3cIbi, I3cIbiType}; +use crate::i3c::i3c_config::I3cTargetConfig; +use crate::i3c::i3c_controller::I3cController; + +impl HalI2cErrorType for I3cController { + type Error = Infallible; +} + +impl I2CCoreTarget for I3cController { + #[inline] + fn init(&mut self, own_addr: u8) -> Result<(), Self::Error> { + if let Some(t) = self.config.target_config.as_mut() { + if t.addr.is_none() { + t.addr = Some(own_addr); + } + } else { + self.config.target_config = + Some(I3cTargetConfig::new(0, Some(own_addr), /*mdb*/ 0xae)); + } + Ok(()) + } + + #[inline] + fn on_address_match(&mut self, addr: u8) -> bool { + self.config + .target_config + .as_ref() + .and_then(|t| t.addr) + .map_or(false, |da| da == addr) + } + + #[inline] + fn on_transaction_start(&mut self, _is_read: bool) {} + + #[inline] + fn on_stop(&mut self) {} +} + +impl I3CCoreTarget for I3cController {} + +impl DynamicAddressable for I3cController { + fn on_dynamic_address_assigned(&mut self, _new_address: u8) { + self.config.sir_allowed_by_sw = true; + } +} + +impl IBICapable for I3cController { + fn wants_ibi(&self) -> bool { + true + } + + fn get_ibi_payload(&mut self, buffer: &mut [u8]) -> Result { + let (da, mdb) = match self.config.target_config.as_ref() { + Some(t) => ( + match t.addr { + Some(da) => da, + None => return Ok(0), + }, + t.mdb, + ), + None => return Ok(0), + }; + + let addr_rnw = (da << 1) | 0x1; + let mut crc = crc8_ccitt(0, &[addr_rnw]); + crc = crc8_ccitt(crc, &[mdb]); + + let payload = [mdb, crc]; + let mut ibi = I3cIbi { + ibi_type: I3cIbiType::TargetIntr, + payload: Some(&payload), + }; + let rc = self + .hw + .target_pending_read_notify(&mut self.config, buffer, &mut ibi); + + match rc { + Ok(()) => Ok(buffer.len() + payload.len()), + _ => Ok(0), + } + } + + fn on_ibi_acknowledged(&mut self) {} +} + +#[inline] +fn crc8_ccitt(mut crc: u8, data: &[u8]) -> u8 { + for &b in data { + let mut x = crc ^ b; + for _ in 0..8 { + x = if (x & 0x80) != 0 { + (x << 1) ^ 0x07 + } else { + x << 1 + }; + } + crc = x; + } + crc +} diff --git a/src/i3c/ibi_workq.rs b/src/i3c/ibi_workq.rs new file mode 100644 index 0000000..6b4b8f3 --- /dev/null +++ b/src/i3c/ibi_workq.rs @@ -0,0 +1,111 @@ +// Licensed under the Apache-2.0 license + +use core::cell::RefCell; +use critical_section::Mutex; +use heapless::spsc::Queue; + +const IBIQ_DEPTH: usize = 16; +const IBI_DATA_MAX: u8 = 16; + +#[derive(Debug, Clone, Copy)] +pub enum IbiWork { + HotJoin, + Sirq { + addr: u8, + len: u8, + data: [u8; IBI_DATA_MAX as usize], + }, + TargetDaAssignment, +} + +static mut IBIQ_BUFS: [Queue; 4] = + [Queue::new(), Queue::new(), Queue::new(), Queue::new()]; + +struct IbiBus { + prod: Option>, + cons: Option>, +} + +static IBI_WORKQS: [Mutex>; 4] = [ + Mutex::new(RefCell::new(IbiBus { + prod: None, + cons: None, + })), + Mutex::new(RefCell::new(IbiBus { + prod: None, + cons: None, + })), + Mutex::new(RefCell::new(IbiBus { + prod: None, + cons: None, + })), + Mutex::new(RefCell::new(IbiBus { + prod: None, + cons: None, + })), +]; + +fn ensure_ibiq_split(bus: usize) { + assert!(bus < 4); + critical_section::with(|cs| { + let mut b = IBI_WORKQS[bus].borrow(cs).borrow_mut(); + if b.prod.is_none() || b.cons.is_none() { + let (p, c) = unsafe { IBIQ_BUFS[bus].split() }; + b.prod = Some(p); + b.cons = Some(c); + } + }); +} + +#[must_use] +pub fn i3c_ibi_workq_consumer( + bus: usize, +) -> heapless::spsc::Consumer<'static, IbiWork, IBIQ_DEPTH> { + assert!(bus < 4); + critical_section::with(|cs| { + let mut b = IBI_WORKQS[bus].borrow(cs).borrow_mut(); + if b.prod.is_none() || b.cons.is_none() { + let (p, c) = unsafe { IBIQ_BUFS[bus].split() }; + b.prod = Some(p); + b.cons = Some(c); + } + b.cons.take().expect("IBI consumer already taken") + }) +} + +pub fn i3c_ibi_work_enqueue_target_da_assignment(bus: usize) { + ensure_ibiq_split(bus); + critical_section::with(|cs| { + if let Some(p) = IBI_WORKQS[bus].borrow(cs).borrow_mut().prod.as_mut() { + let _ = p.enqueue(IbiWork::TargetDaAssignment); + } + }); +} + +#[inline] +pub fn i3c_ibi_work_enqueue_hotjoin(bus: usize) { + ensure_ibiq_split(bus); + critical_section::with(|cs| { + if let Some(p) = IBI_WORKQS[bus].borrow(cs).borrow_mut().prod.as_mut() { + let _ = p.enqueue(IbiWork::HotJoin); + } + }); +} + +#[inline] +pub fn i3c_ibi_work_enqueue_target_irq(bus: usize, addr: u8, data: &[u8]) { + ensure_ibiq_split(bus); + let mut ibi_buf = [0u8; IBI_DATA_MAX as usize]; + let take = core::cmp::min(IBI_DATA_MAX as usize, data.len()); + ibi_buf[..take].copy_from_slice(&data[..take]); + critical_section::with(|cs| { + if let Some(p) = IBI_WORKQS[bus].borrow(cs).borrow_mut().prod.as_mut() { + let _ = p.enqueue(IbiWork::Sirq { + addr, + // len: take as u8, + len: u8::try_from(take).map_err(|_| ()).unwrap_or(IBI_DATA_MAX), + data: ibi_buf, + }); + } + }); +} diff --git a/src/i3c/mod.rs b/src/i3c/mod.rs new file mode 100644 index 0000000..43b21a2 --- /dev/null +++ b/src/i3c/mod.rs @@ -0,0 +1,9 @@ +// Licensed under the Apache-2.0 license + +pub mod ast1060_i3c; +pub mod ccc; +pub mod i3c_config; +pub mod i3c_controller; +pub mod i3c_master; +pub mod i3c_target; +pub mod ibi_workq; diff --git a/src/lib.rs b/src/lib.rs index f7f3465..34c72a7 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -10,6 +10,7 @@ pub mod hash; pub mod hash_owned; pub mod hmac; pub mod i2c; +pub mod i3c; pub mod pinctrl; pub mod rsa; pub mod spi; diff --git a/src/main.rs b/src/main.rs index e352595..4a4b51c 100644 --- a/src/main.rs +++ b/src/main.rs @@ -14,6 +14,7 @@ use aspeed_ddk::ecdsa::AspeedEcdsa; use aspeed_ddk::hace_controller::HaceController; use aspeed_ddk::rsa::AspeedRsa; use aspeed_ddk::spi; + use aspeed_ddk::syscon::{ClockId, ResetId, SysCon}; use fugit::MillisDurationU32 as MilliSeconds; @@ -22,6 +23,8 @@ use aspeed_ddk::tests::functional::gpio_test; use aspeed_ddk::tests::functional::hash_test::run_hash_tests; use aspeed_ddk::tests::functional::hmac_test::run_hmac_tests; use aspeed_ddk::tests::functional::i2c_test; +#[cfg(any(feature = "i3c_master", feature = "i3c_target"))] +use aspeed_ddk::tests::functional::i3c_test; use aspeed_ddk::tests::functional::rsa_test::run_rsa_tests; use aspeed_ddk::tests::functional::timer_test::run_timer_tests; use panic_halt as _; @@ -56,7 +59,7 @@ unsafe fn pre_init() { write_volatile(cache_area_offset as *mut u32, cache_val); let cache_inval_offset: u32 = 0x7e6e_2a54; - let cache_inval_val = 0x8660_0000; + let cache_inval_val = 0x81e0_0000; write_volatile(cache_inval_offset as *mut u32, cache_inval_val); // Enable Cache @@ -361,6 +364,11 @@ fn main() -> ! { i2c_test::test_i2c_master(&mut uart_controller); #[cfg(feature = "i2c_target")] i2c_test::test_i2c_slave(&mut uart_controller); + #[cfg(feature = "i3c_master")] + i3c_test::test_i3c_master(&mut uart_controller); + #[cfg(feature = "i3c_target")] + i3c_test::test_i3c_target(&mut uart_controller); + test_wdt(&mut uart_controller); run_timer_tests(&mut uart_controller); diff --git a/src/pinctrl.rs b/src/pinctrl.rs index e75aa39..732d240 100644 --- a/src/pinctrl.rs +++ b/src/pinctrl.rs @@ -1726,6 +1726,16 @@ paste! { pub const PINCTRL_I2C13: &[PinctrlPin] = &[PIN_SCU4B8_22, PIN_SCU4B8_23, CLR_PIN_SCU418_22, CLR_PIN_SCU418_23]; + pub const PINCTRL_I3C0: &[PinctrlPin] = &[PIN_SCU418_16, PIN_SCU418_17, CLR_PIN_SCU4B8_8, CLR_PIN_SCU4B8_9]; + pub const PINCTRL_I3C1: &[PinctrlPin] = &[PIN_SCU418_18, PIN_SCU418_19, CLR_PIN_SCU4B8_10, CLR_PIN_SCU4B8_11]; + pub const PINCTRL_I3C2: &[PinctrlPin] = &[PIN_SCU418_20, PIN_SCU418_21, CLR_PIN_SCU4B8_12, CLR_PIN_SCU4B8_13]; + pub const PINCTRL_I3C3: &[PinctrlPin] = &[PIN_SCU418_22, PIN_SCU418_23, CLR_PIN_SCU4B8_14, CLR_PIN_SCU4B8_15]; + + pub const PINCTRL_HVI3C0: &[PinctrlPin] = &[CLR_PIN_SCU418_8, CLR_PIN_SCU418_9, CLR_PIN_SCU418_16, CLR_PIN_SCU418_17, PIN_SCU4B8_8, PIN_SCU4B8_9]; + pub const PINCTRL_HVI3C1: &[PinctrlPin] = &[CLR_PIN_SCU418_10, CLR_PIN_SCU418_11, CLR_PIN_SCU418_18, CLR_PIN_SCU418_19, PIN_SCU4B8_10, PIN_SCU4B8_11]; + pub const PINCTRL_HVI3C2: &[PinctrlPin] = &[CLR_PIN_SCU418_12, CLR_PIN_SCU418_13, CLR_PIN_SCU418_20, CLR_PIN_SCU418_21, PIN_SCU4B8_12, PIN_SCU4B8_13]; + pub const PINCTRL_HVI3C3: &[PinctrlPin] = &[CLR_PIN_SCU418_14, CLR_PIN_SCU418_15, CLR_PIN_SCU418_22, CLR_PIN_SCU418_23, PIN_SCU4B8_14, PIN_SCU4B8_15]; + pub const PINCTRL_GPIOA0: &[PinctrlPin] = &[CLR_PIN_SCU410_0, CLR_PIN_SCU4B0_0, CLR_PIN_SCU690_0]; pub const PINCTRL_GPIOA1: &[PinctrlPin] = &[CLR_PIN_SCU410_1, CLR_PIN_SCU4B0_1, CLR_PIN_SCU690_1]; pub const PINCTRL_GPIOA2: &[PinctrlPin] = &[CLR_PIN_SCU410_2, CLR_PIN_SCU4B0_2, CLR_PIN_SCU690_2]; diff --git a/src/tests/functional/i3c_test.rs b/src/tests/functional/i3c_test.rs new file mode 100644 index 0000000..254a307 --- /dev/null +++ b/src/tests/functional/i3c_test.rs @@ -0,0 +1,278 @@ +// Licensed under the Apache-2.0 license + +use crate::common::{DummyDelay, NoOpLogger, UartLogger}; +use crate::i3c::ast1060_i3c::HardwareInterface; +use crate::i3c::ast1060_i3c::I3cMsg; +use crate::i3c::ast1060_i3c::{Ast1060I3c, I3C_MSG_READ, I3C_MSG_STOP, I3C_MSG_WRITE}; +use crate::i3c::i3c_config::I3cConfig; +use crate::i3c::i3c_config::I3cTargetConfig; +use crate::i3c::i3c_controller::I3cController; +use crate::i3c::ibi_workq::{i3c_ibi_workq_consumer, IbiWork}; +use crate::pinctrl; +use crate::uart::{self, Config, UartController}; +use ast1060_pac::Peripherals; +use core::ptr::read_volatile; +use embedded_hal::delay::DelayNs; +use embedded_io::Write; +use proposed_traits::i3c_master::I3c; +// I3cTarget +use proposed_traits::i3c_target::{DynamicAddressable, IBICapable}; + +pub fn dump_i3c_controller_registers(uart: &mut UartController<'_>, base: u32) { + // [7e7a4000] 80000200 00008009 000f40bb 00000000 + // [7e7a4010] 00000000 00000000 00000000 001f0000 + // [7e7a4020] 01010001 00000000 00000000 ffffffff + unsafe { + let reg_base = base as *mut u32; + writeln!(uart, "rust I3C reg dump:\r").unwrap(); + for i in 0..0xc0 { + let v = read_volatile(reg_base.add(i)); + if i % 4 == 0 { + write!(uart, "[{:08x}]", base + u32::try_from(i).unwrap() * 4).unwrap(); + } + write!(uart, " {v:08x}").unwrap(); + if i % 4 == 3 { + writeln!(uart, "\r").unwrap(); + } + } + } +} + +#[allow(clippy::too_many_lines)] +pub fn test_i3c_master(uart: &mut UartController<'_>) { + let peripherals = unsafe { Peripherals::steal() }; + let mut delay = DummyDelay {}; + let mut dbg_uart = UartController::new(peripherals.uart, &mut delay); + + writeln!(uart, "\r\n####### I3C master test #######\r\n").unwrap(); + unsafe { + dbg_uart.init(&Config { + baud_rate: 115_200, + word_length: uart::WordLength::Eight as u8, + parity: uart::Parity::None, + stop_bits: uart::StopBits::One, + clock: 24_000_000, + }); + } + + pinctrl::Pinctrl::apply_pinctrl_group(pinctrl::PINCTRL_HVI3C2); + let hw = Ast1060I3c::::new(UartLogger::new(&mut dbg_uart)); + + let mut ctrl = I3cController { + hw, + config: I3cConfig::new(), + logger: NoOpLogger, + }; + + { + let c = &mut ctrl.config; + c.init_runtime_fields(); + c.is_secondary = false; + c.i2c_scl_hz = 1_000_000; + c.i3c_scl_hz = 12_500_000; + c.i3c_pp_scl_hi_period_ns = 250; + c.i3c_pp_scl_lo_period_ns = 250; + c.i3c_od_scl_hi_period_ns = 0; + c.i3c_od_scl_lo_period_ns = 0; + c.sda_tx_hold_ns = 20; + } + + let mut ibi_cons = i3c_ibi_workq_consumer(ctrl.hw.bus_num() as usize); + // let known_pid = 0x07ec_0503_1000u64; + let known_pid = 0x07ec_a003_2000u64; + let ctrl_dev_slot0 = 0; + ctrl.init(); + + let dyn_addr = if let Some(da) = ctrl.config.addrbook.alloc_from(8) { + ctrl.attach_i3c_dev(known_pid, da, ctrl_dev_slot0).unwrap(); + ctrl.hw.ibi_enable(&mut ctrl.config, da).unwrap(); + writeln!(uart, "Pre-attached dev at slot 0, dyn addr {da}\r").unwrap(); + da + } else { + writeln!(uart, "no dyn addr\r").unwrap(); + return; + }; + + // Dump I3C2 registers + // dump_i3c_controller_registers(uart, 0x7e7a_4000); + writeln!(uart, "ctrl dev at slot 0, dyn addr {dyn_addr}\r").unwrap(); + let mut received_count = 0; + + loop { + if let Some(work) = ibi_cons.dequeue() { + match work { + IbiWork::HotJoin => { + writeln!(uart, "[IBI] hotjoin\r").unwrap(); + let _ = ctrl.handle_hot_join(); + ctrl.assign_dynamic_address(dyn_addr).unwrap(); + } + IbiWork::Sirq { addr, len, data } => { + writeln!(uart, "[IBI] SIRQ from 0x{addr:02x}, len {len}\r").unwrap(); + writeln!(uart, " IBI payload:").unwrap(); + for i in 0..len { + write!(uart, " {:02x}", data[i as usize]).unwrap(); + } + writeln!(uart, "\r").unwrap(); + match ctrl.acknowledge_ibi(addr) { + Ok(()) => {} + Err(e) => { + writeln!(uart, " acknowledge_ibi failed: {e:?}\r").unwrap(); + } + } + let mut rx_buf = [0u8; 128]; + let mut msgs = [I3cMsg { + buf: Some(&mut rx_buf[..]), + actual_len: 128, + num_xfer: 0, + flags: I3C_MSG_READ | I3C_MSG_STOP, + hdr_mode: 0, + hdr_cmd_mode: 0, + }]; + let _ = ctrl.hw.priv_xfer(&mut ctrl.config, known_pid, &mut msgs); + let rx_len = msgs[0].actual_len as usize; + writeln!( + uart, + "[MASTER <== TARGET] MASTER READ: {:02x?}", + &rx_buf[..rx_len] + ) + .unwrap(); + writeln!(uart, "\r").unwrap(); + received_count += 1; + if received_count > 10 { + writeln!(uart, "I3C master test done\r").unwrap(); + break; + } + + // send data to target + let mut tx_buf: [u8; 16] = [ + 0xde, 0xad, 0xbe, 0xef, 0xca, 0xfe, 0xba, 0xbe, 0x11, 0x22, 0x33, 0x44, + 0x55, 0x66, 0x77, 0x88, + ]; + let tx_len = u32::try_from(tx_buf.len()).unwrap(); + + let mut tx_msgs = [I3cMsg { + buf: Some(&mut tx_buf[..]), + actual_len: tx_len, + num_xfer: 0, + flags: I3C_MSG_WRITE | I3C_MSG_STOP, + hdr_mode: 0, + hdr_cmd_mode: 0, + }]; + let _ = ctrl.hw.priv_xfer(&mut ctrl.config, known_pid, &mut tx_msgs); + writeln!(uart, "[MASTER ==> TARGET] MASTER WRITE: {tx_buf:02x?}").unwrap(); + writeln!(uart, "\r").unwrap(); + } + IbiWork::TargetDaAssignment => { + writeln!(uart, "[IBI] TargetDaAssignment\r").unwrap(); + } + } + } + } +} + +pub fn test_i3c_target(uart: &mut UartController<'_>) { + let peripherals = unsafe { Peripherals::steal() }; + let mut delay = DummyDelay {}; + let mut dbg_uart = UartController::new(peripherals.uart, &mut delay); + + writeln!(uart, "\r\n####### I3C target test #######\r\n").unwrap(); + unsafe { + dbg_uart.init(&Config { + baud_rate: 115_200, + word_length: uart::WordLength::Eight as u8, + parity: uart::Parity::None, + stop_bits: uart::StopBits::One, + clock: 24_000_000, + }); + } + + pinctrl::Pinctrl::apply_pinctrl_group(pinctrl::PINCTRL_HVI3C2); + let hw = Ast1060I3c::::new(UartLogger::new(&mut dbg_uart)); + + let mut ctrl = I3cController { + hw, + config: I3cConfig::new(), + logger: NoOpLogger, + }; + + { + let c = &mut ctrl.config; + c.init_runtime_fields(); + // Configure as target + c.is_secondary = true; + c.i2c_scl_hz = 1_000_000; + c.i3c_scl_hz = 12_500_000; + c.i3c_pp_scl_hi_period_ns = 36; + c.i3c_pp_scl_lo_period_ns = 36; + c.i3c_od_scl_hi_period_ns = 0; + c.i3c_od_scl_lo_period_ns = 0; + c.sda_tx_hold_ns = 0; + // c.sda_tx_hold_ns = 20; + c.dcr = 0xcc; + c.target_config = Some(I3cTargetConfig::new(0, Some(0), 0xae)); + } + let mut ibi_cons = i3c_ibi_workq_consumer(ctrl.hw.bus_num() as usize); + ctrl.init(); + let dyn_addr = 8; + let dev_idx = 0; + let _ = ctrl.hw.attach_i3c_dev(dev_idx, dyn_addr); + writeln!( + uart, + "I3C target dev at slot {dev_idx}, dyn addr {dyn_addr}\r" + ) + .unwrap(); + // Dump I3C2 registers + // dump_i3c_controller_registers(uart, 0x7e7a_4000); + let mut delay = DummyDelay {}; + delay.delay_ns(4_000_000_000); + writeln!(uart, "waiting for dynamic address assignment...\r").unwrap(); + let _ = ctrl.hw.target_ibi_raise_hj(&mut ctrl.config); + // dump_i3c_controller_registers(uart, 0x7e7a_4000); + loop { + if let Some(work) = ibi_cons.dequeue() { + match work { + IbiWork::HotJoin => { + // do nothing in target mode + writeln!(uart, "[IBI] hotjoin\r").unwrap(); + } + IbiWork::Sirq { addr, len, data: _ } => { + // do nothing in target mode + writeln!(uart, "[IBI] SIRQ from 0x{addr:02x}, len {len}\r").unwrap(); + } + IbiWork::TargetDaAssignment => { + let mut delay = DummyDelay {}; + delay.delay_ns(4_000_000_000); + writeln!(uart, "[IBI] TargetDaAssignment\r").unwrap(); + let da = ctrl.config.target_config.as_ref().unwrap().addr; + writeln!( + uart, + " dyn addr 0x{:02x} was assigned by master\r", + da.unwrap() + ) + .unwrap(); + ctrl.on_dynamic_address_assigned(da.unwrap()); + break; + } + } + } + } + let mut ibi_count = 0; + loop { + let mut delay = DummyDelay {}; + delay.delay_ns(4_000_000_000); + let mut data = [0u8; 16]; + for (i, b) in data.iter_mut().enumerate() { + *b = u8::try_from(i).unwrap(); + } + writeln!(uart, "send ibi #{ibi_count}\r").unwrap(); + writeln!(uart, "[MASTER <== TARGET] TARGET WRITE: {data:02x?}").unwrap(); + writeln!(uart, "\r").unwrap(); + ctrl.get_ibi_payload(&mut data).unwrap(); + ibi_count += 1; + if ibi_count > 10 { + break; + } + } + + writeln!(uart, "I3C target test done\r").unwrap(); +} diff --git a/src/tests/functional/mod.rs b/src/tests/functional/mod.rs index f37c0d5..259980c 100644 --- a/src/tests/functional/mod.rs +++ b/src/tests/functional/mod.rs @@ -5,6 +5,7 @@ pub mod gpio_test; pub mod hash_test; pub mod hmac_test; pub mod i2c_test; +pub mod i3c_test; pub mod rsa_test; pub mod rsa_test_vec; pub mod timer_test;