i3c: add legacy I2C device support and length CCCs attach_i2c_dev + i2c_write/read/write_read and an embedded-hal I2c impl route legacy devices through DAT slots by static address. Add SETMWL/SETMRL/GETMWL/GETMRL/GETMXDS, and latch target-side MWL/MRL updates from SLV_MAX_LEN. Signed-off-by: Steven Lee <steven_lee@aspeedtech.com>
diff --git a/target/ast10x0/peripherals/i3c/ccc.rs b/target/ast10x0/peripherals/i3c/ccc.rs index 7800dbe..bd9bfdc 100644 --- a/target/ast10x0/peripherals/i3c/ccc.rs +++ b/target/ast10x0/peripherals/i3c/ccc.rs
@@ -7,8 +7,9 @@ use super::config::I3cConfig; use super::constants::{ - I3C_CCC_GETBCR, I3C_CCC_GETDCR, I3C_CCC_GETPID, I3C_CCC_GETSTATUS, I3C_CCC_RSTDAA, - I3C_CCC_SETNEWDA, + I3C_BCR_IBI_PAYLOAD_HAS_DATA_BYTE, I3C_CCC_GETBCR, I3C_CCC_GETDCR, I3C_CCC_GETMRL, + I3C_CCC_GETMWL, I3C_CCC_GETMXDS, I3C_CCC_GETPID, I3C_CCC_GETSTATUS, I3C_CCC_RSTDAA, + I3C_CCC_SETMRL, I3C_CCC_SETMRL_BC, I3C_CCC_SETMWL, I3C_CCC_SETMWL_BC, I3C_CCC_SETNEWDA, }; use super::error::{CccErrorKind, I3cError}; use super::hardware::HardwareInterface; @@ -393,6 +394,253 @@ .map_err(|_| I3cError::CccError(CccErrorKind::Invalid)) } +/// Send a direct write CCC with a small fixed payload. +fn ccc_direct_write<H>( + hw: &mut H, + config: &mut I3cConfig, + id: u8, + da: u8, + payload: &mut [u8], +) -> Result<(), I3cError> +where + H: HardwareInterface, +{ + if da == 0 { + return Err(I3cError::CccError(CccErrorKind::InvalidParam)); + } + let tgt = CccTargetPayload { + addr: da, + rnw: false, + data: Some(payload), + num_xfer: 0, + }; + let mut tgts = [tgt]; + let mut p = CccPayload { + ccc: Some(Ccc { + id, + data: None, + num_xfer: 0, + }), + targets: Some(&mut tgts[..]), + }; + hw.do_ccc(config, &mut p) + .map_err(|_| I3cError::CccError(CccErrorKind::Invalid)) +} + +/// Send a direct read CCC into a small fixed buffer. +fn ccc_direct_read<H>( + hw: &mut H, + config: &mut I3cConfig, + id: u8, + da: u8, + out: &mut [u8], +) -> Result<(), I3cError> +where + H: HardwareInterface, +{ + if da == 0 { + return Err(I3cError::CccError(CccErrorKind::InvalidParam)); + } + let tgt = CccTargetPayload { + addr: da, + rnw: true, + data: Some(out), + num_xfer: 0, + }; + let mut tgts = [tgt]; + let mut p = CccPayload { + ccc: Some(Ccc { + id, + data: None, + num_xfer: 0, + }), + targets: Some(&mut tgts[..]), + }; + hw.do_ccc(config, &mut p) + .map_err(|_| I3cError::CccError(CccErrorKind::Invalid)) +} + +/// Set Maximum Write Length for a device (direct SETMWL); mirrors the value +/// into the attached-device entry on success. +pub fn ccc_setmwl<H>(hw: &mut H, config: &mut I3cConfig, da: u8, mwl: u16) -> Result<(), I3cError> +where + H: HardwareInterface, +{ + let mut payload = mwl.to_be_bytes(); + ccc_direct_write(hw, config, I3C_CCC_SETMWL, da, &mut payload)?; + if let Some(idx) = config.attached.find_dev_idx_by_addr(da) + && let Some(dev) = config.attached.devices.get_mut(idx) + { + dev.mwl = mwl; + } + Ok(()) +} + +/// Set Maximum Read Length for a device (direct SETMRL). `ibi_len` adds the +/// optional third byte (max IBI payload size) for targets whose BCR +/// advertises an IBI payload. Mirrors the values into the attached-device +/// entry on success. +pub fn ccc_setmrl<H>( + hw: &mut H, + config: &mut I3cConfig, + da: u8, + mrl: u16, + ibi_len: Option<u8>, +) -> Result<(), I3cError> +where + H: HardwareInterface, +{ + let be = mrl.to_be_bytes(); + let mut buf3 = [be[0], be[1], 0]; + let payload: &mut [u8] = match ibi_len { + Some(n) => { + buf3[2] = n; + &mut buf3[..3] + } + None => &mut buf3[..2], + }; + ccc_direct_write(hw, config, I3C_CCC_SETMRL, da, payload)?; + if let Some(idx) = config.attached.find_dev_idx_by_addr(da) + && let Some(dev) = config.attached.devices.get_mut(idx) + { + dev.mrl = mrl; + if let Some(n) = ibi_len { + dev.max_ibi = n; + } + } + Ok(()) +} + +/// Broadcast SETMWL to all devices. +pub fn ccc_setmwl_all<H>(hw: &mut H, config: &mut I3cConfig, mwl: u16) -> Result<(), I3cError> +where + H: HardwareInterface, +{ + let mut payload = mwl.to_be_bytes(); + hw.do_ccc( + config, + &mut CccPayload { + ccc: Some(Ccc { + id: I3C_CCC_SETMWL_BC, + data: Some(&mut payload[..]), + num_xfer: 0, + }), + targets: None, + }, + ) + .map_err(|_| I3cError::CccError(CccErrorKind::Invalid)) +} + +/// Broadcast SETMRL to all devices. +pub fn ccc_setmrl_all<H>( + hw: &mut H, + config: &mut I3cConfig, + mrl: u16, + ibi_len: Option<u8>, +) -> Result<(), I3cError> +where + H: HardwareInterface, +{ + let be = mrl.to_be_bytes(); + let mut buf3 = [be[0], be[1], 0]; + let payload: &mut [u8] = match ibi_len { + Some(n) => { + buf3[2] = n; + &mut buf3[..3] + } + None => &mut buf3[..2], + }; + hw.do_ccc( + config, + &mut CccPayload { + ccc: Some(Ccc { + id: I3C_CCC_SETMRL_BC, + data: Some(payload), + num_xfer: 0, + }), + targets: None, + }, + ) + .map_err(|_| I3cError::CccError(CccErrorKind::Invalid)) +} + +/// Get Maximum Write Length from a device (GETMWL); mirrors the value into +/// the attached-device entry. +pub fn ccc_getmwl<H>(hw: &mut H, config: &mut I3cConfig, da: u8) -> Result<u16, I3cError> +where + H: HardwareInterface, +{ + let mut buf = [0u8; 2]; + ccc_direct_read(hw, config, I3C_CCC_GETMWL, da, &mut buf)?; + let mwl = u16::from_be_bytes(buf); + if let Some(idx) = config.attached.find_dev_idx_by_addr(da) + && let Some(dev) = config.attached.devices.get_mut(idx) + { + dev.mwl = mwl; + } + Ok(mwl) +} + +/// Get Maximum Read Length from a device (GETMRL). +/// +/// Returns `(mrl, max_ibi_len)`; the third response byte is present only for +/// targets whose BCR advertises an IBI payload (the attached entry's BCR +/// decides how many bytes are requested). Mirrors the values into the +/// attached-device entry. +pub fn ccc_getmrl<H>( + hw: &mut H, + config: &mut I3cConfig, + da: u8, +) -> Result<(u16, Option<u8>), I3cError> +where + H: HardwareInterface, +{ + let has_ibi_byte = config + .attached + .find_dev_idx_by_addr(da) + .and_then(|idx| config.attached.devices.get(idx)) + .map(|d| u32::from(d.bcr) & I3C_BCR_IBI_PAYLOAD_HAS_DATA_BYTE != 0) + .unwrap_or(false); + + let mut buf = [0u8; 3]; + let want = if has_ibi_byte { 3 } else { 2 }; + // `want` is 2 or 3, always within the buffer. + let out = buf.get_mut(..want).ok_or(I3cError::Invalid)?; + ccc_direct_read(hw, config, I3C_CCC_GETMRL, da, out)?; + + let mrl = u16::from_be_bytes([buf[0], buf[1]]); + let ibi_len = has_ibi_byte.then_some(buf[2]); + if let Some(idx) = config.attached.find_dev_idx_by_addr(da) + && let Some(dev) = config.attached.devices.get_mut(idx) + { + dev.mrl = mrl; + if let Some(n) = ibi_len { + dev.max_ibi = n; + } + } + Ok((mrl, ibi_len)) +} + +/// Get Max Data Speed from a device (GETMXDS format 1). +/// +/// Returns `(max_wr, max_rd)` raw speed bytes; mirrored into the +/// attached-device entry. +pub fn ccc_getmxds<H>(hw: &mut H, config: &mut I3cConfig, da: u8) -> Result<(u8, u8), I3cError> +where + H: HardwareInterface, +{ + let mut buf = [0u8; 2]; + ccc_direct_read(hw, config, I3C_CCC_GETMXDS, da, &mut buf)?; + let (max_wr, max_rd) = (buf[0], buf[1]); + if let Some(idx) = config.attached.find_dev_idx_by_addr(da) + && let Some(dev) = config.attached.devices.get_mut(idx) + { + dev.maxwr = max_wr; + dev.maxrd = max_rd; + } + Ok((max_wr, max_rd)) +} + fn bytes_to_pid(bytes: &[u8]) -> u64 { bytes .iter()
diff --git a/target/ast10x0/peripherals/i3c/config.rs b/target/ast10x0/peripherals/i3c/config.rs index ab9f02a..2665dab 100644 --- a/target/ast10x0/peripherals/i3c/config.rs +++ b/target/ast10x0/peripherals/i3c/config.rs
@@ -321,6 +321,22 @@ self.pos_of(dev_idx) } + /// Find device index by static address (legacy I2C devices) + #[must_use] + pub fn find_dev_idx_by_static_addr(&self, addr: u8) -> Option<usize> { + self.devices + .iter() + .position(|d| d.kind == DevKind::I2c && d.static_addr == addr) + } + + /// Get DAT position by static address (legacy I2C devices) + #[must_use] + pub fn pos_of_static_addr(&self, addr: u8) -> Option<u8> { + let dev_idx = self.find_dev_idx_by_static_addr(addr)?; + self.pos_of(dev_idx) + .or_else(|| self.devices.get(dev_idx).and_then(|d| d.pos)) + } + /// Map a DAT position to a device index #[inline] pub fn map_pos(&mut self, pos: u8, idx: u8) -> bool {
diff --git a/target/ast10x0/peripherals/i3c/constants.rs b/target/ast10x0/peripherals/i3c/constants.rs index d9739af..354aefa 100644 --- a/target/ast10x0/peripherals/i3c/constants.rs +++ b/target/ast10x0/peripherals/i3c/constants.rs
@@ -299,14 +299,25 @@ pub const I3C_CCC_RSTDAA: u8 = 0x06; pub const I3C_CCC_ENTDAA: u8 = 0x07; +/// SETMWL broadcast form. +pub const I3C_CCC_SETMWL_BC: u8 = 0x09; +/// SETMRL broadcast form. +pub const I3C_CCC_SETMRL_BC: u8 = 0x0A; 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; +/// SETMWL direct form. +pub const I3C_CCC_SETMWL: u8 = 0x89; +/// SETMRL direct form. +pub const I3C_CCC_SETMRL: u8 = 0x8A; +pub const I3C_CCC_GETMWL: u8 = 0x8B; +pub const I3C_CCC_GETMRL: u8 = 0x8C; pub const I3C_CCC_GETPID: u8 = 0x8D; pub const I3C_CCC_GETBCR: u8 = 0x8E; pub const I3C_CCC_GETDCR: u8 = 0x8F; pub const I3C_CCC_GETSTATUS: u8 = 0x90; +pub const I3C_CCC_GETMXDS: u8 = 0x94; // CCC event bits pub const I3C_CCC_EVT_INTR: u8 = 1 << 0;
diff --git a/target/ast10x0/peripherals/i3c/controller.rs b/target/ast10x0/peripherals/i3c/controller.rs index 8b086fa..4325ad2 100644 --- a/target/ast10x0/peripherals/i3c/controller.rs +++ b/target/ast10x0/peripherals/i3c/controller.rs
@@ -54,7 +54,7 @@ use super::constants::I3C_BROADCAST_ADDR; use super::error::I3cError; use super::hardware::HardwareInterface; -use super::types::{DevKind, I3cIbi, I3cIbiType, I3cMsg}; +use super::types::{DevKind, I2cOp, I3cIbi, I3cIbiType, I3cMsg}; use embedded_hal::i2c::SevenBitAddress; // ============================================================================= @@ -219,6 +219,88 @@ .map_err(|_| I3cError::AddrInUse) } + /// Attach a legacy I2C device to the bus. + /// + /// The DAT slot is programmed with the device's static address and the + /// legacy-I2C marker; transfers then go through + /// [`i2c_write`](Self::i2c_write)/[`i2c_read`](Self::i2c_read)/ + /// [`i2c_write_read`](Self::i2c_write_read) or the + /// `embedded_hal::i2c::I2c` impl. Detach with + /// [`detach_i3c_dev`](Self::detach_i3c_dev) (by slot) or + /// [`detach_i3c_dev_by_idx`](Self::detach_i3c_dev_by_idx). + pub fn attach_i2c_dev(&mut self, static_addr: u8, slot: u8) -> Result<(), I3cError> { + let (hw, config) = self.parts(); + if static_addr == 0 || static_addr >= I3C_BROADCAST_ADDR { + return Err(I3cError::InvalidArgs); + } + if usize::from(slot) >= super::constants::MAX_DEVICES_PER_BUS { + return Err(I3cError::InvalidArgs); + } + if config + .attached + .by_pos + .get(usize::from(slot)) + .copied() + .flatten() + .is_some() + { + return Err(I3cError::DevAlreadyAttached); + } + + let mut dev = DeviceEntry::new_i2c(static_addr); + dev.pos = Some(slot); + let idx = config + .attached + .attach(dev) + .map_err(|_| I3cError::NoFreeSlot)?; + config + .attached + .map_pos(slot, u8::try_from(idx).map_err(|_| I3cError::InvalidArgs)?); + // The static address occupies the same 7-bit space as dynamic ones. + config.addrbook.mark_use(static_addr, true); + + hw.attach_i2c_dev(slot.into(), static_addr) + } + + /// Write to a legacy I2C device (by static address). + pub fn i2c_write(&mut self, static_addr: u8, data: &[u8]) -> Result<(), I3cError> { + let (hw, config) = self.parts(); + let pos = config + .attached + .pos_of_static_addr(static_addr) + .ok_or(I3cError::NoSuchDev)?; + let mut ops = [I2cOp::Write(data)]; + hw.i2c_priv_xfer(config, pos, &mut ops) + } + + /// Read from a legacy I2C device (by static address). `out` is filled + /// completely on success. + pub fn i2c_read(&mut self, static_addr: u8, out: &mut [u8]) -> Result<(), I3cError> { + let (hw, config) = self.parts(); + let pos = config + .attached + .pos_of_static_addr(static_addr) + .ok_or(I3cError::NoSuchDev)?; + let mut ops = [I2cOp::Read(out)]; + hw.i2c_priv_xfer(config, pos, &mut ops) + } + + /// Write then read (repeated START between) on a legacy I2C device. + pub fn i2c_write_read( + &mut self, + static_addr: u8, + data: &[u8], + out: &mut [u8], + ) -> Result<(), I3cError> { + let (hw, config) = self.parts(); + let pos = config + .attached + .pos_of_static_addr(static_addr) + .ok_or(I3cError::NoSuchDev)?; + let mut ops = [I2cOp::Write(data), I2cOp::Read(out)]; + hw.i2c_priv_xfer(config, pos, &mut ops) + } + /// Detach an I3C device by DAT position pub fn detach_i3c_dev(&mut self, pos: usize) { let (hw, config) = self.parts(); @@ -355,6 +437,15 @@ .or_else(|| self.config.target_config.as_ref().and_then(|t| t.addr)) } + /// Max read/write lengths `(mrl, mwl)` the bus master pushed to this + /// target via SETMRL/SETMWL, if any update was observed (latched by the + /// ISR from `SLV_MAX_LEN`). Target mode only. + #[inline] + #[must_use] + pub fn target_max_lengths(&self) -> Option<(u16, u16)> { + super::hardware::isr_events(self.hw.bus_num() as usize).max_len() + } + /// Set the device's IBI mandatory data byte and enable IBI delivery for `addr`. pub fn enable_ibi(&mut self, addr: u8, mdb: u8) -> Result<(), I3cError> { let (hw, config) = self.parts(); @@ -777,6 +868,50 @@ } } +// ============================================================================= +// embedded-hal I2C bus implementation (legacy I2C devices on the I3C bus) +// ============================================================================= + +impl<'c, H: HardwareInterface> embedded_hal::i2c::ErrorType for I3cController<'c, H, Ready> { + type Error = I3cError; +} + +impl<'c, H: HardwareInterface> embedded_hal::i2c::I2c for I3cController<'c, H, Ready> { + /// Execute an I2C transaction against an attached legacy I2C device. + /// + /// The device must have been attached with + /// [`attach_i2c_dev`](Self::attach_i2c_dev) first (the controller + /// addresses devices through DAT slots, not free-form). Consecutive + /// operations are joined by repeated START; the last one ends with STOP. + fn transaction( + &mut self, + address: SevenBitAddress, + operations: &mut [embedded_hal::i2c::Operation<'_>], + ) -> Result<(), I3cError> { + let (hw, config) = self.parts(); + let pos = config + .attached + .pos_of_static_addr(address) + .ok_or(I3cError::NoSuchDev)?; + + if operations.is_empty() { + return Ok(()); + } + + let mut ops: heapless::Vec<I2cOp<'_>, { super::constants::MAX_PRIV_XFER_CMDS }> = + heapless::Vec::new(); + for op in operations.iter_mut() { + let mapped = match op { + embedded_hal::i2c::Operation::Write(b) => I2cOp::Write(b), + embedded_hal::i2c::Operation::Read(b) => I2cOp::Read(core::mem::take(b)), + }; + ops.push(mapped).map_err(|_| I3cError::TooManyMsgs)?; + } + + hw.i2c_priv_xfer(config, pos, ops.as_mut_slice()) + } +} + /// CRC-8 CCITT calculation (ported from `hal_impl.rs`). #[inline] fn crc8_ccitt(mut crc: u8, data: &[u8]) -> u8 {
diff --git a/target/ast10x0/peripherals/i3c/hardware.rs b/target/ast10x0/peripherals/i3c/hardware.rs index 75344a9..2d34cd4 100644 --- a/target/ast10x0/peripherals/i3c/hardware.rs +++ b/target/ast10x0/peripherals/i3c/hardware.rs
@@ -37,6 +37,7 @@ COMMAND_PORT_CMD, COMMAND_PORT_CP, COMMAND_PORT_DBP, COMMAND_PORT_DEV_COUNT, COMMAND_PORT_DEV_INDEX, COMMAND_PORT_READ_TRANSFER, COMMAND_PORT_ROC, COMMAND_PORT_SPEED, COMMAND_PORT_TID, COMMAND_PORT_TOC, DEV_ADDR_TABLE_IBI_MDB, DEV_ADDR_TABLE_IBI_PEC, + DEV_ADDR_TABLE_LEGACY_I2C_DEV, DEV_ADDR_TABLE_MR_REJECT, DEV_ADDR_TABLE_STATIC_ADDR, DEV_ADDR_TABLE_SIR_REJECT, I3CG_REG1_SCL_IN_SW_MODE_EN, I3CG_REG1_SCL_IN_SW_MODE_VAL, I3CG_REG1_SDA_IN_SW_MODE_EN, I3CG_REG1_SDA_IN_SW_MODE_VAL, I3C_AST10X0_MIPI_MANUF_ID, I3C_BCR_IBI_PAYLOAD_HAS_DATA_BYTE, I3C_BUS_FREE_TIMING_RESET, I3C_BUS_I2C_FMP_TF_MAX_NS, @@ -48,6 +49,7 @@ I3C_DEFAULT_STATIC_ADDR, I3C_GLOBAL_RESET_DEASSERT_MASK, I3C_IBI_DATA_THRESHOLD_MAX, I3C_INIT_POLL_DELAY_NS, I3C_INTR_STATUS_ALL_BITS, I3C_MSG_READ, I3C_OP_TIMEOUT_US, I3C_POLL_MAX_ITERS, IBIQ_STATUS_IBI_DATA_LEN, IBIQ_STATUS_IBI_DATA_LEN_SHIFT, + SLV_EVENT_CTRL_MRL_UPD, SLV_EVENT_CTRL_MWL_UPD, IBIQ_STATUS_IBI_ID, IBIQ_STATUS_IBI_ID_SHIFT, INTR_CCC_UPDATED_STAT, INTR_DYN_ADDR_ASSGN_STAT, INTR_IBI_THLD_STAT, INTR_RESP_READY_STAT, INTR_TRANSFER_ABORT_STAT, INTR_TRANSFER_ERR_STAT, MAX_CMDS, MAX_PRIV_XFER_CMDS, MAX_XFER_DATA_LEN, NSEC_PER_SEC, RESET_CTRL_ALL, @@ -59,7 +61,7 @@ use super::error::I3cError as I3cDrvError; use super::error::I3cError; use super::ibi as ibi_workq; -use super::types::{Completion, I3cCmd, I3cIbi, I3cMsg, I3cXfer, SpeedI3c, Tid}; +use super::types::{Completion, I2cOp, I3cCmd, I3cIbi, I3cMsg, I3cXfer, SpeedI2c, SpeedI3c, Tid}; use super::registers::I3cRegisters; use core::sync::atomic::{AtomicBool, AtomicU32, Ordering}; @@ -125,6 +127,11 @@ pending: AtomicU32, /// Dynamic address assigned by the bus master; bit 8 = valid. dyn_addr: AtomicU32, + /// Raw `SLV_MAX_LEN` (MRL in bits 31:16, MWL in bits 15:0) latched by the + /// ISR when the bus master updates it via SETMRL/SETMWL. + slv_max_len: AtomicU32, + /// `slv_max_len` holds a master-written value (not reset state). + slv_max_len_valid: AtomicBool, /// A deferred fault: the ISR observed a halted/errored engine and left /// recovery (halt/resume sequencing needs the wait policy) to the thread. fault: AtomicBool, @@ -139,6 +146,8 @@ Self { pending: AtomicU32::new(0), dyn_addr: AtomicU32::new(0), + slv_max_len: AtomicU32::new(0), + slv_max_len_valid: AtomicBool::new(false), fault: AtomicBool::new(false), target_ibi_done: Completion::new(), target_data_done: Completion::new(), @@ -164,6 +173,16 @@ None } } + + /// Max read/write lengths `(mrl, mwl)` the bus master set via + /// SETMRL/SETMWL, if any update was observed. + pub(crate) fn max_len(&self) -> Option<(u16, u16)> { + if !self.slv_max_len_valid.load(Ordering::Acquire) { + return None; + } + let v = self.slv_max_len.load(Ordering::Acquire); + Some(((v >> 16) as u16, (v & 0xffff) as u16)) + } } static ISR_EVENTS: [IsrEvents; 4] = [ @@ -293,6 +312,14 @@ // Read-and-clear the event; if the engine halted, defer the // resume sequencing (it needs the wait policy) to the thread. let event = regs.read_slv_event_ctrl(); + // Latch SETMRL/SETMWL updates before the write-back clears the + // update flags (the thread reads them via `max_len`). + if event & (SLV_EVENT_CTRL_MRL_UPD | SLV_EVENT_CTRL_MWL_UPD) != 0 { + events + .slv_max_len + .store(regs.read_slv_max_len(), Ordering::Release); + events.slv_max_len_valid.store(true, Ordering::Release); + } regs.write_slv_event_ctrl(event); if regs.xfer_status() == CM_TFR_STS_TARGET_HALT { events.fault.store(true, Ordering::Release); @@ -553,6 +580,23 @@ /// Attach a device to a DAT position fn attach_i3c_dev(&mut self, pos: usize, addr: u8) -> Result<(), I3cError>; + /// Attach a legacy I2C device to a DAT position (static address, + /// `LEGACY_I2C_DEV` marked, SIR/MR rejected). + fn attach_i2c_dev(&mut self, pos: usize, static_addr: u8) -> Result<(), I3cError>; + + /// Execute a legacy-I2C transaction against the device at DAT `pos`. + /// + /// Consecutive operations are joined by repeated START; the last ends + /// with STOP. **Consumes each `Read` buffer** (left empty in the slice, + /// same contract as [`priv_xfer`](HardwareTransfer::priv_xfer)); the data + /// lands in the caller-owned memory the reborrow came from. + fn i2c_priv_xfer<'a>( + &mut self, + config: &mut I3cConfig, + pos: u8, + ops: &mut [I2cOp<'a>], + ) -> Result<(), I3cError>; + /// Execute a CCC fn do_ccc(&mut self, config: &mut I3cConfig, ccc: &mut CccPayload) -> Result<(), I3cError>; @@ -1499,6 +1543,106 @@ Ok(()) } + fn attach_i2c_dev(&mut self, pos: usize, static_addr: u8) -> Result<(), I3cDrvError> { + // Legacy I2C entry: static address in the low field, the LEGACY bit + // routes transfers through the controller's I2C engine; SIR/MR stay + // rejected (an I2C device cannot raise them). + let raw = DEV_ADDR_TABLE_LEGACY_I2C_DEV + | DEV_ADDR_TABLE_SIR_REJECT + | DEV_ADDR_TABLE_MR_REJECT + | field_prep(DEV_ADDR_TABLE_STATIC_ADDR, u32::from(static_addr)); + self.regs.dat_write_raw(pos, raw); + Ok(()) + } + + fn i2c_priv_xfer<'a>( + &mut self, + config: &mut I3cConfig, + pos: u8, + ops: &mut [I2cOp<'a>], + ) -> Result<(), I3cDrvError> { + if ops.is_empty() { + return Ok(()); + } + // Same TID-width bound as private I3C transfers. + if ops.len() > MAX_PRIV_XFER_CMDS { + return Err(I3cDrvError::TooManyMsgs); + } + // Pre-validate every length before consuming any buffer + // (all-or-nothing, mirroring priv_xfer_build_cmds). + for op in ops.iter() { + let len = match op { + I2cOp::Write(b) => b.len(), + I2cOp::Read(b) => b.len(), + }; + if len == 0 || len > MAX_XFER_DATA_LEN { + return Err(I3cDrvError::Invalid); + } + } + + // The DAT entry marks the device as legacy I2C, so the SPEED field + // selects between the I2C timing sets programmed by init_clock. + let speed = if config.i2c_scl_hz > 400_000 { + SpeedI2c::Fmp + } else { + SpeedI2c::Fm + } as u32; + + let mut cmds: heapless::Vec<I3cCmd<'a>, MAX_CMDS> = heapless::Vec::new(); + let nops = ops.len(); + for (i, op) in ops.iter_mut().enumerate() { + let mut cmd = I3cCmd::new(); + let len = match op { + I2cOp::Write(b) => b.len(), + I2cOp::Read(b) => b.len(), + }; + cmd.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.cmd_lo = field_prep( + COMMAND_PORT_TID, + u32::try_from(i).map_err(|_| I3cDrvError::Invalid)?, + ) | field_prep(COMMAND_PORT_DEV_INDEX, u32::from(pos)) + | field_prep(COMMAND_PORT_SPEED, speed) + | COMMAND_PORT_ROC; + + match op { + I2cOp::Write(b) => { + cmd.tx = Some(*b); + cmd.tx_len = u32::try_from(len).map_err(|_| I3cDrvError::Invalid)?; + } + I2cOp::Read(b) => { + // Move the caller's reborrow into the command (same + // consume contract as priv_xfer); `take` leaves an empty + // slice behind. + let buf: &'a mut [u8] = core::mem::take(b); + cmd.rx = Some(buf); + cmd.rx_len = u32::try_from(len).map_err(|_| I3cDrvError::Invalid)?; + cmd.cmd_lo |= COMMAND_PORT_READ_TRANSFER; + } + } + + if i + 1 == nops { + cmd.cmd_lo |= COMMAND_PORT_TOC; + } + cmds.push(cmd).map_err(|_| I3cDrvError::TooManyMsgs)?; + } + + let mut xfer = I3cXfer::new(cmds.as_mut_slice()); + self.start_xfer(config, &mut xfer); + + if !self.wait_xfer_complete(config, &mut xfer, I3C_OP_TIMEOUT_US) { + return Err(I3cDrvError::Timeout); + } + + match xfer.ret { + 0 => Ok(()), + _ => Err(I3cDrvError::IoError), + } + } + #[allow(clippy::too_many_lines)] fn do_ccc( &mut self,
diff --git a/target/ast10x0/peripherals/i3c/mod.rs b/target/ast10x0/peripherals/i3c/mod.rs index 59417a7..a915d80 100644 --- a/target/ast10x0/peripherals/i3c/mod.rs +++ b/target/ast10x0/peripherals/i3c/mod.rs
@@ -62,8 +62,8 @@ // Core types pub use types::{ - Completion, DevKind, I3cCmd, I3cDeviceId, I3cIbi, I3cIbiType, I3cMsg, I3cPid, I3cStatus, - I3cXfer, SpeedI2c, SpeedI3c, Tid, + Completion, DevKind, I2cOp, I3cCmd, I3cDeviceId, I3cIbi, I3cIbiType, I3cMsg, I3cPid, + I3cStatus, I3cXfer, SpeedI2c, SpeedI3c, Tid, }; // Hardware interface @@ -77,8 +77,9 @@ // CCC operations pub use ccc::{ - ccc_events_all_set, ccc_events_set, ccc_getbcr, ccc_getdcr, ccc_getpid, ccc_getstatus, - ccc_getstatus_fmt1, ccc_rstact_all, ccc_rstdaa_all, ccc_setnewda, Ccc, CccPayload, + ccc_events_all_set, ccc_events_set, ccc_getbcr, ccc_getdcr, ccc_getmrl, ccc_getmwl, + ccc_getmxds, ccc_getpid, ccc_getstatus, ccc_getstatus_fmt1, ccc_rstact_all, ccc_rstdaa_all, + ccc_setmrl, ccc_setmrl_all, ccc_setmwl, ccc_setmwl_all, ccc_setnewda, Ccc, CccPayload, CccRstActDefByte, CccTargetPayload, GetStatusDefByte, GetStatusFormat, GetStatusResp, };
diff --git a/target/ast10x0/peripherals/i3c/registers.rs b/target/ast10x0/peripherals/i3c/registers.rs index fdb61db..3379977 100644 --- a/target/ast10x0/peripherals/i3c/registers.rs +++ b/target/ast10x0/peripherals/i3c/registers.rs
@@ -764,6 +764,12 @@ self.i3c().i3cd078().read().bits() } + /// I3CD07C: read the max write/read length register (MRL in bits 31:16, + /// MWL in bits 15:0; updated by the bus master via SETMRL/SETMWL). + pub(crate) fn read_slv_max_len(&self) -> u32 { + self.i3c().i3cd07c().read().bits() + } + /// I3CD078: write the slave characteristics register. pub(crate) fn write_slv_char_ctrl(&self, val: u32) { self.i3c().i3cd078().write(|w| unsafe { w.bits(val) });
diff --git a/target/ast10x0/peripherals/i3c/types.rs b/target/ast10x0/peripherals/i3c/types.rs index d208dc0..201fa97 100644 --- a/target/ast10x0/peripherals/i3c/types.rs +++ b/target/ast10x0/peripherals/i3c/types.rs
@@ -205,6 +205,22 @@ } // ============================================================================= +// Legacy I2C Operations +// ============================================================================= + +/// One leg of a legacy-I2C transaction on the I3C bus. +/// +/// Mirrors `embedded_hal::i2c::Operation` without pulling that type into the +/// hardware trait. Consecutive operations are joined by repeated START; the +/// last one ends with STOP. +pub enum I2cOp<'a> { + /// Write the bytes to the device. + Write(&'a [u8]), + /// Read into the buffer (filled completely on success). + Read(&'a mut [u8]), +} + +// ============================================================================= // Device Identification // =============================================================================