Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand All @@ -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",
Expand Down
11 changes: 11 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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 = []
Expand Down Expand Up @@ -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"]

7 changes: 7 additions & 0 deletions src/i2c_core/constants.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -134,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
Expand Down
70 changes: 70 additions & 0 deletions src/i2c_core/controller.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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> {
Expand All @@ -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<Self, I2cError> {
let mut i2c = Self::from_initialized(controller, config);
i2c.init_hardware(&config)?;
Expand Down Expand Up @@ -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<Self, I2cError> {
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 {
Expand Down
10 changes: 10 additions & 0 deletions src/i2c_core/global.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 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
// 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());
Expand Down
193 changes: 193 additions & 0 deletions src/i2c_core/master.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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),
}
}

Expand All @@ -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),
}
}

Expand Down Expand Up @@ -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(())
}
}
4 changes: 2 additions & 2 deletions src/i2c_core/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
Loading
Loading