use std::path::Path;
use std::sync::Mutex;
use i2cdev::core::I2CDevice;
use i2cdev::linux::{LinuxI2CDevice, LinuxI2CError};
use hdmi_hal::scdc::ScdcTransport;
use crate::error::{I2cDevError, I2cErrorKind, I2cTransactionError, MessagePhase};
const SCDC_ADDRESS: u16 = 0x54;
pub(crate) trait SmbusDevice {
fn smbus_read_byte_data(&mut self, reg: u8) -> Result<u8, LinuxI2CError>;
fn smbus_write_byte_data(&mut self, reg: u8, value: u8) -> Result<(), LinuxI2CError>;
}
impl SmbusDevice for LinuxI2CDevice {
fn smbus_read_byte_data(&mut self, reg: u8) -> Result<u8, LinuxI2CError> {
I2CDevice::smbus_read_byte_data(self, reg)
}
fn smbus_write_byte_data(&mut self, reg: u8, value: u8) -> Result<(), LinuxI2CError> {
I2CDevice::smbus_write_byte_data(self, reg, value)
}
}
#[allow(private_bounds)]
#[derive(Debug)]
pub struct I2cDevTransport<D: SmbusDevice = LinuxI2CDevice> {
device: Mutex<D>,
}
impl I2cDevTransport<LinuxI2CDevice> {
pub fn open(path: impl AsRef<Path>) -> Result<Self, I2cDevError> {
let path = path.as_ref();
let device =
LinuxI2CDevice::new(path, SCDC_ADDRESS).map_err(|e| I2cDevError::DeviceOpenFailed {
path: path.to_path_buf(),
source: e.into(),
})?;
Ok(Self {
device: Mutex::new(device),
})
}
}
#[cfg(test)]
#[allow(private_bounds)]
impl<D: SmbusDevice> I2cDevTransport<D> {
pub(crate) fn from_device(device: D) -> Self {
Self {
device: Mutex::new(device),
}
}
}
impl<D: SmbusDevice> ScdcTransport for I2cDevTransport<D> {
type Error = I2cDevError;
fn read(&self, reg: u8) -> Result<u8, I2cDevError> {
let mut device = self
.device
.lock()
.expect("I2cDevTransport device mutex was poisoned");
device.smbus_read_byte_data(reg).map_err(|e| {
I2cDevError::Transaction(I2cTransactionError {
phase: MessagePhase::Write { register: reg },
kind: map_errno(to_raw_errno(e)),
})
})
}
fn write(&mut self, reg: u8, value: u8) -> Result<(), I2cDevError> {
let mut device = self
.device
.lock()
.expect("I2cDevTransport device mutex was poisoned");
device.smbus_write_byte_data(reg, value).map_err(|e| {
I2cDevError::Transaction(I2cTransactionError {
phase: MessagePhase::Write { register: reg },
kind: map_errno(to_raw_errno(e)),
})
})
}
}
fn to_raw_errno(e: LinuxI2CError) -> i32 {
match e {
LinuxI2CError::Errno(n) => n,
LinuxI2CError::Io(io) => io.raw_os_error().unwrap_or(0),
}
}
pub(crate) fn map_errno(errno: i32) -> I2cErrorKind {
match errno {
libc::ENXIO => I2cErrorKind::AddressNack,
libc::EAGAIN => I2cErrorKind::ArbitrationLost,
libc::ETIMEDOUT => I2cErrorKind::ClockStretchTimeout,
libc::EBUSY => I2cErrorKind::BusBusy,
libc::EPROTO => I2cErrorKind::BusProtocolError,
libc::ESHUTDOWN | libc::ENODEV => I2cErrorKind::AdapterGone,
e => I2cErrorKind::Unknown { errno: e },
}
}
#[cfg(test)]
#[path = "transport_tests.rs"]
mod tests;