| // Licensed under the Apache-2.0 license |
| // SPDX-License-Identifier: Apache-2.0 |
| |
| //! 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, true), |
| I2cXferMode::BufferMode => self.write_buffer_mode(addr, bytes, true), |
| I2cXferMode::DmaMode => self.write_dma_mode(addr, bytes, true), |
| } |
| } |
| |
| /// 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 with repeated-START (no STOP between phases). |
| /// |
| /// The write phase omits `STOP_CMD` so the hardware holds SCL low |
| /// (clock-stretch) after the last TX ACK. The read's `START_CMD` on a |
| /// held bus is interpreted by the hardware as a repeated-START, matching |
| /// MCTP/SMBus semantics. |
| /// |
| /// This works in the polling model because the CPU issues the read command |
| /// within microseconds of write completion — well within the clock-stretch |
| /// window. Requires `smbus_timeout` disabled (or set long enough) in the |
| /// bus config to prevent the hardware releasing the bus before the read |
| /// command is issued. |
| pub fn write_read( |
| &mut self, |
| addr: u8, |
| bytes: &[u8], |
| buffer: &mut [u8], |
| ) -> Result<(), I2cError> { |
| // Write without STOP — bus stays held for repeated-START |
| let result = match self.xfer_mode { |
| I2cXferMode::ByteMode => self.write_byte_mode(addr, bytes, false), |
| I2cXferMode::BufferMode => self.write_buffer_mode(addr, bytes, false), |
| I2cXferMode::DmaMode => self.write_dma_mode(addr, bytes, false), |
| }; |
| // Read — START on held bus = repeated-START |
| result.and_then(|()| 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 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], stop: bool) -> 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 (omitted when caller wants repeated-START) |
| if is_last && stop { |
| 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], stop: bool) -> 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 |
| .get(offset..offset + chunk_len) |
| .ok_or(I2cError::Invalid)?; |
| 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 (omitted when caller wants repeated-START) |
| if is_last && stop { |
| 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 = buffer |
| .get_mut(offset..offset + chunk_len) |
| .ok_or(I2cError::Invalid)?; |
| 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 { |
| // Workaround: master/slave packet mode TX_ACK stuck issue. |
| // When master gets TX_ACK mid-transaction (no STOP yet) while slave |
| // packet mode is active, the slave state machine latches a spurious |
| // RX_DONE and will NACK the next master byte. Pulse i2cs28 to clear it. |
| // Ref: Zephyr i2c_aspeed.c aspeed_i2c_master_irq() ~line 1284 |
| if status & (constants::AST_I2CM_TX_ACK | constants::AST_I2CM_NORMAL_STOP) |
| == constants::AST_I2CM_TX_ACK |
| { |
| if self.regs().i2cs28().read().enbl_slave_pkt_op_mode().bit() { |
| let slave_cmd = self.regs().i2cs28().read().bits(); |
| self.regs().i2cs28().write(|w| unsafe { w.bits(0) }); |
| self.regs().i2cs28().write(|w| unsafe { w.bits(slave_cmd) }); |
| } |
| } |
| |
| 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], stop: bool) -> 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 |
| .get(offset..offset + chunk_len) |
| .ok_or(I2cError::Invalid)?; |
| let is_first = offset == 0; |
| let is_last = offset + chunk_len >= total_len; |
| |
| // Copy chunk to master DMA buffer (non-cached SRAM) |
| { |
| let dma_buf = self |
| .master_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.master_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; |
| } |
| // Add STOP on last chunk (omitted when caller wants repeated-START) |
| if is_last && stop { |
| 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.master_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.master_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 master DMA buffer into caller's buffer |
| { |
| let dma_buf = self.master_dma_buf.as_deref().ok_or(I2cError::Invalid)?; |
| buffer |
| .get_mut(offset..offset + chunk_len) |
| .ok_or(I2cError::Invalid)? |
| .copy_from_slice(dma_buf.get(..chunk_len).ok_or(I2cError::Invalid)?); |
| } |
| |
| #[allow(clippy::cast_possible_truncation)] |
| { |
| self.current_xfer_cnt += chunk_len as u32; |
| } |
| offset += chunk_len; |
| } |
| |
| Ok(()) |
| } |
| } |