From 7cceb9bea421dddaecab53f8eee01230afea4c72 Mon Sep 17 00:00:00 2001 From: bunnie Date: Thu, 7 Nov 2024 16:00:20 +0800 Subject: [PATCH] add APIs and glue logic for implementation of shared I2C resource The API now provides, in addition to stand-alone read/write transactions, the ability to send a group of transactions that will be executed in-order without any other transactions in between. This allows better compatibility with devices that are designed assuming they are the sole target on the bus. Note that the current implementation does not thread out the I2C handler into its own thread so the service provider blocks until all the I2C transactions are done. Adding a thread to handle the I2C stuff would allow other requests to make progress but at the moment this isn't required. --- services/cram-hal-service/src/api.rs | 37 +++++++++++++++++++ services/cram-hal-service/src/lib.rs | 3 ++ services/cram-hal-service/src/main.rs | 51 ++++++++++++++++++++++++++- 3 files changed, 90 insertions(+), 1 deletion(-) diff --git a/services/cram-hal-service/src/api.rs b/services/cram-hal-service/src/api.rs index 5331969d3..6573037ed 100644 --- a/services/cram-hal-service/src/api.rs +++ b/services/cram-hal-service/src/api.rs @@ -41,6 +41,9 @@ pub enum Opcode { // blocking scalar ConfigureUdmaEvent = 8, + /// I2C operations + I2c = 9, + /// Exit server Quit = 255, @@ -105,3 +108,37 @@ pub struct IoxConfigMessage { pub slow_slew: Option, pub strength: Option, } + +#[derive(Debug, rkyv::Archive, rkyv::Serialize, rkyv::Deserialize, PartialEq, Eq)] +pub enum I2cTransactionType { + Write, + Read, + ReadRepeatedStart, +} + +#[derive(Debug, rkyv::Archive, rkyv::Serialize, rkyv::Deserialize)] +pub enum I2cResult { + /// For the outbound message holder + Pending, + /// Returns # of bytes read or written if successful + Ack(usize), + /// An error occurred. + Nack, +} + +#[derive(rkyv::Archive, rkyv::Serialize, rkyv::Deserialize)] +pub struct I2cTransaction { + pub i2c_type: I2cTransactionType, + pub device: u8, + pub address: u8, + pub data: Vec, + pub result: I2cResult, +} + +#[derive(rkyv::Archive, rkyv::Serialize, rkyv::Deserialize)] +pub struct I2cTransactions { + pub transactions: Vec, +} +impl From> for I2cTransactions { + fn from(value: Vec) -> Self { Self { transactions: value } } +} diff --git a/services/cram-hal-service/src/lib.rs b/services/cram-hal-service/src/lib.rs index f0b4e2e5a..4f78f8771 100644 --- a/services/cram-hal-service/src/lib.rs +++ b/services/cram-hal-service/src/lib.rs @@ -1,16 +1,19 @@ pub mod api; +pub mod i2c_lib; pub mod iox_lib; pub mod keyboard; pub mod trng; use api::Opcode; use cramium_hal::udma::{EventChannel, PeriphEventType, PeriphId, UdmaGlobalConfig}; +pub use i2c_lib::*; pub use iox_lib::*; use num_traits::*; /// Do not change this constant, it is hard-coded into libraries in order to break /// circular dependencies on the IFRAM block. pub const SERVER_NAME_CRAM_HAL: &str = "_Cramium-SoC HAL_"; +pub const PERCLK: u32 = 100_000_000; use core::sync::atomic::{AtomicU32, Ordering}; static REFCOUNT: AtomicU32 = AtomicU32::new(0); diff --git a/services/cram-hal-service/src/main.rs b/services/cram-hal-service/src/main.rs index 6975e7712..66d5dac47 100644 --- a/services/cram-hal-service/src/main.rs +++ b/services/cram-hal-service/src/main.rs @@ -4,7 +4,7 @@ mod hw; use api::*; use cramium_hal::{ iox, - udma::{EventChannel, GlobalConfig, PeriphId}, + udma::{EventChannel, GlobalConfig, I2cApi, PeriphId}, }; #[cfg(feature = "quantum-timer")] use utralib::utra; @@ -139,6 +139,27 @@ fn main() { .expect("couldn't map UDMA global control"); let udma_global = GlobalConfig::new(udma_global_csr.as_mut_ptr() as *mut u32); + let i2c_channel = cramium_hal::board::setup_i2c_pins(&iox); + udma_global.clock_on(PeriphId::from(i2c_channel)); + let i2c_pages = xous::syscall::map_memory( + xous::MemoryAddress::new(cramium_hal::board::I2C_IFRAM_ADDR), + None, + 4096, + xous::MemoryFlags::R | xous::MemoryFlags::W, + ) + .expect("couldn't claim I2C IFRAM page"); + + let i2c_ifram = unsafe { + cramium_hal::ifram::IframRange::from_raw_parts( + cramium_hal::board::I2C_IFRAM_ADDR, + i2c_pages.as_ptr() as usize, + i2c_pages.len(), + ) + }; + let mut i2c = unsafe { + cramium_hal::udma::I2c::new_with_ifram(i2c_channel, 400_000, cram_hal_service::PERCLK, i2c_ifram) + }; + // -------------------- begin timer workaround code // This code should go away with NTO as we have a proper, private ticktimer unit. let mut pio_ss = xous_pio::PioSharedState::new(); @@ -338,6 +359,34 @@ fn main() { udma_global.map_event_with_offset(periph, event_offset, to_channel); } } + Opcode::I2c => { + let mut buf = unsafe { + xous_ipc::Buffer::from_memory_message_mut(msg.body.memory_message_mut().unwrap()) + }; + let mut list = buf.to_original::().expect("I2c message format error"); + for transaction in list.transactions.iter_mut() { + match transaction.i2c_type { + I2cTransactionType::Write => { + match i2c.i2c_write(transaction.device, transaction.address, &transaction.data) { + Ok(b) => transaction.result = I2cResult::Ack(b), + _ => transaction.result = I2cResult::Nack, + } + } + I2cTransactionType::Read | I2cTransactionType::ReadRepeatedStart => { + match i2c.i2c_read( + transaction.device, + transaction.address, + &mut transaction.data, + transaction.i2c_type == I2cTransactionType::ReadRepeatedStart, + ) { + Ok(b) => transaction.result = I2cResult::Ack(b), + _ => transaction.result = I2cResult::Nack, + } + } + } + } + buf.replace(list).expect("I2c message format error"); + } Opcode::InvalidCall => { log::error!("Invalid opcode received: {:?}", msg); }