stm32/i2c: remove _timeout public API, share more code between v1/v2.
This commit is contained in:
parent
88e77c733c
commit
7044e53af4
@ -1,17 +1,23 @@
|
|||||||
#![macro_use]
|
#![macro_use]
|
||||||
|
|
||||||
use core::marker::PhantomData;
|
|
||||||
|
|
||||||
use crate::dma::NoDma;
|
|
||||||
use crate::interrupt;
|
|
||||||
|
|
||||||
#[cfg_attr(i2c_v1, path = "v1.rs")]
|
#[cfg_attr(i2c_v1, path = "v1.rs")]
|
||||||
#[cfg_attr(i2c_v2, path = "v2.rs")]
|
#[cfg_attr(i2c_v2, path = "v2.rs")]
|
||||||
mod _version;
|
mod _version;
|
||||||
pub use _version::*;
|
|
||||||
use embassy_sync::waitqueue::AtomicWaker;
|
|
||||||
|
|
||||||
use crate::peripherals;
|
use core::future::Future;
|
||||||
|
use core::marker::PhantomData;
|
||||||
|
|
||||||
|
use embassy_hal_internal::{into_ref, Peripheral, PeripheralRef};
|
||||||
|
use embassy_sync::waitqueue::AtomicWaker;
|
||||||
|
#[cfg(feature = "time")]
|
||||||
|
use embassy_time::{Duration, Instant};
|
||||||
|
|
||||||
|
use crate::dma::NoDma;
|
||||||
|
use crate::gpio::sealed::AFType;
|
||||||
|
use crate::gpio::Pull;
|
||||||
|
use crate::interrupt::typelevel::Interrupt;
|
||||||
|
use crate::time::Hertz;
|
||||||
|
use crate::{interrupt, peripherals};
|
||||||
|
|
||||||
/// I2C error.
|
/// I2C error.
|
||||||
#[derive(Debug, PartialEq, Eq)]
|
#[derive(Debug, PartialEq, Eq)]
|
||||||
@ -33,6 +39,148 @@ pub enum Error {
|
|||||||
ZeroLengthTransfer,
|
ZeroLengthTransfer,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// I2C config
|
||||||
|
#[non_exhaustive]
|
||||||
|
#[derive(Copy, Clone)]
|
||||||
|
pub struct Config {
|
||||||
|
/// Enable internal pullup on SDA.
|
||||||
|
///
|
||||||
|
/// Using external pullup resistors is recommended for I2C. If you do
|
||||||
|
/// have external pullups you should not enable this.
|
||||||
|
pub sda_pullup: bool,
|
||||||
|
/// Enable internal pullup on SCL.
|
||||||
|
///
|
||||||
|
/// Using external pullup resistors is recommended for I2C. If you do
|
||||||
|
/// have external pullups you should not enable this.
|
||||||
|
pub scl_pullup: bool,
|
||||||
|
/// Timeout.
|
||||||
|
#[cfg(feature = "time")]
|
||||||
|
pub timeout: embassy_time::Duration,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for Config {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self {
|
||||||
|
sda_pullup: false,
|
||||||
|
scl_pullup: false,
|
||||||
|
#[cfg(feature = "time")]
|
||||||
|
timeout: embassy_time::Duration::from_millis(1000),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// I2C driver.
|
||||||
|
pub struct I2c<'d, T: Instance, TXDMA = NoDma, RXDMA = NoDma> {
|
||||||
|
_peri: PeripheralRef<'d, T>,
|
||||||
|
#[allow(dead_code)]
|
||||||
|
tx_dma: PeripheralRef<'d, TXDMA>,
|
||||||
|
#[allow(dead_code)]
|
||||||
|
rx_dma: PeripheralRef<'d, RXDMA>,
|
||||||
|
#[cfg(feature = "time")]
|
||||||
|
timeout: Duration,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'d, T: Instance, TXDMA, RXDMA> I2c<'d, T, TXDMA, RXDMA> {
|
||||||
|
/// Create a new I2C driver.
|
||||||
|
pub fn new(
|
||||||
|
peri: impl Peripheral<P = T> + 'd,
|
||||||
|
scl: impl Peripheral<P = impl SclPin<T>> + 'd,
|
||||||
|
sda: impl Peripheral<P = impl SdaPin<T>> + 'd,
|
||||||
|
_irq: impl interrupt::typelevel::Binding<T::EventInterrupt, EventInterruptHandler<T>>
|
||||||
|
+ interrupt::typelevel::Binding<T::ErrorInterrupt, ErrorInterruptHandler<T>>
|
||||||
|
+ 'd,
|
||||||
|
tx_dma: impl Peripheral<P = TXDMA> + 'd,
|
||||||
|
rx_dma: impl Peripheral<P = RXDMA> + 'd,
|
||||||
|
freq: Hertz,
|
||||||
|
config: Config,
|
||||||
|
) -> Self {
|
||||||
|
into_ref!(peri, scl, sda, tx_dma, rx_dma);
|
||||||
|
|
||||||
|
T::enable_and_reset();
|
||||||
|
|
||||||
|
scl.set_as_af_pull(
|
||||||
|
scl.af_num(),
|
||||||
|
AFType::OutputOpenDrain,
|
||||||
|
match config.scl_pullup {
|
||||||
|
true => Pull::Up,
|
||||||
|
false => Pull::None,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
sda.set_as_af_pull(
|
||||||
|
sda.af_num(),
|
||||||
|
AFType::OutputOpenDrain,
|
||||||
|
match config.sda_pullup {
|
||||||
|
true => Pull::Up,
|
||||||
|
false => Pull::None,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
unsafe { T::EventInterrupt::enable() };
|
||||||
|
unsafe { T::ErrorInterrupt::enable() };
|
||||||
|
|
||||||
|
let mut this = Self {
|
||||||
|
_peri: peri,
|
||||||
|
tx_dma,
|
||||||
|
rx_dma,
|
||||||
|
#[cfg(feature = "time")]
|
||||||
|
timeout: config.timeout,
|
||||||
|
};
|
||||||
|
|
||||||
|
this.init(freq, config);
|
||||||
|
|
||||||
|
this
|
||||||
|
}
|
||||||
|
|
||||||
|
fn timeout(&self) -> Timeout {
|
||||||
|
Timeout {
|
||||||
|
#[cfg(feature = "time")]
|
||||||
|
deadline: Instant::now() + self.timeout,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Copy, Clone)]
|
||||||
|
struct Timeout {
|
||||||
|
#[cfg(feature = "time")]
|
||||||
|
deadline: Instant,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(dead_code)]
|
||||||
|
impl Timeout {
|
||||||
|
#[cfg(not(feature = "time"))]
|
||||||
|
#[inline]
|
||||||
|
fn check(self) -> Result<(), Error> {
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "time")]
|
||||||
|
#[inline]
|
||||||
|
fn check(self) -> Result<(), Error> {
|
||||||
|
if Instant::now() > self.deadline {
|
||||||
|
Err(Error::Timeout)
|
||||||
|
} else {
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(not(feature = "time"))]
|
||||||
|
#[inline]
|
||||||
|
fn with<R>(self, fut: impl Future<Output = Result<R, Error>>) -> impl Future<Output = Result<R, Error>> {
|
||||||
|
fut
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "time")]
|
||||||
|
#[inline]
|
||||||
|
fn with<R>(self, fut: impl Future<Output = Result<R, Error>>) -> impl Future<Output = Result<R, Error>> {
|
||||||
|
use futures::FutureExt;
|
||||||
|
|
||||||
|
embassy_futures::select::select(embassy_time::Timer::at(self.deadline), fut).map(|r| match r {
|
||||||
|
embassy_futures::select::Either::First(_) => Err(Error::Timeout),
|
||||||
|
embassy_futures::select::Either::Second(r) => r,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub(crate) mod sealed {
|
pub(crate) mod sealed {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
|
@ -1,20 +1,14 @@
|
|||||||
use core::future::poll_fn;
|
use core::future::poll_fn;
|
||||||
use core::marker::PhantomData;
|
|
||||||
use core::task::Poll;
|
use core::task::Poll;
|
||||||
|
|
||||||
use embassy_embedded_hal::SetConfig;
|
use embassy_embedded_hal::SetConfig;
|
||||||
use embassy_futures::select::{select, Either};
|
use embassy_futures::select::{select, Either};
|
||||||
use embassy_hal_internal::drop::OnDrop;
|
use embassy_hal_internal::drop::OnDrop;
|
||||||
use embassy_hal_internal::{into_ref, PeripheralRef};
|
|
||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
use crate::dma::{NoDma, Transfer};
|
use crate::dma::Transfer;
|
||||||
use crate::gpio::sealed::AFType;
|
|
||||||
use crate::gpio::Pull;
|
|
||||||
use crate::interrupt::typelevel::Interrupt;
|
|
||||||
use crate::pac::i2c;
|
use crate::pac::i2c;
|
||||||
use crate::time::Hertz;
|
use crate::time::Hertz;
|
||||||
use crate::{interrupt, Peripheral};
|
|
||||||
|
|
||||||
pub unsafe fn on_interrupt<T: Instance>() {
|
pub unsafe fn on_interrupt<T: Instance>() {
|
||||||
let regs = T::regs();
|
let regs = T::regs();
|
||||||
@ -30,55 +24,8 @@ pub unsafe fn on_interrupt<T: Instance>() {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
#[non_exhaustive]
|
|
||||||
#[derive(Copy, Clone, Default)]
|
|
||||||
pub struct Config {
|
|
||||||
pub sda_pullup: bool,
|
|
||||||
pub scl_pullup: bool,
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct I2c<'d, T: Instance, TXDMA = NoDma, RXDMA = NoDma> {
|
|
||||||
phantom: PhantomData<&'d mut T>,
|
|
||||||
#[allow(dead_code)]
|
|
||||||
tx_dma: PeripheralRef<'d, TXDMA>,
|
|
||||||
#[allow(dead_code)]
|
|
||||||
rx_dma: PeripheralRef<'d, RXDMA>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'d, T: Instance, TXDMA, RXDMA> I2c<'d, T, TXDMA, RXDMA> {
|
impl<'d, T: Instance, TXDMA, RXDMA> I2c<'d, T, TXDMA, RXDMA> {
|
||||||
pub fn new(
|
pub(crate) fn init(&mut self, freq: Hertz, _config: Config) {
|
||||||
_peri: impl Peripheral<P = T> + 'd,
|
|
||||||
scl: impl Peripheral<P = impl SclPin<T>> + 'd,
|
|
||||||
sda: impl Peripheral<P = impl SdaPin<T>> + 'd,
|
|
||||||
_irq: impl interrupt::typelevel::Binding<T::EventInterrupt, EventInterruptHandler<T>>
|
|
||||||
+ interrupt::typelevel::Binding<T::ErrorInterrupt, ErrorInterruptHandler<T>>
|
|
||||||
+ 'd,
|
|
||||||
tx_dma: impl Peripheral<P = TXDMA> + 'd,
|
|
||||||
rx_dma: impl Peripheral<P = RXDMA> + 'd,
|
|
||||||
freq: Hertz,
|
|
||||||
config: Config,
|
|
||||||
) -> Self {
|
|
||||||
into_ref!(scl, sda, tx_dma, rx_dma);
|
|
||||||
|
|
||||||
T::enable_and_reset();
|
|
||||||
|
|
||||||
scl.set_as_af_pull(
|
|
||||||
scl.af_num(),
|
|
||||||
AFType::OutputOpenDrain,
|
|
||||||
match config.scl_pullup {
|
|
||||||
true => Pull::Up,
|
|
||||||
false => Pull::None,
|
|
||||||
},
|
|
||||||
);
|
|
||||||
sda.set_as_af_pull(
|
|
||||||
sda.af_num(),
|
|
||||||
AFType::OutputOpenDrain,
|
|
||||||
match config.sda_pullup {
|
|
||||||
true => Pull::Up,
|
|
||||||
false => Pull::None,
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
T::regs().cr1().modify(|reg| {
|
T::regs().cr1().modify(|reg| {
|
||||||
reg.set_pe(false);
|
reg.set_pe(false);
|
||||||
//reg.set_anfoff(false);
|
//reg.set_anfoff(false);
|
||||||
@ -101,15 +48,6 @@ impl<'d, T: Instance, TXDMA, RXDMA> I2c<'d, T, TXDMA, RXDMA> {
|
|||||||
T::regs().cr1().modify(|reg| {
|
T::regs().cr1().modify(|reg| {
|
||||||
reg.set_pe(true);
|
reg.set_pe(true);
|
||||||
});
|
});
|
||||||
|
|
||||||
unsafe { T::EventInterrupt::enable() };
|
|
||||||
unsafe { T::ErrorInterrupt::enable() };
|
|
||||||
|
|
||||||
Self {
|
|
||||||
phantom: PhantomData,
|
|
||||||
tx_dma,
|
|
||||||
rx_dma,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn check_and_clear_error_flags() -> Result<i2c::regs::Sr1, Error> {
|
fn check_and_clear_error_flags() -> Result<i2c::regs::Sr1, Error> {
|
||||||
@ -169,12 +107,7 @@ impl<'d, T: Instance, TXDMA, RXDMA> I2c<'d, T, TXDMA, RXDMA> {
|
|||||||
Ok(sr1)
|
Ok(sr1)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn write_bytes(
|
fn write_bytes(&mut self, addr: u8, bytes: &[u8], timeout: Timeout) -> Result<(), Error> {
|
||||||
&mut self,
|
|
||||||
addr: u8,
|
|
||||||
bytes: &[u8],
|
|
||||||
check_timeout: impl Fn() -> Result<(), Error>,
|
|
||||||
) -> Result<(), Error> {
|
|
||||||
// Send a START condition
|
// Send a START condition
|
||||||
|
|
||||||
T::regs().cr1().modify(|reg| {
|
T::regs().cr1().modify(|reg| {
|
||||||
@ -183,7 +116,7 @@ impl<'d, T: Instance, TXDMA, RXDMA> I2c<'d, T, TXDMA, RXDMA> {
|
|||||||
|
|
||||||
// Wait until START condition was generated
|
// Wait until START condition was generated
|
||||||
while !Self::check_and_clear_error_flags()?.start() {
|
while !Self::check_and_clear_error_flags()?.start() {
|
||||||
check_timeout()?;
|
timeout.check()?;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Also wait until signalled we're master and everything is waiting for us
|
// Also wait until signalled we're master and everything is waiting for us
|
||||||
@ -193,7 +126,7 @@ impl<'d, T: Instance, TXDMA, RXDMA> I2c<'d, T, TXDMA, RXDMA> {
|
|||||||
let sr2 = T::regs().sr2().read();
|
let sr2 = T::regs().sr2().read();
|
||||||
!sr2.msl() && !sr2.busy()
|
!sr2.msl() && !sr2.busy()
|
||||||
} {
|
} {
|
||||||
check_timeout()?;
|
timeout.check()?;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set up current address, we're trying to talk to
|
// Set up current address, we're trying to talk to
|
||||||
@ -203,7 +136,7 @@ impl<'d, T: Instance, TXDMA, RXDMA> I2c<'d, T, TXDMA, RXDMA> {
|
|||||||
// Wait for the address to be acknowledged
|
// Wait for the address to be acknowledged
|
||||||
// Check for any I2C errors. If a NACK occurs, the ADDR bit will never be set.
|
// Check for any I2C errors. If a NACK occurs, the ADDR bit will never be set.
|
||||||
while !Self::check_and_clear_error_flags()?.addr() {
|
while !Self::check_and_clear_error_flags()?.addr() {
|
||||||
check_timeout()?;
|
timeout.check()?;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Clear condition by reading SR2
|
// Clear condition by reading SR2
|
||||||
@ -211,20 +144,20 @@ impl<'d, T: Instance, TXDMA, RXDMA> I2c<'d, T, TXDMA, RXDMA> {
|
|||||||
|
|
||||||
// Send bytes
|
// Send bytes
|
||||||
for c in bytes {
|
for c in bytes {
|
||||||
self.send_byte(*c, &check_timeout)?;
|
self.send_byte(*c, timeout)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Fallthrough is success
|
// Fallthrough is success
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn send_byte(&self, byte: u8, check_timeout: impl Fn() -> Result<(), Error>) -> Result<(), Error> {
|
fn send_byte(&self, byte: u8, timeout: Timeout) -> Result<(), Error> {
|
||||||
// Wait until we're ready for sending
|
// Wait until we're ready for sending
|
||||||
while {
|
while {
|
||||||
// Check for any I2C errors. If a NACK occurs, the ADDR bit will never be set.
|
// Check for any I2C errors. If a NACK occurs, the ADDR bit will never be set.
|
||||||
!Self::check_and_clear_error_flags()?.txe()
|
!Self::check_and_clear_error_flags()?.txe()
|
||||||
} {
|
} {
|
||||||
check_timeout()?;
|
timeout.check()?;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Push out a byte of data
|
// Push out a byte of data
|
||||||
@ -235,32 +168,27 @@ impl<'d, T: Instance, TXDMA, RXDMA> I2c<'d, T, TXDMA, RXDMA> {
|
|||||||
// Check for any potential error conditions.
|
// Check for any potential error conditions.
|
||||||
!Self::check_and_clear_error_flags()?.btf()
|
!Self::check_and_clear_error_flags()?.btf()
|
||||||
} {
|
} {
|
||||||
check_timeout()?;
|
timeout.check()?;
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn recv_byte(&self, check_timeout: impl Fn() -> Result<(), Error>) -> Result<u8, Error> {
|
fn recv_byte(&self, timeout: Timeout) -> Result<u8, Error> {
|
||||||
while {
|
while {
|
||||||
// Check for any potential error conditions.
|
// Check for any potential error conditions.
|
||||||
Self::check_and_clear_error_flags()?;
|
Self::check_and_clear_error_flags()?;
|
||||||
|
|
||||||
!T::regs().sr1().read().rxne()
|
!T::regs().sr1().read().rxne()
|
||||||
} {
|
} {
|
||||||
check_timeout()?;
|
timeout.check()?;
|
||||||
}
|
}
|
||||||
|
|
||||||
let value = T::regs().dr().read().dr();
|
let value = T::regs().dr().read().dr();
|
||||||
Ok(value)
|
Ok(value)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn blocking_read_timeout(
|
fn blocking_read_timeout(&mut self, addr: u8, buffer: &mut [u8], timeout: Timeout) -> Result<(), Error> {
|
||||||
&mut self,
|
|
||||||
addr: u8,
|
|
||||||
buffer: &mut [u8],
|
|
||||||
check_timeout: impl Fn() -> Result<(), Error>,
|
|
||||||
) -> Result<(), Error> {
|
|
||||||
if let Some((last, buffer)) = buffer.split_last_mut() {
|
if let Some((last, buffer)) = buffer.split_last_mut() {
|
||||||
// Send a START condition and set ACK bit
|
// Send a START condition and set ACK bit
|
||||||
T::regs().cr1().modify(|reg| {
|
T::regs().cr1().modify(|reg| {
|
||||||
@ -270,7 +198,7 @@ impl<'d, T: Instance, TXDMA, RXDMA> I2c<'d, T, TXDMA, RXDMA> {
|
|||||||
|
|
||||||
// Wait until START condition was generated
|
// Wait until START condition was generated
|
||||||
while !Self::check_and_clear_error_flags()?.start() {
|
while !Self::check_and_clear_error_flags()?.start() {
|
||||||
check_timeout()?;
|
timeout.check()?;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Also wait until signalled we're master and everything is waiting for us
|
// Also wait until signalled we're master and everything is waiting for us
|
||||||
@ -278,7 +206,7 @@ impl<'d, T: Instance, TXDMA, RXDMA> I2c<'d, T, TXDMA, RXDMA> {
|
|||||||
let sr2 = T::regs().sr2().read();
|
let sr2 = T::regs().sr2().read();
|
||||||
!sr2.msl() && !sr2.busy()
|
!sr2.msl() && !sr2.busy()
|
||||||
} {
|
} {
|
||||||
check_timeout()?;
|
timeout.check()?;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set up current address, we're trying to talk to
|
// Set up current address, we're trying to talk to
|
||||||
@ -287,7 +215,7 @@ impl<'d, T: Instance, TXDMA, RXDMA> I2c<'d, T, TXDMA, RXDMA> {
|
|||||||
// Wait until address was sent
|
// Wait until address was sent
|
||||||
// Wait for the address to be acknowledged
|
// Wait for the address to be acknowledged
|
||||||
while !Self::check_and_clear_error_flags()?.addr() {
|
while !Self::check_and_clear_error_flags()?.addr() {
|
||||||
check_timeout()?;
|
timeout.check()?;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Clear condition by reading SR2
|
// Clear condition by reading SR2
|
||||||
@ -295,7 +223,7 @@ impl<'d, T: Instance, TXDMA, RXDMA> I2c<'d, T, TXDMA, RXDMA> {
|
|||||||
|
|
||||||
// Receive bytes into buffer
|
// Receive bytes into buffer
|
||||||
for c in buffer {
|
for c in buffer {
|
||||||
*c = self.recv_byte(&check_timeout)?;
|
*c = self.recv_byte(timeout)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Prepare to send NACK then STOP after next byte
|
// Prepare to send NACK then STOP after next byte
|
||||||
@ -305,11 +233,11 @@ impl<'d, T: Instance, TXDMA, RXDMA> I2c<'d, T, TXDMA, RXDMA> {
|
|||||||
});
|
});
|
||||||
|
|
||||||
// Receive last byte
|
// Receive last byte
|
||||||
*last = self.recv_byte(&check_timeout)?;
|
*last = self.recv_byte(timeout)?;
|
||||||
|
|
||||||
// Wait for the STOP to be sent.
|
// Wait for the STOP to be sent.
|
||||||
while T::regs().cr1().read().stop() {
|
while T::regs().cr1().read().stop() {
|
||||||
check_timeout()?;
|
timeout.check()?;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Fallthrough is success
|
// Fallthrough is success
|
||||||
@ -320,48 +248,33 @@ impl<'d, T: Instance, TXDMA, RXDMA> I2c<'d, T, TXDMA, RXDMA> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn blocking_read(&mut self, addr: u8, read: &mut [u8]) -> Result<(), Error> {
|
pub fn blocking_read(&mut self, addr: u8, read: &mut [u8]) -> Result<(), Error> {
|
||||||
self.blocking_read_timeout(addr, read, || Ok(()))
|
self.blocking_read_timeout(addr, read, self.timeout())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn blocking_write_timeout(
|
pub fn blocking_write(&mut self, addr: u8, write: &[u8]) -> Result<(), Error> {
|
||||||
&mut self,
|
let timeout = self.timeout();
|
||||||
addr: u8,
|
|
||||||
write: &[u8],
|
self.write_bytes(addr, write, timeout)?;
|
||||||
check_timeout: impl Fn() -> Result<(), Error>,
|
|
||||||
) -> Result<(), Error> {
|
|
||||||
self.write_bytes(addr, write, &check_timeout)?;
|
|
||||||
// Send a STOP condition
|
// Send a STOP condition
|
||||||
T::regs().cr1().modify(|reg| reg.set_stop(true));
|
T::regs().cr1().modify(|reg| reg.set_stop(true));
|
||||||
// Wait for STOP condition to transmit.
|
// Wait for STOP condition to transmit.
|
||||||
while T::regs().cr1().read().stop() {
|
while T::regs().cr1().read().stop() {
|
||||||
check_timeout()?;
|
timeout.check()?;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Fallthrough is success
|
// Fallthrough is success
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn blocking_write(&mut self, addr: u8, write: &[u8]) -> Result<(), Error> {
|
pub fn blocking_write_read(&mut self, addr: u8, write: &[u8], read: &mut [u8]) -> Result<(), Error> {
|
||||||
self.blocking_write_timeout(addr, write, || Ok(()))
|
let timeout = self.timeout();
|
||||||
}
|
|
||||||
|
|
||||||
pub fn blocking_write_read_timeout(
|
self.write_bytes(addr, write, timeout)?;
|
||||||
&mut self,
|
self.blocking_read_timeout(addr, read, timeout)?;
|
||||||
addr: u8,
|
|
||||||
write: &[u8],
|
|
||||||
read: &mut [u8],
|
|
||||||
check_timeout: impl Fn() -> Result<(), Error>,
|
|
||||||
) -> Result<(), Error> {
|
|
||||||
self.write_bytes(addr, write, &check_timeout)?;
|
|
||||||
self.blocking_read_timeout(addr, read, &check_timeout)?;
|
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn blocking_write_read(&mut self, addr: u8, write: &[u8], read: &mut [u8]) -> Result<(), Error> {
|
|
||||||
self.blocking_write_read_timeout(addr, write, read, || Ok(()))
|
|
||||||
}
|
|
||||||
|
|
||||||
// Async
|
// Async
|
||||||
|
|
||||||
#[inline] // pretty sure this should always be inlined
|
#[inline] // pretty sure this should always be inlined
|
||||||
|
@ -4,37 +4,13 @@ use core::task::Poll;
|
|||||||
|
|
||||||
use embassy_embedded_hal::SetConfig;
|
use embassy_embedded_hal::SetConfig;
|
||||||
use embassy_hal_internal::drop::OnDrop;
|
use embassy_hal_internal::drop::OnDrop;
|
||||||
use embassy_hal_internal::{into_ref, PeripheralRef};
|
|
||||||
#[cfg(feature = "time")]
|
|
||||||
use embassy_time::{Duration, Instant};
|
|
||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
use crate::dma::{NoDma, Transfer};
|
use crate::dma::Transfer;
|
||||||
use crate::gpio::sealed::AFType;
|
|
||||||
use crate::gpio::Pull;
|
|
||||||
use crate::interrupt::typelevel::Interrupt;
|
|
||||||
use crate::pac::i2c;
|
use crate::pac::i2c;
|
||||||
use crate::time::Hertz;
|
use crate::time::Hertz;
|
||||||
use crate::{interrupt, Peripheral};
|
|
||||||
|
|
||||||
#[cfg(feature = "time")]
|
pub(crate) unsafe fn on_interrupt<T: Instance>() {
|
||||||
fn timeout_fn(timeout: Duration) -> impl Fn() -> Result<(), Error> {
|
|
||||||
let deadline = Instant::now() + timeout;
|
|
||||||
move || {
|
|
||||||
if Instant::now() > deadline {
|
|
||||||
Err(Error::Timeout)
|
|
||||||
} else {
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(not(feature = "time"))]
|
|
||||||
pub fn no_timeout_fn() -> impl Fn() -> Result<(), Error> {
|
|
||||||
move || Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
pub unsafe fn on_interrupt<T: Instance>() {
|
|
||||||
let regs = T::regs();
|
let regs = T::regs();
|
||||||
let isr = regs.isr().read();
|
let isr = regs.isr().read();
|
||||||
|
|
||||||
@ -48,70 +24,8 @@ pub unsafe fn on_interrupt<T: Instance>() {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
#[non_exhaustive]
|
|
||||||
#[derive(Copy, Clone)]
|
|
||||||
pub struct Config {
|
|
||||||
pub sda_pullup: bool,
|
|
||||||
pub scl_pullup: bool,
|
|
||||||
#[cfg(feature = "time")]
|
|
||||||
pub transaction_timeout: Duration,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Default for Config {
|
|
||||||
fn default() -> Self {
|
|
||||||
Self {
|
|
||||||
sda_pullup: false,
|
|
||||||
scl_pullup: false,
|
|
||||||
#[cfg(feature = "time")]
|
|
||||||
transaction_timeout: Duration::from_millis(100),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct I2c<'d, T: Instance, TXDMA = NoDma, RXDMA = NoDma> {
|
|
||||||
_peri: PeripheralRef<'d, T>,
|
|
||||||
#[allow(dead_code)]
|
|
||||||
tx_dma: PeripheralRef<'d, TXDMA>,
|
|
||||||
#[allow(dead_code)]
|
|
||||||
rx_dma: PeripheralRef<'d, RXDMA>,
|
|
||||||
#[cfg(feature = "time")]
|
|
||||||
timeout: Duration,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'d, T: Instance, TXDMA, RXDMA> I2c<'d, T, TXDMA, RXDMA> {
|
impl<'d, T: Instance, TXDMA, RXDMA> I2c<'d, T, TXDMA, RXDMA> {
|
||||||
pub fn new(
|
pub(crate) fn init(&mut self, freq: Hertz, _config: Config) {
|
||||||
peri: impl Peripheral<P = T> + 'd,
|
|
||||||
scl: impl Peripheral<P = impl SclPin<T>> + 'd,
|
|
||||||
sda: impl Peripheral<P = impl SdaPin<T>> + 'd,
|
|
||||||
_irq: impl interrupt::typelevel::Binding<T::EventInterrupt, EventInterruptHandler<T>>
|
|
||||||
+ interrupt::typelevel::Binding<T::ErrorInterrupt, ErrorInterruptHandler<T>>
|
|
||||||
+ 'd,
|
|
||||||
tx_dma: impl Peripheral<P = TXDMA> + 'd,
|
|
||||||
rx_dma: impl Peripheral<P = RXDMA> + 'd,
|
|
||||||
freq: Hertz,
|
|
||||||
config: Config,
|
|
||||||
) -> Self {
|
|
||||||
into_ref!(peri, scl, sda, tx_dma, rx_dma);
|
|
||||||
|
|
||||||
T::enable_and_reset();
|
|
||||||
|
|
||||||
scl.set_as_af_pull(
|
|
||||||
scl.af_num(),
|
|
||||||
AFType::OutputOpenDrain,
|
|
||||||
match config.scl_pullup {
|
|
||||||
true => Pull::Up,
|
|
||||||
false => Pull::None,
|
|
||||||
},
|
|
||||||
);
|
|
||||||
sda.set_as_af_pull(
|
|
||||||
sda.af_num(),
|
|
||||||
AFType::OutputOpenDrain,
|
|
||||||
match config.sda_pullup {
|
|
||||||
true => Pull::Up,
|
|
||||||
false => Pull::None,
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
T::regs().cr1().modify(|reg| {
|
T::regs().cr1().modify(|reg| {
|
||||||
reg.set_pe(false);
|
reg.set_pe(false);
|
||||||
reg.set_anfoff(false);
|
reg.set_anfoff(false);
|
||||||
@ -130,17 +44,6 @@ impl<'d, T: Instance, TXDMA, RXDMA> I2c<'d, T, TXDMA, RXDMA> {
|
|||||||
T::regs().cr1().modify(|reg| {
|
T::regs().cr1().modify(|reg| {
|
||||||
reg.set_pe(true);
|
reg.set_pe(true);
|
||||||
});
|
});
|
||||||
|
|
||||||
unsafe { T::EventInterrupt::enable() };
|
|
||||||
unsafe { T::ErrorInterrupt::enable() };
|
|
||||||
|
|
||||||
Self {
|
|
||||||
_peri: peri,
|
|
||||||
tx_dma,
|
|
||||||
rx_dma,
|
|
||||||
#[cfg(feature = "time")]
|
|
||||||
timeout: config.transaction_timeout,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn master_stop(&mut self) {
|
fn master_stop(&mut self) {
|
||||||
@ -153,7 +56,7 @@ impl<'d, T: Instance, TXDMA, RXDMA> I2c<'d, T, TXDMA, RXDMA> {
|
|||||||
stop: Stop,
|
stop: Stop,
|
||||||
reload: bool,
|
reload: bool,
|
||||||
restart: bool,
|
restart: bool,
|
||||||
check_timeout: impl Fn() -> Result<(), Error>,
|
timeout: Timeout,
|
||||||
) -> Result<(), Error> {
|
) -> Result<(), Error> {
|
||||||
assert!(length < 256);
|
assert!(length < 256);
|
||||||
|
|
||||||
@ -162,7 +65,7 @@ impl<'d, T: Instance, TXDMA, RXDMA> I2c<'d, T, TXDMA, RXDMA> {
|
|||||||
// automatically. This could be up to 50% of a bus
|
// automatically. This could be up to 50% of a bus
|
||||||
// cycle (ie. up to 0.5/freq)
|
// cycle (ie. up to 0.5/freq)
|
||||||
while T::regs().cr2().read().start() {
|
while T::regs().cr2().read().start() {
|
||||||
check_timeout()?;
|
timeout.check()?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -189,20 +92,14 @@ impl<'d, T: Instance, TXDMA, RXDMA> I2c<'d, T, TXDMA, RXDMA> {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn master_write(
|
fn master_write(address: u8, length: usize, stop: Stop, reload: bool, timeout: Timeout) -> Result<(), Error> {
|
||||||
address: u8,
|
|
||||||
length: usize,
|
|
||||||
stop: Stop,
|
|
||||||
reload: bool,
|
|
||||||
check_timeout: impl Fn() -> Result<(), Error>,
|
|
||||||
) -> Result<(), Error> {
|
|
||||||
assert!(length < 256);
|
assert!(length < 256);
|
||||||
|
|
||||||
// Wait for any previous address sequence to end
|
// Wait for any previous address sequence to end
|
||||||
// automatically. This could be up to 50% of a bus
|
// automatically. This could be up to 50% of a bus
|
||||||
// cycle (ie. up to 0.5/freq)
|
// cycle (ie. up to 0.5/freq)
|
||||||
while T::regs().cr2().read().start() {
|
while T::regs().cr2().read().start() {
|
||||||
check_timeout()?;
|
timeout.check()?;
|
||||||
}
|
}
|
||||||
|
|
||||||
let reload = if reload {
|
let reload = if reload {
|
||||||
@ -227,15 +124,11 @@ impl<'d, T: Instance, TXDMA, RXDMA> I2c<'d, T, TXDMA, RXDMA> {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn master_continue(
|
fn master_continue(length: usize, reload: bool, timeout: Timeout) -> Result<(), Error> {
|
||||||
length: usize,
|
|
||||||
reload: bool,
|
|
||||||
check_timeout: impl Fn() -> Result<(), Error>,
|
|
||||||
) -> Result<(), Error> {
|
|
||||||
assert!(length < 256 && length > 0);
|
assert!(length < 256 && length > 0);
|
||||||
|
|
||||||
while !T::regs().isr().read().tcr() {
|
while !T::regs().isr().read().tcr() {
|
||||||
check_timeout()?;
|
timeout.check()?;
|
||||||
}
|
}
|
||||||
|
|
||||||
let reload = if reload {
|
let reload = if reload {
|
||||||
@ -261,7 +154,7 @@ impl<'d, T: Instance, TXDMA, RXDMA> I2c<'d, T, TXDMA, RXDMA> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn wait_txe(&self, check_timeout: impl Fn() -> Result<(), Error>) -> Result<(), Error> {
|
fn wait_txe(&self, timeout: Timeout) -> Result<(), Error> {
|
||||||
loop {
|
loop {
|
||||||
let isr = T::regs().isr().read();
|
let isr = T::regs().isr().read();
|
||||||
if isr.txe() {
|
if isr.txe() {
|
||||||
@ -278,11 +171,11 @@ impl<'d, T: Instance, TXDMA, RXDMA> I2c<'d, T, TXDMA, RXDMA> {
|
|||||||
return Err(Error::Nack);
|
return Err(Error::Nack);
|
||||||
}
|
}
|
||||||
|
|
||||||
check_timeout()?;
|
timeout.check()?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn wait_rxne(&self, check_timeout: impl Fn() -> Result<(), Error>) -> Result<(), Error> {
|
fn wait_rxne(&self, timeout: Timeout) -> Result<(), Error> {
|
||||||
loop {
|
loop {
|
||||||
let isr = T::regs().isr().read();
|
let isr = T::regs().isr().read();
|
||||||
if isr.rxne() {
|
if isr.rxne() {
|
||||||
@ -299,11 +192,11 @@ impl<'d, T: Instance, TXDMA, RXDMA> I2c<'d, T, TXDMA, RXDMA> {
|
|||||||
return Err(Error::Nack);
|
return Err(Error::Nack);
|
||||||
}
|
}
|
||||||
|
|
||||||
check_timeout()?;
|
timeout.check()?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn wait_tc(&self, check_timeout: impl Fn() -> Result<(), Error>) -> Result<(), Error> {
|
fn wait_tc(&self, timeout: Timeout) -> Result<(), Error> {
|
||||||
loop {
|
loop {
|
||||||
let isr = T::regs().isr().read();
|
let isr = T::regs().isr().read();
|
||||||
if isr.tc() {
|
if isr.tc() {
|
||||||
@ -320,17 +213,11 @@ impl<'d, T: Instance, TXDMA, RXDMA> I2c<'d, T, TXDMA, RXDMA> {
|
|||||||
return Err(Error::Nack);
|
return Err(Error::Nack);
|
||||||
}
|
}
|
||||||
|
|
||||||
check_timeout()?;
|
timeout.check()?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn read_internal(
|
fn read_internal(&mut self, address: u8, read: &mut [u8], restart: bool, timeout: Timeout) -> Result<(), Error> {
|
||||||
&mut self,
|
|
||||||
address: u8,
|
|
||||||
read: &mut [u8],
|
|
||||||
restart: bool,
|
|
||||||
check_timeout: impl Fn() -> Result<(), Error>,
|
|
||||||
) -> Result<(), Error> {
|
|
||||||
let completed_chunks = read.len() / 255;
|
let completed_chunks = read.len() / 255;
|
||||||
let total_chunks = if completed_chunks * 255 == read.len() {
|
let total_chunks = if completed_chunks * 255 == read.len() {
|
||||||
completed_chunks
|
completed_chunks
|
||||||
@ -345,17 +232,17 @@ impl<'d, T: Instance, TXDMA, RXDMA> I2c<'d, T, TXDMA, RXDMA> {
|
|||||||
Stop::Automatic,
|
Stop::Automatic,
|
||||||
last_chunk_idx != 0,
|
last_chunk_idx != 0,
|
||||||
restart,
|
restart,
|
||||||
&check_timeout,
|
timeout,
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
for (number, chunk) in read.chunks_mut(255).enumerate() {
|
for (number, chunk) in read.chunks_mut(255).enumerate() {
|
||||||
if number != 0 {
|
if number != 0 {
|
||||||
Self::master_continue(chunk.len(), number != last_chunk_idx, &check_timeout)?;
|
Self::master_continue(chunk.len(), number != last_chunk_idx, timeout)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
for byte in chunk {
|
for byte in chunk {
|
||||||
// Wait until we have received something
|
// Wait until we have received something
|
||||||
self.wait_rxne(&check_timeout)?;
|
self.wait_rxne(timeout)?;
|
||||||
|
|
||||||
*byte = T::regs().rxdr().read().rxdata();
|
*byte = T::regs().rxdr().read().rxdata();
|
||||||
}
|
}
|
||||||
@ -363,13 +250,7 @@ impl<'d, T: Instance, TXDMA, RXDMA> I2c<'d, T, TXDMA, RXDMA> {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn write_internal(
|
fn write_internal(&mut self, address: u8, write: &[u8], send_stop: bool, timeout: Timeout) -> Result<(), Error> {
|
||||||
&mut self,
|
|
||||||
address: u8,
|
|
||||||
write: &[u8],
|
|
||||||
send_stop: bool,
|
|
||||||
check_timeout: impl Fn() -> Result<(), Error>,
|
|
||||||
) -> Result<(), Error> {
|
|
||||||
let completed_chunks = write.len() / 255;
|
let completed_chunks = write.len() / 255;
|
||||||
let total_chunks = if completed_chunks * 255 == write.len() {
|
let total_chunks = if completed_chunks * 255 == write.len() {
|
||||||
completed_chunks
|
completed_chunks
|
||||||
@ -386,7 +267,7 @@ impl<'d, T: Instance, TXDMA, RXDMA> I2c<'d, T, TXDMA, RXDMA> {
|
|||||||
write.len().min(255),
|
write.len().min(255),
|
||||||
Stop::Software,
|
Stop::Software,
|
||||||
last_chunk_idx != 0,
|
last_chunk_idx != 0,
|
||||||
&check_timeout,
|
timeout,
|
||||||
) {
|
) {
|
||||||
if send_stop {
|
if send_stop {
|
||||||
self.master_stop();
|
self.master_stop();
|
||||||
@ -396,14 +277,14 @@ impl<'d, T: Instance, TXDMA, RXDMA> I2c<'d, T, TXDMA, RXDMA> {
|
|||||||
|
|
||||||
for (number, chunk) in write.chunks(255).enumerate() {
|
for (number, chunk) in write.chunks(255).enumerate() {
|
||||||
if number != 0 {
|
if number != 0 {
|
||||||
Self::master_continue(chunk.len(), number != last_chunk_idx, &check_timeout)?;
|
Self::master_continue(chunk.len(), number != last_chunk_idx, timeout)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
for byte in chunk {
|
for byte in chunk {
|
||||||
// Wait until we are allowed to send data
|
// Wait until we are allowed to send data
|
||||||
// (START has been ACKed or last byte when
|
// (START has been ACKed or last byte when
|
||||||
// through)
|
// through)
|
||||||
if let Err(err) = self.wait_txe(&check_timeout) {
|
if let Err(err) = self.wait_txe(timeout) {
|
||||||
if send_stop {
|
if send_stop {
|
||||||
self.master_stop();
|
self.master_stop();
|
||||||
}
|
}
|
||||||
@ -414,7 +295,7 @@ impl<'d, T: Instance, TXDMA, RXDMA> I2c<'d, T, TXDMA, RXDMA> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Wait until the write finishes
|
// Wait until the write finishes
|
||||||
let result = self.wait_tc(&check_timeout);
|
let result = self.wait_tc(timeout);
|
||||||
if send_stop {
|
if send_stop {
|
||||||
self.master_stop();
|
self.master_stop();
|
||||||
}
|
}
|
||||||
@ -427,7 +308,7 @@ impl<'d, T: Instance, TXDMA, RXDMA> I2c<'d, T, TXDMA, RXDMA> {
|
|||||||
write: &[u8],
|
write: &[u8],
|
||||||
first_slice: bool,
|
first_slice: bool,
|
||||||
last_slice: bool,
|
last_slice: bool,
|
||||||
check_timeout: impl Fn() -> Result<(), Error>,
|
timeout: Timeout,
|
||||||
) -> Result<(), Error>
|
) -> Result<(), Error>
|
||||||
where
|
where
|
||||||
TXDMA: crate::i2c::TxDma<T>,
|
TXDMA: crate::i2c::TxDma<T>,
|
||||||
@ -473,10 +354,10 @@ impl<'d, T: Instance, TXDMA, RXDMA> I2c<'d, T, TXDMA, RXDMA> {
|
|||||||
total_len.min(255),
|
total_len.min(255),
|
||||||
Stop::Software,
|
Stop::Software,
|
||||||
(total_len > 255) || !last_slice,
|
(total_len > 255) || !last_slice,
|
||||||
&check_timeout,
|
timeout,
|
||||||
)?;
|
)?;
|
||||||
} else {
|
} else {
|
||||||
Self::master_continue(total_len.min(255), (total_len > 255) || !last_slice, &check_timeout)?;
|
Self::master_continue(total_len.min(255), (total_len > 255) || !last_slice, timeout)?;
|
||||||
T::regs().cr1().modify(|w| w.set_tcie(true));
|
T::regs().cr1().modify(|w| w.set_tcie(true));
|
||||||
}
|
}
|
||||||
} else if !(isr.tcr() || isr.tc()) {
|
} else if !(isr.tcr() || isr.tc()) {
|
||||||
@ -487,7 +368,7 @@ impl<'d, T: Instance, TXDMA, RXDMA> I2c<'d, T, TXDMA, RXDMA> {
|
|||||||
} else {
|
} else {
|
||||||
let last_piece = (remaining_len <= 255) && last_slice;
|
let last_piece = (remaining_len <= 255) && last_slice;
|
||||||
|
|
||||||
if let Err(e) = Self::master_continue(remaining_len.min(255), !last_piece, &check_timeout) {
|
if let Err(e) = Self::master_continue(remaining_len.min(255), !last_piece, timeout) {
|
||||||
return Poll::Ready(Err(e));
|
return Poll::Ready(Err(e));
|
||||||
}
|
}
|
||||||
T::regs().cr1().modify(|w| w.set_tcie(true));
|
T::regs().cr1().modify(|w| w.set_tcie(true));
|
||||||
@ -502,7 +383,7 @@ impl<'d, T: Instance, TXDMA, RXDMA> I2c<'d, T, TXDMA, RXDMA> {
|
|||||||
|
|
||||||
if last_slice {
|
if last_slice {
|
||||||
// This should be done already
|
// This should be done already
|
||||||
self.wait_tc(&check_timeout)?;
|
self.wait_tc(timeout)?;
|
||||||
self.master_stop();
|
self.master_stop();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -516,7 +397,7 @@ impl<'d, T: Instance, TXDMA, RXDMA> I2c<'d, T, TXDMA, RXDMA> {
|
|||||||
address: u8,
|
address: u8,
|
||||||
buffer: &mut [u8],
|
buffer: &mut [u8],
|
||||||
restart: bool,
|
restart: bool,
|
||||||
check_timeout: impl Fn() -> Result<(), Error>,
|
timeout: Timeout,
|
||||||
) -> Result<(), Error>
|
) -> Result<(), Error>
|
||||||
where
|
where
|
||||||
RXDMA: crate::i2c::RxDma<T>,
|
RXDMA: crate::i2c::RxDma<T>,
|
||||||
@ -558,7 +439,7 @@ impl<'d, T: Instance, TXDMA, RXDMA> I2c<'d, T, TXDMA, RXDMA> {
|
|||||||
Stop::Software,
|
Stop::Software,
|
||||||
total_len > 255,
|
total_len > 255,
|
||||||
restart,
|
restart,
|
||||||
&check_timeout,
|
timeout,
|
||||||
)?;
|
)?;
|
||||||
} else if !(isr.tcr() || isr.tc()) {
|
} else if !(isr.tcr() || isr.tc()) {
|
||||||
// poll_fn was woken without an interrupt present
|
// poll_fn was woken without an interrupt present
|
||||||
@ -568,7 +449,7 @@ impl<'d, T: Instance, TXDMA, RXDMA> I2c<'d, T, TXDMA, RXDMA> {
|
|||||||
} else {
|
} else {
|
||||||
let last_piece = remaining_len <= 255;
|
let last_piece = remaining_len <= 255;
|
||||||
|
|
||||||
if let Err(e) = Self::master_continue(remaining_len.min(255), !last_piece, &check_timeout) {
|
if let Err(e) = Self::master_continue(remaining_len.min(255), !last_piece, timeout) {
|
||||||
return Poll::Ready(Err(e));
|
return Poll::Ready(Err(e));
|
||||||
}
|
}
|
||||||
T::regs().cr1().modify(|w| w.set_tcie(true));
|
T::regs().cr1().modify(|w| w.set_tcie(true));
|
||||||
@ -582,7 +463,7 @@ impl<'d, T: Instance, TXDMA, RXDMA> I2c<'d, T, TXDMA, RXDMA> {
|
|||||||
dma_transfer.await;
|
dma_transfer.await;
|
||||||
|
|
||||||
// This should be done already
|
// This should be done already
|
||||||
self.wait_tc(&check_timeout)?;
|
self.wait_tc(timeout)?;
|
||||||
self.master_stop();
|
self.master_stop();
|
||||||
|
|
||||||
drop(on_drop);
|
drop(on_drop);
|
||||||
@ -592,41 +473,31 @@ impl<'d, T: Instance, TXDMA, RXDMA> I2c<'d, T, TXDMA, RXDMA> {
|
|||||||
|
|
||||||
// =========================
|
// =========================
|
||||||
// Async public API
|
// Async public API
|
||||||
#[cfg(feature = "time")]
|
|
||||||
pub async fn write(&mut self, address: u8, write: &[u8]) -> Result<(), Error>
|
|
||||||
where
|
|
||||||
TXDMA: crate::i2c::TxDma<T>,
|
|
||||||
{
|
|
||||||
if write.is_empty() {
|
|
||||||
self.write_internal(address, write, true, timeout_fn(self.timeout))
|
|
||||||
} else {
|
|
||||||
embassy_time::with_timeout(
|
|
||||||
self.timeout,
|
|
||||||
self.write_dma_internal(address, write, true, true, timeout_fn(self.timeout)),
|
|
||||||
)
|
|
||||||
.await
|
|
||||||
.unwrap_or(Err(Error::Timeout))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(not(feature = "time"))]
|
/// Write.
|
||||||
pub async fn write(&mut self, address: u8, write: &[u8]) -> Result<(), Error>
|
pub async fn write(&mut self, address: u8, write: &[u8]) -> Result<(), Error>
|
||||||
where
|
where
|
||||||
TXDMA: crate::i2c::TxDma<T>,
|
TXDMA: crate::i2c::TxDma<T>,
|
||||||
{
|
{
|
||||||
|
let timeout = self.timeout();
|
||||||
if write.is_empty() {
|
if write.is_empty() {
|
||||||
self.write_internal(address, write, true, no_timeout_fn())
|
self.write_internal(address, write, true, timeout)
|
||||||
} else {
|
} else {
|
||||||
self.write_dma_internal(address, write, true, true, no_timeout_fn())
|
timeout
|
||||||
|
.with(self.write_dma_internal(address, write, true, true, timeout))
|
||||||
.await
|
.await
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "time")]
|
/// Write multiple buffers.
|
||||||
|
///
|
||||||
|
/// The buffers are concatenated in a single write transaction.
|
||||||
pub async fn write_vectored(&mut self, address: u8, write: &[&[u8]]) -> Result<(), Error>
|
pub async fn write_vectored(&mut self, address: u8, write: &[&[u8]]) -> Result<(), Error>
|
||||||
where
|
where
|
||||||
TXDMA: crate::i2c::TxDma<T>,
|
TXDMA: crate::i2c::TxDma<T>,
|
||||||
{
|
{
|
||||||
|
let timeout = self.timeout();
|
||||||
|
|
||||||
if write.is_empty() {
|
if write.is_empty() {
|
||||||
return Err(Error::ZeroLengthTransfer);
|
return Err(Error::ZeroLengthTransfer);
|
||||||
}
|
}
|
||||||
@ -638,123 +509,49 @@ impl<'d, T: Instance, TXDMA, RXDMA> I2c<'d, T, TXDMA, RXDMA> {
|
|||||||
let next = iter.next();
|
let next = iter.next();
|
||||||
let is_last = next.is_none();
|
let is_last = next.is_none();
|
||||||
|
|
||||||
embassy_time::with_timeout(
|
let fut = self.write_dma_internal(address, c, first, is_last, timeout);
|
||||||
self.timeout,
|
timeout.with(fut).await?;
|
||||||
self.write_dma_internal(address, c, first, is_last, timeout_fn(self.timeout)),
|
|
||||||
)
|
|
||||||
.await
|
|
||||||
.unwrap_or(Err(Error::Timeout))?;
|
|
||||||
first = false;
|
first = false;
|
||||||
current = next;
|
current = next;
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(not(feature = "time"))]
|
/// Read.
|
||||||
pub async fn write_vectored(&mut self, address: u8, write: &[&[u8]]) -> Result<(), Error>
|
|
||||||
where
|
|
||||||
TXDMA: crate::i2c::TxDma<T>,
|
|
||||||
{
|
|
||||||
if write.is_empty() {
|
|
||||||
return Err(Error::ZeroLengthTransfer);
|
|
||||||
}
|
|
||||||
let mut iter = write.iter();
|
|
||||||
|
|
||||||
let mut first = true;
|
|
||||||
let mut current = iter.next();
|
|
||||||
while let Some(c) = current {
|
|
||||||
let next = iter.next();
|
|
||||||
let is_last = next.is_none();
|
|
||||||
|
|
||||||
self.write_dma_internal(address, c, first, is_last, no_timeout_fn())
|
|
||||||
.await?;
|
|
||||||
first = false;
|
|
||||||
current = next;
|
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(feature = "time")]
|
|
||||||
pub async fn read(&mut self, address: u8, buffer: &mut [u8]) -> Result<(), Error>
|
pub async fn read(&mut self, address: u8, buffer: &mut [u8]) -> Result<(), Error>
|
||||||
where
|
where
|
||||||
RXDMA: crate::i2c::RxDma<T>,
|
RXDMA: crate::i2c::RxDma<T>,
|
||||||
{
|
{
|
||||||
|
let timeout = self.timeout();
|
||||||
|
|
||||||
if buffer.is_empty() {
|
if buffer.is_empty() {
|
||||||
self.read_internal(address, buffer, false, timeout_fn(self.timeout))
|
self.read_internal(address, buffer, false, timeout)
|
||||||
} else {
|
} else {
|
||||||
embassy_time::with_timeout(
|
let fut = self.read_dma_internal(address, buffer, false, timeout);
|
||||||
self.timeout,
|
timeout.with(fut).await
|
||||||
self.read_dma_internal(address, buffer, false, timeout_fn(self.timeout)),
|
|
||||||
)
|
|
||||||
.await
|
|
||||||
.unwrap_or(Err(Error::Timeout))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(not(feature = "time"))]
|
/// Write, restart, read.
|
||||||
pub async fn read(&mut self, address: u8, buffer: &mut [u8]) -> Result<(), Error>
|
|
||||||
where
|
|
||||||
RXDMA: crate::i2c::RxDma<T>,
|
|
||||||
{
|
|
||||||
if buffer.is_empty() {
|
|
||||||
self.read_internal(address, buffer, false, no_timeout_fn())
|
|
||||||
} else {
|
|
||||||
self.read_dma_internal(address, buffer, false, no_timeout_fn()).await
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(feature = "time")]
|
|
||||||
pub async fn write_read(&mut self, address: u8, write: &[u8], read: &mut [u8]) -> Result<(), Error>
|
pub async fn write_read(&mut self, address: u8, write: &[u8], read: &mut [u8]) -> Result<(), Error>
|
||||||
where
|
where
|
||||||
TXDMA: super::TxDma<T>,
|
TXDMA: super::TxDma<T>,
|
||||||
RXDMA: super::RxDma<T>,
|
RXDMA: super::RxDma<T>,
|
||||||
{
|
{
|
||||||
let start_instant = Instant::now();
|
let timeout = self.timeout();
|
||||||
let check_timeout = timeout_fn(self.timeout);
|
|
||||||
if write.is_empty() {
|
if write.is_empty() {
|
||||||
self.write_internal(address, write, false, &check_timeout)?;
|
self.write_internal(address, write, false, timeout)?;
|
||||||
} else {
|
} else {
|
||||||
embassy_time::with_timeout(
|
let fut = self.write_dma_internal(address, write, true, true, timeout);
|
||||||
self.timeout,
|
timeout.with(fut).await?;
|
||||||
self.write_dma_internal(address, write, true, true, &check_timeout),
|
|
||||||
)
|
|
||||||
.await
|
|
||||||
.unwrap_or(Err(Error::Timeout))?;
|
|
||||||
}
|
|
||||||
|
|
||||||
let time_left_until_timeout = self.timeout - Instant::now().duration_since(start_instant);
|
|
||||||
|
|
||||||
if read.is_empty() {
|
|
||||||
self.read_internal(address, read, true, &check_timeout)?;
|
|
||||||
} else {
|
|
||||||
embassy_time::with_timeout(
|
|
||||||
time_left_until_timeout,
|
|
||||||
self.read_dma_internal(address, read, true, &check_timeout),
|
|
||||||
)
|
|
||||||
.await
|
|
||||||
.unwrap_or(Err(Error::Timeout))?;
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(not(feature = "time"))]
|
|
||||||
pub async fn write_read(&mut self, address: u8, write: &[u8], read: &mut [u8]) -> Result<(), Error>
|
|
||||||
where
|
|
||||||
TXDMA: super::TxDma<T>,
|
|
||||||
RXDMA: super::RxDma<T>,
|
|
||||||
{
|
|
||||||
let no_timeout = no_timeout_fn();
|
|
||||||
if write.is_empty() {
|
|
||||||
self.write_internal(address, write, false, &no_timeout)?;
|
|
||||||
} else {
|
|
||||||
self.write_dma_internal(address, write, true, true, &no_timeout).await?;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if read.is_empty() {
|
if read.is_empty() {
|
||||||
self.read_internal(address, read, true, &no_timeout)?;
|
self.read_internal(address, read, true, timeout)?;
|
||||||
} else {
|
} else {
|
||||||
self.read_dma_internal(address, read, true, &no_timeout).await?;
|
let fut = self.read_dma_internal(address, read, true, timeout);
|
||||||
|
timeout.with(fut).await?;
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
@ -763,105 +560,35 @@ impl<'d, T: Instance, TXDMA, RXDMA> I2c<'d, T, TXDMA, RXDMA> {
|
|||||||
// =========================
|
// =========================
|
||||||
// Blocking public API
|
// Blocking public API
|
||||||
|
|
||||||
#[cfg(feature = "time")]
|
/// Blocking read.
|
||||||
pub fn blocking_read_timeout(&mut self, address: u8, read: &mut [u8], timeout: Duration) -> Result<(), Error> {
|
|
||||||
self.read_internal(address, read, false, timeout_fn(timeout))
|
|
||||||
// Automatic Stop
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(not(feature = "time"))]
|
|
||||||
pub fn blocking_read_timeout(
|
|
||||||
&mut self,
|
|
||||||
address: u8,
|
|
||||||
read: &mut [u8],
|
|
||||||
check_timeout: impl Fn() -> Result<(), Error>,
|
|
||||||
) -> Result<(), Error> {
|
|
||||||
self.read_internal(address, read, false, check_timeout)
|
|
||||||
// Automatic Stop
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(feature = "time")]
|
|
||||||
pub fn blocking_read(&mut self, address: u8, read: &mut [u8]) -> Result<(), Error> {
|
pub fn blocking_read(&mut self, address: u8, read: &mut [u8]) -> Result<(), Error> {
|
||||||
self.blocking_read_timeout(address, read, self.timeout)
|
self.read_internal(address, read, false, self.timeout())
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(not(feature = "time"))]
|
|
||||||
pub fn blocking_read(&mut self, address: u8, read: &mut [u8]) -> Result<(), Error> {
|
|
||||||
self.blocking_read_timeout(address, read, || Ok(()))
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(feature = "time")]
|
|
||||||
pub fn blocking_write_timeout(&mut self, address: u8, write: &[u8], timeout: Duration) -> Result<(), Error> {
|
|
||||||
self.write_internal(address, write, true, timeout_fn(timeout))
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(not(feature = "time"))]
|
|
||||||
pub fn blocking_write_timeout(
|
|
||||||
&mut self,
|
|
||||||
address: u8,
|
|
||||||
write: &[u8],
|
|
||||||
check_timeout: impl Fn() -> Result<(), Error>,
|
|
||||||
) -> Result<(), Error> {
|
|
||||||
self.write_internal(address, write, true, check_timeout)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(feature = "time")]
|
|
||||||
pub fn blocking_write(&mut self, address: u8, write: &[u8]) -> Result<(), Error> {
|
|
||||||
self.blocking_write_timeout(address, write, self.timeout)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(not(feature = "time"))]
|
|
||||||
pub fn blocking_write(&mut self, address: u8, write: &[u8]) -> Result<(), Error> {
|
|
||||||
self.blocking_write_timeout(address, write, || Ok(()))
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(feature = "time")]
|
|
||||||
pub fn blocking_write_read_timeout(
|
|
||||||
&mut self,
|
|
||||||
address: u8,
|
|
||||||
write: &[u8],
|
|
||||||
read: &mut [u8],
|
|
||||||
timeout: Duration,
|
|
||||||
) -> Result<(), Error> {
|
|
||||||
let check_timeout = timeout_fn(timeout);
|
|
||||||
self.write_internal(address, write, false, &check_timeout)?;
|
|
||||||
self.read_internal(address, read, true, &check_timeout)
|
|
||||||
// Automatic Stop
|
// Automatic Stop
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(not(feature = "time"))]
|
/// Blocking write.
|
||||||
pub fn blocking_write_read_timeout(
|
pub fn blocking_write(&mut self, address: u8, write: &[u8]) -> Result<(), Error> {
|
||||||
&mut self,
|
self.write_internal(address, write, true, self.timeout())
|
||||||
address: u8,
|
}
|
||||||
write: &[u8],
|
|
||||||
read: &mut [u8],
|
/// Blocking write, restart, read.
|
||||||
check_timeout: impl Fn() -> Result<(), Error>,
|
pub fn blocking_write_read(&mut self, address: u8, write: &[u8], read: &mut [u8]) -> Result<(), Error> {
|
||||||
) -> Result<(), Error> {
|
let timeout = self.timeout();
|
||||||
self.write_internal(address, write, false, &check_timeout)?;
|
self.write_internal(address, write, false, timeout)?;
|
||||||
self.read_internal(address, read, true, &check_timeout)
|
self.read_internal(address, read, true, timeout)
|
||||||
// Automatic Stop
|
// Automatic Stop
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "time")]
|
/// Blocking write multiple buffers.
|
||||||
pub fn blocking_write_read(&mut self, address: u8, write: &[u8], read: &mut [u8]) -> Result<(), Error> {
|
///
|
||||||
self.blocking_write_read_timeout(address, write, read, self.timeout)
|
/// The buffers are concatenated in a single write transaction.
|
||||||
}
|
pub fn blocking_write_vectored(&mut self, address: u8, write: &[&[u8]]) -> Result<(), Error> {
|
||||||
|
|
||||||
#[cfg(not(feature = "time"))]
|
|
||||||
pub fn blocking_write_read(&mut self, address: u8, write: &[u8], read: &mut [u8]) -> Result<(), Error> {
|
|
||||||
self.blocking_write_read_timeout(address, write, read, || Ok(()))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn blocking_write_vectored_with_timeout(
|
|
||||||
&mut self,
|
|
||||||
address: u8,
|
|
||||||
write: &[&[u8]],
|
|
||||||
check_timeout: impl Fn() -> Result<(), Error>,
|
|
||||||
) -> Result<(), Error> {
|
|
||||||
if write.is_empty() {
|
if write.is_empty() {
|
||||||
return Err(Error::ZeroLengthTransfer);
|
return Err(Error::ZeroLengthTransfer);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let timeout = self.timeout();
|
||||||
|
|
||||||
let first_length = write[0].len();
|
let first_length = write[0].len();
|
||||||
let last_slice_index = write.len() - 1;
|
let last_slice_index = write.len() - 1;
|
||||||
|
|
||||||
@ -870,7 +597,7 @@ impl<'d, T: Instance, TXDMA, RXDMA> I2c<'d, T, TXDMA, RXDMA> {
|
|||||||
first_length.min(255),
|
first_length.min(255),
|
||||||
Stop::Software,
|
Stop::Software,
|
||||||
(first_length > 255) || (last_slice_index != 0),
|
(first_length > 255) || (last_slice_index != 0),
|
||||||
&check_timeout,
|
timeout,
|
||||||
) {
|
) {
|
||||||
self.master_stop();
|
self.master_stop();
|
||||||
return Err(err);
|
return Err(err);
|
||||||
@ -890,7 +617,7 @@ impl<'d, T: Instance, TXDMA, RXDMA> I2c<'d, T, TXDMA, RXDMA> {
|
|||||||
if let Err(err) = Self::master_continue(
|
if let Err(err) = Self::master_continue(
|
||||||
slice_len.min(255),
|
slice_len.min(255),
|
||||||
(idx != last_slice_index) || (slice_len > 255),
|
(idx != last_slice_index) || (slice_len > 255),
|
||||||
&check_timeout,
|
timeout,
|
||||||
) {
|
) {
|
||||||
self.master_stop();
|
self.master_stop();
|
||||||
return Err(err);
|
return Err(err);
|
||||||
@ -902,7 +629,7 @@ impl<'d, T: Instance, TXDMA, RXDMA> I2c<'d, T, TXDMA, RXDMA> {
|
|||||||
if let Err(err) = Self::master_continue(
|
if let Err(err) = Self::master_continue(
|
||||||
chunk.len(),
|
chunk.len(),
|
||||||
(number != last_chunk_idx) || (idx != last_slice_index),
|
(number != last_chunk_idx) || (idx != last_slice_index),
|
||||||
&check_timeout,
|
timeout,
|
||||||
) {
|
) {
|
||||||
self.master_stop();
|
self.master_stop();
|
||||||
return Err(err);
|
return Err(err);
|
||||||
@ -913,7 +640,7 @@ impl<'d, T: Instance, TXDMA, RXDMA> I2c<'d, T, TXDMA, RXDMA> {
|
|||||||
// Wait until we are allowed to send data
|
// Wait until we are allowed to send data
|
||||||
// (START has been ACKed or last byte when
|
// (START has been ACKed or last byte when
|
||||||
// through)
|
// through)
|
||||||
if let Err(err) = self.wait_txe(&check_timeout) {
|
if let Err(err) = self.wait_txe(timeout) {
|
||||||
self.master_stop();
|
self.master_stop();
|
||||||
return Err(err);
|
return Err(err);
|
||||||
}
|
}
|
||||||
@ -925,41 +652,10 @@ impl<'d, T: Instance, TXDMA, RXDMA> I2c<'d, T, TXDMA, RXDMA> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Wait until the write finishes
|
// Wait until the write finishes
|
||||||
let result = self.wait_tc(&check_timeout);
|
let result = self.wait_tc(timeout);
|
||||||
self.master_stop();
|
self.master_stop();
|
||||||
result
|
result
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "time")]
|
|
||||||
pub fn blocking_write_vectored_timeout(
|
|
||||||
&mut self,
|
|
||||||
address: u8,
|
|
||||||
write: &[&[u8]],
|
|
||||||
timeout: Duration,
|
|
||||||
) -> Result<(), Error> {
|
|
||||||
let check_timeout = timeout_fn(timeout);
|
|
||||||
self.blocking_write_vectored_with_timeout(address, write, check_timeout)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(not(feature = "time"))]
|
|
||||||
pub fn blocking_write_vectored_timeout(
|
|
||||||
&mut self,
|
|
||||||
address: u8,
|
|
||||||
write: &[&[u8]],
|
|
||||||
check_timeout: impl Fn() -> Result<(), Error>,
|
|
||||||
) -> Result<(), Error> {
|
|
||||||
self.blocking_write_vectored_with_timeout(address, write, check_timeout)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(feature = "time")]
|
|
||||||
pub fn blocking_write_vectored(&mut self, address: u8, write: &[&[u8]]) -> Result<(), Error> {
|
|
||||||
self.blocking_write_vectored_timeout(address, write, self.timeout)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(not(feature = "time"))]
|
|
||||||
pub fn blocking_write_vectored(&mut self, address: u8, write: &[&[u8]]) -> Result<(), Error> {
|
|
||||||
self.blocking_write_vectored_timeout(address, write, || Ok(()))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'d, T: Instance, TXDMA, RXDMA> Drop for I2c<'d, T, TXDMA, RXDMA> {
|
impl<'d, T: Instance, TXDMA, RXDMA> Drop for I2c<'d, T, TXDMA, RXDMA> {
|
||||||
|
Loading…
Reference in New Issue
Block a user