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.
This commit is contained in:
bunnie 2024-11-07 16:00:20 +08:00
parent a11f2f7f54
commit 7cceb9bea4
3 changed files with 90 additions and 1 deletions

View file

@ -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<iox::IoxEnable>,
pub strength: Option<iox::IoxDriveStrength>,
}
#[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<u8>,
pub result: I2cResult,
}
#[derive(rkyv::Archive, rkyv::Serialize, rkyv::Deserialize)]
pub struct I2cTransactions {
pub transactions: Vec<I2cTransaction>,
}
impl From<Vec<I2cTransaction>> for I2cTransactions {
fn from(value: Vec<I2cTransaction>) -> Self { Self { transactions: value } }
}

View file

@ -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);

View file

@ -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::<I2cTransactions, _>().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);
}