Skip to content

Commit

Permalink
apis/i2c_master: Initial support
Browse files Browse the repository at this point in the history
Signed-off-by: Alistair Francis <alistair.francis@wdc.com>
  • Loading branch information
alistair23 committed Feb 14, 2024
1 parent 3e5e672 commit cd8ecfb
Show file tree
Hide file tree
Showing 4 changed files with 238 additions and 0 deletions.
2 changes: 2 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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_leds = { path = "apis/leds" }
libtock_low_level_debug = { path = "apis/low_level_debug" }
Expand Down Expand Up @@ -60,6 +61,7 @@ members = [
"apis/buzzer",
"apis/console",
"apis/gpio",
"apis/i2c_master",
"apis/i2c_master_slave",
"apis/leds",
"apis/low_level_debug",
Expand Down
16 changes: 16 additions & 0 deletions apis/i2c_master/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
[package]
name = "libtock_i2c_master"
version = "0.1.0"
authors = [
"Tock Project Developers <tock-dev@googlegroups.com>",
"Alistair Francis <alistair.francis@wdc.com>",
]
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" }

216 changes: 216 additions & 0 deletions apis/i2c_master/src/lib.rs
Original file line number Diff line number Diff line change
@@ -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: Syscalls, C: Config = DefaultConfig>(S, C);

impl<S: Syscalls, C: Config> I2CMaster<S, C> {
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<Option<(u32, u32, u32)>> = 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::<C, DRIVER_NUM, { rw_allow::MASTER }>(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.into(),
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<Option<(u32, u32, u32)>> = 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::<C, DRIVER_NUM, { rw_allow::MASTER }>(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<Option<(u32, u32, u32)>> = 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::<C, DRIVER_NUM, { rw_allow::MASTER }>(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<T: platform::allow_ro::Config + platform::allow_rw::Config + platform::subscribe::Config>
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;
}
4 changes: 4 additions & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<super::runtime::TockSyscalls>;
}
pub mod i2c_master_slave {
use libtock_i2c_master_slave as i2c_master_slave;
pub type I2CMasterSlave = i2c_master_slave::I2CMasterSlave<super::runtime::TockSyscalls>;
Expand Down

0 comments on commit cd8ecfb

Please sign in to comment.