From f906932df4fd3c6c5012539e81a49e5d1dfc3cf9 Mon Sep 17 00:00:00 2001 From: Anthony Rocha Date: Fri, 24 Apr 2026 14:06:07 -0700 Subject: [PATCH 1/5] i2c_core: add DMA mode for master/slave transfers --- BUILD.bazel | 4 +- src/i2c_core/constants.rs | 3 + src/i2c_core/controller.rs | 70 ++++++++++++++ src/i2c_core/master.rs | 193 +++++++++++++++++++++++++++++++++++++ src/i2c_core/mod.rs | 4 +- src/i2c_core/slave.rs | 68 +++++++++++++ src/i2c_core/transfer.rs | 2 + src/i2c_core/types.rs | 9 ++ src/spi/spidmairqtest.rs | 8 ++ 9 files changed, 358 insertions(+), 3 deletions(-) diff --git a/BUILD.bazel b/BUILD.bazel index 1ed5729..c465869 100644 --- a/BUILD.bazel +++ b/BUILD.bazel @@ -29,6 +29,7 @@ rust_library( deps = [ "@oot_crates_no_std__aspeed-ddk-0.1.0//:build_script_build", "@oot_crates_no_std__ast1060-pac-0.1.0//:ast1060_pac", + "@oot_crates_no_std__bitflags-2.11.0//:bitflags", "@oot_crates_no_std__cortex-m-0.7.7//:cortex_m", "@oot_crates_no_std__cortex-m-rt-0.7.5//:cortex_m_rt", "@oot_crates_no_std__critical-section-1.2.0//:critical_section", @@ -42,7 +43,8 @@ rust_library( "@oot_crates_no_std__openprot-hal-blocking-0.1.0//:openprot_hal_blocking", "@oot_crates_no_std__panic-halt-1.0.0//:panic_halt", "@oot_crates_no_std__proposed-traits-0.1.0//:proposed_traits", - "@oot_crates_no_std__zerocopy-0.8.39//:zerocopy", + "@oot_crates_no_std__static_cell-2.1.1//:static_cell", + "@oot_crates_no_std__zerocopy-0.8.47//:zerocopy", ], proc_macro_deps = [ "@oot_crates_no_std__paste-1.0.15//:paste", diff --git a/src/i2c_core/constants.rs b/src/i2c_core/constants.rs index e982940..566ae58 100644 --- a/src/i2c_core/constants.rs +++ b/src/i2c_core/constants.rs @@ -45,6 +45,9 @@ pub const I2C_FAST_PLUS_MODE_HZ: u32 = 1_000_000; /// Buffer mode maximum size (32 bytes) pub const BUFFER_MODE_SIZE: usize = 32; +/// DMA mode maximum transfer size (4096 bytes, hardware limit) +pub const DMA_MODE_MAX_SIZE: usize = 4096; + /// I2C buffer size register value /// Reference: `ast1060_i2c.rs:97` pub const I2C_BUF_SIZE: u8 = 0x20; diff --git a/src/i2c_core/controller.rs b/src/i2c_core/controller.rs index d801b21..a8f6c08 100644 --- a/src/i2c_core/controller.rs +++ b/src/i2c_core/controller.rs @@ -30,6 +30,8 @@ pub struct Ast1060I2c<'a> { pub(crate) current_xfer_cnt: u32, /// Completion flag for synchronous operations pub(crate) completion: bool, + /// DMA buffer for DMA mode (non-cached SRAM, caller-owned) + pub(crate) dma_buf: Option<&'a mut [u8]>, } impl<'a> Ast1060I2c<'a> { @@ -43,6 +45,7 @@ impl<'a> Ast1060I2c<'a> { /// - Interrupt enable /// /// Use [`from_initialized`] if hardware is already configured. + /// For DMA mode, set `controller.dma_buf` before calling; see [`I2cController`]. pub fn new(controller: &'a I2cController<'a>, config: I2cConfig) -> Result { let mut i2c = Self::from_initialized(controller, config); i2c.init_hardware(&config)?; @@ -92,9 +95,76 @@ impl<'a> Ast1060I2c<'a> { current_len: 0, current_xfer_cnt: 0, completion: false, + dma_buf: None, } } + /// Create I2C instance with DMA mode support. + /// + /// Like [`from_initialized`] but also provides a DMA buffer for use when + /// `xfer_mode == I2cXferMode::DmaMode`. The buffer must reside in + /// non-cached SRAM (e.g. `#[link_section = ".ram_nc"]`) so that the + /// DMA engine and CPU see the same data without cache maintenance. + /// + /// # Example + /// + /// ```rust,no_run + /// use aspeed_ddk::i2c_core::*; + /// use aspeed_ddk::common::DmaBuffer; + /// + /// #[link_section = ".ram_nc"] + /// static mut DMA_BUF: DmaBuffer<4096> = DmaBuffer::new(); + /// + /// let config = I2cConfig { xfer_mode: I2cXferMode::DmaMode, ..I2cConfig::with_static_clocks() }; + /// let dma_buf: &mut [u8] = unsafe { &mut DMA_BUF.buf }; + /// let mut i2c = Ast1060I2c::new_with_dma(&controller, config, dma_buf)?; + /// ``` + pub fn new_with_dma( + controller: &'a I2cController<'a>, + config: I2cConfig, + dma_buf: &'a mut [u8], + ) -> Result { + let mut i2c = Self::from_initialized(controller, config); + i2c.dma_buf = Some(dma_buf); + i2c.init_hardware(&config)?; + Ok(i2c) + } + + /// Create I2C instance from pre-initialized hardware with DMA buffer (NO hardware init) + /// + /// Like [`from_initialized`] but attaches a DMA buffer for use when + /// `xfer_mode == I2cXferMode::DmaMode`. Use this per-operation after the + /// bus has already been initialized via [`new_with_dma`], to avoid the + /// overhead of re-running hardware initialization. + /// + /// The buffer must reside in non-cached SRAM (e.g. `#[link_section = ".ram_nc"]`). + /// + /// # Example + /// + /// ```rust,no_run + /// use aspeed_ddk::i2c_core::*; + /// use aspeed_ddk::common::DmaBuffer; + /// + /// #[link_section = ".ram_nc"] + /// static mut DMA_BUF: DmaBuffer<4096> = DmaBuffer::new(); + /// + /// let config = I2cConfig { xfer_mode: I2cXferMode::DmaMode, ..I2cConfig::with_static_clocks() }; + /// // Hardware was already initialized via new_with_dma() at startup. + /// let dma_buf: &mut [u8] = unsafe { &mut DMA_BUF.buf }; + /// let mut i2c = Ast1060I2c::from_initialized_with_dma(&controller, config, dma_buf); + /// i2c.write(addr, data)?; + /// ``` + #[must_use] + pub fn from_initialized_with_dma( + controller: &'a I2cController<'a>, + config: I2cConfig, + dma_buf: &'a mut [u8], + ) -> Self { + let mut i2c = Self::from_initialized(controller, config); + i2c.dma_buf = Some(dma_buf); + i2c + } + /// Get access to registers (visible to other modules) #[inline] pub(crate) fn regs(&self) -> &RegisterBlock { diff --git a/src/i2c_core/master.rs b/src/i2c_core/master.rs index 68382a9..4818735 100644 --- a/src/i2c_core/master.rs +++ b/src/i2c_core/master.rs @@ -39,6 +39,7 @@ impl Ast1060I2c<'_> { match self.xfer_mode { I2cXferMode::ByteMode => self.write_byte_mode(addr, bytes), I2cXferMode::BufferMode => self.write_buffer_mode(addr, bytes), + I2cXferMode::DmaMode => self.write_dma_mode(addr, bytes), } } @@ -51,6 +52,7 @@ impl Ast1060I2c<'_> { match self.xfer_mode { I2cXferMode::ByteMode => self.read_byte_mode(addr, buffer), I2cXferMode::BufferMode => self.read_buffer_mode(addr, buffer), + I2cXferMode::DmaMode => self.read_dma_mode(addr, buffer), } } @@ -402,4 +404,195 @@ impl Ast1060I2c<'_> { Ok(()) } + + // ========================================================================= + // DMA mode + // + // Uses system SRAM (non-cached, caller-allocated) as the I2C DMA buffer. + // The DMA engine can move up to 4096 bytes in a single START/STOP + // transaction. + // + // Register layout for DMA master TX: + // i2cm1c: dmatx_buf_len_byte = (len-1), dmatx_buf_len_wr_enbl_for_cur_write_cmd = 1 + // i2cm30: sdramdmabuffer_base_addr = physical address of DMA buffer + // For DMA master RX: + // i2cm1c: dmarx_buf_len_byte = (len-1), dmarx_buf_len_wr_enbl_for_cur_write_cmd = 1 + // i2cm34: sdramdmabuffer_base_addr1 = physical address of DMA buffer + // + // Reference: aspeed-rust/src/i2c/ast1060_i2c.rs aspeed_i2c_write/read DmaMode branch + // ========================================================================= + + /// Write in DMA mode (up to 4096 bytes in a single transaction) + /// + /// The DMA buffer supplied to [`Ast1060I2c::new_with_dma`] is used as the + /// staging area. For transfers larger than `DMA_MODE_MAX_SIZE` the data is + /// chunked into successive START-less continuation transactions (i.e. the bus + /// is NOT released between chunks). + fn write_dma_mode(&mut self, addr: u8, bytes: &[u8]) -> Result<(), I2cError> { + let total_len = bytes.len(); + let mut offset = 0; + + self.current_addr = addr; + #[allow(clippy::cast_possible_truncation)] + { + self.current_len = total_len as u32; + } + self.current_xfer_cnt = 0; + + while offset < total_len { + let chunk_len = core::cmp::min(constants::DMA_MODE_MAX_SIZE, total_len - offset); + let chunk = &bytes[offset..offset + chunk_len]; + let is_first = offset == 0; + let is_last = offset + chunk_len >= total_len; + + // Copy chunk to DMA buffer (non-cached SRAM) + { + let dma_buf = self.dma_buf.as_deref_mut().ok_or(I2cError::Invalid)?; + if dma_buf.len() < chunk_len { + return Err(I2cError::Invalid); + } + dma_buf[..chunk_len].copy_from_slice(chunk); + } + + let phy_addr = { + let dma_buf = self.dma_buf.as_deref().ok_or(I2cError::Invalid)?; + dma_buf.as_ptr() as u32 + }; + + // Set DMA TX length in i2cm1c (len - 1) + #[allow(clippy::cast_possible_truncation)] + self.regs().i2cm1c().write(|w| unsafe { + w.dmatx_buf_len_byte() + .bits((chunk_len - 1) as u16) + .dmatx_buf_len_wr_enbl_for_cur_write_cmd() + .set_bit() + }); + + // Set DMA TX buffer base address in i2cm30 + self.regs() + .i2cm30() + .write(|w| unsafe { w.sdramdmabuffer_base_addr().bits(phy_addr) }); + + self.clear_interrupts(0xffff_ffff); + self.completion = false; + + // Build command + let mut cmd = constants::AST_I2CM_PKT_EN + | constants::AST_I2CM_TX_CMD + | constants::AST_I2CM_TX_DMA_EN; + + if is_first { + cmd |= constants::ast_i2cm_pkt_addr(addr) | constants::AST_I2CM_START_CMD; + } + if is_last { + cmd |= constants::AST_I2CM_STOP_CMD; + } + + self.regs().i2cm18().write(|w| unsafe { w.bits(cmd) }); + + self.wait_completion(constants::DEFAULT_TIMEOUT_US)?; + + let status = self.regs().i2cm14().read().bits(); + if status & constants::AST_I2CM_PKT_ERROR != 0 { + if status & constants::AST_I2CM_TX_NAK != 0 { + return Err(I2cError::NoAcknowledge); + } + return Err(I2cError::Abnormal); + } + + #[allow(clippy::cast_possible_truncation)] + { + self.current_xfer_cnt += chunk_len as u32; + } + offset += chunk_len; + } + + Ok(()) + } + + /// Read in DMA mode (up to 4096 bytes in a single transaction) + fn read_dma_mode(&mut self, addr: u8, buffer: &mut [u8]) -> Result<(), I2cError> { + let total_len = buffer.len(); + let mut offset = 0; + + self.current_addr = addr; + #[allow(clippy::cast_possible_truncation)] + { + self.current_len = total_len as u32; + } + self.current_xfer_cnt = 0; + + while offset < total_len { + let chunk_len = core::cmp::min(constants::DMA_MODE_MAX_SIZE, total_len - offset); + let is_first = offset == 0; + let is_last = offset + chunk_len >= total_len; + + { + let dma_buf = self.dma_buf.as_deref().ok_or(I2cError::Invalid)?; + if dma_buf.len() < chunk_len { + return Err(I2cError::Invalid); + } + } + + let phy_addr = { + let dma_buf = self.dma_buf.as_deref().ok_or(I2cError::Invalid)?; + dma_buf.as_ptr() as u32 + }; + + // Set DMA RX length in i2cm1c (len - 1) + #[allow(clippy::cast_possible_truncation)] + self.regs().i2cm1c().modify(|_, w| unsafe { + w.dmarx_buf_len_byte() + .bits((chunk_len - 1) as u16) + .dmarx_buf_len_wr_enbl_for_cur_write_cmd() + .set_bit() + }); + + // Set DMA RX buffer base address in i2cm34 + self.regs() + .i2cm34() + .modify(|_, w| unsafe { w.sdramdmabuffer_base_addr1().bits(phy_addr) }); + + self.clear_interrupts(0xffff_ffff); + self.completion = false; + + // Build command + let mut cmd = constants::AST_I2CM_PKT_EN + | constants::AST_I2CM_RX_CMD + | constants::AST_I2CM_RX_DMA_EN; + + if is_first { + cmd |= constants::ast_i2cm_pkt_addr(addr) | constants::AST_I2CM_START_CMD; + } + if is_last { + cmd |= constants::AST_I2CM_RX_CMD_LAST | constants::AST_I2CM_STOP_CMD; + } + + self.regs().i2cm18().write(|w| unsafe { w.bits(cmd) }); + + self.wait_completion(constants::DEFAULT_TIMEOUT_US)?; + + let status = self.regs().i2cm14().read().bits(); + if status & constants::AST_I2CM_PKT_ERROR != 0 { + if status & constants::AST_I2CM_TX_NAK != 0 { + return Err(I2cError::NoAcknowledge); + } + return Err(I2cError::Abnormal); + } + + // Copy from DMA buffer into caller's buffer + { + let dma_buf = self.dma_buf.as_deref().ok_or(I2cError::Invalid)?; + buffer[offset..offset + chunk_len].copy_from_slice(&dma_buf[..chunk_len]); + } + + #[allow(clippy::cast_possible_truncation)] + { + self.current_xfer_cnt += chunk_len as u32; + } + offset += chunk_len; + } + + Ok(()) + } } diff --git a/src/i2c_core/mod.rs b/src/i2c_core/mod.rs index 442440d..613a11b 100644 --- a/src/i2c_core/mod.rs +++ b/src/i2c_core/mod.rs @@ -10,8 +10,8 @@ //! //! - Multi-master support //! - Master and slave (target) mode -//! - Buffer mode with 32-byte hardware FIFO -//! - Byte-by-byte mode for simple transfers +//! - **Buffer mode with 32-byte hardware FIFO** +//! - **DMA mode with up to 4096-byte transfers** (requires non-cached SRAM buffer) //! - Clock stretching and bus recovery //! - `SMBus` alert support //! - Configurable speeds: Standard (100kHz), Fast (400kHz), Fast-plus (1MHz) diff --git a/src/i2c_core/slave.rs b/src/i2c_core/slave.rs index 427a254..dd538f2 100644 --- a/src/i2c_core/slave.rs +++ b/src/i2c_core/slave.rs @@ -15,6 +15,12 @@ const BUFFER_SIZE: usize = 32; /// Maximum slave receive buffer size (hardware limitation) pub const SLAVE_BUFFER_SIZE: usize = 256; +/// Slave RX DMA enable bit in slave command register (i2cs28 bit 9). +/// +/// When set, the hardware writes received bytes into the DMA buffer pointed to +/// by i2cs38/i2cs3c instead of the 32-byte FIFO. Supports up to 4096-byte transfers. +const AST_I2CS_RX_DMA_EN: u32 = 1 << 9; + /// Slave mode configuration #[derive(Debug, Clone, Copy)] pub struct SlaveConfig { @@ -155,6 +161,32 @@ impl Ast1060I2c<'_> { self.regs() .i2cc0c() .write(|w| unsafe { w.rx_pool_buffer_size().bits(constants::I2C_BUF_SIZE - 1) }); + } else if self.xfer_mode == I2cXferMode::DmaMode { + if let Some(dma_buf) = self.dma_buf.as_deref_mut() { + // Arm slave DMA: point hardware at the non-cached buffer and enable RX_DMA. + // i2cs38/i2cs3c hold the physical DMA buffer address (same address in + // both registers — the hardware uses both for different address widths). + // i2cs2c sets the DMA receive length and enables the length register. + let dma_addr = dma_buf.as_mut_ptr() as u32; + let dma_len = u16::try_from(dma_buf.len().min(4096) - 1).unwrap_or(u16::MAX); + unsafe { + self.regs().i2cs38().write(|w| w.bits(dma_addr)); + self.regs().i2cs3c().write(|w| w.bits(dma_addr)); + self.regs().i2cs2c().write(|w| { + w.dmarx_buf_len_byte() + .bits(dma_len) + .dmarx_buf_len_wr_enbl_for_cur_cmd() + .set_bit() + }); + } + cmd |= AST_I2CS_RX_DMA_EN; + } else { + // No DMA buffer provided — fall back to buffer mode. + cmd |= constants::AST_I2CS_RX_BUFF_EN; + self.regs().i2cc0c().write(|w| unsafe { + w.rx_pool_buffer_size().bits(constants::I2C_BUF_SIZE - 1) + }); + } } else { cmd &= !constants::AST_I2CS_PKT_MODE_EN; } @@ -252,6 +284,42 @@ impl Ast1060I2c<'_> { self.regs().i2cs28().write(|w| w.bits(cmd)); } + Ok(to_read) + } else if self.xfer_mode == I2cXferMode::DmaMode { + // DMA mode: the hardware has already DMA'd into `self.dma_buf`. + // Read actual received byte count from the DMA status register. + let hw_len = self.regs().i2cs4c().read().dmarx_actual_len_byte().bits() as usize; + let to_read = hw_len.min(buffer.len()); + + if let Some(dma_buf) = self.dma_buf.as_deref() { + let src_len = to_read.min(dma_buf.len()); + buffer[..src_len].copy_from_slice(&dma_buf[..src_len]); + } + + // Re-arm slave DMA for next receive + let mut cmd = constants::AST_I2CS_ACTIVE_ALL | constants::AST_I2CS_PKT_MODE_EN; + if let Some(dma_buf) = self.dma_buf.as_deref_mut() { + let dma_addr = dma_buf.as_mut_ptr() as u32; + let dma_len = u16::try_from(dma_buf.len().min(4096) - 1).unwrap_or(u16::MAX); + unsafe { + self.regs().i2cs4c().write(|w| w.bits(0)); + self.regs().i2cs38().write(|w| w.bits(dma_addr)); + self.regs().i2cs3c().write(|w| w.bits(dma_addr)); + self.regs().i2cs2c().write(|w| { + w.dmarx_buf_len_byte() + .bits(dma_len) + .dmarx_buf_len_wr_enbl_for_cur_cmd() + .set_bit() + }); + } + cmd |= AST_I2CS_RX_DMA_EN; + } else { + cmd |= constants::AST_I2CS_RX_BUFF_EN; + } + unsafe { + self.regs().i2cs28().write(|w| w.bits(cmd)); + } + Ok(to_read) } else { // byte mode diff --git a/src/i2c_core/transfer.rs b/src/i2c_core/transfer.rs index c325f70..b74dcf7 100644 --- a/src/i2c_core/transfer.rs +++ b/src/i2c_core/transfer.rs @@ -41,6 +41,8 @@ impl Ast1060I2c<'_> { Ok(()) } I2cXferMode::BufferMode => self.start_buffer_mode(addr, is_read, len), + // DMA mode transfers are initiated directly in master.rs (write_dma_mode / read_dma_mode) + I2cXferMode::DmaMode => Err(super::error::I2cError::Invalid), } } diff --git a/src/i2c_core/types.rs b/src/i2c_core/types.rs index 71b28aa..ed448d2 100644 --- a/src/i2c_core/types.rs +++ b/src/i2c_core/types.rs @@ -209,6 +209,9 @@ impl Default for I2cConfig { /// The application must pre-configure I2C global clocks (I2CG10) /// before creating an I2C driver instance. This default reads /// the actual clock values from hardware registers. + /// + /// For DMA mode, use [`I2cConfig`] directly and set `xfer_mode` to + /// [`I2cXferMode::DmaMode`], then pass a DMA buffer via [`I2cController::dma_buf`]. fn default() -> Self { Self { xfer_mode: I2cXferMode::BufferMode, @@ -249,6 +252,12 @@ pub enum I2cXferMode { ByteMode, /// Buffer mode (up to 32 bytes via hardware buffer) BufferMode, + /// DMA mode (up to 4096 bytes via system memory DMA) + /// + /// Requires a pre-allocated, cache-coherent DMA buffer passed to + /// [`Ast1060I2c::new_with_dma`]. The buffer must be placed in a + /// non-cached memory region (e.g. `#[link_section = ".ram_nc"]`). + DmaMode, } /// I2C bus speed diff --git a/src/spi/spidmairqtest.rs b/src/spi/spidmairqtest.rs index b87994e..18e9da3 100644 --- a/src/spi/spidmairqtest.rs +++ b/src/spi/spidmairqtest.rs @@ -88,6 +88,7 @@ impl App { } } + #[cfg(feature = "isr-handlers")] unsafe fn fmc_irq(&mut self) { log_uart!(&mut self.uart, "fmc_irq"); let Some(fmc) = self.fmc_controller.as_mut() else { @@ -122,6 +123,7 @@ impl App { } } + #[cfg(feature = "isr-handlers")] unsafe fn spi_irq(&mut self) { log_uart!(&mut self.uart, "spi_irq"); let Some(spi) = self.spi_controller.as_mut() else { @@ -238,11 +240,17 @@ pub fn init_spidmairq_app_once() { fn app_mut() -> &'static mut App { unsafe { APP_PTR.as_mut().expect("APP not initialized") } } +#[cfg(feature = "isr-handlers")] #[no_mangle] pub extern "C" fn fmc() { unsafe { app_mut().fmc_irq() }; } +#[cfg(not(feature = "isr-handlers"))] +/// Placeholder: ISR handler disabled (build with `isr-handlers` feature to enable) +pub fn fmc_isr() {} + +#[cfg(feature = "isr-handlers")] #[no_mangle] pub extern "C" fn spi() { unsafe { app_mut().spi_irq() }; From 84863919b6d707c55b4712393f2cd11f9ea27096 Mon Sep 17 00:00:00 2001 From: Anthony Rocha Date: Fri, 24 Apr 2026 14:23:00 -0700 Subject: [PATCH 2/5] i2c_core: align slave DMA flow and add dma functional test --- src/i2c_core/slave.rs | 76 ++++++++++++++++------ tests-hw/src/i2c_core_test.rs | 116 ++++++++++++++++++++++++++++++++++ 2 files changed, 171 insertions(+), 21 deletions(-) diff --git a/src/i2c_core/slave.rs b/src/i2c_core/slave.rs index dd538f2..ae9da4a 100644 --- a/src/i2c_core/slave.rs +++ b/src/i2c_core/slave.rs @@ -121,6 +121,56 @@ impl SlaveBuffer { } impl Ast1060I2c<'_> { + #[inline] + fn slave_rx_len(&self) -> usize { + if self.xfer_mode == I2cXferMode::DmaMode { + self.regs().i2cs4c().read().dmarx_actual_len_byte().bits() as usize + } else { + self.regs() + .i2cc0c() + .read() + .actual_rxd_pool_buffer_size() + .bits() as usize + } + } + + /// Arm slave receive path based on transfer mode. + /// + /// This mirrors the old AST1060 driver behavior where packet-slave IRQ + /// branches re-arm either RX FIFO or RX DMA depending on `xfer_mode`. + fn arm_slave_receive(&mut self, cmd: &mut u32) { + if self.xfer_mode == I2cXferMode::DmaMode { + if let Some(dma_buf) = self.dma_buf.as_deref_mut() { + let dma_addr = dma_buf.as_mut_ptr() as u32; + let dma_len = u16::try_from(dma_buf.len().min(4096) - 1).unwrap_or(u16::MAX); + unsafe { + self.regs().i2cs4c().write(|w| w.bits(0)); + self.regs().i2cs38().write(|w| w.bits(dma_addr)); + self.regs().i2cs3c().write(|w| w.bits(dma_addr)); + self.regs().i2cs2c().write(|w| { + w.dmarx_buf_len_byte() + .bits(dma_len) + .dmarx_buf_len_wr_enbl_for_cur_cmd() + .set_bit() + }); + } + *cmd |= AST_I2CS_RX_DMA_EN; + } else { + *cmd |= constants::AST_I2CS_RX_BUFF_EN; + self.regs().i2cc0c().write(|w| unsafe { + w.rx_pool_buffer_size().bits(constants::I2C_BUF_SIZE - 1) + }); + } + } else if self.xfer_mode == I2cXferMode::BufferMode { + *cmd |= constants::AST_I2CS_RX_BUFF_EN; + self.regs() + .i2cc0c() + .write(|w| unsafe { w.rx_pool_buffer_size().bits(constants::I2C_BUF_SIZE - 1) }); + } else { + *cmd &= !constants::AST_I2CS_PKT_MODE_EN; + } + } + /// Configure the controller for slave mode pub fn configure_slave(&mut self, config: &SlaveConfig) -> Result<(), I2cError> { // Ensure master mode is disabled first @@ -408,14 +458,14 @@ impl Ast1060I2c<'_> { | constants::AST_I2CS_WAIT_RX_DMA { // S: Sw|D - cmd |= constants::AST_I2CS_RX_BUFF_EN; + self.arm_slave_receive(&mut cmd); unsafe { self.regs().i2cs28().write(|w| w.bits(cmd)); } return Some(SlaveEvent::WriteRequest); } else if sts == constants::AST_I2CS_SLAVE_MATCH | constants::AST_I2CS_STOP { // S: Sw|P - cmd |= constants::AST_I2CS_RX_BUFF_EN; + self.arm_slave_receive(&mut cmd); unsafe { self.regs().i2cs28().write(|w| w.bits(cmd)); } @@ -447,13 +497,7 @@ impl Ast1060I2c<'_> { { // S: (Sw)|D|(P) return Some(SlaveEvent::DataReceived { - len: usize::from( - self.regs() - .i2cc0c() - .read() - .actual_rxd_pool_buffer_size() - .bits(), - ), + len: self.slave_rx_len(), }); } else if sts == constants::AST_I2CS_RX_DONE | constants::AST_I2CS_WAIT_TX_DMA || sts @@ -463,13 +507,7 @@ impl Ast1060I2c<'_> { { // S: rx_done | wait_tx return Some(SlaveEvent::DataReceivedAndSent { - rx_len: usize::from( - self.regs() - .i2cc0c() - .read() - .actual_rxd_pool_buffer_size() - .bits(), - ), + rx_len: self.slave_rx_len(), tx_len: usize::from( self.regs().i2cc0c().read().tx_data_byte_count().bits() + 1, ), @@ -488,11 +526,7 @@ impl Ast1060I2c<'_> { || sts == constants::AST_I2CS_STOP { // S: (TX_NAK)|P - self.regs().i2cc0c().write(|w| unsafe { - w.rx_pool_buffer_size().bits(constants::I2C_BUF_SIZE - 1) - }); - - cmd |= constants::AST_I2CS_RX_BUFF_EN; + self.arm_slave_receive(&mut cmd); unsafe { self.regs().i2cs28().write(|w| w.bits(cmd)); } diff --git a/tests-hw/src/i2c_core_test.rs b/tests-hw/src/i2c_core_test.rs index dd12e3f..8233a47 100644 --- a/tests-hw/src/i2c_core_test.rs +++ b/tests-hw/src/i2c_core_test.rs @@ -268,6 +268,121 @@ pub fn test_i2c_core_master(uart: &mut UartController<'_>) { writeln!(uart, "####### I2C Core Master Test Complete #######\r\n").ok(); } +/// Test I2C master DMA path (`i2c_core` API) +/// +/// Exercises `Ast1060I2c::new_with_dma` and +/// `Ast1060I2c::from_initialized_with_dma` with payloads larger than +/// the 32-byte FIFO size to ensure the DMA transfer path is selected. +pub fn test_i2c_core_master_dma(uart: &mut UartController<'_>) { + writeln!(uart, "\r\n####### I2C Core Master DMA Test #######\r").ok(); + + let peripherals = unsafe { Peripherals::steal() }; + + let i2c_regs = &peripherals.i2c1; + let buff_regs = &peripherals.i2cbuff1; + + let Some(controller_id) = Controller::new(1) else { + writeln!(uart, "FAIL: Invalid controller ID\r").ok(); + return; + }; + let controller = I2cController { + controller: controller_id, + registers: i2c_regs, + buff_registers: buff_regs, + }; + + let config = I2cConfig { + speed: I2cSpeed::Standard, + xfer_mode: I2cXferMode::DmaMode, + multi_master: true, + smbus_timeout: true, + smbus_alert: false, + clock_config: ClockConfig::ast1060_default(), + }; + + // DMA staging buffer (non-cached placement is required in production, + // but this functional test still validates API/flow and error handling). + let mut dma_buf = [0u8; 4096]; + + let mut i2c = match Ast1060I2c::new_with_dma(&controller, config, &mut dma_buf) { + Ok(i) => { + writeln!(uart, "I2C1 DMA initialized via new_with_dma\r").ok(); + i + } + Err(e) => { + writeln!(uart, "FAIL: I2C1 DMA init error: {e:?}\r").ok(); + return; + } + }; + + // Apply pin control for I2C1 + pinctrl::Pinctrl::apply_pinctrl_group(pinctrl::PINCTRL_I2C1); + + // Test device: ADT7490 at 0x2e (NoAck expected in QEMU) + let addr: u8 = 0x2e; + + // Build payload > 32 bytes to force DMA path instead of FIFO path. + let mut wr_large = [0u8; 40]; + wr_large[0] = 0x4e; // register pointer + for (idx, b) in (1u8..).zip(wr_large.iter_mut().skip(1)) { + *b = idx; + } + + { + writeln!(uart, "Test 1: DMA write (40 bytes) to 0x{addr:02X}...").ok(); + let result = i2c.write(addr, &wr_large); + log_i2c_result(uart, "dma_write_40", &result); + match result { + Ok(()) => writeln!(uart, " PASS: DMA write OK\r").ok(), + Err(I2cError::NoAcknowledge) => { + writeln!(uart, " INFO: NoAck (expected in QEMU - no device)\r").ok() + } + Err(I2cError::Timeout) => writeln!(uart, " INFO: Timeout (expected in QEMU)\r").ok(), + Err(e) => writeln!(uart, " FAIL: DMA write error: {e:?}\r").ok(), + }; + + let mut rd_large = [0u8; 40]; + writeln!(uart, "Test 2: DMA read (40 bytes) from 0x{addr:02X}...").ok(); + let result = i2c.read(addr, &mut rd_large); + log_i2c_result(uart, "dma_read_40", &result); + match result { + Ok(()) => writeln!(uart, " PASS: DMA read OK\r").ok(), + Err(I2cError::NoAcknowledge) => { + writeln!(uart, " INFO: NoAck (expected in QEMU - no device)\r").ok() + } + Err(I2cError::Timeout) => writeln!(uart, " INFO: Timeout (expected in QEMU)\r").ok(), + Err(e) => writeln!(uart, " FAIL: DMA read error: {e:?}\r").ok(), + }; + } + + // Also exercise the lightweight from_initialized_with_dma() API. + let mut i2c_fast = Ast1060I2c::from_initialized_with_dma(&controller, config, &mut dma_buf); + + let reg_addr = [0x4e_u8]; + let mut data = [0u8; 1]; + writeln!( + uart, + "Test 3: DMA write_read via from_initialized_with_dma..." + ) + .ok(); + let result = i2c_fast.write_read(addr, ®_addr, &mut data); + log_i2c_result(uart, "dma_write_read", &result); + match result { + Ok(()) => writeln!(uart, " PASS: DMA write_read OK\r").ok(), + Err(I2cError::NoAcknowledge) => { + writeln!(uart, " INFO: NoAck (expected in QEMU - no device)\r").ok() + } + Err(I2cError::Timeout) => writeln!(uart, " INFO: Timeout (expected in QEMU)\r").ok(), + Err(e) => writeln!(uart, " FAIL: DMA write_read error: {e:?}\r").ok(), + }; + + writeln!( + uart, + "####### I2C Core Master DMA Test Complete #######\r\n" + ) + .ok(); +} + /// Test I2C slave configuration (setup only - actual slave requires external master) pub fn test_i2c_core_slave_config(uart: &mut UartController<'_>) { writeln!(uart, "\r\n####### I2C Core Slave Config Test #######\r").ok(); @@ -371,6 +486,7 @@ pub fn run_i2c_core_tests(uart: &mut UartController<'_>) { test_i2c_core_slave_config(uart); test_i2c_core_init(uart); test_i2c_core_master(uart); + test_i2c_core_master_dma(uart); writeln!(uart, "\r\n========================================\r").ok(); writeln!(uart, " I2C CORE TESTS COMPLETE\r").ok(); From 193fd826a6405af5a32c0f1b90f6fb87f88c04b1 Mon Sep 17 00:00:00 2001 From: "linlin.xu" Date: Mon, 27 Apr 2026 11:05:00 -0700 Subject: [PATCH 3/5] i2c_core: fix slave rx path in dma mode and add test code. --- src/i2c_core/constants.rs | 4 ++++ src/i2c_core/slave.rs | 14 ++++++++++++-- 2 files changed, 16 insertions(+), 2 deletions(-) diff --git a/src/i2c_core/constants.rs b/src/i2c_core/constants.rs index 566ae58..67dd863 100644 --- a/src/i2c_core/constants.rs +++ b/src/i2c_core/constants.rs @@ -137,6 +137,10 @@ pub const AST_I2CM_SMBUS_ALT: u32 = 1 << 12; pub const AST_I2CS_PKT_MODE_EN: u32 = 1 << 16; /// Active on all addresses pub const AST_I2CS_ACTIVE_ALL: u32 = 0x3 << 17; +/// Enable slave RX DMA +pub const AST_I2CS_RX_DMA_EN: u32 = 1 << 9; +/// Enable slave TX DMA +pub const AST_I2CS_TX_DMA_EN: u32 = 1 << 8; /// Enable slave RX buffer pub const AST_I2CS_RX_BUFF_EN: u32 = 1 << 7; /// Enable slave TX buffer diff --git a/src/i2c_core/slave.rs b/src/i2c_core/slave.rs index ae9da4a..0fc83d1 100644 --- a/src/i2c_core/slave.rs +++ b/src/i2c_core/slave.rs @@ -255,7 +255,7 @@ impl Ast1060I2c<'_> { /// Enable slave mode interrupts fn enable_slave_interrupts(&mut self) { let mut mask = constants::AST_I2CS_PKT_DONE | constants::AST_I2CS_INACTIVE_TO; - if self.xfer_mode == I2cXferMode::BufferMode { + if self.xfer_mode == I2cXferMode::BufferMode || self.xfer_mode == I2cXferMode::DmaMode { mask |= constants::AST_I2CM_ABNORMAL | constants::AST_I2CM_NORMAL_STOP | constants::AST_I2CM_RX_DONE @@ -408,6 +408,14 @@ impl Ast1060I2c<'_> { self.regs().i2cs28().write(|w| w.bits(cmd)); } Ok(to_write) + } else if self.xfer_mode == I2cXferMode::DmaMode { + // Trigger slave transmit + let mut cmd = constants::AST_I2CS_ACTIVE_ALL | constants::AST_I2CS_PKT_MODE_EN; + cmd |= constants::AST_I2CS_TX_DMA_EN; + unsafe { + self.regs().i2cs28().write(|w| w.bits(cmd)); + } + Ok(1) } else { // byte mode let cmd = constants::AST_I2CS_ACTIVE_ALL | constants::AST_I2CS_TX_CMD; @@ -462,7 +470,9 @@ impl Ast1060I2c<'_> { unsafe { self.regs().i2cs28().write(|w| w.bits(cmd)); } - return Some(SlaveEvent::WriteRequest); + return Some(SlaveEvent::DataReceived { + len: self.slave_rx_len(), + }); } else if sts == constants::AST_I2CS_SLAVE_MATCH | constants::AST_I2CS_STOP { // S: Sw|P self.arm_slave_receive(&mut cmd); From 1a69149ed3ac6e6a8bf282e4849afe618c58f1f7 Mon Sep 17 00:00:00 2001 From: Will Marone Date: Tue, 28 Apr 2026 12:03:39 -0700 Subject: [PATCH 4/5] fix slave dma tx --- src/i2c_core/global.rs | 10 ++++++++++ src/i2c_core/slave.rs | 34 ++++++++++++++++++++++++++++++++-- 2 files changed, 42 insertions(+), 2 deletions(-) diff --git a/src/i2c_core/global.rs b/src/i2c_core/global.rs index 856c237..32a7072 100644 --- a/src/i2c_core/global.rs +++ b/src/i2c_core/global.rs @@ -58,6 +58,16 @@ pub fn init_i2c_global() { let scu = &*ast1060_pac::Scu::ptr(); let i2cg = &*ast1060_pac::I2cglobal::ptr(); + // CRITICAL: Unlock SCU registers first! + // AST chips have a protection register that must be unlocked + // Write magic value 0x1688A8A8 to SCU000 to unlock + scu.scu000().write(|w| w.bits(0x1688A8A8)); + + // Enable I2C clock by clearing clock stop bit + // SCU084 is Clock Stop Control Clear register for Group 0 + // Bit 2 controls I2C clock + scu.scu084().write(|w| w.bits(1 << 2)); + // Reset I2C/SMBus controller (assert reset) // Reference: ast1060_i2c.rs:327 scu.scu050().write(|w| w.rst_i2csmbus_ctrl().set_bit()); diff --git a/src/i2c_core/slave.rs b/src/i2c_core/slave.rs index 0fc83d1..1787f42 100644 --- a/src/i2c_core/slave.rs +++ b/src/i2c_core/slave.rs @@ -409,13 +409,43 @@ impl Ast1060I2c<'_> { } Ok(to_write) } else if self.xfer_mode == I2cXferMode::DmaMode { - // Trigger slave transmit + // In DMA mode, copy data to DMA buffer and set TX length + let dma_buf = self.dma_buf.as_deref_mut().ok_or(I2cError::Invalid)?; + + // Copy data to DMA buffer starting at offset 0 + let to_write = data.len().min(dma_buf.len()); + unsafe { + core::ptr::copy_nonoverlapping( + data.as_ptr(), + dma_buf.as_mut_ptr(), + to_write, + ); + } + + // Clear TX status/offset register + unsafe { + self.regs().i2cs4c().write(|w| w.bits(0)); + } + + // Set TX length (len - 1) and enable write + let tx_len = u16::try_from(to_write - 1).map_err(|_| I2cError::Invalid)?; + unsafe { + self.regs().i2cs2c().modify(|_, w| { + w.dmatx_buf_len_byte() + .bits(tx_len) + .dmatx_buf_len_wr_enbl_for_cur_cmd() + .set_bit() + }); + } + + // Trigger slave transmit with TX DMA enabled let mut cmd = constants::AST_I2CS_ACTIVE_ALL | constants::AST_I2CS_PKT_MODE_EN; cmd |= constants::AST_I2CS_TX_DMA_EN; unsafe { self.regs().i2cs28().write(|w| w.bits(cmd)); } - Ok(1) + + Ok(to_write) } else { // byte mode let cmd = constants::AST_I2CS_ACTIVE_ALL | constants::AST_I2CS_TX_CMD; From 2edc693b170af56f79052b385f19bf0c8809e14b Mon Sep 17 00:00:00 2001 From: Will Marone Date: Tue, 28 Apr 2026 12:42:59 -0700 Subject: [PATCH 5/5] Add physical tests * master/slave dma tests - 64-byte transfer from master to slave then read back and verify * register dump - Inspect I2C registers --- Cargo.toml | 11 ++ src/i2c_core/global.rs | 4 +- src/i2c_core/slave.rs | 6 +- src/rx-tx-test/i2c_master_dma.rs | 260 ++++++++++++++++++++++++++++ src/rx-tx-test/i2c_slave_dma.rs | 279 +++++++++++++++++++++++++++++++ 5 files changed, 553 insertions(+), 7 deletions(-) create mode 100644 src/rx-tx-test/i2c_master_dma.rs create mode 100644 src/rx-tx-test/i2c_slave_dma.rs diff --git a/Cargo.toml b/Cargo.toml index e44414a..d69b035 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -17,6 +17,7 @@ edition = "2021" [features] default = [] std = [] +arm = [] # Enable ARM-only binaries isr-handlers = [] # Export ISR handlers with #[no_mangle] - disable for kernel integration i2c_master = [] i2c_target = [] @@ -45,3 +46,13 @@ zerocopy = { version = "0.8.25", features = ["derive"] } critical-section = "1.2" cortex-m = { version = "0.7.7", features = ["critical-section-single-core"] } +[[bin]] +name = "i2c_master_dma" +path = "src/rx-tx-test/i2c_master_dma.rs" +required-features = ["arm"] + +[[bin]] +name = "i2c_slave_dma" +path = "src/rx-tx-test/i2c_slave_dma.rs" +required-features = ["arm"] + diff --git a/src/i2c_core/global.rs b/src/i2c_core/global.rs index 32a7072..9ed8371 100644 --- a/src/i2c_core/global.rs +++ b/src/i2c_core/global.rs @@ -60,8 +60,8 @@ pub fn init_i2c_global() { // CRITICAL: Unlock SCU registers first! // AST chips have a protection register that must be unlocked - // Write magic value 0x1688A8A8 to SCU000 to unlock - scu.scu000().write(|w| w.bits(0x1688A8A8)); + // Write magic value 0x1688_A8A8 to SCU000 to unlock + scu.scu000().write(|w| w.bits(0x1688_A8A8)); // Enable I2C clock by clearing clock stop bit // SCU084 is Clock Stop Control Clear register for Group 0 diff --git a/src/i2c_core/slave.rs b/src/i2c_core/slave.rs index 1787f42..780e712 100644 --- a/src/i2c_core/slave.rs +++ b/src/i2c_core/slave.rs @@ -415,11 +415,7 @@ impl Ast1060I2c<'_> { // Copy data to DMA buffer starting at offset 0 let to_write = data.len().min(dma_buf.len()); unsafe { - core::ptr::copy_nonoverlapping( - data.as_ptr(), - dma_buf.as_mut_ptr(), - to_write, - ); + core::ptr::copy_nonoverlapping(data.as_ptr(), dma_buf.as_mut_ptr(), to_write); } // Clear TX status/offset register diff --git a/src/rx-tx-test/i2c_master_dma.rs b/src/rx-tx-test/i2c_master_dma.rs new file mode 100644 index 0000000..1473f82 --- /dev/null +++ b/src/rx-tx-test/i2c_master_dma.rs @@ -0,0 +1,260 @@ +// Licensed under the Apache-2.0 license + +//! I2C Master (Requester) Application with DMA +//! +//! This application demonstrates I2C master mode using DMA to send and receive +//! a 64-byte data chunk to/from an I2C slave device. +//! +//! # Hardware Setup +//! - Uses I2C2 controller +//! - Target slave address: 0x50 +//! - Sends 64 bytes of test data +//! - Receives 64 bytes back from slave + +#![no_std] +#![no_main] + +use aspeed_ddk::common::DmaBuffer; +use aspeed_ddk::i2c_core::{ + self, Ast1060I2c, ClockConfig, Controller, I2cConfig, I2cController, I2cSpeed, I2cXferMode, +}; +use aspeed_ddk::pinctrl; +use aspeed_ddk::uart_core::{UartConfig, UartController}; +use ast1060_pac::{self, Peripherals}; +use core::ptr::{read_volatile, write_volatile}; +use cortex_m_rt::{entry, pre_init}; +use embedded_io::Write; +use panic_halt as _; + +// I2C Configuration +const I2C_MASTER_CTRL_ID: u8 = 2; +const SLAVE_ADDRESS: u8 = 0x50; +const DATA_SIZE: usize = 64; + +// DMA buffer in non-cached RAM section +#[link_section = ".ram_nc"] +static mut DMA_BUF: DmaBuffer<4096> = DmaBuffer::new(); + +#[pre_init] +unsafe fn pre_init() { + let jtag_pinmux_offset: u32 = 0x7e6e_2000 + 0x41c; + let mut reg: u32; + reg = read_volatile(jtag_pinmux_offset as *const u32); + reg |= 0x1f << 25; + write_volatile(jtag_pinmux_offset as *mut u32, reg); + + // Disable Cache + let cache_ctrl_offset: u32 = 0x7e6e_2a58; + write_volatile(cache_ctrl_offset as *mut u32, 0); + + // Configure Cache Area and Invalidation + let cache_area_offset: u32 = 0x7e6e_2a50; + let cache_val = 0x000f_ffff; + write_volatile(cache_area_offset as *mut u32, cache_val); + + let cache_inval_offset: u32 = 0x7e6e_2a54; + let cache_inval_val = 0x81e0_0000; + write_volatile(cache_inval_offset as *mut u32, cache_inval_val); + + // Enable Cache + write_volatile(cache_ctrl_offset as *mut u32, 1); +} + +#[entry] +fn main() -> ! { + // Get UART register block and create controller + let uart_regs = unsafe { &*ast1060_pac::Uart::ptr() }; + let mut uart = UartController::new(uart_regs); + uart.init(&UartConfig::default()).unwrap(); + + // Early startup message + let _ = writeln!(&mut uart, "\r\n\r\n***** PROGRAM STARTED *****\r"); + let _ = writeln!(&mut uart, "\r\n========================================\r"); + let _ = writeln!(&mut uart, "I2C Master DMA Application\r"); + let _ = writeln!(&mut uart, "Version: 1.0\r"); + let _ = writeln!(&mut uart, "========================================\r\n"); + + let peripherals = unsafe { Peripherals::steal() }; + + let _ = writeln!(&mut uart, "[MASTER] Initializing I2C global registers...\r"); + + // Initialize I2C global registers (must be called once before using I2C) + i2c_core::init_i2c_global(); + + let _ = writeln!( + &mut uart, + "[MASTER] Step 1: Applying pin control for I2C2...\r" + ); + + // Apply pin control for I2C2 + pinctrl::Pinctrl::apply_pinctrl_group(pinctrl::PINCTRL_I2C2); + let _ = writeln!(&mut uart, "[MASTER] Step 1: Pin control applied\r"); + + let _ = writeln!(&mut uart, "[MASTER] Step 2: Getting I2C2 registers...\r"); + + unsafe { + // Get I2C2 registers + let i2c_regs = &peripherals.i2c2; + let buff_regs = &peripherals.i2cbuff2; + let _ = writeln!(&mut uart, "[MASTER] Step 2: Registers obtained\r"); + + let _ = writeln!(&mut uart, "[MASTER] Step 3: Creating controller ID...\r"); + let Some(controller_id) = Controller::new(I2C_MASTER_CTRL_ID) else { + let _ = writeln!(&mut uart, "[FAIL] Invalid controller ID\r"); + panic!("Invalid controller ID"); + }; + let _ = writeln!(&mut uart, "[MASTER] Step 3: Controller ID created\r"); + + let _ = writeln!( + &mut uart, + "[MASTER] Step 4: Creating I2C controller wrapper...\r" + ); + let controller = I2cController { + controller: controller_id, + registers: i2c_regs, + buff_registers: buff_regs, + }; + let _ = writeln!(&mut uart, "[MASTER] Step 4: Controller wrapper created\r"); + + // Configure I2C in DMA mode + let _ = writeln!( + &mut uart, + "[MASTER] Step 5: Configuring I2C in DMA mode...\r" + ); + let config = I2cConfig { + speed: I2cSpeed::Standard, + xfer_mode: I2cXferMode::DmaMode, + multi_master: true, + smbus_timeout: true, + smbus_alert: false, + clock_config: ClockConfig::ast1060_default(), + }; + let _ = writeln!(&mut uart, "[MASTER] Step 5: Config created\r"); + + // Get DMA buffer + let _ = writeln!(&mut uart, "[MASTER] Step 6: Getting DMA buffer...\r"); + let dma_buf: &mut [u8] = &mut DMA_BUF.buf; + let _ = writeln!( + &mut uart, + "[MASTER] Step 6: DMA buffer at {:p}, size {}\r", + dma_buf.as_ptr(), + dma_buf.len() + ); + + let _ = writeln!(&mut uart, "[MASTER] Step 7: Calling new_with_dma()...\r"); + let mut i2c = match Ast1060I2c::new_with_dma(&controller, config, dma_buf) { + Ok(i) => { + let _ = writeln!( + &mut uart, + "[MASTER] Step 7: I2C1 DMA initialized successfully ✓\r" + ); + i + } + Err(e) => { + let _ = writeln!(&mut uart, "[FAIL] Step 7: I2C1 DMA init error: {e:?}\r"); + panic!("I2C init failed"); + } + }; + + // Check bus state + let _ = writeln!(&mut uart, "[MASTER] Step 7b: Checking I2C bus state...\r"); + let is_busy = i2c.is_bus_busy(); + let _ = writeln!(&mut uart, "[MASTER] Step 7b: Bus busy = {}\r", is_busy); + if is_busy { + let _ = writeln!( + &mut uart, + "[WARN] I2C bus is busy after init! This may indicate a problem.\r" + ); + } + + // Prepare 64-byte test data + let _ = writeln!(&mut uart, "[MASTER] Step 8: Preparing test data...\r"); + let mut write_data = [0u8; DATA_SIZE]; + for (idx, byte) in write_data.iter_mut().enumerate() { + *byte = (idx as u8).wrapping_add(0x10); + } + let _ = writeln!(&mut uart, "[MASTER] Step 8: Test data prepared\r"); + + let _ = writeln!( + &mut uart, + "[MASTER] Step 9: Sending {} bytes to slave 0x{:02X}...\r", + DATA_SIZE, SLAVE_ADDRESS + ); + let _ = writeln!(&mut uart, " Data to send: {:02X?}...\r", &write_data[..8]); + + // Send data to slave + let _ = writeln!(&mut uart, "[MASTER] Step 9: Calling i2c.write()...\r"); + match i2c.write(SLAVE_ADDRESS, &write_data) { + Ok(()) => { + let _ = writeln!( + &mut uart, + "[MASTER] Step 9: Write completed successfully ✓\r" + ); + } + Err(e) => { + let _ = writeln!(&mut uart, "[FAIL] Step 9: Write error: {e:?}\r"); + let _ = writeln!( + &mut uart, + "[INFO] This may be expected if slave is not ready\r" + ); + // Don't panic, continue to show the issue + } + } + + // Small delay to let slave process + for _ in 0..100_000 { + cortex_m::asm::nop(); + } + + // Read data back from slave + let mut read_data = [0u8; DATA_SIZE]; + let _ = writeln!( + &mut uart, + "[MASTER] Reading {} bytes from slave 0x{:02X}...\r", + DATA_SIZE, SLAVE_ADDRESS + ); + + match i2c.read(SLAVE_ADDRESS, &mut read_data) { + Ok(()) => { + let _ = writeln!(&mut uart, "[MASTER] Read completed successfully\r"); + let _ = writeln!(&mut uart, " Data: {:02X?}...\r", &read_data[..8]); + } + Err(e) => { + let _ = writeln!(&mut uart, "[FAIL] Read error: {e:?}\r"); + panic!("Read failed"); + } + } + + // Verify data + let mut matches = 0; + for (idx, (&sent, &received)) in write_data.iter().zip(read_data.iter()).enumerate() { + if sent == received { + matches += 1; + } else { + let _ = writeln!( + &mut uart, + "[WARN] Mismatch at index {}: sent=0x{:02X}, received=0x{:02X}\r", + idx, sent, received + ); + } + } + + let _ = writeln!( + &mut uart, + "\n[MASTER] Transfer complete: {}/{} bytes matched\r", + matches, DATA_SIZE + ); + let _ = writeln!(&mut uart, "========================================\r\n"); + + if matches == DATA_SIZE { + let _ = writeln!(&mut uart, "[SUCCESS] All data matched!\r"); + } else { + let _ = writeln!(&mut uart, "[FAIL] Data mismatch detected\r"); + } + } + + // Halt + loop { + cortex_m::asm::wfi(); + } +} diff --git a/src/rx-tx-test/i2c_slave_dma.rs b/src/rx-tx-test/i2c_slave_dma.rs new file mode 100644 index 0000000..d0cac1f --- /dev/null +++ b/src/rx-tx-test/i2c_slave_dma.rs @@ -0,0 +1,279 @@ +// Licensed under the Apache-2.0 license + +//! I2C Slave (Receiver) Application with DMA +//! +//! This application demonstrates I2C slave mode using DMA to receive and send +//! a 64-byte data chunk to/from an I2C master device. +//! +//! # Hardware Setup +//! - Uses I2C2 controller +//! - Slave address: 0x50 +//! - Receives 64 bytes from master +//! - Echoes back the same 64 bytes to master + +#![no_std] +#![no_main] + +use aspeed_ddk::common::DmaBuffer; +use aspeed_ddk::i2c_core::{ + self, Ast1060I2c, ClockConfig, Controller, I2cConfig, I2cController, I2cSpeed, I2cXferMode, + SlaveConfig, SlaveEvent, +}; +use aspeed_ddk::pinctrl; +use aspeed_ddk::uart_core::{UartConfig, UartController}; +use ast1060_pac::{self, Peripherals}; +use core::ptr::{read_volatile, write_volatile}; +use cortex_m_rt::{entry, pre_init}; +use embedded_io::Write; +use panic_halt as _; + +// I2C Configuration +const I2C_SLAVE_CTRL_ID: u8 = 2; +const SLAVE_ADDRESS: u8 = 0x50; +const DATA_SIZE: usize = 64; +const MAX_TRANSACTIONS: u32 = 2; + +// DMA buffer in non-cached RAM section +// In DMA mode, ALL data transfers use this buffer! +#[link_section = ".ram_nc"] +static mut DMA_BUF: DmaBuffer<4096> = DmaBuffer::new(); + +#[pre_init] +unsafe fn pre_init() { + let jtag_pinmux_offset: u32 = 0x7e6e_2000 + 0x41c; + let mut reg: u32; + reg = read_volatile(jtag_pinmux_offset as *const u32); + reg |= 0x1f << 25; + write_volatile(jtag_pinmux_offset as *mut u32, reg); + + // Disable Cache + let cache_ctrl_offset: u32 = 0x7e6e_2a58; + write_volatile(cache_ctrl_offset as *mut u32, 0); + + // Configure Cache Area and Invalidation + let cache_area_offset: u32 = 0x7e6e_2a50; + let cache_val = 0x000f_ffff; + write_volatile(cache_area_offset as *mut u32, cache_val); + + let cache_inval_offset: u32 = 0x7e6e_2a54; + let cache_inval_val = 0x81e0_0000; + write_volatile(cache_inval_offset as *mut u32, cache_inval_val); + + // Enable Cache + write_volatile(cache_ctrl_offset as *mut u32, 1); +} + +#[entry] +fn main() -> ! { + let peripherals = unsafe { Peripherals::steal() }; + + // Get UART register block and create controller + let uart_regs = unsafe { &*ast1060_pac::Uart::ptr() }; + let mut uart = UartController::new(uart_regs); + uart.init(&UartConfig::default()).unwrap(); + + // Early startup message + let _ = writeln!(&mut uart, "\r\n\r\n***** PROGRAM STARTED *****\r"); + let _ = writeln!(&mut uart, "\r\n========================================\r"); + let _ = writeln!(&mut uart, "I2C Slave DMA Application\r"); + let _ = writeln!(&mut uart, "Version: 1.0\r"); + let _ = writeln!(&mut uart, "========================================\r\n"); + + let _ = writeln!(&mut uart, "[SLAVE] Initializing I2C global registers...\r"); + + // Initialize I2C global registers (must be called once before using I2C) + i2c_core::init_i2c_global(); + + let _ = writeln!( + &mut uart, + "[SLAVE] Initializing I2C2 in DMA mode at address 0x{:02X}...\r", + SLAVE_ADDRESS + ); + + unsafe { + // Apply pin control for I2C2 + pinctrl::Pinctrl::apply_pinctrl_group(pinctrl::PINCTRL_I2C2); + + // Get I2C2 registers + let i2c_regs = &peripherals.i2c2; + let buff_regs = &peripherals.i2cbuff2; + + let Some(controller_id) = Controller::new(I2C_SLAVE_CTRL_ID) else { + let _ = writeln!(&mut uart, "[FAIL] Invalid controller ID\r"); + panic!("Invalid controller ID"); + }; + + let controller = I2cController { + controller: controller_id, + registers: i2c_regs, + buff_registers: buff_regs, + }; + + // Configure I2C in DMA mode + let config = I2cConfig { + speed: I2cSpeed::Standard, + xfer_mode: I2cXferMode::DmaMode, + multi_master: false, + smbus_timeout: true, + smbus_alert: false, + clock_config: ClockConfig::ast1060_default(), + }; + + // Get DMA buffer + let dma_buf: &mut [u8] = &mut DMA_BUF.buf; + + let mut i2c = match Ast1060I2c::new_with_dma(&controller, config, dma_buf) { + Ok(i) => { + let _ = writeln!( + &mut uart, + "[SLAVE] I2C2 DMA mode initialized successfully\r" + ); + i + } + Err(e) => { + let _ = writeln!(&mut uart, "[FAIL] I2C2 init error: {e:?}\r"); + panic!("I2C init failed"); + } + }; + + // Configure as slave + let slave_cfg = match SlaveConfig::new(SLAVE_ADDRESS) { + Ok(cfg) => cfg, + Err(e) => { + let _ = writeln!(&mut uart, "[FAIL] Invalid slave config: {e:?}\r"); + panic!("Slave config failed"); + } + }; + + if let Err(e) = i2c.configure_slave(&slave_cfg) { + let _ = writeln!(&mut uart, "[FAIL] Configure slave error: {e:?}\r"); + panic!("Configure slave failed"); + } + + let _ = writeln!( + &mut uart, + "[SLAVE] Configured at address 0x{:02X}\r", + SLAVE_ADDRESS + ); + + let _ = writeln!(&mut uart, "[SLAVE] Waiting for master transactions...\r\n"); + + // Event loop + let mut transaction_count = 0u32; + let mut poll_count = 0u32; + + loop { + if let Some(event) = i2c.handle_slave_interrupt() { + match event { + SlaveEvent::WriteRequest => { + let _ = writeln!(&mut uart, "[SLAVE] Write request from master\r"); + } + SlaveEvent::DataReceived { len } => { + let _ = writeln!(&mut uart, "[SLAVE] DataReceived: len={}\r", len); + + // In DMA mode, data is already in DMA_BUF + // Read it to get the actual length and re-arm RX + let mut temp_buf = [0u8; 256]; + if let Ok(n) = i2c.slave_read(&mut temp_buf) { + let _ = writeln!( + &mut uart, + " Received {} bytes (including address): {:02X?}...\r", + n, + &temp_buf[..8.min(n)] + ); + + // Skip the first byte (address byte 0xA0) when echoing + // The hardware saves the address byte due to AST_I2CC_SLAVE_PKT_SAVE_ADDR + if n > 1 { + let data_bytes = &temp_buf[1..n]; + let _ = writeln!( + &mut uart, + " Data bytes to echo: {} ({:02X?}...)\r", + data_bytes.len(), + &data_bytes[..8.min(data_bytes.len())] + ); + + if let Err(e) = i2c.slave_write(data_bytes) { + let _ = + writeln!(&mut uart, "[WARN] Slave write error: {e:?}\r"); + } else { + let _ = writeln!( + &mut uart, + " Prepared to echo {} bytes\r", + data_bytes.len() + ); + } + } + } + transaction_count += 1; + } + SlaveEvent::ReadRequest => { + let _ = writeln!(&mut uart, "[SLAVE] Read request from master\r"); + // Buffer should already be prepared from DataReceived + } + SlaveEvent::DataSent { len } => { + let _ = writeln!(&mut uart, "[SLAVE] Sent {} bytes\r", len); + transaction_count += 1; + } + SlaveEvent::DataReceivedAndSent { rx_len, tx_len } => { + let _ = writeln!( + &mut uart, + "[SLAVE] DataReceivedAndSent: rx_len={}, tx_len={}\r", + rx_len, tx_len + ); + + // This event means the previous slave_write() was used + // tx_len tells us how much was actually sent + let _ = writeln!( + &mut uart, + " Hardware sent {} bytes from previous slave_write\r", + tx_len + ); + + // Don't call slave_write again here - the data was already sent! + // Just log what happened + let _ = writeln!( + &mut uart, + " TX completed: {:02X?}...\r", + &DMA_BUF.buf[..8.min(tx_len)] + ); + + transaction_count += 1; + } + SlaveEvent::Stop => { + let _ = writeln!(&mut uart, "[SLAVE] Stop condition\r"); + } + } + } + + poll_count += 1; + if poll_count.is_multiple_of(1_000_000) { + let _ = writeln!( + &mut uart, + "[SLAVE] Waiting... (transactions: {})\r", + transaction_count + ); + } + + // Exit after some transactions + if transaction_count >= MAX_TRANSACTIONS { + let _ = writeln!( + &mut uart, + "\n[SLAVE] Completed {} transactions\r", + transaction_count + ); + break; + } + } + + i2c.disable_slave(); + let _ = writeln!(&mut uart, "[SLAVE] Slave mode disabled\r"); + let _ = writeln!(&mut uart, "========================================\r\n"); + let _ = writeln!(&mut uart, "[SUCCESS] Slave application completed\r"); + } + + // Halt + loop { + cortex_m::asm::wfi(); + } +}