use embedded_hal::i2c::{self, I2c, Operation, SevenBitAddress};
use super::MCP2221;
use crate::Error;
use crate::constants::MAX_I2C_TRANSFER_PLUS_1;
impl i2c::Error for Error {
fn kind(&self) -> embedded_hal::i2c::ErrorKind {
use embedded_hal::i2c::NoAcknowledgeSource::Address;
match self {
Error::I2cAddressNack => i2c::ErrorKind::NoAcknowledge(Address),
_ => i2c::ErrorKind::Other,
}
}
}
impl i2c::ErrorType for MCP2221 {
type Error = Error;
}
fn same_operation_type(a: &Operation, b: &Operation) -> bool {
std::mem::discriminant(a) == std::mem::discriminant(b)
}
fn sum_and_check_lengths<'a>(
maybe_ops: Option<&'a &'a mut &'_ mut [Operation<'_>]>,
) -> Result<Option<usize>, Error> {
maybe_ops
.map(|ops| {
let sum: usize = ops
.iter()
.map(|op| match op {
Operation::Read(items) => items.len(),
Operation::Write(items) => items.len(),
})
.sum();
match sum {
MAX_I2C_TRANSFER_PLUS_1.. => Err(Error::I2cTransferTooLong),
0 => Err(Error::I2cTransferEmpty),
sum => Ok(sum),
}
})
.transpose()
}
type MaybeOps<'a, 'b, 'c> = Option<&'a mut &'b mut [Operation<'c>]>;
type WritesReads<'a, 'b, 'c> = (MaybeOps<'a, 'b, 'c>, MaybeOps<'a, 'b, 'c>);
fn try_get_valid_operations<'a, 'b, 'c>(
ops: &'a mut [&'b mut [Operation<'c>]],
) -> Result<WritesReads<'a, 'b, 'c>, Error> {
match ops {
[_, _, _, ..] => Err(Error::I2cUnsupportedEmbeddedHalTransaction),
[[Operation::Read(_), ..], [Operation::Write(_), ..]] => {
Err(Error::I2cUnsupportedEmbeddedHalTransaction)
}
[
writes @ [Operation::Write(_), ..],
reads @ [Operation::Read(_), ..],
] => Ok((Some(writes), Some(reads))),
[reads @ [Operation::Read(_), ..]] => Ok((None, Some(reads))),
[writes @ [Operation::Write(_), ..]] => Ok((Some(writes), None)),
_ => unreachable!(),
}
}
impl I2c<SevenBitAddress> for MCP2221 {
fn transaction(
&mut self,
address: SevenBitAddress,
operations: &mut [Operation<'_>],
) -> Result<(), Self::Error> {
let mut chunked: Vec<&mut [Operation<'_>]> =
operations.chunk_by_mut(same_operation_type).collect();
let (writes, reads) = try_get_valid_operations(chunked.as_mut_slice())?;
let write_length = sum_and_check_lengths(writes.as_ref())?;
let read_length = sum_and_check_lengths(reads.as_ref())?;
if let Some(writes) = writes {
let mut write_data: Vec<u8> = Vec::with_capacity(write_length.unwrap());
for op in writes.iter() {
let Operation::Write(buf) = op else {
unreachable!("Chunk checks ensure only writes here.")
};
write_data.extend_from_slice(buf);
}
if read_length.is_none() {
self.i2c_write(address, &write_data)?
} else {
self.i2c_write_no_stop(address, &write_data)?
};
}
if let Some(reads) = reads {
let mut our_buffer = vec![0u8; read_length.unwrap()];
if write_length.is_none() {
self.i2c_read(address, our_buffer.as_mut_slice())?;
} else {
self.i2c_read_repeated_start(address, our_buffer.as_mut_slice())?;
};
let mut copied_so_far = 0;
for op in reads.iter_mut() {
let Operation::Read(their_buffer) = op else {
unreachable!("Chunk checks ensure only reads here.");
};
let start = copied_so_far;
let end = start + their_buffer.len();
their_buffer.copy_from_slice(&our_buffer[start..end]);
copied_so_far = end;
}
}
Ok(())
}
fn read(&mut self, address: SevenBitAddress, read: &mut [u8]) -> Result<(), Self::Error> {
self.i2c_read(address, read)
}
fn write(&mut self, address: SevenBitAddress, write: &[u8]) -> Result<(), Self::Error> {
self.i2c_write(address, write)
}
fn write_read(
&mut self,
address: SevenBitAddress,
write: &[u8],
read: &mut [u8],
) -> Result<(), Self::Error> {
self.i2c_write_read(address, write, read)
}
}
#[cfg(feature = "async")]
mod eh_async {
use embedded_hal::i2c::{I2c as BlockingI2c, Operation};
use embedded_hal_async::i2c::I2c as AsyncI2c;
impl AsyncI2c for crate::MCP2221 {
async fn transaction(
&mut self,
address: u8,
operations: &mut [Operation<'_>],
) -> Result<(), Self::Error> {
BlockingI2c::transaction(self, address, operations)
}
async fn read(&mut self, address: u8, read: &mut [u8]) -> Result<(), Self::Error> {
BlockingI2c::read(self, address, read)
}
async fn write(&mut self, address: u8, write: &[u8]) -> Result<(), Self::Error> {
BlockingI2c::write(self, address, write)
}
async fn write_read(
&mut self,
address: u8,
write: &[u8],
read: &mut [u8],
) -> Result<(), Self::Error> {
BlockingI2c::write_read(self, address, write, read)
}
}
}