diff --git a/embassy-rp/src/i2c.rs b/embassy-rp/src/i2c.rs
index 40e85c66f..cd5364393 100644
--- a/embassy-rp/src/i2c.rs
+++ b/embassy-rp/src/i2c.rs
@@ -617,6 +617,28 @@ mod eh02 {
             self.blocking_write_read(address, bytes, buffer)
         }
     }
+
+    impl<'d, T: Instance, M: Mode> embedded_hal_02::blocking::i2c::Transactional for I2c<'d, T, M> {
+        type Error = Error;
+
+        fn exec(
+            &mut self,
+            address: u8,
+            operations: &mut [embedded_hal_02::blocking::i2c::Operation<'_>],
+        ) -> Result<(), Self::Error> {
+            Self::setup(address.into())?;
+            for i in 0..operations.len() {
+                let last = i == operations.len() - 1;
+                match &mut operations[i] {
+                    embedded_hal_02::blocking::i2c::Operation::Read(buf) => {
+                        self.read_blocking_internal(buf, false, last)?
+                    }
+                    embedded_hal_02::blocking::i2c::Operation::Write(buf) => self.write_blocking_internal(buf, last)?,
+                }
+            }
+            Ok(())
+        }
+    }
 }
 
 #[cfg(feature = "unstable-traits")]
diff --git a/embassy-stm32/src/ipcc.rs b/embassy-stm32/src/ipcc.rs
new file mode 100644
index 000000000..903aeca30
--- /dev/null
+++ b/embassy-stm32/src/ipcc.rs
@@ -0,0 +1,178 @@
+use embassy_hal_common::{into_ref, Peripheral, PeripheralRef};
+
+use crate::ipcc::sealed::Instance;
+use crate::peripherals::IPCC;
+use crate::rcc::sealed::RccPeripheral;
+
+#[non_exhaustive]
+#[derive(Clone, Copy, Default)]
+pub struct Config {
+    // TODO: add IPCC peripheral configuration, if any, here
+    // reserved for future use
+}
+
+#[derive(Debug, Clone, Copy)]
+#[repr(C)]
+pub enum IpccChannel {
+    Channel1 = 0,
+    Channel2 = 1,
+    Channel3 = 2,
+    Channel4 = 3,
+    Channel5 = 4,
+    Channel6 = 5,
+}
+
+pub(crate) mod sealed {
+    pub trait Instance: crate::rcc::RccPeripheral {
+        fn regs() -> crate::pac::ipcc::Ipcc;
+        fn set_cpu2(enabled: bool);
+    }
+}
+
+pub struct Ipcc<'d> {
+    _peri: PeripheralRef<'d, IPCC>,
+}
+
+impl<'d> Ipcc<'d> {
+    pub fn new(peri: impl Peripheral<P = IPCC> + 'd, _config: Config) -> Self {
+        into_ref!(peri);
+
+        Self { _peri: peri }
+    }
+
+    pub fn init(&mut self) {
+        IPCC::enable();
+        IPCC::reset();
+        IPCC::set_cpu2(true);
+
+        unsafe { _configure_pwr() };
+
+        let regs = IPCC::regs();
+
+        unsafe {
+            regs.cpu(0).cr().modify(|w| {
+                w.set_rxoie(true);
+                w.set_txfie(true);
+            })
+        }
+    }
+
+    pub fn c1_set_rx_channel(&mut self, channel: IpccChannel, enabled: bool) {
+        let regs = IPCC::regs();
+
+        // If bit is set to 1 then interrupt is disabled
+        unsafe { regs.cpu(0).mr().modify(|w| w.set_chom(channel as usize, !enabled)) }
+    }
+
+    pub fn c1_get_rx_channel(&self, channel: IpccChannel) -> bool {
+        let regs = IPCC::regs();
+
+        // If bit is set to 1 then interrupt is disabled
+        unsafe { !regs.cpu(0).mr().read().chom(channel as usize) }
+    }
+
+    pub fn c2_set_rx_channel(&mut self, channel: IpccChannel, enabled: bool) {
+        let regs = IPCC::regs();
+
+        // If bit is set to 1 then interrupt is disabled
+        unsafe { regs.cpu(1).mr().modify(|w| w.set_chom(channel as usize, !enabled)) }
+    }
+
+    pub fn c2_get_rx_channel(&self, channel: IpccChannel) -> bool {
+        let regs = IPCC::regs();
+
+        // If bit is set to 1 then interrupt is disabled
+        unsafe { !regs.cpu(1).mr().read().chom(channel as usize) }
+    }
+
+    pub fn c1_set_tx_channel(&mut self, channel: IpccChannel, enabled: bool) {
+        let regs = IPCC::regs();
+
+        // If bit is set to 1 then interrupt is disabled
+        unsafe { regs.cpu(0).mr().modify(|w| w.set_chfm(channel as usize, !enabled)) }
+    }
+
+    pub fn c1_get_tx_channel(&self, channel: IpccChannel) -> bool {
+        let regs = IPCC::regs();
+
+        // If bit is set to 1 then interrupt is disabled
+        unsafe { !regs.cpu(0).mr().read().chfm(channel as usize) }
+    }
+
+    pub fn c2_set_tx_channel(&mut self, channel: IpccChannel, enabled: bool) {
+        let regs = IPCC::regs();
+
+        // If bit is set to 1 then interrupt is disabled
+        unsafe { regs.cpu(1).mr().modify(|w| w.set_chfm(channel as usize, !enabled)) }
+    }
+
+    pub fn c2_get_tx_channel(&self, channel: IpccChannel) -> bool {
+        let regs = IPCC::regs();
+
+        // If bit is set to 1 then interrupt is disabled
+        unsafe { !regs.cpu(1).mr().read().chfm(channel as usize) }
+    }
+
+    /// clears IPCC receive channel status for CPU1
+    pub fn c1_clear_flag_channel(&mut self, channel: IpccChannel) {
+        let regs = IPCC::regs();
+
+        unsafe { regs.cpu(0).scr().write(|w| w.set_chc(channel as usize, true)) }
+    }
+
+    /// clears IPCC receive channel status for CPU2
+    pub fn c2_clear_flag_channel(&mut self, channel: IpccChannel) {
+        let regs = IPCC::regs();
+
+        unsafe { regs.cpu(1).scr().write(|w| w.set_chc(channel as usize, true)) }
+    }
+
+    pub fn c1_set_flag_channel(&mut self, channel: IpccChannel) {
+        let regs = IPCC::regs();
+
+        unsafe { regs.cpu(0).scr().write(|w| w.set_chs(channel as usize, true)) }
+    }
+
+    pub fn c2_set_flag_channel(&mut self, channel: IpccChannel) {
+        let regs = IPCC::regs();
+
+        unsafe { regs.cpu(1).scr().write(|w| w.set_chs(channel as usize, true)) }
+    }
+
+    pub fn c1_is_active_flag(&self, channel: IpccChannel) -> bool {
+        let regs = IPCC::regs();
+
+        unsafe { regs.cpu(0).sr().read().chf(channel as usize) }
+    }
+
+    pub fn c2_is_active_flag(&self, channel: IpccChannel) -> bool {
+        let regs = IPCC::regs();
+
+        unsafe { regs.cpu(1).sr().read().chf(channel as usize) }
+    }
+
+    pub fn is_tx_pending(&self, channel: IpccChannel) -> bool {
+        !self.c1_is_active_flag(channel) && self.c1_get_tx_channel(channel)
+    }
+
+    pub fn is_rx_pending(&self, channel: IpccChannel) -> bool {
+        self.c2_is_active_flag(channel) && self.c1_get_rx_channel(channel)
+    }
+}
+
+impl sealed::Instance for crate::peripherals::IPCC {
+    fn regs() -> crate::pac::ipcc::Ipcc {
+        crate::pac::IPCC
+    }
+
+    fn set_cpu2(enabled: bool) {
+        unsafe { crate::pac::PWR.cr4().modify(|w| w.set_c2boot(enabled)) }
+    }
+}
+
+unsafe fn _configure_pwr() {
+    let rcc = crate::pac::RCC;
+
+    // set RF wake-up clock = LSE
+    rcc.csr().modify(|w| w.set_rfwkpsel(0b01));
+}
diff --git a/embassy-stm32/src/lib.rs b/embassy-stm32/src/lib.rs
index 24a26eddd..599041302 100644
--- a/embassy-stm32/src/lib.rs
+++ b/embassy-stm32/src/lib.rs
@@ -44,6 +44,8 @@ pub mod i2c;
 #[cfg(crc)]
 pub mod crc;
 pub mod flash;
+#[cfg(stm32wb)]
+pub mod ipcc;
 pub mod pwm;
 #[cfg(quadspi)]
 pub mod qspi;
diff --git a/embassy-stm32/src/usart/mod.rs b/embassy-stm32/src/usart/mod.rs
index b8656b586..266561659 100644
--- a/embassy-stm32/src/usart/mod.rs
+++ b/embassy-stm32/src/usart/mod.rs
@@ -497,28 +497,24 @@ impl<'d, T: BasicInstance, RxDma> UartRx<'d, T, RxDma> {
                 unreachable!();
             }
 
-            if !enable_idle_line_detection {
-                transfer.await;
+            if enable_idle_line_detection {
+                // clear idle flag
+                let sr = sr(r).read();
+                // This read also clears the error and idle interrupt flags on v1.
+                rdr(r).read_volatile();
+                clear_interrupt_flags(r, sr);
 
-                return Ok(ReadCompletionEvent::DmaCompleted);
+                // enable idle interrupt
+                r.cr1().modify(|w| {
+                    w.set_idleie(true);
+                });
             }
-
-            // clear idle flag
-            let sr = sr(r).read();
-            // This read also clears the error and idle interrupt flags on v1.
-            rdr(r).read_volatile();
-            clear_interrupt_flags(r, sr);
-
-            // enable idle interrupt
-            r.cr1().modify(|w| {
-                w.set_idleie(true);
-            });
         }
 
         compiler_fence(Ordering::SeqCst);
 
-        // future which completes when idle line is detected
-        let idle = poll_fn(move |cx| {
+        // future which completes when idle line or error is detected
+        let abort = poll_fn(move |cx| {
             let s = T::state();
 
             s.rx_waker.register(cx.waker());
@@ -554,7 +550,7 @@ impl<'d, T: BasicInstance, RxDma> UartRx<'d, T, RxDma> {
                 }
             }
 
-            if sr.idle() {
+            if enable_idle_line_detection && sr.idle() {
                 // Idle line detected
                 return Poll::Ready(Ok(()));
             }
@@ -565,7 +561,7 @@ impl<'d, T: BasicInstance, RxDma> UartRx<'d, T, RxDma> {
         // wait for the first of DMA request or idle line detected to completes
         // select consumes its arguments
         // when transfer is dropped, it will stop the DMA request
-        let r = match select(transfer, idle).await {
+        let r = match select(transfer, abort).await {
             // DMA transfer completed first
             Either::Left(((), _)) => Ok(ReadCompletionEvent::DmaCompleted),