Add i2c peripheral to the ast10x0 peripheral crate
diff --git a/target/ast10x0/peripherals/BUILD.bazel b/target/ast10x0/peripherals/BUILD.bazel index 254e9d4..ee911c5 100644 --- a/target/ast10x0/peripherals/BUILD.bazel +++ b/target/ast10x0/peripherals/BUILD.bazel
@@ -10,6 +10,19 @@ name = "peripherals", srcs = [ "lib.rs", + "i2c/constants.rs", + "i2c/controller.rs", + "i2c/error.rs", + "i2c/global.rs", + "i2c/hal_impl.rs", + "i2c/master.rs", + "i2c/mod.rs", + "i2c/recovery.rs", + "i2c/slave.rs", + "i2c/target_adapter.rs", + "i2c/timing.rs", + "i2c/transfer.rs", + "i2c/types.rs", "scu/mod.rs", "scu/registers.rs", "scu/types.rs", @@ -26,6 +39,7 @@ deps = [ "@ast1060_pac", "@rust_crates//:bitflags", + "@rust_crates//:embedded-hal", "@rust_crates//:embedded-hal-nb", "@rust_crates//:embedded-io", "@rust_crates//:nb",
diff --git a/target/ast10x0/peripherals/i2c/constants.rs b/target/ast10x0/peripherals/i2c/constants.rs new file mode 100644 index 0000000..67dd863 --- /dev/null +++ b/target/ast10x0/peripherals/i2c/constants.rs
@@ -0,0 +1,178 @@ +// Licensed under the Apache-2.0 license + +//! Hardware constants for AST1060 I2C controller +//! +//! # Reference Implementation +//! +//! Constants derived from the original working code: +//! - **aspeed-rust/src/i2c/ast1060_i2c.rs** lines 55-100 +//! +//! # Register Map (New Register Definition Mode) +//! +//! | Offset | Register | Description | +//! |--------|----------|-------------| +//! | 0x00 | I2CC00 | Function Control Register | +//! | 0x04 | I2CC04 | AC Timing Register | +//! | 0x08 | I2CC08 | Byte Buffer / Line Status Register | +//! | 0x0C | I2CC0C | Pool Buffer Control Register | +//! | 0x10 | I2CM10 | Master Interrupt Control Register | +//! | 0x14 | I2CM14 | Master Interrupt Status Register (write-to-clear) | +//! | 0x18 | I2CM18 | Master Command Register | +//! | 0x1C | I2CM1C | Master DMA Length Register | +//! +//! # Critical Register Notes +//! +//! - **i2cm18**: Command register - write all command bits here +//! - **i2cm14**: Status register - read status, write to clear interrupts +//! - **i2cc08**: Byte data register for byte mode (`tx_byte_buffer`, `rx_byte_buffer`) +//! - **i2cc0c**: Buffer size register for buffer mode (`tx_data_byte_count`, `rx_pool_buffer_size`) + +/// HPLL frequency (1 `GHz`) +pub const HPLL_FREQ: u32 = 1_000_000_000; + +/// ASPEED I2C bus clock (100 `MHz` typical) +pub const ASPEED_I2C_BUS_CLK_HZ: u32 = 100_000_000; + +/// Standard mode speed (100 kHz) +pub const I2C_STANDARD_MODE_HZ: u32 = 100_000; + +/// Fast mode speed (400 kHz) +pub const I2C_FAST_MODE_HZ: u32 = 400_000; + +/// Fast-plus mode speed (1 `MHz`) +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; + +/// Default timeout in microseconds +pub const DEFAULT_TIMEOUT_US: u32 = 1_000_000; + +/// Maximum retry attempts for operations +pub const MAX_RETRY_ATTEMPTS: u32 = 3; + +// ============================================================================ +// Register bit definitions +// Reference: aspeed-rust/src/i2c/ast1060_i2c.rs lines 55-100 +// ============================================================================ + +// Function Control Register (I2CC00) +/// Enable slave function +pub const AST_I2CC_SLAVE_EN: u32 = 1 << 1; +/// Enable master function +pub const AST_I2CC_MASTER_EN: u32 = 1 << 0; +/// Disable multi-master capability +pub const AST_I2CC_MULTI_MASTER_DIS: u32 = 1 << 15; +/// Enable save address byte into buffer for Slave Packet mode receive command (I2CC00 bit 20) +/// Per AST1060 datasheet section 13.3.1, when this bit is set, the slave address byte +/// is saved into the receive buffer in slave packet mode. +pub const AST_I2CC_SLAVE_PKT_SAVE_ADDR: u32 = 1 << 20; + +// Master Command Register (I2CM18) bit definitions +// Reference: ast1060_i2c.rs:58-69 +/// Enable packet mode +pub const AST_I2CM_PKT_EN: u32 = 1 << 16; +/// Enable RX DMA mode +pub const AST_I2CM_RX_DMA_EN: u32 = 1 << 9; +/// Enable TX DMA mode +pub const AST_I2CM_TX_DMA_EN: u32 = 1 << 8; +/// Enable RX buffer mode +pub const AST_I2CM_RX_BUFF_EN: u32 = 1 << 7; +/// Enable TX buffer mode +pub const AST_I2CM_TX_BUFF_EN: u32 = 1 << 6; +/// Send STOP condition +pub const AST_I2CM_STOP_CMD: u32 = 1 << 5; +/// RX command with NACK (last byte) +pub const AST_I2CM_RX_CMD_LAST: u32 = 1 << 4; +/// RX command with ACK +pub const AST_I2CM_RX_CMD: u32 = 1 << 3; +/// TX command +pub const AST_I2CM_TX_CMD: u32 = 1 << 1; +/// Send START condition +pub const AST_I2CM_START_CMD: u32 = 1 << 0; + +// Master Interrupt Status Register (I2CM14) bit definitions +// Reference: ast1060_i2c.rs:71-77 +// NOTE: These bits overlap with command bits but have different meanings when read from i2cm14 +/// SCL low timeout +pub const AST_I2CM_SCL_LOW_TO: u32 = 1 << 6; +/// Abnormal STOP condition +pub const AST_I2CM_ABNORMAL: u32 = 1 << 5; +/// Normal STOP condition +pub const AST_I2CM_NORMAL_STOP: u32 = 1 << 4; +/// Arbitration loss +pub const AST_I2CM_ARBIT_LOSS: u32 = 1 << 3; +/// RX transfer done +pub const AST_I2CM_RX_DONE: u32 = 1 << 2; +/// TX received NACK +pub const AST_I2CM_TX_NAK: u32 = 1 << 1; +/// TX received ACK +pub const AST_I2CM_TX_ACK: u32 = 1 << 0; + +// Packet mode status (I2CM14 bits 12-17) +// Reference: ast1060_i2c.rs:86-92 +/// Packet mode error +pub const AST_I2CM_PKT_ERROR: u32 = 1 << 17; +/// Packet mode done +pub const AST_I2CM_PKT_DONE: u32 = 1 << 16; +/// Bus recovery failed +pub const AST_I2CM_BUS_RECOVER_FAIL: u32 = 1 << 15; +/// SDA data line timeout +pub const AST_I2CM_SDA_DL_TO: u32 = 1 << 14; +/// Bus recovery done +pub const AST_I2CM_BUS_RECOVER: u32 = 1 << 13; +/// `SMBus` alert +pub const AST_I2CM_SMBUS_ALT: u32 = 1 << 12; + +// Slave mode constants +// Reference: ast1060_i2c.rs:83-84 and 99-125 +/// Enable slave packet mode +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 +pub const AST_I2CS_TX_BUFF_EN: u32 = 1 << 6; +/// Slave TX command +pub const AST_I2CS_TX_CMD: u32 = 1 << 2; +/// Slave address matched +pub const AST_I2CS_SLAVE_MATCH: u32 = 1 << 7; +/// STOP condition received +pub const AST_I2CS_STOP: u32 = 1 << 4; +/// Slave RX done NACK +pub const AST_I2CS_RX_DONE_NAK: u32 = 1 << 3; +/// Slave RX done +pub const AST_I2CS_RX_DONE: u32 = 1 << 2; +/// Slave TX got NACK +pub const AST_I2CS_TX_NAK: u32 = 1 << 1; +/// Slave TX got ACK +pub const AST_I2CS_TX_ACK: u32 = 1 << 0; +/// Slave inactive timeout +pub const AST_I2CS_INACTIVE_TO: u32 = 1 << 15; +/// Slave packet mode done +pub const AST_I2CS_PKT_DONE: u32 = 1 << 16; +/// Slave packet mode error +pub const AST_I2CS_PKT_ERROR: u32 = 1 << 17; +/// Waiting for TX DMA +pub const AST_I2CS_WAIT_TX_DMA: u32 = 1 << 25; +/// Waiting for RX DMA +pub const AST_I2CS_WAIT_RX_DMA: u32 = 1 << 24; + +/// Helper to build packet mode address field +#[inline] +#[must_use] +pub fn ast_i2cm_pkt_addr(addr: u8) -> u32 { + u32::from(addr & 0x7F) << 24 +}
diff --git a/target/ast10x0/peripherals/i2c/controller.rs b/target/ast10x0/peripherals/i2c/controller.rs new file mode 100644 index 0000000..4a6d48f --- /dev/null +++ b/target/ast10x0/peripherals/i2c/controller.rs
@@ -0,0 +1,288 @@ +// Licensed under the Apache-2.0 license + +//! AST1060 I2C controller implementation + +use core::cell::UnsafeCell; +use core::marker::PhantomData; + +use super::timing::configure_timing; +use super::types::{I2cConfig, I2cXferMode}; +use super::{constants, error::I2cError}; +use ast1060_pac::{i2c::RegisterBlock, i2cbuff::RegisterBlock as BuffRegisterBlock}; + +/// Main I2C hardware abstraction. +/// +/// Wraps a raw `*const RegisterBlock` and `*const BuffRegisterBlock` pair +/// for one AST1060 I2C controller. Mirrors the [`Usart`](super::super::uart::Usart) +/// pattern: the constructor is `unsafe`; ownership/coordination is the +/// caller's responsibility. +/// +/// `Y` is the caller-supplied yield closure. [`wait_completion`] calls +/// it as `(yield_ns)(100_000)` between status polls so the runtime can +/// hand the CPU back to a scheduler instead of busy-looping. For a +/// pure busy-wait, pass `|_| core::hint::spin_loop()`. +#[allow(clippy::struct_excessive_bools)] +pub struct Ast1060I2c<'a, Y: FnMut(u32)> { + regs: *const RegisterBlock, + buff_regs: *const BuffRegisterBlock, + + /// Transfer mode (visible to other modules for optimization decisions) + pub(crate) xfer_mode: I2cXferMode, + multi_master: bool, + smbus_alert: bool, + #[allow(dead_code)] + bus_recover: bool, + + // Transfer state (visible to transfer/master modules) + /// Current device address being communicated with + pub(crate) current_addr: u8, + /// Current transfer length + pub(crate) current_len: u32, + /// Bytes transferred so far + 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]>, + /// Cooperative yield invoked between status polls in + /// [`wait_completion`]. Argument is the suggested wait window in + /// nanoseconds. + pub(crate) yield_ns: Y, + + /// Makes `Ast1060I2c` `!Sync` so the raw register pointers can't be + /// shared across threads without explicit synchronization. + _not_sync: PhantomData<UnsafeCell<()>>, +} + +impl<'a, Y: FnMut(u32)> Ast1060I2c<'a, Y> { + /// Create a new I2C instance and initialize hardware. + /// + /// Performs full hardware init (controller reset, multi-master, + /// timing, interrupts). Use [`from_initialized`] when the bus has + /// already been brought up by application code or a previous + /// `new()` call. + /// + /// # Safety + /// + /// - `regs` and `buff_regs` must be valid, non-null pointers to the + /// AST1060 I2C register block and its companion buffer block for + /// the **same** controller instance. + /// - The pointed register blocks must remain valid for the lifetime + /// of this `Ast1060I2c`. + /// - Caller must enforce global ownership/coordination so concurrent + /// mutable access does not occur through other code paths. + pub unsafe fn new( + regs: *const RegisterBlock, + buff_regs: *const BuffRegisterBlock, + config: &I2cConfig, + yield_ns: Y, + ) -> Result<Self, I2cError> { + let mut i2c = unsafe { Self::from_initialized(regs, buff_regs, config, yield_ns) }; + i2c.init_hardware(&config)?; + Ok(i2c) + } + + /// Create I2C instance from pre-initialized hardware (NO hardware init). + /// + /// Lightweight constructor that wraps register pointers without + /// writing to hardware. Use when: + /// - Hardware was already initialized by app `main.rs` before kernel start + /// - Hardware was initialized by a previous `new()` call + /// - You want to avoid redundant re-initialization overhead + /// + /// # Safety + /// + /// Same contract as [`new`]. Additionally, the caller must ensure + /// hardware is already configured: I2C global registers (I2CG0C, + /// I2CG10) are set (call [`super::global::init_i2c_global`] ONCE in + /// the app before use); controller is enabled (I2CC00); timing is + /// configured; pin mux is configured. + #[must_use] + pub unsafe fn from_initialized( + regs: *const RegisterBlock, + buff_regs: *const BuffRegisterBlock, + config: &I2cConfig, + yield_ns: Y, + ) -> Self { + Self { + regs, + buff_regs, + xfer_mode: config.xfer_mode, + multi_master: config.multi_master, + smbus_alert: config.smbus_alert, + bus_recover: true, + current_addr: 0, + current_len: 0, + current_xfer_cnt: 0, + completion: false, + dma_buf: None, + yield_ns, + _not_sync: PhantomData, + } + } + + /// Create I2C instance with DMA mode support. + /// + /// Like [`new`] but also attaches 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. + /// + /// # Safety + /// + /// Same contract as [`new`]. Additionally `dma_buf` must remain valid + /// for the lifetime of this `Ast1060I2c` and must be in a memory + /// region the DMA engine can address coherently with the CPU. + pub unsafe fn new_with_dma( + regs: *const RegisterBlock, + buff_regs: *const BuffRegisterBlock, + config: &I2cConfig, + dma_buf: &'a mut [u8], + yield_ns: Y, + ) -> Result<Self, I2cError> { + let mut i2c = unsafe { Self::from_initialized(regs, buff_regs, config, yield_ns) }; + 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"]`). + /// + /// # Safety + /// + /// Same contract as [`from_initialized`] and [`new_with_dma`]. + #[must_use] + pub unsafe fn from_initialized_with_dma( + regs: *const RegisterBlock, + buff_regs: *const BuffRegisterBlock, + config: &I2cConfig, + dma_buf: &'a mut [u8], + yield_ns: Y, + ) -> Self { + let mut i2c = unsafe { Self::from_initialized(regs, buff_regs, config, yield_ns) }; + i2c.dma_buf = Some(dma_buf); + i2c + } + + /// Get access to registers (visible to other modules) + #[inline] + pub(crate) fn regs(&self) -> &RegisterBlock { + // SAFETY: `Ast1060I2c` construction is `unsafe`, so the caller + // upholds pointer validity, non-nullness, and ownership. + unsafe { &*self.regs } + } + + /// Get access to buffer registers (visible to other modules) + #[inline] + pub(crate) fn buff_regs(&self) -> &BuffRegisterBlock { + // SAFETY: see `regs`. + unsafe { &*self.buff_regs } + } + + /// Initialize hardware + #[inline(never)] + pub fn init_hardware(&mut self, config: &I2cConfig) -> Result<(), I2cError> { + // PRECONDITION: I2C global registers must be initialized by app before use. + // See: super::global::init_i2c_global(). + + // Reset I2C controller + unsafe { + self.regs().i2cc00().write(|w| w.bits(0)); + } + + // Configure multi-master + if !self.multi_master { + self.regs() + .i2cc00() + .modify(|_, w| w.dis_multimaster_capability_for_master_fn_only().set_bit()); + } + + // Enable master function and bus auto-release + self.regs().i2cc00().modify(|_, w| { + w.enbl_bus_autorelease_when_scllow_sdalow_or_slave_mode_inactive_timeout() + .set_bit() + .enbl_master_fn() + .set_bit() + }); + + // Configure timing + configure_timing(self.regs(), config)?; + + // Clear all interrupts + unsafe { + self.regs().i2cm14().write(|w| w.bits(0xffff_ffff)); + } + + // Enable interrupts for packet mode + self.regs().i2cm10().modify(|_, w| { + w.enbl_pkt_cmd_done_int() + .set_bit() + .enbl_bus_recover_done_int() + .set_bit() + }); + + if self.smbus_alert { + self.regs() + .i2cm10() + .modify(|_, w| w.enbl_smbus_dev_alert_int().set_bit()); + } + + Ok(()) + } + + /// Enable interrupts + pub fn enable_interrupts(&mut self, mask: u32) { + unsafe { + self.regs().i2cm10().write(|w| w.bits(mask)); + } + } + + /// Clear interrupts + pub fn clear_interrupts(&mut self, mask: u32) { + unsafe { + self.regs().i2cm14().write(|w| w.bits(mask)); + } + } + + /// Check if bus is busy + /// + /// Checks if any I2C transfer is currently active by examining status register bits. + #[must_use] + pub fn is_bus_busy(&self) -> bool { + let status = self.regs().i2cm14().read().bits(); + // Bus is busy if any transfer command bits are set + (status + & (constants::AST_I2CM_TX_CMD + | constants::AST_I2CM_RX_CMD + | constants::AST_I2CM_START_CMD)) + != 0 + } + + /// Wait for completion with timeout (visible to master/transfer modules). + /// Calls the caller-supplied `yield_ns` closure between status polls. + pub(crate) fn wait_completion(&mut self, timeout_us: u32) -> Result<(), I2cError> { + let mut timeout = timeout_us; + self.completion = false; + + while timeout > 0 && !self.completion { + self.handle_interrupt()?; + timeout = timeout.saturating_sub(1); + (self.yield_ns)(100_000); + } + + if self.completion { + Ok(()) + } else { + Err(I2cError::Timeout) + } + } +} +
diff --git a/target/ast10x0/peripherals/i2c/error.rs b/target/ast10x0/peripherals/i2c/error.rs new file mode 100644 index 0000000..304f921 --- /dev/null +++ b/target/ast10x0/peripherals/i2c/error.rs
@@ -0,0 +1,30 @@ +// Licensed under the Apache-2.0 license + +//! Core error types without OS dependencies + +/// I2C error type +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +pub enum I2cError { + /// Data overrun + Overrun, + /// No acknowledge from device + NoAcknowledge, + /// Operation timeout + Timeout, + /// Bus recovery failed + BusRecoveryFailed, + /// Bus error + Bus, + /// Bus busy + Busy, + /// Invalid parameter + Invalid, + /// Abnormal condition + Abnormal, + /// Arbitration loss (multi-master) + ArbitrationLoss, + /// Slave mode error + SlaveError, + /// Invalid address + InvalidAddress, +}
diff --git a/target/ast10x0/peripherals/i2c/global.rs b/target/ast10x0/peripherals/i2c/global.rs new file mode 100644 index 0000000..3b0e0d5 --- /dev/null +++ b/target/ast10x0/peripherals/i2c/global.rs
@@ -0,0 +1,79 @@ +// Licensed under the Apache-2.0 license + +//! I2C global hardware initialization utility +//! +//! Configures I2C global registers (i2cg0c, i2cg10). +//! This must be called once after SCU has been configured by the board layer. +//! +//! # Initialization Order +//! +//! The board layer must call init functions in this order: +//! 1. Enable I2C clock via SCU +//! 2. Assert I2C reset via SCU +//! 3. Delay +//! 4. Deassert I2C reset via SCU +//! 5. Delay +//! 6. Call `init_i2c_global()` to configure I2C registers +//! +//! Example in board crate: +//! ```ignore +//! let scu = unsafe { ScuRegisters::new_global() }; +//! scu.ungate_clock_mask(ClockRegisterHalf::Lower, 1 << 2); +//! scu.assert_reset_mask(ScuRegisterHalf::Upper, 1 << 2); +//! delay_us(1000); +//! scu.deassert_reset_mask(ScuRegisterHalf::Upper, 1 << 2); +//! delay_us(1000); +//! unsafe { i2c::init_i2c_global() }; +//! ``` +//! +//! # Register Details +//! +//! ## I2CG0C - I2C Global Control Register +//! - `clk_divider_mode_sel`: Enable new clock divider mode +//! - `reg_definition_sel`: Select new register definition +//! - `select_the_action_when_slave_pkt_mode_rxbuf_empty`: RX buffer empty action +//! +//! ## I2CG10 - I2C Global Clock Divider Register +//! Value: `0x6222_0803` +//! - Bits [31:24] = 0x62: Base clk4 for auto recovery timeout +//! - Bits [23:16] = 0x22: Base clk3 for Standard-mode (100kHz), tBuf=5.76us +//! - Bits [15:8] = 0x08: Base clk2 for Fast-mode (400kHz), tBuf=1.6us +//! - Bits [7:0] = 0x03: Base clk1 for Fast-mode Plus (1MHz), tBuf=0.8us +//! +//! Based on APB clock = 50MHz: +//! ```text +//! div : scl : baseclk [APB/((div/2) + 1)] : tBuf [1/bclk * 16] +//! 0x03 : 1MHz : 20MHz : 0.8us (Fast-mode Plus) +//! 0x08 : 400kHz : 10MHz : 1.6us (Fast-mode) +//! 0x22 : 99.21kHz : 2.77MHz : 5.76us (Standard-mode) +//! ``` + +use ast1060_pac; + +/// Configure I2C global registers (one-time init, after SCU setup). +/// +/// # Safety +/// - Must be called only once, after SCU has enabled I2C clock and deasserted reset. +/// - Not thread-safe; caller must ensure single invocation. +/// - Assumes I2C controller clock is already enabled by the board layer. +pub unsafe fn init_i2c_global() { + let i2cg = unsafe { &*ast1060_pac::I2cglobal::ptr() }; + + // Configure global settings + i2cg.i2cg0c().write(|w| { + w.clk_divider_mode_sel() + .set_bit() + .reg_definition_sel() + .set_bit() + .select_the_action_when_slave_pkt_mode_rxbuf_empty() + .set_bit() + }); + + // Set base clock dividers for different speeds + // APB clk: 50MHz + // I2CG10[31:24] = 0x62: base clk4 for i2c auto recovery timeout counter + // I2CG10[23:16] = 0x22: base clk3 for Standard-mode (100kHz) min tBuf 4.7us + // I2CG10[15:8] = 0x08: base clk2 for Fast-mode (400kHz) min tBuf 1.3us + // I2CG10[7:0] = 0x03: base clk1 for Fast-mode Plus (1MHz) min tBuf 0.5us + unsafe { i2cg.i2cg10().write(|w| w.bits(0x6222_0803)) }; +}
diff --git a/target/ast10x0/peripherals/i2c/hal_impl.rs b/target/ast10x0/peripherals/i2c/hal_impl.rs new file mode 100644 index 0000000..604bcd2 --- /dev/null +++ b/target/ast10x0/peripherals/i2c/hal_impl.rs
@@ -0,0 +1,269 @@ +// Licensed under the Apache-2.0 license + +//! embedded-hal trait implementations for AST1060 I2C driver +//! +//! This module provides [embedded-hal](https://docs.rs/embedded-hal) trait implementations +//! for the AST1060 I2C controller, enabling compatibility with the broader Rust embedded +//! ecosystem. +//! +//! # Implementation Status +//! +//! Currently implements: +//! - [`embedded_hal::i2c::I2c`] - Master mode I2C operations +//! - [`embedded_hal::i2c::Error`] - Standard error type mapping +//! +//! This allows the AST1060 I2C driver to be used with any crate that accepts embedded-hal +//! I2C traits, such as device drivers, protocols, and middleware. +//! +//! # Hardware Features +//! +//! The AST1060 I2C controller supports: +//! - **14 independent I2C controllers** +//! - **Buffer mode with 32-byte FIFO** for efficient transfers +//! - **Multi-master capability** with arbitration +//! - **Standard (100 kHz) and Fast (400 kHz) modes** +//! - **Slave/target mode** with hardware packet mode and interrupts +//! - **`SMBus` protocol support** +//! - **Automatic bus recovery** +//! +//! # Slave Mode API +//! +//! Slave functionality is provided through hardware-specific methods: +//! - [`configure_slave()`](super::controller::Ast1060I2c::configure_slave) - Configure slave address and mode +//! - [`slave_read()`](super::controller::Ast1060I2c::slave_read) - Read data received from master +//! - [`slave_write()`](super::controller::Ast1060I2c::slave_write) - Write response data for master +//! - [`handle_slave_interrupt()`](super::controller::Ast1060I2c::handle_slave_interrupt) - Process slave events +//! +//! These methods are more ergonomic and hardware-aware than generic HAL traits, +//! exposing AST1060-specific features like packet mode and structured interrupt events. +//! +//! # Integration +//! +//! For Hubris RTOS integration, see the `drv-ast1060-i2c-refactor` crate which +//! wraps this driver to implement the `drv-i2c-types::I2cHardware` trait. +//! +//! # Example Usage +//! +//! ```no_run +//! use embedded_hal::i2c::I2c; +//! use aspeed_ddk::i2c_core::controller::Ast1060I2c; +//! use aspeed_ddk::i2c_core::types::{I2cConfig, I2cSpeed}; +//! +//! // Initialize I2C controller 0 at 400 kHz +//! let config = I2cConfig::new(I2cSpeed::Fast); +//! let mut i2c = unsafe { Ast1060I2c::new(0) }; +//! i2c.init_hardware(&config); +//! +//! // Use embedded-hal I2c trait methods +//! i2c.write(0x50, &[0x10, 0x20])?; +//! let mut buffer = [0u8; 4]; +//! i2c.read(0x51, &mut buffer)?; +//! i2c.write_read(0x52, &[0x00], &mut buffer)?; +//! # Ok::<(), aspeed_ddk::i2c_core::error::I2cError>(()) +//! ``` + +use super::controller::Ast1060I2c; +use super::error::I2cError; +use embedded_hal::i2c::{ + Error, ErrorKind, ErrorType, I2c, NoAcknowledgeSource, Operation, SevenBitAddress, +}; + +// ============================================================================ +// embedded-hal Error Trait Implementation +// ============================================================================ + +/// Implements the embedded-hal Error trait for `I2cError`. +/// +/// Maps AST1060-specific I2C errors to standard embedded-hal `ErrorKind` values. +/// This enables interoperability with any code using the embedded-hal I2C traits. +/// +/// # Error Mappings +/// +/// - `NoAcknowledge` → `ErrorKind::NoAcknowledge` - Slave did not acknowledge +/// - `ArbitrationLoss` → `ErrorKind::ArbitrationLoss` - Lost bus arbitration +/// - `Bus`, `BusRecoveryFailed` → `ErrorKind::Bus` - Bus errors +/// - `Overrun` → `ErrorKind::Overrun` - Data overrun/underrun +/// - All others → `ErrorKind::Other` - Hardware-specific errors +impl Error for I2cError { + fn kind(&self) -> ErrorKind { + match self { + I2cError::NoAcknowledge => ErrorKind::NoAcknowledge(NoAcknowledgeSource::Unknown), + I2cError::ArbitrationLoss => ErrorKind::ArbitrationLoss, + I2cError::Bus | I2cError::BusRecoveryFailed => ErrorKind::Bus, + I2cError::Overrun => ErrorKind::Overrun, + I2cError::Timeout + | I2cError::Busy + | I2cError::Invalid + | I2cError::Abnormal + | I2cError::SlaveError + | I2cError::InvalidAddress => ErrorKind::Other, + } + } +} + +/// Associates the `I2cError` type with `Ast1060I2c` for embedded-hal trait implementations. +impl<Y: FnMut(u32)> ErrorType for Ast1060I2c<'_, Y> { + type Error = I2cError; +} + +// ============================================================================ +// embedded-hal I2c Trait Implementation (Master Mode) +// ============================================================================ + +/// Implements the embedded-hal I2c trait for `Ast1060I2c`. +/// +/// Provides standard I2C master mode operations compatible with the embedded-hal ecosystem. +/// This implementation uses the AST1060's buffer mode with a 32-byte FIFO for efficient +/// data transfer. +/// +/// # Supported Operations +/// +/// - **write**: Send data to a slave device +/// - **read**: Receive data from a slave device +/// - **`write_read`**: Write then read without releasing the bus (combined transaction) +/// - **transaction**: Execute a sequence of read/write operations +/// +/// # Buffer Mode Limitations +/// +/// The AST1060 hardware uses a 32-byte FIFO buffer. For transfers larger than 32 bytes, +/// the driver automatically splits them into multiple hardware transactions while +/// maintaining the logical transaction semantics. +/// +/// # Example +/// +/// ```no_run +/// use embedded_hal::i2c::I2c; +/// # use aspeed_ddk::i2c_core::controller::Ast1060I2c; +/// # let mut i2c = unsafe { Ast1060I2c::new(0) }; +/// +/// // Write data to slave at address 0x50 +/// i2c.write(0x50, &[0x00, 0x01, 0x02])?; +/// +/// // Read data from slave at address 0x51 +/// let mut buffer = [0u8; 4]; +/// i2c.read(0x51, &mut buffer)?; +/// +/// // Combined write-read transaction +/// i2c.write_read(0x52, &[0xA0], &mut buffer)?; +/// # Ok::<(), aspeed_ddk::i2c_core::error::I2cError>(()) +/// ``` +impl<Y: FnMut(u32)> I2c<SevenBitAddress> for Ast1060I2c<'_, Y> { + /// Writes data to an I2C slave device. + /// + /// # Arguments + /// + /// * `address` - 7-bit I2C slave address + /// * `bytes` - Data bytes to write + /// + /// # Errors + /// + /// Returns an error if: + /// - The slave does not acknowledge (`NoAcknowledge`) + /// - Bus arbitration is lost (`ArbitrationLoss`) + /// - A bus error occurs (`Bus`) + /// - The operation times out (`Timeout`) + fn write(&mut self, address: SevenBitAddress, bytes: &[u8]) -> Result<(), Self::Error> { + Ast1060I2c::write(self, address, bytes) + } + + /// Reads data from an I2C slave device. + /// + /// # Arguments + /// + /// * `address` - 7-bit I2C slave address + /// * `buffer` - Buffer to store received data + /// + /// # Errors + /// + /// Returns an error if: + /// - The slave does not acknowledge (`NoAcknowledge`) + /// - Bus arbitration is lost (`ArbitrationLoss`) + /// - A bus error occurs (`Bus`) + /// - The operation times out (`Timeout`) + fn read(&mut self, address: SevenBitAddress, buffer: &mut [u8]) -> Result<(), Self::Error> { + Ast1060I2c::read(self, address, buffer) + } + + /// Performs a combined write-read transaction without releasing the bus. + /// + /// This is commonly used to write a register address followed by reading + /// the register value in a single atomic transaction. + /// + /// # Arguments + /// + /// * `address` - 7-bit I2C slave address + /// * `bytes` - Data bytes to write first + /// * `buffer` - Buffer to store read data + /// + /// # Errors + /// + /// Returns an error if: + /// - The slave does not acknowledge (`NoAcknowledge`) + /// - Bus arbitration is lost (`ArbitrationLoss`) + /// - A bus error occurs (`Bus`) + /// - The operation times out (`Timeout`) + /// + /// # Example + /// + /// ```no_run + /// # use embedded_hal::i2c::I2c; + /// # use aspeed_ddk::i2c_core::controller::Ast1060I2c; + /// # let mut i2c = unsafe { Ast1060I2c::new(0) }; + /// // Read from register 0x10 at slave address 0x50 + /// let mut data = [0u8; 4]; + /// i2c.write_read(0x50, &[0x10], &mut data)?; + /// # Ok::<(), aspeed_ddk::i2c_core::error::I2cError>(()) + /// ``` + fn write_read( + &mut self, + address: SevenBitAddress, + bytes: &[u8], + buffer: &mut [u8], + ) -> Result<(), Self::Error> { + Ast1060I2c::write_read(self, address, bytes, buffer) + } + + /// Executes a sequence of I2C operations as a single transaction. + /// + /// Each operation is executed in order. The bus is held between operations + /// and only released after all operations complete or an error occurs. + /// + /// # Arguments + /// + /// * `address` - 7-bit I2C slave address for all operations + /// * `operations` - Slice of read/write operations to execute + /// + /// # Errors + /// + /// Returns an error if any operation fails. Subsequent operations are not + /// executed if an error occurs. + /// + /// # Example + /// + /// ```no_run + /// # use embedded_hal::i2c::{I2c, Operation}; + /// # use aspeed_ddk::i2c_core::controller::Ast1060I2c; + /// # let mut i2c = unsafe { Ast1060I2c::new(0) }; + /// let mut read_buf = [0u8; 4]; + /// let mut ops = [ + /// Operation::Write(&[0x10]), // Write register address + /// Operation::Read(&mut read_buf), // Read register value + /// ]; + /// i2c.transaction(0x50, &mut ops)?; + /// # Ok::<(), aspeed_ddk::i2c_core::error::I2cError>(()) + /// ``` + fn transaction( + &mut self, + address: SevenBitAddress, + operations: &mut [Operation<'_>], + ) -> Result<(), Self::Error> { + // Execute each operation in sequence + for op in operations { + match op { + Operation::Read(buf) => self.read(address, buf)?, + Operation::Write(data) => self.write(address, data)?, + } + } + Ok(()) + } +}
diff --git a/target/ast10x0/peripherals/i2c/master.rs b/target/ast10x0/peripherals/i2c/master.rs new file mode 100644 index 0000000..d473981 --- /dev/null +++ b/target/ast10x0/peripherals/i2c/master.rs
@@ -0,0 +1,598 @@ +// Licensed under the Apache-2.0 license + +//! Master mode operations +//! +//! # Reference Implementation +//! +//! This follows the transfer logic from the original working code: +//! - **aspeed-rust/src/i2c/ast1060_i2c.rs** lines 960-1120 +//! - `aspeed_i2c_read()` - RX command building for byte/buffer/DMA modes +//! - `aspeed_i2c_write()` - TX command building for byte/buffer/DMA modes +//! - `i2c_aspeed_transfer()` - Main transfer entry point +//! +//! # Key Register Usage +//! +//! - **i2cm18** (Master Command Register): All command bits written here +//! - Command: `PKT_EN | pkt_addr(addr) | START_CMD | TX/RX_CMD | BUFF_EN | STOP_CMD` +//! - Reference: `ast1060_i2c.rs:1024` and `ast1060_i2c.rs:1107` +//! +//! - **i2cc08** (Byte Buffer Register): TX/RX byte data for byte mode +//! - `tx_byte_buffer()`: Write byte to transmit (`ast1060_i2c.rs:1101`) +//! - `rx_byte_buffer()`: Read received byte (`ast1060_i2c.rs:790`) +//! +//! - **i2cc0c** (Buffer Size Register): Buffer sizes for buffer mode +//! - `tx_data_byte_count()`: Set TX count (`ast1060_i2c.rs:1089`) +//! - `rx_pool_buffer_size()`: Set RX size (`ast1060_i2c.rs:1011`) +//! +//! - **i2cm14** (Interrupt Status Register): Read status, write-to-clear +//! - Reference: `ast1060_i2c.rs:849-870` (`aspeed_i2c_master_irq`) + +use super::{constants, controller::Ast1060I2c, error::I2cError, types::I2cXferMode}; + +impl<Y: FnMut(u32)> Ast1060I2c<'_, Y> { + /// Write bytes to an I2C device + pub fn write(&mut self, addr: u8, bytes: &[u8]) -> Result<(), I2cError> { + if bytes.is_empty() { + return Ok(()); + } + + 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), + } + } + + /// Read bytes from an I2C device + pub fn read(&mut self, addr: u8, buffer: &mut [u8]) -> Result<(), I2cError> { + if buffer.is_empty() { + return Ok(()); + } + + 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), + } + } + + /// Write then read (combined transaction) + pub fn write_read( + &mut self, + addr: u8, + bytes: &[u8], + buffer: &mut [u8], + ) -> Result<(), I2cError> { + // Write phase + self.write(addr, bytes)?; + + // Read phase + self.read(addr, buffer)?; + + Ok(()) + } + + /// Write in byte mode (for small transfers) + /// + /// Uses i2cc08 for TX byte data buffer, i2cm18 for commands. + /// Only sends START on first byte, STOP on last byte. + fn write_byte_mode(&mut self, addr: u8, bytes: &[u8]) -> Result<(), I2cError> { + let msg_len = bytes.len(); + + // Initialize transfer state + self.current_addr = addr; + #[allow(clippy::cast_possible_truncation)] + { + self.current_len = msg_len as u32; + } + self.current_xfer_cnt = 0; + self.completion = false; + + // Clear any previous status + self.clear_interrupts(0xffff_ffff); + + for (i, &byte) in bytes.iter().enumerate() { + let is_first = i == 0; + let is_last = i == msg_len - 1; + + // Write data byte to TX byte buffer (i2cc08) + self.regs() + .i2cc08() + .modify(|_, w| unsafe { w.tx_byte_buffer().bits(byte) }); + + // Build command + let mut cmd = constants::AST_I2CM_PKT_EN | constants::AST_I2CM_TX_CMD; + + // Only send START and address on first byte + if is_first { + cmd |= constants::ast_i2cm_pkt_addr(addr) | constants::AST_I2CM_START_CMD; + } + + // Send STOP on last byte + if is_last { + cmd |= constants::AST_I2CM_STOP_CMD; + } + + // Issue command to i2cm18 + self.regs().i2cm18().write(|w| unsafe { w.bits(cmd) }); + + // Wait for completion + self.completion = false; + self.wait_completion(constants::DEFAULT_TIMEOUT_US)?; + + // Check for errors (read from i2cm14 - interrupt status register) + let status = self.regs().i2cm14().read().bits(); + if status & constants::AST_I2CM_TX_NAK != 0 { + return Err(I2cError::NoAcknowledge); + } + + self.current_xfer_cnt += 1; + } + + Ok(()) + } + + /// Read in byte mode + /// + /// Uses i2cc08 for RX byte data buffer, i2cm18 for commands. + /// Only sends START on first byte, NACK+STOP on last byte. + fn read_byte_mode(&mut self, addr: u8, buffer: &mut [u8]) -> Result<(), I2cError> { + let msg_len = buffer.len(); + + // Initialize transfer state + self.current_addr = addr; + #[allow(clippy::cast_possible_truncation)] + { + self.current_len = msg_len as u32; + } + self.current_xfer_cnt = 0; + self.completion = false; + + // Clear any previous status + self.clear_interrupts(0xffff_ffff); + + for (i, byte) in buffer.iter_mut().enumerate() { + let is_first = i == 0; + let is_last = i == msg_len - 1; + + // Build command + let mut cmd = constants::AST_I2CM_PKT_EN | constants::AST_I2CM_RX_CMD; + + // Only send START and address on first byte + if is_first { + cmd |= constants::ast_i2cm_pkt_addr(addr) | constants::AST_I2CM_START_CMD; + } + + // Send NACK and STOP on last byte + if is_last { + cmd |= constants::AST_I2CM_RX_CMD_LAST | constants::AST_I2CM_STOP_CMD; + } + + // Issue command to i2cm18 + self.regs().i2cm18().write(|w| unsafe { w.bits(cmd) }); + + // Wait for completion + self.completion = false; + self.wait_completion(constants::DEFAULT_TIMEOUT_US)?; + + // Read data from RX byte buffer (i2cc08) + *byte = self.regs().i2cc08().read().rx_byte_buffer().bits(); + + // Check status (read from i2cm14 - interrupt status register) + let status = self.regs().i2cm14().read().bits(); + if status & constants::AST_I2CM_TX_NAK != 0 { + return Err(I2cError::NoAcknowledge); + } + + self.current_xfer_cnt += 1; + } + + Ok(()) + } + + /// Write in buffer mode (optimal for 2-32 bytes) + /// + /// Uses hardware buffer for efficient multi-byte transfers. + /// Single transaction model: START+addr on first chunk only, + /// subsequent chunks continue the transaction without re-addressing. + /// Reference: `ast1060_i2c.rs` `do_i2cm_tx()` continuation logic + fn write_buffer_mode(&mut self, addr: u8, bytes: &[u8]) -> Result<(), I2cError> { + let total_len = bytes.len(); + let mut offset = 0; + + // Initialize transfer state + 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::BUFFER_MODE_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 data to hardware buffer BEFORE issuing command + self.copy_to_buffer(chunk)?; + + // Set TX byte count in i2cc0c (len - 1) + #[allow(clippy::cast_possible_truncation)] + self.regs() + .i2cc0c() + .modify(|_, w| unsafe { w.tx_data_byte_count().bits((chunk_len - 1) as u8) }); + + // Clear interrupts before command + self.clear_interrupts(0xffff_ffff); + self.completion = false; + + // Build command based on chunk position + // First chunk: PKT_EN + addr + START + TX_CMD + TX_BUFF_EN + // Subsequent chunks: PKT_EN + TX_CMD + TX_BUFF_EN (NO START, NO addr) + let mut cmd = constants::AST_I2CM_PKT_EN + | constants::AST_I2CM_TX_CMD + | constants::AST_I2CM_TX_BUFF_EN; + + // Only send START and address on first chunk + if is_first { + cmd |= constants::ast_i2cm_pkt_addr(addr) | constants::AST_I2CM_START_CMD; + } + + // Add STOP on last chunk + if is_last { + cmd |= constants::AST_I2CM_STOP_CMD; + } + + // Issue command to i2cm18 + self.regs().i2cm18().write(|w| unsafe { w.bits(cmd) }); + + // Wait for completion + self.wait_completion(constants::DEFAULT_TIMEOUT_US)?; + + // Check for errors + 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 buffer mode + /// + /// Uses hardware buffer for efficient multi-byte transfers. + /// Single transaction model: START+addr on first chunk only, + /// subsequent chunks continue the transaction without re-addressing. + /// Reference: `ast1060_i2c.rs` `do_i2cm_rx()` lines 762-810 + fn read_buffer_mode(&mut self, addr: u8, buffer: &mut [u8]) -> Result<(), I2cError> { + let total_len = buffer.len(); + let mut offset = 0; + + // Initialize transfer state + 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::BUFFER_MODE_SIZE, total_len - offset); + let is_first = offset == 0; + let is_last = offset + chunk_len >= total_len; + + // Set RX buffer size in i2cc0c (len - 1) + #[allow(clippy::cast_possible_truncation)] + self.regs() + .i2cc0c() + .modify(|_, w| unsafe { w.rx_pool_buffer_size().bits((chunk_len - 1) as u8) }); + + // Clear interrupts before command + self.clear_interrupts(0xffff_ffff); + self.completion = false; + + // Build command based on chunk position + // First chunk: PKT_EN + addr + START + RX_CMD + RX_BUFF_EN + // Subsequent chunks: PKT_EN + RX_CMD + RX_BUFF_EN (NO START, NO addr) + let mut cmd = constants::AST_I2CM_PKT_EN + | constants::AST_I2CM_RX_CMD + | constants::AST_I2CM_RX_BUFF_EN; + + // Only send START and address on first chunk + if is_first { + cmd |= constants::ast_i2cm_pkt_addr(addr) | constants::AST_I2CM_START_CMD; + } + + // Add NACK and STOP on last chunk + if is_last { + cmd |= constants::AST_I2CM_RX_CMD_LAST | constants::AST_I2CM_STOP_CMD; + } + + // Issue command to i2cm18 + self.regs().i2cm18().write(|w| unsafe { w.bits(cmd) }); + + // Wait for completion + self.wait_completion(constants::DEFAULT_TIMEOUT_US)?; + + // Check for errors + 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 hardware buffer AFTER successful transfer + let chunk = &mut buffer[offset..offset + chunk_len]; + self.copy_from_buffer(chunk)?; + + #[allow(clippy::cast_possible_truncation)] + { + self.current_xfer_cnt += chunk_len as u32; + } + offset += chunk_len; + } + + Ok(()) + } + + /// Handle interrupt (process completion status) + pub fn handle_interrupt(&mut self) -> Result<(), I2cError> { + let status = self.regs().i2cm14().read().bits(); + + // Check for packet mode completion + if status & constants::AST_I2CM_PKT_DONE != 0 { + self.completion = true; + self.clear_interrupts(constants::AST_I2CM_PKT_DONE); + + // Check for errors + if status & constants::AST_I2CM_PKT_ERROR != 0 { + if status & constants::AST_I2CM_TX_NAK != 0 { + return Err(I2cError::NoAcknowledge); + } + if status & constants::AST_I2CM_ARBIT_LOSS != 0 { + return Err(I2cError::ArbitrationLoss); + } + if status & constants::AST_I2CM_ABNORMAL != 0 { + return Err(I2cError::Abnormal); + } + return Err(I2cError::Bus); + } + + return Ok(()); + } + + // Check for byte mode completion + if status & (constants::AST_I2CM_TX_ACK | constants::AST_I2CM_RX_DONE) != 0 { + self.completion = true; + self.clear_interrupts(status); + return Ok(()); + } + + // Check for errors + if status & constants::AST_I2CM_TX_NAK != 0 { + self.clear_interrupts(status); + return Err(I2cError::NoAcknowledge); + } + + if status & constants::AST_I2CM_ABNORMAL != 0 { + self.clear_interrupts(status); + return Err(I2cError::Abnormal); + } + + if status & constants::AST_I2CM_ARBIT_LOSS != 0 { + self.clear_interrupts(status); + return Err(I2cError::ArbitrationLoss); + } + + if status & constants::AST_I2CM_SCL_LOW_TO != 0 { + self.clear_interrupts(status); + return Err(I2cError::Timeout); + } + + 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/target/ast10x0/peripherals/i2c/mod.rs b/target/ast10x0/peripherals/i2c/mod.rs new file mode 100644 index 0000000..583f3d6 --- /dev/null +++ b/target/ast10x0/peripherals/i2c/mod.rs
@@ -0,0 +1,81 @@ +// Licensed under the Apache-2.0 license + +//! AST1060 I2C bare-metal driver core +//! +//! This module provides a portable, hardware-abstraction layer for the AST1060 I2C controller. +//! It is designed to be usable in both bare-metal and RTOS environments without requiring +//! OS-specific dependencies. +//! +//! # Features +//! +//! - Multi-master support +//! - Master and slave (target) mode +//! - **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) +//! +//! # Architecture +//! +//! The driver is split into focused modules: +//! +//! - `controller`: Core hardware abstraction and initialization +//! - `master`: Master mode operations (read/write) +//! - `slave`: Slave (target) mode operations +//! - `transfer`: Low-level transfer state machine +//! - `timing`: Clock timing configuration +//! - `recovery`: Bus recovery mechanisms +//! - `types`: Core type definitions +//! - `error`: Error types +//! - `constants`: Hardware register constants +//! +//! # Usage Example +//! +//! ```rust,no_run +//! use ast10x0_peripherals::i2c::*; +//! use ast1060_pac; +//! +//! // Initialize I2C global registers ONCE before any controller use. +//! init_i2c_global(); +//! +//! // Build the I2C driver around UART1's register block. The yield +//! // closure is invoked between status polls inside wait_completion. +//! let config = I2cConfig::default(); +//! let mut i2c = unsafe { +//! Ast1060I2c::new( +//! ast1060_pac::I2c1::ptr(), +//! ast1060_pac::I2cbuff1::ptr(), +//! config, +//! |_ns| core::hint::spin_loop(), +//! )? +//! }; +//! +//! // Perform a master read. +//! let mut data = [0u8; 4]; +//! i2c.read(0x50, &mut data)?; +//! ``` + +mod constants; +mod controller; +mod error; +mod global; +mod hal_impl; +mod master; +mod recovery; +mod slave; +mod timing; +mod transfer; +mod types; + +// Re-export public API +pub use constants::*; +pub use controller::Ast1060I2c; +pub use error::I2cError; +pub use global::init_i2c_global; +pub use slave::{SlaveBuffer, SlaveConfig, SlaveEvent}; +pub use types::*; + +// Re-export HAL implementations for external use +#[allow(unused_imports)] +pub use hal_impl::*;
diff --git a/target/ast10x0/peripherals/i2c/recovery.rs b/target/ast10x0/peripherals/i2c/recovery.rs new file mode 100644 index 0000000..0ebdc96 --- /dev/null +++ b/target/ast10x0/peripherals/i2c/recovery.rs
@@ -0,0 +1,67 @@ +// Licensed under the Apache-2.0 license + +//! Bus recovery implementation + +use super::{constants, controller::Ast1060I2c, error::I2cError}; + +impl<Y: FnMut(u32)> Ast1060I2c<'_, Y> { + /// Recover the I2C bus from stuck condition + pub fn recover_bus(&mut self) -> Result<(), I2cError> { + // Disable master and slave functionality + self.regs() + .i2cc00() + .modify(|_, w| w.enbl_master_fn().clear_bit().enbl_slave_fn().clear_bit()); + + // Enable master functionality + self.regs() + .i2cc00() + .modify(|_, w| w.enbl_master_fn().set_bit()); + + // Clear all interrupts + self.clear_interrupts(0xffff_ffff); + + // Check SDA/SCL state before attempting recovery + // Only recover if SDA is stuck low while SCL is high + let line_status = self.regs().i2cc08().read(); + if line_status.sampled_sdaline_state().bit() || !line_status.sampled_sclline_state().bit() { + // SDA is not stuck low, or SCL is also stuck - can't recover this way + return Err(I2cError::BusRecoveryFailed); + } + + // Issue bus recovery command via I2CM18 (command register) + self.regs() + .i2cm18() + .modify(|_, w| w.enbl_bus_recover_cmd().set_bit()); + + // Wait for recovery completion + let mut timeout = 100_000; // 100ms + while timeout > 0 { + let status = self.regs().i2cm14().read().bits(); + + if status & constants::AST_I2CM_BUS_RECOVER != 0 { + self.clear_interrupts(constants::AST_I2CM_BUS_RECOVER); + + if status & constants::AST_I2CM_BUS_RECOVER_FAIL != 0 { + return Err(I2cError::BusRecoveryFailed); + } + + return Ok(()); + } + + timeout -= 1; + } + + Err(I2cError::Timeout) + } + + /// Check if bus recovery is needed + #[must_use] + pub fn needs_recovery(&self) -> bool { + let status = self.regs().i2cm14().read().bits(); + + // Check for stuck conditions + (status & constants::AST_I2CM_SCL_LOW_TO != 0) + || (status & constants::AST_I2CM_SDA_DL_TO != 0) + || (status & constants::AST_I2CM_ABNORMAL != 0) + } +}
diff --git a/target/ast10x0/peripherals/i2c/slave.rs b/target/ast10x0/peripherals/i2c/slave.rs new file mode 100644 index 0000000..f969e26 --- /dev/null +++ b/target/ast10x0/peripherals/i2c/slave.rs
@@ -0,0 +1,640 @@ +// Licensed under the Apache-2.0 license + +//! AST1060 I2C Slave/Target Mode Implementation +//! +//! This module provides slave (target) mode functionality for the AST1060 I2C controllers. +//! In slave mode, the controller responds to requests from an external I2C master. + +use super::I2cXferMode; + +use super::{constants, controller::Ast1060I2c, error::I2cError}; + +/// Hardware buffer size (32 bytes / 8 DWORDs) +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 { + /// Primary slave address (7-bit) + pub address: u8, + /// Enable packet mode for slave + pub packet_mode: bool, + /// Use buffer mode (32 bytes) vs byte mode (1 byte) + pub buffer_mode: bool, +} + +impl SlaveConfig { + /// Create a new slave configuration + pub fn new(address: u8) -> Result<Self, I2cError> { + if address > 0x7F { + return Err(I2cError::InvalidAddress); + } + + Ok(Self { + address, + packet_mode: true, // Recommended for performance + buffer_mode: true, // Recommended for performance + }) + } +} + +/// Slave mode events +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum SlaveEvent { + /// Master is requesting to read from us (we need to send data) + ReadRequest, + /// Master is writing to us (we're receiving data) + WriteRequest, + /// Data received from master + DataReceived { len: usize }, + /// Data sent to master + DataSent { len: usize }, + /// Data received from master and send data to master (combined event) + DataReceivedAndSent { rx_len: usize, tx_len: usize }, + /// Stop condition received + Stop, +} + +/// Slave mode data buffer for application-level buffering +pub struct SlaveBuffer { + data: [u8; SLAVE_BUFFER_SIZE], + len: usize, +} + +impl Default for SlaveBuffer { + fn default() -> Self { + Self::new() + } +} + +impl SlaveBuffer { + #[must_use] + pub const fn new() -> Self { + Self { + data: [0u8; SLAVE_BUFFER_SIZE], + len: 0, + } + } + + #[must_use] + pub fn data(&self) -> &[u8] { + &self.data[..self.len] + } + + pub fn data_mut(&mut self) -> &mut [u8] { + &mut self.data[..self.len] + } + + pub fn set_len(&mut self, len: usize) { + self.len = len.min(SLAVE_BUFFER_SIZE); + } + + #[must_use] + pub fn len(&self) -> usize { + self.len + } + + #[must_use] + pub fn is_empty(&self) -> bool { + self.len == 0 + } + + pub fn clear(&mut self) { + self.len = 0; + } + + pub fn write(&mut self, data: &[u8]) -> usize { + let to_copy = data.len().min(SLAVE_BUFFER_SIZE); + self.data[..to_copy].copy_from_slice(&data[..to_copy]); + self.len = to_copy; + to_copy + } +} + +impl<Y: FnMut(u32)> Ast1060I2c<'_, Y> { + #[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 + self.regs() + .i2cc00() + .modify(|_, w| w.enbl_master_fn().clear_bit()); + + // Set slave address + self.regs().i2cs40().write(|w| unsafe { + w.slave_dev_addr1() + .bits(config.address) + .enbl_slave_dev_addr1only_for_new_reg_mode() + .bit(true) + }); + + // Clear slave interrupts + self.clear_slave_interrupts(); + + // Enable slave mode and save address byte in packet mode (I2CC00 bit 20) + // This makes the hardware include the destination address byte in the receive buffer + // which is required for MCTP-over-SMBus (DSP0237) packet format. + self.regs().i2cc00().modify(|r, w| unsafe { + w.bits( + r.bits() | constants::AST_I2CC_SLAVE_EN | constants::AST_I2CC_SLAVE_PKT_SAVE_ADDR, + ) + }); + + // Configure slave mode + let mut cmd = 0u32; + + if config.packet_mode { + cmd |= constants::AST_I2CS_PKT_MODE_EN; + cmd |= constants::AST_I2CS_ACTIVE_ALL; + } + + 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 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; + } + + // Set slave command register + unsafe { + self.regs().i2cs28().write(|w| w.bits(cmd)); + } + + // Enable slave interrupts + self.enable_slave_interrupts(); + + Ok(()) + } + + /// 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 || self.xfer_mode == I2cXferMode::DmaMode { + mask |= constants::AST_I2CM_ABNORMAL + | constants::AST_I2CM_NORMAL_STOP + | constants::AST_I2CM_RX_DONE + | constants::AST_I2CM_TX_ACK; + } + + unsafe { + self.regs().i2cs20().write(|w| w.bits(mask)); + } + } + + /// Clear slave mode interrupts + fn clear_slave_interrupts(&mut self) { + unsafe { + self.regs().i2cs24().write(|w| w.bits(0xFFFF_FFFF)); + let _ = self.regs().i2cs24().read().bits(); + } + } + + /// Enable slave mode (re-enable after disable) + /// + /// This re-enables slave mode and interrupts without reconfiguring the address. + /// Use `configure_slave()` for initial setup, this for re-enabling after `disable_slave()`. + pub fn enable_slave(&mut self) { + // Enable slave mode + self.regs() + .i2cc00() + .modify(|_, w| w.enbl_slave_fn().set_bit()); + + // Enable slave interrupts + self.enable_slave_interrupts(); + } + + /// Disable slave mode + pub fn disable_slave(&mut self) { + // Disable interrupts + unsafe { + self.regs().i2cs20().write(|w| w.bits(0)); + } + + // Clear interrupts + self.clear_slave_interrupts(); + + // Disable slave mode + self.regs() + .i2cc00() + .modify(|_, w| w.enbl_slave_fn().clear_bit()); + } + + /// Check if slave has received data + #[must_use] + pub fn slave_has_data(&self) -> bool { + let status = self.regs().i2cs24().read().bits(); + (status & constants::AST_I2CS_RX_DONE) != 0 + } + + /// Read data received in slave mode + pub fn slave_read(&mut self, buffer: &mut [u8]) -> Result<usize, I2cError> { + // Get receive length from buffer length register + if self.xfer_mode == I2cXferMode::BufferMode { + let len = self + .regs() + .i2cc0c() + .read() + .actual_rxd_pool_buffer_size() + .bits() as usize; + let to_read = len.min(buffer.len()).min(BUFFER_SIZE); + + // Read from buffer + self.copy_from_buffer(&mut buffer[..to_read])?; + + // Re-enable RX buffer + let mut cmd = constants::AST_I2CS_ACTIVE_ALL | constants::AST_I2CS_PKT_MODE_EN; + cmd |= constants::AST_I2CS_RX_BUFF_EN; + unsafe { + 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 + buffer[0] = self.regs().i2cc08().read().rx_byte_buffer().bits(); + + let cmd = constants::AST_I2CS_ACTIVE_ALL; + self.regs().i2cs28().write(|w| unsafe { w.bits(cmd) }); + + self.clear_slave_interrupts(); + Ok(1) + } + } + + /// Write data to send in slave mode (in response to read request) + pub fn slave_write(&mut self, data: &[u8]) -> Result<usize, I2cError> { + if data.is_empty() { + return Ok(0); + } + + if self.xfer_mode == I2cXferMode::BufferMode { + let to_write = 1; + + // Copy data to buffer + self.copy_to_buffer(&data[..to_write])?; + + // Set transfer length + #[allow(clippy::cast_possible_truncation)] + self.regs() + .i2cc0c() + .write(|w| unsafe { w.tx_data_byte_count().bits(to_write as u8 - 1) }); + + // Trigger slave transmit + let mut cmd = constants::AST_I2CS_ACTIVE_ALL | constants::AST_I2CS_PKT_MODE_EN; + cmd |= constants::AST_I2CS_TX_BUFF_EN; + unsafe { + self.regs().i2cs28().write(|w| w.bits(cmd)); + } + Ok(to_write) + } else if self.xfer_mode == I2cXferMode::DmaMode { + // 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(to_write) + } else { + // byte mode + let cmd = constants::AST_I2CS_ACTIVE_ALL | constants::AST_I2CS_TX_CMD; + unsafe { + self.regs() + .i2cc08() + .write(|w| w.tx_byte_buffer().bits(data[0])); + self.regs().i2cs28().write(|w| w.bits(cmd)); + } + self.clear_slave_interrupts(); + + Ok(1) + } + } + + /// Handle slave mode interrupt + #[allow(clippy::too_many_lines)] + pub fn handle_slave_interrupt(&mut self) -> Option<SlaveEvent> { + let status = self.regs().i2cs24().read().bits(); + + if status == 0 { + return None; + } + + // Check for errors first + if (status & constants::AST_I2CS_PKT_ERROR) != 0 { + self.clear_slave_interrupts(); + return None; + } + + if (status & constants::AST_I2CS_PKT_DONE) != 0 { + let mut cmd: u32 = constants::AST_I2CS_ACTIVE_ALL | constants::AST_I2CS_PKT_MODE_EN; + unsafe { + self.regs() + .i2cs24() + .write(|w| w.bits(constants::AST_I2CS_PKT_DONE)); + } + let sts = status & (!(constants::AST_I2CS_PKT_DONE | constants::AST_I2CS_PKT_ERROR)); + if sts == constants::AST_I2CS_SLAVE_MATCH + || sts == constants::AST_I2CS_SLAVE_MATCH | constants::AST_I2CS_RX_DONE + { + // S: Sw + return Some(SlaveEvent::WriteRequest); + } else if sts == constants::AST_I2CS_SLAVE_MATCH | constants::AST_I2CS_WAIT_RX_DMA + || sts + == constants::AST_I2CS_SLAVE_MATCH + | constants::AST_I2CS_RX_DONE + | constants::AST_I2CS_WAIT_RX_DMA + { + // S: Sw|D + self.arm_slave_receive(&mut cmd); + unsafe { + self.regs().i2cs28().write(|w| w.bits(cmd)); + } + 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); + unsafe { + self.regs().i2cs28().write(|w| w.bits(cmd)); + } + return Some(SlaveEvent::Stop); + } else if sts == constants::AST_I2CS_RX_DONE | constants::AST_I2CS_STOP + || sts == constants::AST_I2CS_RX_DONE | constants::AST_I2CS_WAIT_RX_DMA + || sts + == constants::AST_I2CS_RX_DONE + | constants::AST_I2CS_WAIT_RX_DMA + | constants::AST_I2CS_STOP + || sts + == constants::AST_I2CS_RX_DONE_NAK + | constants::AST_I2CS_RX_DONE + | constants::AST_I2CS_STOP + || sts + == constants::AST_I2CS_SLAVE_MATCH + | constants::AST_I2CS_RX_DONE + | constants::AST_I2CS_STOP + || sts + == constants::AST_I2CS_SLAVE_MATCH + | constants::AST_I2CS_RX_DONE + | constants::AST_I2CS_WAIT_RX_DMA + | constants::AST_I2CS_STOP + || sts + == constants::AST_I2CS_SLAVE_MATCH + | constants::AST_I2CS_RX_DONE_NAK + | constants::AST_I2CS_RX_DONE + | constants::AST_I2CS_STOP + { + // S: (Sw)|D|(P) + return Some(SlaveEvent::DataReceived { + len: self.slave_rx_len(), + }); + } else if sts == constants::AST_I2CS_RX_DONE | constants::AST_I2CS_WAIT_TX_DMA + || sts + == constants::AST_I2CS_SLAVE_MATCH + | constants::AST_I2CS_RX_DONE + | constants::AST_I2CS_WAIT_TX_DMA + { + // S: rx_done | wait_tx + return Some(SlaveEvent::DataReceivedAndSent { + rx_len: self.slave_rx_len(), + tx_len: usize::from( + self.regs().i2cc0c().read().tx_data_byte_count().bits() + 1, + ), + }); + } else if sts == constants::AST_I2CS_SLAVE_MATCH | constants::AST_I2CS_WAIT_TX_DMA { + // S: Sw | wait_tx + return Some(SlaveEvent::DataSent { + len: usize::from(self.regs().i2cc0c().read().tx_data_byte_count().bits() + 1), + }); + } else if sts == constants::AST_I2CS_WAIT_TX_DMA { + // S: wait_tx + return Some(SlaveEvent::DataSent { + len: usize::from(self.regs().i2cc0c().read().tx_data_byte_count().bits() + 1), + }); + } else if sts == constants::AST_I2CS_TX_NAK | constants::AST_I2CS_STOP + || sts == constants::AST_I2CS_STOP + { + // S: (TX_NAK)|P + self.arm_slave_receive(&mut cmd); + unsafe { + self.regs().i2cs28().write(|w| w.bits(cmd)); + } + } else { + // TODO packet slave sts + } + } else { + //byte irq + let cmd: u32 = constants::AST_I2CS_ACTIVE_ALL; + + if status + == constants::AST_I2CS_SLAVE_MATCH + | constants::AST_I2CS_RX_DONE + | constants::AST_I2CS_WAIT_RX_DMA + { + // S: Sw|D + let _byte_data = self.regs().i2cc08().read().rx_byte_buffer().bits(); + self.regs().i2cs28().write(|w| unsafe { w.bits(cmd) }); + self.regs().i2cs24().write(|w| unsafe { w.bits(status) }); + self.regs().i2cs24().read().bits(); + return Some(SlaveEvent::WriteRequest); + } else if status + == constants::AST_I2CS_SLAVE_MATCH + | constants::AST_I2CS_RX_DONE + | constants::AST_I2CS_WAIT_RX_DMA + | constants::AST_I2CS_STOP + | constants::AST_I2CS_TX_NAK + || status + == constants::AST_I2CS_SLAVE_MATCH + | constants::AST_I2CS_RX_DONE + | constants::AST_I2CS_WAIT_RX_DMA + | constants::AST_I2CS_STOP + { + // S: Sw|D|P + let _byte_data = self.regs().i2cc08().read().rx_byte_buffer().bits(); + self.regs().i2cs28().write(|w| unsafe { w.bits(cmd) }); + self.regs().i2cs24().write(|w| unsafe { w.bits(status) }); + return Some(SlaveEvent::WriteRequest); + } else if status == constants::AST_I2CS_RX_DONE | constants::AST_I2CS_WAIT_RX_DMA { + // S: rD + return Some(SlaveEvent::DataReceived { len: 1 }); + } else if status + == constants::AST_I2CS_SLAVE_MATCH + | constants::AST_I2CS_RX_DONE + | constants::AST_I2CS_WAIT_TX_DMA + { + // S: Sr|D + // received one byte + let _byte_data = self.regs().i2cc08().read().rx_byte_buffer().bits(); + return Some(SlaveEvent::DataSent { len: 1 }); + } else if status == constants::AST_I2CS_TX_ACK | constants::AST_I2CS_WAIT_TX_DMA { + // S: tD + return Some(SlaveEvent::DataSent { len: 1 }); + } else if status == constants::AST_I2CS_STOP + || status == constants::AST_I2CS_STOP | constants::AST_I2CS_TX_NAK + || status + == constants::AST_I2CS_SLAVE_MATCH + | constants::AST_I2CS_STOP + | constants::AST_I2CS_TX_NAK + || status + == constants::AST_I2CS_SLAVE_MATCH + | constants::AST_I2CS_WAIT_RX_DMA + | constants::AST_I2CS_STOP + | constants::AST_I2CS_TX_NAK + { + // S: P + self.regs().i2cs28().write(|w| unsafe { w.bits(cmd) }); + self.regs().i2cs24().write(|w| unsafe { w.bits(status) }); + return Some(SlaveEvent::Stop); + } + // TODO byte slave sts + } + None + } +}
diff --git a/target/ast10x0/peripherals/i2c/target_adapter.rs b/target/ast10x0/peripherals/i2c/target_adapter.rs new file mode 100644 index 0000000..14b438a --- /dev/null +++ b/target/ast10x0/peripherals/i2c/target_adapter.rs
@@ -0,0 +1,274 @@ +// Licensed under the Apache-2.0 license + +//! I2C Target (Slave) Adapter +//! +//! This module provides an adapter that bridges the low-level [`Ast1060I2c`] +//! slave API with a user-supplied `TargetCallbacks` implementation. The +//! adapter polls hardware events from the controller and dispatches them +//! to the callback methods, keeping the hardware layer (slave.rs) +//! decoupled from any particular target abstraction. +//! +//! # Example +//! +//! ```rust,ignore +//! use ast10x0_peripherals::i2c::target_adapter::{TargetAdapter, TargetCallbacks}; +//! +//! struct MyTarget { /* ... */ } +//! impl TargetCallbacks for MyTarget { /* ... */ } +//! +//! let mut adapter = TargetAdapter::new(i2c_controller, MyTarget { /* ... */ }); +//! adapter.configure(0x42)?; +//! +//! // In an interrupt handler or poll loop: +//! adapter.process_events()?; +//! ``` + +use super::controller::Ast1060I2c; +use super::error::I2cError; +use super::slave::{SlaveConfig, SlaveEvent}; + +/// Internal hardware buffer for adapter operations +const ADAPTER_BUFFER_SIZE: usize = 32; + +/// Target adapter that bridges hardware events to [`TargetCallbacks`]. +/// +/// Wraps an [`Ast1060I2c`] controller and a user-provided callback +/// implementation, mapping hardware slave events to the appropriate +/// callback methods. +/// +/// # Type Parameters +/// +/// * `'a` - Lifetime of the I2C register references +/// * `T` - User's [`TargetCallbacks`] implementation +/// * `Y` - Yield closure threaded through the underlying [`Ast1060I2c`] +pub struct TargetAdapter<'a, T, Y: FnMut(u32)> { + /// The underlying hardware controller + i2c: Ast1060I2c<'a, Y>, + /// User's target implementation + target: T, + /// Internal receive buffer + rx_buffer: [u8; ADAPTER_BUFFER_SIZE], + /// Internal transmit buffer + tx_buffer: [u8; ADAPTER_BUFFER_SIZE], + /// Flag indicating we're in a transaction + in_transaction: bool, +} + +impl<'a, T, Y: FnMut(u32)> TargetAdapter<'a, T, Y> { + /// Create a new target adapter. + /// + /// # Arguments + /// + /// * `i2c` - The I2C controller configured for the desired bus + /// * `target` - User's target implementation + /// + /// # Returns + /// + /// A new `TargetAdapter` instance ready to be configured. + pub fn new(i2c: Ast1060I2c<'a, Y>, target: T) -> Self { + Self { + i2c, + target, + rx_buffer: [0u8; ADAPTER_BUFFER_SIZE], + tx_buffer: [0u8; ADAPTER_BUFFER_SIZE], + in_transaction: false, + } + } + + /// Get a reference to the underlying target implementation. + pub fn target(&self) -> &T { + &self.target + } + + /// Get a mutable reference to the underlying target implementation. + pub fn target_mut(&mut self) -> &mut T { + &mut self.target + } + + /// Get a reference to the underlying I2C controller. + pub fn controller(&self) -> &Ast1060I2c<'a, Y> { + &self.i2c + } + + /// Get a mutable reference to the underlying I2C controller. + pub fn controller_mut(&mut self) -> &mut Ast1060I2c<'a, Y> { + &mut self.i2c + } + + /// Decompose the adapter into its parts. + /// + /// Returns the I2C controller and target implementation. + pub fn into_parts(self) -> (Ast1060I2c<'a, Y>, T) { + (self.i2c, self.target) + } +} + +impl<'a, T, Y: FnMut(u32)> TargetAdapter<'a, T, Y> +where + T: TargetCallbacks, +{ + /// Configure the controller for target (slave) mode. + /// + /// # Arguments + /// + /// * `address` - The 7-bit I2C address to respond to + /// + /// # Returns + /// + /// `Ok(())` on success, or an error if configuration fails. + pub fn configure(&mut self, address: u8) -> Result<(), I2cError> { + let config = SlaveConfig::new(address)?; + self.i2c.configure_slave(&config)?; + + // Initialize the target with the address + self.target + .on_init(address) + .map_err(|_| I2cError::SlaveError)?; + + self.in_transaction = false; + Ok(()) + } + + /// Disable target mode. + pub fn disable(&mut self) { + self.i2c.disable_slave(); + self.in_transaction = false; + } + + /// Process pending hardware events. + /// + /// This method should be called from an interrupt handler or poll loop. + /// It reads hardware status, maps events to trait callbacks, and handles + /// data transfer between hardware and the target implementation. + /// + /// # Returns + /// + /// `Ok(Some(event))` if an event was processed, `Ok(None)` if no events pending, + /// or an error if processing failed. + pub fn process_events(&mut self) -> Result<Option<SlaveEvent>, I2cError> { + let Some(event) = self.i2c.handle_slave_interrupt() else { + return Ok(None); + }; + + match event { + SlaveEvent::AddressMatch => { + if !self.in_transaction { + self.target.on_transaction_start(false); + self.in_transaction = true; + } + self.target.on_address_match(); + } + + SlaveEvent::ReadRequest => { + // Master wants to read from us - get data from target + let len = self + .target + .on_read(&mut self.tx_buffer) + .map_err(|_| I2cError::SlaveError)?; + + // Send data to master + if len > 0 { + let to_send = len.min(ADAPTER_BUFFER_SIZE); + self.i2c.slave_write(&self.tx_buffer[..to_send])?; + } + } + + SlaveEvent::WriteRequest => { + // Master wants to write to us - nothing to do here, + // data will arrive in DataReceived + } + + SlaveEvent::DataReceived { len } => { + // Read data from hardware + let read_len = self.i2c.slave_read(&mut self.rx_buffer)?; + let actual_len = read_len.min(len); + + if actual_len > 0 { + // Pass data to target + self.target + .on_write(&self.rx_buffer[..actual_len]) + .map_err(|_| I2cError::SlaveError)?; + } + } + + SlaveEvent::DataSent { len: _ } => { + // Data was sent successfully - target can track if needed + self.target.on_data_sent(); + } + + SlaveEvent::Stop => { + self.target.on_stop(); + self.in_transaction = false; + } + } + + Ok(Some(event)) + } + + /// Poll for and process all pending events. + /// + /// Continues processing until no more events are available. + /// + /// # Returns + /// + /// The count of events processed, or an error if processing failed. + pub fn process_all_events(&mut self) -> Result<usize, I2cError> { + let mut count: usize = 0; + while self.process_events()?.is_some() { + count = count.saturating_add(1); + } + Ok(count) + } +} + +/// Target callback trait for adapter integration. +/// +/// This trait defines the callbacks that the adapter invokes in response to +/// hardware events from the I2C controller in slave mode. Implementors plug +/// in their own logic for address-match, read, write, and stop events. +pub trait TargetCallbacks { + /// Error type for callback operations + type Error; + + /// Called when the target is initialized with an address. + /// + /// # Arguments + /// + /// * `address` - The 7-bit I2C address assigned to this target + fn on_init(&mut self, address: u8) -> Result<(), Self::Error>; + + /// Called when a transaction starts. + /// + /// # Arguments + /// + /// * `repeated` - True if this is a repeated start condition + fn on_transaction_start(&mut self, repeated: bool); + + /// Called when our address is matched. + fn on_address_match(&mut self); + + /// Called when the master requests data (read operation). + /// + /// # Arguments + /// + /// * `buffer` - Buffer to fill with data to send to master + /// + /// # Returns + /// + /// Number of bytes written to buffer, or an error. + fn on_read(&mut self, buffer: &mut [u8]) -> Result<usize, Self::Error>; + + /// Called when the master sends data (write operation). + /// + /// # Arguments + /// + /// * `data` - Data received from the master + fn on_write(&mut self, data: &[u8]) -> Result<(), Self::Error>; + + /// Called when data has been sent to the master. + fn on_data_sent(&mut self) {} + + /// Called when a stop condition is received. + fn on_stop(&mut self); +} +
diff --git a/target/ast10x0/peripherals/i2c/timing.rs b/target/ast10x0/peripherals/i2c/timing.rs new file mode 100644 index 0000000..3e93030 --- /dev/null +++ b/target/ast10x0/peripherals/i2c/timing.rs
@@ -0,0 +1,223 @@ +// Licensed under the Apache-2.0 license + +//! Timing configuration for I2C +//! +//! The AST1060 I2C controller uses a hierarchical clock system: +//! 1. HPLL (1 `GHz`) → APB clock (typically 50 `MHz`) +//! 2. APB clock → 4 base clocks (configured in I2CG10) +//! 3. Base clock → SCL frequency via per-controller divider +//! +//! This module calculates the optimal divider chain to achieve +//! the requested I2C bus speed while meeting timing specifications. +//! +//! # Design: Dependency Injection +//! +//! Clock frequencies are provided via `ClockConfig` in `I2cConfig`, rather than +//! being read directly from SCU/I2C global registers. This provides: +//! - **Decoupling**: I2C module has no hidden dependencies on SCU +//! - **Testability**: Unit tests can inject known clock values +//! - **Portability**: Different board configurations are easily supported +//! - **Clear ownership**: System init owns clock detection, I2C just uses values +//! +//! # I2C Timing Requirements +//! +//! | Mode | Speed | tLOW (min) | tHIGH (min) | tBuf (min) | +//! |------------|----------|------------|-------------|------------| +//! | Standard | 100 kHz | 4.7 µs | 4.0 µs | 4.7 µs | +//! | Fast | 400 kHz | 1.3 µs | 0.6 µs | 1.3 µs | +//! | Fast-plus | 1 `MHz` | 0.5 µs | 0.26 µs | 0.5 µs | +//! +//! # Clock Derivation +//! +//! The I2C global register I2CG10 configures four base clock dividers: +//! ```text +//! I2CG10[7:0] = base_clk1 divisor → Fast-plus (1 MHz) source +//! I2CG10[15:8] = base_clk2 divisor → Fast (400 kHz) source +//! I2CG10[23:16] = base_clk3 divisor → Standard (100 kHz) source +//! I2CG10[31:24] = base_clk4 divisor → Recovery timeout source +//! ``` +//! +//! Default I2CG10 value: `0x6222_0803` +//! With 50 `MHz` APB clock: +//! - `base_clk1` (0x03): 50M / ((3+2)/2) = 20 `MHz` → 1 `MHz` with div=20 +//! - `base_clk2` (0x08): 50M / ((8+2)/2) = 10 `MHz` → 400 kHz with div=25 +//! - `base_clk3` (0x22): 50M / ((34+2)/2) = 2.77 `MHz` → 100 kHz with div=28 + +use super::error::I2cError; +use super::types::{ClockConfig, I2cConfig, I2cSpeed}; +use ast1060_pac::i2c::RegisterBlock; +use core::cmp::min; + +/// Maximum divider ratio per base clock +const MAX_DIVIDER_RATIO: u32 = 32; + +/// I2C speed frequencies in Hz +const SPEED_STANDARD: u32 = 100_000; +const SPEED_FAST: u32 = 400_000; +const SPEED_FAST_PLUS: u32 = 1_000_000; + +/// Configure I2C timing based on speed and injected clock configuration +/// +/// Uses the `ClockConfig` from `I2cConfig` to calculate proper SCL timing +/// that meets I2C specifications. No direct hardware register access for +/// clock detection - all clock values come from the injected config. +/// +/// # Arguments +/// +/// * `regs` - Reference to the I2C controller register block +/// * `config` - I2C configuration containing speed, timeout, and clock settings +/// +/// # Returns +/// +/// * `Ok(())` on successful configuration +/// * `Err(I2cError::Invalid)` if timing cannot be achieved +pub fn configure_timing(regs: &RegisterBlock, config: &I2cConfig) -> Result<(), I2cError> { + let clocks = &config.clock_config; + + // Step 1: Get target speed + let target_speed = match config.speed { + I2cSpeed::Standard => SPEED_STANDARD, + I2cSpeed::Fast => SPEED_FAST, + I2cSpeed::FastPlus => SPEED_FAST_PLUS, + }; + + // Step 2: Find best base clock and divider using injected clock config + let (div, divider_ratio) = calculate_divider(clocks, target_speed)?; + + // Step 3: Calculate SCL high/low times + // I2C spec: tLOW should be slightly longer than tHIGH + // Default ratio: 9/16 low, 7/16 high (asymmetric for spec compliance) + let scl_low = calculate_scl_low(divider_ratio); + let scl_high = calculate_scl_high(divider_ratio, scl_low); + let scl_high_min = scl_high.saturating_sub(1); + + // Step 4: Build timeout configuration if enabled + let (timeout_divisor, timeout_timer) = if config.smbus_timeout { + // SMBus timeout: 25-35ms per specification + // With base_clk_divisor=2, timer=8 gives approximately 35ms timeout + (2u8, 8u8) + } else { + (0u8, 0u8) + }; + + // Step 5: Write AC timing register with all fields in a single write + // This avoids corrupting fields with multiple separate writes + regs.i2cc04().write(|w| unsafe { + w.base_clk_divisor_tbase_clk() + .bits((div & 0xf) as u8) + .cycles_of_master_sclclklow_pulse_width_tcklow() + .bits(scl_low) + .cycles_of_master_sclclkhigh_pulse_width_tckhigh() + .bits(scl_high) + .cycles_of_master_sclclkhigh_minimum_pulse_width_tckhigh_min() + .bits(scl_high_min) + .timeout_base_clk_divisor_tout_base_clk() + .bits(timeout_divisor) + .timeout_timer() + .bits(timeout_timer) + }); + + Ok(()) +} + +/// Calculate optimal base clock selection and divider ratio +/// +/// Tries each base clock in order (APB, clk1, clk2, clk3, clk4) and finds +/// the first one that can achieve the target speed with a divider ≤ 32. +/// +/// # Arguments +/// +/// * `clocks` - Injected clock configuration +/// * `target_speed` - Desired I2C bus speed in Hz +/// +/// # Returns +/// +/// * `(div, ratio)` - Base clock selector (0-4+) and divider ratio +fn calculate_divider(clocks: &ClockConfig, target_speed: u32) -> Result<(u32, u32), I2cError> { + // Avoid division by zero + if target_speed == 0 { + return Err(I2cError::Invalid); + } + + // Try each base clock in order, finding first that works with divider <= 32 + let candidates = [ + (0u32, clocks.apb_clock_hz), + (1u32, clocks.base_clk1_hz), + (2u32, clocks.base_clk2_hz), + (3u32, clocks.base_clk3_hz), + ]; + + for (div, base_clk) in candidates { + if base_clk == 0 { + continue; // Skip unconfigured clocks + } + if base_clk / target_speed <= MAX_DIVIDER_RATIO { + let mut ratio = base_clk / target_speed; + // Round up if we'd exceed target speed + if ratio > 0 && base_clk / ratio > target_speed { + ratio = ratio.saturating_add(1); + } + // Ensure ratio is at least 1 + if ratio == 0 { + ratio = 1; + } + return Ok((div, ratio)); + } + } + + // Fall back to base_clk4 with extended divider for very slow speeds + if clocks.base_clk4_hz == 0 { + return Err(I2cError::Invalid); + } + + let mut div = 4u32; + let mut ratio = clocks.base_clk4_hz / target_speed; + let mut inc = 0u32; + + // Shift ratio down until it fits in MAX_DIVIDER_RATIO + while ratio.saturating_add(inc) > MAX_DIVIDER_RATIO { + inc |= ratio & 1; // Track if we need to round up + ratio >>= 1; + div = div.saturating_add(1); + } + ratio = ratio.saturating_add(inc); + + // Round up if needed + if ratio > 0 && clocks.base_clk4_hz / ratio > target_speed { + ratio = ratio.saturating_add(1); + } + + // Clamp to valid range + ratio = min(ratio, MAX_DIVIDER_RATIO); + if ratio == 0 { + ratio = 1; + } + div &= 0xf; + + Ok((div, ratio)) +} + +/// Calculate SCL low time cycles +/// +/// I2C specification requires tLOW to be longer than tHIGH. +/// We use 9/16 of the period for low time (56.25%). +fn calculate_scl_low(divider_ratio: u32) -> u8 { + // scl_low = (ratio * 9/16) - 1, clamped to 4 bits + let scl_low = (divider_ratio * 9 / 16).saturating_sub(1); + #[allow(clippy::cast_possible_truncation)] + let result = min(scl_low, 0xf) as u8; + result +} + +/// Calculate SCL high time cycles +/// +/// High time is the remainder after low time and 2 cycles for edges. +fn calculate_scl_high(divider_ratio: u32, scl_low: u8) -> u8 { + // scl_high = ratio - scl_low - 2 (2 cycles for rise/fall edges) + let scl_high = divider_ratio + .saturating_sub(u32::from(scl_low)) + .saturating_sub(2); + #[allow(clippy::cast_possible_truncation)] + let result = min(scl_high, 0xf) as u8; + result +}
diff --git a/target/ast10x0/peripherals/i2c/transfer.rs b/target/ast10x0/peripherals/i2c/transfer.rs new file mode 100644 index 0000000..5610034 --- /dev/null +++ b/target/ast10x0/peripherals/i2c/transfer.rs
@@ -0,0 +1,190 @@ +// Licensed under the Apache-2.0 license + +//! Transfer mode implementation +//! +//! Note: The `start_transfer`, `start_byte_mode`, and `start_buffer_mode` functions +//! are kept for potential future use or testing. The main byte/buffer mode logic +//! is now in master.rs with inline command building for better control. + +use super::{constants, controller::Ast1060I2c, error::I2cError, types::I2cXferMode}; + +#[allow(dead_code)] +impl<Y: FnMut(u32)> Ast1060I2c<'_, Y> { + /// Start a transfer (common setup for byte/buffer modes) + /// + /// Note: Currently unused - byte/buffer mode functions in master.rs + /// handle command building directly for better control. + pub(crate) fn start_transfer( + &mut self, + addr: u8, + is_read: bool, + len: usize, + ) -> Result<(), I2cError> { + if len == 0 || len > 255 { + return Err(I2cError::Invalid); + } + + self.current_addr = addr; + #[allow(clippy::cast_possible_truncation)] + { + self.current_len = len as u32; + } + self.current_xfer_cnt = 0; + self.completion = false; + + // Clear any previous status + self.clear_interrupts(0xffff_ffff); + + match self.xfer_mode { + I2cXferMode::ByteMode => { + self.start_byte_mode(addr, is_read, len); + 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), + } + } + + /// Start transfer in byte mode + /// + /// Byte mode uses packet mode with single-byte transfers. + /// Command register is i2cm18, data register is i2cc08. + fn start_byte_mode(&mut self, addr: u8, is_read: bool, len: usize) { + // Build command: packet mode + address + start + let mut cmd = constants::AST_I2CM_PKT_EN + | constants::ast_i2cm_pkt_addr(addr) + | constants::AST_I2CM_START_CMD; + + if is_read { + cmd |= constants::AST_I2CM_RX_CMD; + // For last byte (or single byte), send NACK and STOP + if len == 1 { + cmd |= constants::AST_I2CM_RX_CMD_LAST | constants::AST_I2CM_STOP_CMD; + } + } else { + cmd |= constants::AST_I2CM_TX_CMD; + // For last byte (or single byte), send STOP + if len == 1 { + cmd |= constants::AST_I2CM_STOP_CMD; + } + } + + // Issue command to i2cm18 (Master Command Register) + unsafe { + self.regs().i2cm18().write(|w| w.bits(cmd)); + } + } + + /// Start transfer in buffer mode (up to 32 bytes) + /// + /// Buffer mode uses packet mode with hardware buffer for multi-byte transfers. + /// All command bits go to i2cm18 in a single write. + fn start_buffer_mode(&mut self, addr: u8, is_read: bool, len: usize) -> Result<(), I2cError> { + if len > constants::BUFFER_MODE_SIZE { + return Err(I2cError::Invalid); + } + + // Configure buffer size in i2cc0c before issuing command + #[allow(clippy::cast_possible_truncation)] + if is_read { + // Set RX buffer size (len - 1) + self.regs() + .i2cc0c() + .modify(|_, w| unsafe { w.rx_pool_buffer_size().bits((len - 1) as u8) }); + } else { + // Set TX byte count (len - 1) + self.regs() + .i2cc0c() + .modify(|_, w| unsafe { w.tx_data_byte_count().bits((len - 1) as u8) }); + } + + // Build command: PKT_EN + address + START + TX/RX + BUFF_EN + STOP + let mut cmd = constants::AST_I2CM_PKT_EN + | constants::ast_i2cm_pkt_addr(addr) + | constants::AST_I2CM_START_CMD; + + if is_read { + cmd |= constants::AST_I2CM_RX_CMD + | constants::AST_I2CM_RX_BUFF_EN + | constants::AST_I2CM_RX_CMD_LAST; + } else { + cmd |= constants::AST_I2CM_TX_CMD | constants::AST_I2CM_TX_BUFF_EN; + } + + // Add stop for last chunk + cmd |= constants::AST_I2CM_STOP_CMD; + + // Issue command to i2cm18 (Master Command Register) - single write + unsafe { + self.regs().i2cm18().write(|w| w.bits(cmd)); + } + + Ok(()) + } + + /// Copy data to hardware buffer (for writes) + pub(crate) fn copy_to_buffer(&mut self, data: &[u8]) -> Result<(), I2cError> { + if data.len() > constants::BUFFER_MODE_SIZE { + return Err(I2cError::Invalid); + } + + let buff_regs = self.buff_regs(); + let mut idx = 0; + + while idx < data.len() { + // Pack bytes into DWORD (little-endian) + let mut dword: u32 = 0; + for byte_pos in 0..4 { + if idx + byte_pos < data.len() { + dword |= u32::from(data[idx + byte_pos]) << (byte_pos * 8); + } + } + + let dword_idx = idx / 4; + if dword_idx >= 8 { + return Err(I2cError::Invalid); + } + + // Write to buffer register array (AST1060 has 8 DWORDs = 32 bytes) + unsafe { + buff_regs.buff(dword_idx).write(|w| w.bits(dword)); + } + + idx += 4; + } + + Ok(()) + } + + /// Copy data from hardware buffer (for reads) + pub(crate) fn copy_from_buffer(&self, data: &mut [u8]) -> Result<(), I2cError> { + if data.len() > constants::BUFFER_MODE_SIZE { + return Err(I2cError::Invalid); + } + + let buff_regs = self.buff_regs(); + let mut idx = 0; + + while idx < data.len() { + let dword_idx = idx / 4; + if dword_idx >= 8 { + return Err(I2cError::Invalid); + } + + // Read from buffer register array + let dword = buff_regs.buff(dword_idx).read().bits(); + + // Extract bytes from DWORD (little-endian) + for byte_pos in 0..4 { + if idx + byte_pos < data.len() { + data[idx + byte_pos] = ((dword >> (byte_pos * 8)) & 0xFF) as u8; + } + } + + idx += 4; + } + + Ok(()) + } +}
diff --git a/target/ast10x0/peripherals/i2c/types.rs b/target/ast10x0/peripherals/i2c/types.rs new file mode 100644 index 0000000..4a7519b --- /dev/null +++ b/target/ast10x0/peripherals/i2c/types.rs
@@ -0,0 +1,266 @@ +// Licensed under the Apache-2.0 license + +//! Core types for AST1060 I2C driver +//! +//! These types are portable and have no OS dependencies. + +use ast1060_pac; + +/// I2C controller identifier (0-13 for AST1060) +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub struct Controller(pub u8); + +impl Controller { + /// Create a new controller instance + #[must_use] + pub const fn new(id: u8) -> Option<Self> { + if id < 14 { + Some(Self(id)) + } else { + None + } + } + + /// Get the controller ID + #[must_use] + pub const fn id(&self) -> u8 { + self.0 + } +} + +// Implement conversions for compatibility +impl From<Controller> for u8 { + fn from(c: Controller) -> u8 { + c.0 + } +} + +// NOTE: We intentionally don't implement From<u8> for Controller +// because it would bypass validation. Use Controller::new() or TryFrom instead. +impl TryFrom<u8> for Controller { + type Error = (); + + fn try_from(id: u8) -> Result<Self, Self::Error> { + Self::new(id).ok_or(()) + } +} + +// ============================================================================ +// Design Note: I2C Address Validation +// ============================================================================ +// +// embedded-hal defines `SevenBitAddress` as a type alias for `u8`: +// pub type SevenBitAddress = u8; +// +// This means I2C addresses are NOT validated at the type level in embedded-hal. +// We follow this convention for compatibility, but validate at runtime in: +// - SlaveConfig::new() - validates slave address is ≤ 0x7F +// - Master operations - hardware will NAK invalid addresses +// +// Alternative approaches considered but rejected: +// 1. Custom newtype - breaks embedded-hal I2c<SevenBitAddress> trait impl +// 2. Const generics - too restrictive, addresses often come from runtime config +// +// The tradeoff is: embedded-hal compatibility > compile-time address validation + +/// Clock configuration for I2C timing calculations +/// +/// This struct decouples the I2C driver from direct SCU/I2C global register access, +/// enabling: +/// - Unit testing without hardware +/// - Portability across different board configurations +/// - Clear ownership boundaries between system initialization and peripheral drivers +/// +/// # Usage +/// +/// For typical AST1060 configurations, use `ClockConfig::ast1060_default()`. +/// For custom configurations or testing, construct directly. +/// +/// # Example +/// +/// ```rust,no_run +/// use aspeed_ddk::i2c_core::ClockConfig; +/// +/// // Use default AST1060 configuration (50 MHz APB) +/// let clocks = ClockConfig::ast1060_default(); +/// +/// // Or read from hardware during system init +/// let clocks = ClockConfig::from_hardware(); +/// ``` +#[derive(Debug, Clone, Copy)] +pub struct ClockConfig { + /// APB bus clock frequency in Hz + pub apb_clock_hz: u32, + /// Base clock 1 frequency in Hz (for Fast-plus mode, ~20 `MHz`) + pub base_clk1_hz: u32, + /// Base clock 2 frequency in Hz (for Fast mode, ~10 `MHz`) + pub base_clk2_hz: u32, + /// Base clock 3 frequency in Hz (for Standard mode, ~2.77 `MHz`) + pub base_clk3_hz: u32, + /// Base clock 4 frequency in Hz (for recovery timeout) + pub base_clk4_hz: u32, +} + +impl ClockConfig { + /// HPLL frequency (1 `GHz`) used for APB clock derivation + const HPLL_FREQ: u32 = 1_000_000_000; + + /// Default AST1060 clock configuration + /// + /// Based on typical AST1060 configuration with: + /// - APB clock: 50 `MHz` (HPLL / 20) + /// - I2CG10 register: `0x6222_0803` + /// + /// This can be used when the actual hardware configuration matches + /// these defaults, or for testing purposes. + #[must_use] + pub const fn ast1060_default() -> Self { + // APB = 50 MHz (HPLL / ((9+1) * 2)) + // I2CG10 = 0x6222_0803: + // base_clk1 divisor = 0x03 → 50M / ((3+2)/2) = 20 MHz + // base_clk2 divisor = 0x08 → 50M / ((8+2)/2) = 10 MHz + // base_clk3 divisor = 0x22 → 50M / ((34+2)/2) = 2.77 MHz + // base_clk4 divisor = 0x62 → 50M / ((98+2)/2) = 1 MHz + Self { + apb_clock_hz: 50_000_000, + base_clk1_hz: 20_000_000, + base_clk2_hz: 10_000_000, + base_clk3_hz: 2_770_000, + base_clk4_hz: 1_000_000, + } + } + + /// Read clock configuration from hardware registers + /// + /// This reads the actual APB clock divider from SCU and the I2C base + /// clock dividers from I2C global registers. + /// + /// # Safety + /// + /// This function accesses SCU and I2C global registers. It should be + /// called during system initialization when these peripherals are + /// properly configured. + #[must_use] + pub fn from_hardware() -> Self { + // Read APB clock from SCU + let scu = unsafe { &*ast1060_pac::Scu::ptr() }; + let apb_divider = u32::from(scu.scu310().read().apbbus_pclkdivider_sel().bits()); + let apb_clock_hz = Self::HPLL_FREQ / ((apb_divider + 1) * 2); + + // Read base clock dividers from I2C global registers + let i2cg = unsafe { &*ast1060_pac::I2cglobal::ptr() }; + let i2cg10 = i2cg.i2cg10().read(); + + // Formula: base_clk = APB * 10 / ((divisor + 2) * 10 / 2) + let calc_base = |divisor: u8| -> u32 { + let divisor_u32 = u32::from(divisor); + (apb_clock_hz * 10) / ((divisor_u32 + 2) * 10 / 2) + }; + + Self { + apb_clock_hz, + base_clk1_hz: calc_base(i2cg10.base_clk1divisor_basedivider1().bits()), + base_clk2_hz: calc_base(i2cg10.base_clk2divisor_basedivider2().bits()), + base_clk3_hz: calc_base(i2cg10.base_clk3divisor_basedivider3().bits()), + base_clk4_hz: calc_base(i2cg10.base_clk4divisor_basedivider4().bits()), + } + } +} + +impl Default for ClockConfig { + fn default() -> Self { + Self::ast1060_default() + } +} + +/// I2C configuration +/// +/// Default configuration is optimized for MCTP-over-I2C: +/// - Fast mode (400 kHz) - standard MCTP speed +/// - Buffer mode - efficient for MCTP packet transfers +/// - `SMBus` timeout enabled - required for robust MCTP operation +/// - Clock config read from hardware - assumes app pre-configured I2CG10 +#[derive(Debug, Clone, Copy)] +pub struct I2cConfig { + /// Transfer mode (byte-by-byte or buffer) + pub xfer_mode: I2cXferMode, + /// Bus speed (Standard/Fast/FastPlus) + pub speed: I2cSpeed, + /// Enable multi-master support + pub multi_master: bool, + /// Enable `SMBus` timeout detection (25-35ms per `SMBus` spec) + pub smbus_timeout: bool, + /// Enable `SMBus` alert interrupt + pub smbus_alert: bool, + /// Clock configuration for timing calculations + pub clock_config: ClockConfig, +} + +impl Default for I2cConfig { + /// Create default I2C configuration reading clocks from hardware + /// + /// 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, set `xfer_mode` to [`I2cXferMode::DmaMode`] and + /// pass a DMA buffer via + /// [`Ast1060I2c::new_with_dma`](super::controller::Ast1060I2c::new_with_dma). + fn default() -> Self { + Self { + xfer_mode: I2cXferMode::BufferMode, + speed: I2cSpeed::Fast, + multi_master: false, + smbus_timeout: true, + smbus_alert: false, + clock_config: ClockConfig::from_hardware(), + } + } +} + +impl I2cConfig { + /// Create configuration with static default clocks (for testing) + /// + /// Uses hardcoded AST1060 default clock values. Prefer `default()` + /// which reads actual values from hardware. + #[must_use] + pub fn with_static_clocks() -> Self { + Self { + clock_config: ClockConfig::ast1060_default(), + ..Self { + xfer_mode: I2cXferMode::BufferMode, + speed: I2cSpeed::Fast, + multi_master: false, + smbus_timeout: true, + smbus_alert: false, + clock_config: ClockConfig::ast1060_default(), + } + } + } +} + +/// Transfer mode selection +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum I2cXferMode { + /// Byte-by-byte mode (1 byte at a time) + 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 +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum I2cSpeed { + /// Standard mode: 100 kHz + Standard, + /// Fast mode: 400 kHz + Fast, + /// Fast-plus mode: 1 `MHz` + FastPlus, +}
diff --git a/target/ast10x0/peripherals/lib.rs b/target/ast10x0/peripherals/lib.rs index b9084e9..141dead 100644 --- a/target/ast10x0/peripherals/lib.rs +++ b/target/ast10x0/peripherals/lib.rs
@@ -3,5 +3,6 @@ #![no_std] +pub mod i2c; pub mod scu; pub mod uart;