diff --git a/Cargo.toml b/Cargo.toml index 9f27ebd6..584a5581 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -25,6 +25,7 @@ libtock_buzzer = { path = "apis/buzzer" } libtock_console = { path = "apis/console" } libtock_debug_panic = { path = "panic_handlers/debug_panic" } libtock_gpio = { path = "apis/gpio" } +libtock_i2c_master = { path = "apis/i2c_master" } libtock_i2c_master_slave = { path = "apis/i2c_master_slave" } libtock_key_value = { path = "apis/key_value" } libtock_leds = { path = "apis/leds" } @@ -61,6 +62,7 @@ members = [ "apis/buzzer", "apis/console", "apis/gpio", + "apis/i2c_master", "apis/i2c_master_slave", "apis/key_value", "apis/leds", diff --git a/apis/i2c_master/Cargo.toml b/apis/i2c_master/Cargo.toml new file mode 100644 index 00000000..9b22245d --- /dev/null +++ b/apis/i2c_master/Cargo.toml @@ -0,0 +1,16 @@ +[package] +name = "libtock_i2c_master" +version = "0.1.0" +authors = [ + "Tock Project Developers ", + "Alistair Francis ", +] +license = "Apache-2.0 OR MIT" +edition = "2021" +repository = "https://www.github.com/tock/libtock-rs" +rust-version.workspace = true +description = "libtock I2C master driver" + +[dependencies] +libtock_platform = { path = "../../platform" } + diff --git a/apis/i2c_master/src/lib.rs b/apis/i2c_master/src/lib.rs new file mode 100644 index 00000000..82eb8135 --- /dev/null +++ b/apis/i2c_master/src/lib.rs @@ -0,0 +1,216 @@ +#![no_std] + +use core::cell::Cell; +use libtock_platform as platform; +use libtock_platform::allow_rw::AllowRw; +use libtock_platform::share; +use libtock_platform::subscribe::Subscribe; +use libtock_platform::{DefaultConfig, ErrorCode, Syscalls}; + +pub struct I2CMaster(S, C); + +impl I2CMaster { + pub fn exists() -> Result<(), ErrorCode> { + S::command(DRIVER_NUM, i2c_master_cmd::EXISTS, 0, 0).to_result() + } + + /// # Summary + /// + /// Perform an I2C write followed by a read. + /// + /// TODO: Add async support + /// + /// # Parameter + /// + /// * `addr`: Slave device address + /// * `buf`: Buffer + /// * `w_len`: Number of bytes to write from @w_buf + /// * `r_len`: Number of bytes to read into @r_buf + /// + /// # Returns + /// On success: Returns Ok(()) + /// On failure: Err(ErrorCode) + pub fn i2c_master_write_read_sync( + addr: u16, + buf: &mut [u8], + w_len: u16, + r_len: u16, + ) -> Result<(), ErrorCode> { + if w_len as usize > buf.len() || r_len as usize > buf.len() { + return Err(ErrorCode::NoMem); + } + let called: Cell> = Cell::new(None); + let cmd_arg0: u32 = (w_len as u32) << 8 | addr as u32; + share::scope::< + ( + AllowRw<_, DRIVER_NUM, { rw_allow::MASTER }>, + Subscribe<_, DRIVER_NUM, { subscribe::MASTER_WRITE }>, + ), + _, + _, + >(|handle| { + let (allow_rw, subscribe) = handle.split(); + S::allow_rw::(allow_rw, buf)?; + S::subscribe::<_, _, C, DRIVER_NUM, { subscribe::MASTER_READ_WRITE }>( + subscribe, &called, + )?; + + S::command( + DRIVER_NUM, + i2c_master_cmd::MASTER_WRITE, + cmd_arg0, + r_len.into(), + ) + .to_result()?; + + loop { + S::yield_wait(); + if let Some((r0, status, _)) = called.get() { + assert_eq!(r0, 0); + return match status { + 0 => Ok(()), + e_status => Err(e_status.try_into().unwrap_or(ErrorCode::Fail)), + }; + } + } + }) + } + + /// # Summary + /// + /// Write to an I2C device the data from the buffer pointed by @buf. This function is + /// synchronous and returns only when the operation has completed. + /// + /// TODO: Add async support + /// + /// # Parameter + /// + /// * `addr`: Slave device address + /// * `buf`: Storage buffer, this should be bigger than @len + /// * `len`: Number of bytes to read into @buf + /// + /// # Returns + /// On success: Returns Ok(()) + /// On failure: Err(ErrorCode) + pub fn i2c_master_write_sync(addr: u16, buf: &mut [u8], len: u16) -> Result<(), ErrorCode> { + let called: Cell> = Cell::new(None); + share::scope::< + ( + AllowRw<_, DRIVER_NUM, { rw_allow::MASTER }>, + Subscribe<_, DRIVER_NUM, { subscribe::MASTER_WRITE }>, + ), + _, + _, + >(|handle| { + let (allow_rw, subscribe) = handle.split(); + S::allow_rw::(allow_rw, buf)?; + S::subscribe::<_, _, C, DRIVER_NUM, { subscribe::MASTER_WRITE }>(subscribe, &called)?; + + S::command( + DRIVER_NUM, + i2c_master_cmd::MASTER_WRITE, + addr.into(), + len.into(), + ) + .to_result()?; + + loop { + S::yield_wait(); + if let Some((r0, status, _)) = called.get() { + assert_eq!(r0, 0); + return match status { + 0 => Ok(()), + e_status => Err(e_status.try_into().unwrap_or(ErrorCode::Fail)), + }; + } + } + }) + } + + /// # Summary + /// + /// Read from an I2C device the data to the buffer pointed by @buf. This function is + /// synchronous and returns only when the operation has completed. + /// + /// TODO: Add async support + /// + /// # Parameter + /// + /// * `addr`: Slave device address + /// * `buf`: Storage buffer, this should be bigger than @len + /// * `len`: Number of bytes to read into @buf + /// + /// # Returns + /// On success: Returns Ok(()) + /// On failure: Err(ErrorCode) + pub fn i2c_master_read_sync(addr: u16, buf: &mut [u8], len: u16) -> Result<(), ErrorCode> { + let called: Cell> = Cell::new(None); + share::scope::< + ( + AllowRw<_, DRIVER_NUM, { rw_allow::MASTER }>, + Subscribe<_, DRIVER_NUM, { subscribe::MASTER_READ }>, + ), + _, + _, + >(|handle| { + let (allow_rw, subscribe) = handle.split(); + S::allow_rw::(allow_rw, buf)?; + S::subscribe::<_, _, C, DRIVER_NUM, { subscribe::MASTER_READ }>(subscribe, &called)?; + + S::command( + DRIVER_NUM, + i2c_master_cmd::MASTER_READ, + addr.into(), + len.into(), + ) + .to_result()?; + + loop { + S::yield_wait(); + if let Some((r0, status, _)) = called.get() { + assert_eq!(r0, 0); + return match status { + 0 => Ok(()), + e_status => Err(e_status.try_into().unwrap_or(ErrorCode::Fail)), + }; + } + } + }) + } +} + +/// System call configuration trait for `I2CMaster`. +pub trait Config: + platform::allow_ro::Config + platform::allow_rw::Config + platform::subscribe::Config +{ +} +impl + Config for T +{ +} + +// ----------------------------------------------------------------------------- +// Driver number and command IDs +// ----------------------------------------------------------------------------- +const DRIVER_NUM: u32 = 0x20003; + +#[allow(unused)] +mod subscribe { + pub const MASTER_READ: u32 = 0; + pub const MASTER_WRITE: u32 = 0; + pub const MASTER_READ_WRITE: u32 = 0; +} + +/// Ids for read-write allow buffers +#[allow(unused)] +mod rw_allow { + pub const MASTER: u32 = 1; +} + +#[allow(unused)] +mod i2c_master_cmd { + pub const EXISTS: u32 = 0; + pub const MASTER_WRITE: u32 = 1; + pub const MASTER_READ: u32 = 2; + pub const MASTER_WRITE_READ: u32 = 3; +} diff --git a/apis/i2c_master_slave/src/lib.rs b/apis/i2c_master_slave/src/lib.rs index c2d3c8c4..81a7ca3f 100644 --- a/apis/i2c_master_slave/src/lib.rs +++ b/apis/i2c_master_slave/src/lib.rs @@ -11,6 +11,10 @@ use libtock_platform::{DefaultConfig, ErrorCode, Syscalls}; pub struct I2CMasterSlave(S, C); impl I2CMasterSlave { + pub fn exists() -> Result<(), ErrorCode> { + S::command(DRIVER_NUM, i2c_master_slave_cmd::EXISTS, 0, 0).to_result() + } + /// # Summary /// /// Perform an I2C write to the slave device on @addr. @@ -427,6 +431,7 @@ mod i2c_buffers { #[allow(unused)] mod i2c_master_slave_cmd { + pub const EXISTS: u32 = 0; pub const MASTER_WRITE: u32 = 1; pub const MASTER_READ: u32 = 2; pub const SLAVE_START_LISTEN: u32 = 3; diff --git a/src/lib.rs b/src/lib.rs index d86765b7..90d0ff29 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -51,6 +51,10 @@ pub mod gpio { PullDown, PullNone, PullUp, }; } +pub mod i2c_master { + use libtock_i2c_master as i2c_master; + pub type I2CMaster = i2c_master::I2CMaster; +} pub mod i2c_master_slave { use libtock_i2c_master_slave as i2c_master_slave; pub type I2CMasterSlave = i2c_master_slave::I2CMasterSlave;