From 02da66aec8432f71b8f3121a972044191df2d7e8 Mon Sep 17 00:00:00 2001
From: Dario Nieuwenhuis <dirbaio@dirbaio.net>
Date: Mon, 15 Apr 2024 21:47:37 +0200
Subject: [PATCH 1/4] stm32/dma: add ChannelAndRequest helper.

---
 embassy-stm32/src/dma/mod.rs  |  3 ++
 embassy-stm32/src/dma/util.rs | 60 +++++++++++++++++++++++++++++++++++
 embassy-stm32/src/spi/mod.rs  | 40 +++++++++++------------
 embassy-stm32/src/traits.rs   |  7 ++--
 4 files changed, 86 insertions(+), 24 deletions(-)
 create mode 100644 embassy-stm32/src/dma/util.rs

diff --git a/embassy-stm32/src/dma/mod.rs b/embassy-stm32/src/dma/mod.rs
index 7e3681469..8766d0a60 100644
--- a/embassy-stm32/src/dma/mod.rs
+++ b/embassy-stm32/src/dma/mod.rs
@@ -16,6 +16,9 @@ mod dmamux;
 #[cfg(dmamux)]
 pub use dmamux::*;
 
+mod util;
+pub(crate) use util::*;
+
 pub(crate) mod ringbuffer;
 pub mod word;
 
diff --git a/embassy-stm32/src/dma/util.rs b/embassy-stm32/src/dma/util.rs
new file mode 100644
index 000000000..962ea2501
--- /dev/null
+++ b/embassy-stm32/src/dma/util.rs
@@ -0,0 +1,60 @@
+use embassy_hal_internal::PeripheralRef;
+
+use super::word::Word;
+use super::{AnyChannel, Request, Transfer, TransferOptions};
+
+/// Convenience wrapper, contains a channel and a request number.
+///
+/// Commonly used in peripheral drivers that own DMA channels.
+pub(crate) struct ChannelAndRequest<'d> {
+    pub channel: PeripheralRef<'d, AnyChannel>,
+    pub request: Request,
+}
+
+impl<'d> ChannelAndRequest<'d> {
+    pub unsafe fn read<'a, W: Word>(
+        &'a mut self,
+        peri_addr: *mut W,
+        buf: &'a mut [W],
+        options: TransferOptions,
+    ) -> Transfer<'a> {
+        Transfer::new_read(&mut self.channel, self.request, peri_addr, buf, options)
+    }
+
+    pub unsafe fn read_raw<'a, W: Word>(
+        &'a mut self,
+        peri_addr: *mut W,
+        buf: *mut [W],
+        options: TransferOptions,
+    ) -> Transfer<'a> {
+        Transfer::new_read_raw(&mut self.channel, self.request, peri_addr, buf, options)
+    }
+
+    pub unsafe fn write<'a, W: Word>(
+        &'a mut self,
+        buf: &'a [W],
+        peri_addr: *mut W,
+        options: TransferOptions,
+    ) -> Transfer<'a> {
+        Transfer::new_write(&mut self.channel, self.request, buf, peri_addr, options)
+    }
+
+    pub unsafe fn write_raw<'a, W: Word>(
+        &'a mut self,
+        buf: *const [W],
+        peri_addr: *mut W,
+        options: TransferOptions,
+    ) -> Transfer<'a> {
+        Transfer::new_write_raw(&mut self.channel, self.request, buf, peri_addr, options)
+    }
+
+    pub unsafe fn write_repeated<'a, W: Word>(
+        &'a mut self,
+        repeated: &'a W,
+        count: usize,
+        peri_addr: *mut W,
+        options: TransferOptions,
+    ) -> Transfer<'a> {
+        Transfer::new_write_repeated(&mut self.channel, self.request, repeated, count, peri_addr, options)
+    }
+}
diff --git a/embassy-stm32/src/spi/mod.rs b/embassy-stm32/src/spi/mod.rs
index a4465e289..303b85346 100644
--- a/embassy-stm32/src/spi/mod.rs
+++ b/embassy-stm32/src/spi/mod.rs
@@ -9,7 +9,7 @@ use embassy_futures::join::join;
 use embassy_hal_internal::{into_ref, PeripheralRef};
 pub use embedded_hal_02::spi::{Mode, Phase, Polarity, MODE_0, MODE_1, MODE_2, MODE_3};
 
-use crate::dma::{slice_ptr_parts, word, AnyChannel, Request, Transfer};
+use crate::dma::{slice_ptr_parts, word, ChannelAndRequest};
 use crate::gpio::{AFType, AnyPin, Pull, SealedPin as _, Speed};
 use crate::mode::{Async, Blocking, Mode as PeriMode};
 use crate::pac::spi::{regs, vals, Spi as Regs};
@@ -97,8 +97,8 @@ pub struct Spi<'d, T: Instance, M: PeriMode> {
     sck: Option<PeripheralRef<'d, AnyPin>>,
     mosi: Option<PeripheralRef<'d, AnyPin>>,
     miso: Option<PeripheralRef<'d, AnyPin>>,
-    txdma: Option<(PeripheralRef<'d, AnyChannel>, Request)>,
-    rxdma: Option<(PeripheralRef<'d, AnyChannel>, Request)>,
+    txdma: Option<ChannelAndRequest<'d>>,
+    rxdma: Option<ChannelAndRequest<'d>>,
     _phantom: PhantomData<M>,
     current_word_size: word_impl::Config,
 }
@@ -109,8 +109,8 @@ impl<'d, T: Instance, M: PeriMode> Spi<'d, T, M> {
         sck: Option<PeripheralRef<'d, AnyPin>>,
         mosi: Option<PeripheralRef<'d, AnyPin>>,
         miso: Option<PeripheralRef<'d, AnyPin>>,
-        txdma: Option<(PeripheralRef<'d, AnyChannel>, Request)>,
-        rxdma: Option<(PeripheralRef<'d, AnyChannel>, Request)>,
+        txdma: Option<ChannelAndRequest<'d>>,
+        rxdma: Option<ChannelAndRequest<'d>>,
         config: Config,
     ) -> Self {
         into_ref!(peri);
@@ -593,9 +593,8 @@ impl<'d, T: Instance> Spi<'d, T, Async> {
             w.set_spe(false);
         });
 
-        let (txdma, tx_request) = self.txdma.as_mut().unwrap();
         let tx_dst = T::REGS.tx_ptr();
-        let tx_f = unsafe { Transfer::new_write(txdma, *tx_request, data, tx_dst, Default::default()) };
+        let tx_f = unsafe { self.txdma.as_mut().unwrap().write(data, tx_dst, Default::default()) };
 
         set_txdmaen(T::REGS, true);
         T::REGS.cr1().modify(|w| {
@@ -632,22 +631,16 @@ impl<'d, T: Instance> Spi<'d, T, Async> {
 
         let clock_byte_count = data.len();
 
-        let (rxdma, rx_request) = self.rxdma.as_mut().unwrap();
         let rx_src = T::REGS.rx_ptr();
-        let rx_f = unsafe { Transfer::new_read(rxdma, *rx_request, rx_src, data, Default::default()) };
+        let rx_f = unsafe { self.rxdma.as_mut().unwrap().read(rx_src, data, Default::default()) };
 
-        let (txdma, tx_request) = self.txdma.as_mut().unwrap();
         let tx_dst = T::REGS.tx_ptr();
         let clock_byte = 0x00u8;
         let tx_f = unsafe {
-            Transfer::new_write_repeated(
-                txdma,
-                *tx_request,
-                &clock_byte,
-                clock_byte_count,
-                tx_dst,
-                Default::default(),
-            )
+            self.txdma
+                .as_mut()
+                .unwrap()
+                .write_repeated(&clock_byte, clock_byte_count, tx_dst, Default::default())
         };
 
         set_txdmaen(T::REGS, true);
@@ -685,13 +678,16 @@ impl<'d, T: Instance> Spi<'d, T, Async> {
 
         set_rxdmaen(T::REGS, true);
 
-        let (rxdma, rx_request) = self.rxdma.as_mut().unwrap();
         let rx_src = T::REGS.rx_ptr();
-        let rx_f = unsafe { Transfer::new_read_raw(rxdma, *rx_request, rx_src, read, Default::default()) };
+        let rx_f = unsafe { self.rxdma.as_mut().unwrap().read_raw(rx_src, read, Default::default()) };
 
-        let (txdma, tx_request) = self.txdma.as_mut().unwrap();
         let tx_dst = T::REGS.tx_ptr();
-        let tx_f = unsafe { Transfer::new_write_raw(txdma, *tx_request, write, tx_dst, Default::default()) };
+        let tx_f = unsafe {
+            self.txdma
+                .as_mut()
+                .unwrap()
+                .write_raw(write, tx_dst, Default::default())
+        };
 
         set_txdmaen(T::REGS, true);
         T::REGS.cr1().modify(|w| {
diff --git a/embassy-stm32/src/traits.rs b/embassy-stm32/src/traits.rs
index 539302c49..f603f661f 100644
--- a/embassy-stm32/src/traits.rs
+++ b/embassy-stm32/src/traits.rs
@@ -73,8 +73,11 @@ macro_rules! dma_trait_impl {
 macro_rules! new_dma {
     ($name:ident) => {{
         let dma = $name.into_ref();
-        let req = dma.request();
-        Some((dma.map_into(), req))
+        let request = dma.request();
+        Some(crate::dma::ChannelAndRequest {
+            channel: dma.map_into(),
+            request,
+        })
     }};
 }
 

From 09a284e9592cc426b5a3c3b72b3f02c0c4844742 Mon Sep 17 00:00:00 2001
From: Dario Nieuwenhuis <dirbaio@dirbaio.net>
Date: Mon, 15 Apr 2024 21:48:10 +0200
Subject: [PATCH 2/4] stm32: rename mod traits to macros.

---
 embassy-stm32/src/lib.rs                   | 2 +-
 embassy-stm32/src/{traits.rs => macros.rs} | 0
 2 files changed, 1 insertion(+), 1 deletion(-)
 rename embassy-stm32/src/{traits.rs => macros.rs} (100%)

diff --git a/embassy-stm32/src/lib.rs b/embassy-stm32/src/lib.rs
index 1f0f85936..7d2b49ff4 100644
--- a/embassy-stm32/src/lib.rs
+++ b/embassy-stm32/src/lib.rs
@@ -15,8 +15,8 @@ mod fmt;
 include!(concat!(env!("OUT_DIR"), "/_macros.rs"));
 
 // Utilities
+mod macros;
 pub mod time;
-mod traits;
 /// Operating modes for peripherals.
 pub mod mode {
     trait SealedMode {}
diff --git a/embassy-stm32/src/traits.rs b/embassy-stm32/src/macros.rs
similarity index 100%
rename from embassy-stm32/src/traits.rs
rename to embassy-stm32/src/macros.rs

From 2eab099b85a7e0ba10d1f558dce89112cd577c68 Mon Sep 17 00:00:00 2001
From: Dario Nieuwenhuis <dirbaio@dirbaio.net>
Date: Mon, 15 Apr 2024 21:56:08 +0200
Subject: [PATCH 3/4] stm32/spi: rename rxdma, txdma -> rx_dma, tx_dma.

---
 embassy-stm32/src/spi/mod.rs | 54 ++++++++++++++++++------------------
 1 file changed, 27 insertions(+), 27 deletions(-)

diff --git a/embassy-stm32/src/spi/mod.rs b/embassy-stm32/src/spi/mod.rs
index 303b85346..c39ef1913 100644
--- a/embassy-stm32/src/spi/mod.rs
+++ b/embassy-stm32/src/spi/mod.rs
@@ -97,8 +97,8 @@ pub struct Spi<'d, T: Instance, M: PeriMode> {
     sck: Option<PeripheralRef<'d, AnyPin>>,
     mosi: Option<PeripheralRef<'d, AnyPin>>,
     miso: Option<PeripheralRef<'d, AnyPin>>,
-    txdma: Option<ChannelAndRequest<'d>>,
-    rxdma: Option<ChannelAndRequest<'d>>,
+    tx_dma: Option<ChannelAndRequest<'d>>,
+    rx_dma: Option<ChannelAndRequest<'d>>,
     _phantom: PhantomData<M>,
     current_word_size: word_impl::Config,
 }
@@ -109,8 +109,8 @@ impl<'d, T: Instance, M: PeriMode> Spi<'d, T, M> {
         sck: Option<PeripheralRef<'d, AnyPin>>,
         mosi: Option<PeripheralRef<'d, AnyPin>>,
         miso: Option<PeripheralRef<'d, AnyPin>>,
-        txdma: Option<ChannelAndRequest<'d>>,
-        rxdma: Option<ChannelAndRequest<'d>>,
+        tx_dma: Option<ChannelAndRequest<'d>>,
+        rx_dma: Option<ChannelAndRequest<'d>>,
         config: Config,
     ) -> Self {
         into_ref!(peri);
@@ -209,8 +209,8 @@ impl<'d, T: Instance, M: PeriMode> Spi<'d, T, M> {
             sck,
             mosi,
             miso,
-            txdma,
-            rxdma,
+            tx_dma,
+            rx_dma,
             current_word_size: <u8 as SealedWord>::CONFIG,
             _phantom: PhantomData,
         }
@@ -479,8 +479,8 @@ impl<'d, T: Instance> Spi<'d, T, Async> {
         sck: impl Peripheral<P = impl SckPin<T>> + 'd,
         mosi: impl Peripheral<P = impl MosiPin<T>> + 'd,
         miso: impl Peripheral<P = impl MisoPin<T>> + 'd,
-        txdma: impl Peripheral<P = impl TxDma<T>> + 'd,
-        rxdma: impl Peripheral<P = impl RxDma<T>> + 'd,
+        tx_dma: impl Peripheral<P = impl TxDma<T>> + 'd,
+        rx_dma: impl Peripheral<P = impl RxDma<T>> + 'd,
         config: Config,
     ) -> Self {
         Self::new_inner(
@@ -488,8 +488,8 @@ impl<'d, T: Instance> Spi<'d, T, Async> {
             new_pin!(sck, AFType::OutputPushPull, Speed::VeryHigh, config.sck_pull_mode()),
             new_pin!(mosi, AFType::OutputPushPull, Speed::VeryHigh),
             new_pin!(miso, AFType::Input, Speed::VeryHigh),
-            new_dma!(txdma),
-            new_dma!(rxdma),
+            new_dma!(tx_dma),
+            new_dma!(rx_dma),
             config,
         )
     }
@@ -499,7 +499,7 @@ impl<'d, T: Instance> Spi<'d, T, Async> {
         peri: impl Peripheral<P = T> + 'd,
         sck: impl Peripheral<P = impl SckPin<T>> + 'd,
         miso: impl Peripheral<P = impl MisoPin<T>> + 'd,
-        rxdma: impl Peripheral<P = impl RxDma<T>> + 'd,
+        rx_dma: impl Peripheral<P = impl RxDma<T>> + 'd,
         config: Config,
     ) -> Self {
         Self::new_inner(
@@ -508,7 +508,7 @@ impl<'d, T: Instance> Spi<'d, T, Async> {
             None,
             new_pin!(miso, AFType::Input, Speed::VeryHigh),
             None,
-            new_dma!(rxdma),
+            new_dma!(rx_dma),
             config,
         )
     }
@@ -518,7 +518,7 @@ impl<'d, T: Instance> Spi<'d, T, Async> {
         peri: impl Peripheral<P = T> + 'd,
         sck: impl Peripheral<P = impl SckPin<T>> + 'd,
         mosi: impl Peripheral<P = impl MosiPin<T>> + 'd,
-        txdma: impl Peripheral<P = impl TxDma<T>> + 'd,
+        tx_dma: impl Peripheral<P = impl TxDma<T>> + 'd,
         config: Config,
     ) -> Self {
         Self::new_inner(
@@ -526,7 +526,7 @@ impl<'d, T: Instance> Spi<'d, T, Async> {
             new_pin!(sck, AFType::OutputPushPull, Speed::VeryHigh, config.sck_pull_mode()),
             new_pin!(mosi, AFType::OutputPushPull, Speed::VeryHigh),
             None,
-            new_dma!(txdma),
+            new_dma!(tx_dma),
             None,
             config,
         )
@@ -538,7 +538,7 @@ impl<'d, T: Instance> Spi<'d, T, Async> {
     pub fn new_txonly_nosck(
         peri: impl Peripheral<P = T> + 'd,
         mosi: impl Peripheral<P = impl MosiPin<T>> + 'd,
-        txdma: impl Peripheral<P = impl TxDma<T>> + 'd,
+        tx_dma: impl Peripheral<P = impl TxDma<T>> + 'd,
         config: Config,
     ) -> Self {
         Self::new_inner(
@@ -546,7 +546,7 @@ impl<'d, T: Instance> Spi<'d, T, Async> {
             None,
             new_pin!(mosi, AFType::OutputPushPull, Speed::VeryHigh),
             None,
-            new_dma!(txdma),
+            new_dma!(tx_dma),
             None,
             config,
         )
@@ -556,8 +556,8 @@ impl<'d, T: Instance> Spi<'d, T, Async> {
     /// Useful for on chip peripherals like SUBGHZ which are hardwired.
     pub fn new_subghz(
         peri: impl Peripheral<P = T> + 'd,
-        txdma: impl Peripheral<P = impl TxDma<T>> + 'd,
-        rxdma: impl Peripheral<P = impl RxDma<T>> + 'd,
+        tx_dma: impl Peripheral<P = impl TxDma<T>> + 'd,
+        rx_dma: impl Peripheral<P = impl RxDma<T>> + 'd,
     ) -> Self {
         // see RM0453 rev 1 section 7.2.13 page 291
         // The SUBGHZSPI_SCK frequency is obtained by PCLK3 divided by two.
@@ -569,17 +569,17 @@ impl<'d, T: Instance> Spi<'d, T, Async> {
         config.bit_order = BitOrder::MsbFirst;
         config.frequency = freq;
 
-        Self::new_inner(peri, None, None, None, new_dma!(txdma), new_dma!(rxdma), config)
+        Self::new_inner(peri, None, None, None, new_dma!(tx_dma), new_dma!(rx_dma), config)
     }
 
     #[allow(dead_code)]
     pub(crate) fn new_internal(
         peri: impl Peripheral<P = T> + 'd,
-        txdma: impl Peripheral<P = impl TxDma<T>> + 'd,
-        rxdma: impl Peripheral<P = impl RxDma<T>> + 'd,
+        tx_dma: impl Peripheral<P = impl TxDma<T>> + 'd,
+        rx_dma: impl Peripheral<P = impl RxDma<T>> + 'd,
         config: Config,
     ) -> Self {
-        Self::new_inner(peri, None, None, None, new_dma!(txdma), new_dma!(rxdma), config)
+        Self::new_inner(peri, None, None, None, new_dma!(tx_dma), new_dma!(rx_dma), config)
     }
 
     /// SPI write, using DMA.
@@ -594,7 +594,7 @@ impl<'d, T: Instance> Spi<'d, T, Async> {
         });
 
         let tx_dst = T::REGS.tx_ptr();
-        let tx_f = unsafe { self.txdma.as_mut().unwrap().write(data, tx_dst, Default::default()) };
+        let tx_f = unsafe { self.tx_dma.as_mut().unwrap().write(data, tx_dst, Default::default()) };
 
         set_txdmaen(T::REGS, true);
         T::REGS.cr1().modify(|w| {
@@ -632,12 +632,12 @@ impl<'d, T: Instance> Spi<'d, T, Async> {
         let clock_byte_count = data.len();
 
         let rx_src = T::REGS.rx_ptr();
-        let rx_f = unsafe { self.rxdma.as_mut().unwrap().read(rx_src, data, Default::default()) };
+        let rx_f = unsafe { self.rx_dma.as_mut().unwrap().read(rx_src, data, Default::default()) };
 
         let tx_dst = T::REGS.tx_ptr();
         let clock_byte = 0x00u8;
         let tx_f = unsafe {
-            self.txdma
+            self.tx_dma
                 .as_mut()
                 .unwrap()
                 .write_repeated(&clock_byte, clock_byte_count, tx_dst, Default::default())
@@ -679,11 +679,11 @@ impl<'d, T: Instance> Spi<'d, T, Async> {
         set_rxdmaen(T::REGS, true);
 
         let rx_src = T::REGS.rx_ptr();
-        let rx_f = unsafe { self.rxdma.as_mut().unwrap().read_raw(rx_src, read, Default::default()) };
+        let rx_f = unsafe { self.rx_dma.as_mut().unwrap().read_raw(rx_src, read, Default::default()) };
 
         let tx_dst = T::REGS.tx_ptr();
         let tx_f = unsafe {
-            self.txdma
+            self.tx_dma
                 .as_mut()
                 .unwrap()
                 .write_raw(write, tx_dst, Default::default())

From 913bb19a34b88e996972c6666679b2b99ae01ce0 Mon Sep 17 00:00:00 2001
From: Dario Nieuwenhuis <dirbaio@dirbaio.net>
Date: Mon, 15 Apr 2024 23:40:12 +0200
Subject: [PATCH 4/4] stm32/i2c: remove DMA generic params.

---
 embassy-stm32/src/i2c/mod.rs                  |  61 +-
 embassy-stm32/src/i2c/v1.rs                   |  50 +-
 embassy-stm32/src/i2c/v2.rs                   | 522 +++++++++---------
 examples/stm32f4/src/bin/i2c.rs               |  18 +-
 examples/stm32l4/src/bin/i2c.rs               |  18 +-
 .../stm32l4/src/bin/i2c_blocking_async.rs     |  18 +-
 .../src/bin/spe_adin1110_http_server.rs       |   2 +-
 examples/stm32u5/src/bin/i2c.rs               |  18 +-
 8 files changed, 312 insertions(+), 395 deletions(-)

diff --git a/embassy-stm32/src/i2c/mod.rs b/embassy-stm32/src/i2c/mod.rs
index a46061d54..ccbea9831 100644
--- a/embassy-stm32/src/i2c/mod.rs
+++ b/embassy-stm32/src/i2c/mod.rs
@@ -14,9 +14,10 @@ use embassy_sync::waitqueue::AtomicWaker;
 #[cfg(feature = "time")]
 use embassy_time::{Duration, Instant};
 
-use crate::dma::NoDma;
+use crate::dma::ChannelAndRequest;
 use crate::gpio::{AFType, Pull};
 use crate::interrupt::typelevel::Interrupt;
+use crate::mode::{Async, Blocking, Mode};
 use crate::time::Hertz;
 use crate::{interrupt, peripherals};
 
@@ -71,17 +72,16 @@ impl Default for Config {
 }
 
 /// I2C driver.
-pub struct I2c<'d, T: Instance, TXDMA = NoDma, RXDMA = NoDma> {
+pub struct I2c<'d, T: Instance, M: Mode> {
     _peri: PeripheralRef<'d, T>,
-    #[allow(dead_code)]
-    tx_dma: PeripheralRef<'d, TXDMA>,
-    #[allow(dead_code)]
-    rx_dma: PeripheralRef<'d, RXDMA>,
+    tx_dma: Option<ChannelAndRequest<'d>>,
+    rx_dma: Option<ChannelAndRequest<'d>>,
     #[cfg(feature = "time")]
     timeout: Duration,
+    _phantom: PhantomData<M>,
 }
 
-impl<'d, T: Instance, TXDMA, RXDMA> I2c<'d, T, TXDMA, RXDMA> {
+impl<'d, T: Instance> I2c<'d, T, Async> {
     /// Create a new I2C driver.
     pub fn new(
         peri: impl Peripheral<P = T> + 'd,
@@ -90,12 +90,40 @@ impl<'d, T: Instance, TXDMA, RXDMA> I2c<'d, T, TXDMA, RXDMA> {
         _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,
+        tx_dma: impl Peripheral<P = impl TxDma<T>> + 'd,
+        rx_dma: impl Peripheral<P = impl RxDma<T>> + 'd,
         freq: Hertz,
         config: Config,
     ) -> Self {
-        into_ref!(peri, scl, sda, tx_dma, rx_dma);
+        Self::new_inner(peri, scl, sda, new_dma!(tx_dma), new_dma!(rx_dma), freq, config)
+    }
+}
+
+impl<'d, T: Instance> I2c<'d, T, Blocking> {
+    /// Create a new blocking I2C driver.
+    pub fn new_blocking(
+        peri: impl Peripheral<P = T> + 'd,
+        scl: impl Peripheral<P = impl SclPin<T>> + 'd,
+        sda: impl Peripheral<P = impl SdaPin<T>> + 'd,
+        freq: Hertz,
+        config: Config,
+    ) -> Self {
+        Self::new_inner(peri, scl, sda, None, None, freq, config)
+    }
+}
+
+impl<'d, T: Instance, M: Mode> I2c<'d, T, M> {
+    /// Create a new I2C driver.
+    fn new_inner(
+        peri: impl Peripheral<P = T> + 'd,
+        scl: impl Peripheral<P = impl SclPin<T>> + 'd,
+        sda: impl Peripheral<P = impl SdaPin<T>> + 'd,
+        tx_dma: Option<ChannelAndRequest<'d>>,
+        rx_dma: Option<ChannelAndRequest<'d>>,
+        freq: Hertz,
+        config: Config,
+    ) -> Self {
+        into_ref!(peri, scl, sda);
 
         T::enable_and_reset();
 
@@ -125,6 +153,7 @@ impl<'d, T: Instance, TXDMA, RXDMA> I2c<'d, T, TXDMA, RXDMA> {
             rx_dma,
             #[cfg(feature = "time")]
             timeout: config.timeout,
+            _phantom: PhantomData,
         };
 
         this.init(freq, config);
@@ -249,7 +278,7 @@ foreach_peripheral!(
     };
 );
 
-impl<'d, T: Instance> embedded_hal_02::blocking::i2c::Read for I2c<'d, T> {
+impl<'d, T: Instance, M: Mode> embedded_hal_02::blocking::i2c::Read for I2c<'d, T, M> {
     type Error = Error;
 
     fn read(&mut self, address: u8, buffer: &mut [u8]) -> Result<(), Self::Error> {
@@ -257,7 +286,7 @@ impl<'d, T: Instance> embedded_hal_02::blocking::i2c::Read for I2c<'d, T> {
     }
 }
 
-impl<'d, T: Instance> embedded_hal_02::blocking::i2c::Write for I2c<'d, T> {
+impl<'d, T: Instance, M: Mode> embedded_hal_02::blocking::i2c::Write for I2c<'d, T, M> {
     type Error = Error;
 
     fn write(&mut self, address: u8, write: &[u8]) -> Result<(), Self::Error> {
@@ -265,7 +294,7 @@ impl<'d, T: Instance> embedded_hal_02::blocking::i2c::Write for I2c<'d, T> {
     }
 }
 
-impl<'d, T: Instance> embedded_hal_02::blocking::i2c::WriteRead for I2c<'d, T> {
+impl<'d, T: Instance, M: Mode> embedded_hal_02::blocking::i2c::WriteRead for I2c<'d, T, M> {
     type Error = Error;
 
     fn write_read(&mut self, address: u8, write: &[u8], read: &mut [u8]) -> Result<(), Self::Error> {
@@ -289,11 +318,11 @@ impl embedded_hal_1::i2c::Error for Error {
     }
 }
 
-impl<'d, T: Instance, TXDMA, RXDMA> embedded_hal_1::i2c::ErrorType for I2c<'d, T, TXDMA, RXDMA> {
+impl<'d, T: Instance, M: Mode> embedded_hal_1::i2c::ErrorType for I2c<'d, T, M> {
     type Error = Error;
 }
 
-impl<'d, T: Instance> embedded_hal_1::i2c::I2c for I2c<'d, T, NoDma, NoDma> {
+impl<'d, T: Instance, M: Mode> embedded_hal_1::i2c::I2c for I2c<'d, T, M> {
     fn read(&mut self, address: u8, read: &mut [u8]) -> Result<(), Self::Error> {
         self.blocking_read(address, read)
     }
@@ -315,7 +344,7 @@ impl<'d, T: Instance> embedded_hal_1::i2c::I2c for I2c<'d, T, NoDma, NoDma> {
     }
 }
 
-impl<'d, T: Instance, TXDMA: TxDma<T>, RXDMA: RxDma<T>> embedded_hal_async::i2c::I2c for I2c<'d, T, TXDMA, RXDMA> {
+impl<'d, T: Instance> embedded_hal_async::i2c::I2c for I2c<'d, T, Async> {
     async fn read(&mut self, address: u8, read: &mut [u8]) -> Result<(), Self::Error> {
         self.read(address, read).await
     }
diff --git a/embassy-stm32/src/i2c/v1.rs b/embassy-stm32/src/i2c/v1.rs
index d45c48b24..13a473344 100644
--- a/embassy-stm32/src/i2c/v1.rs
+++ b/embassy-stm32/src/i2c/v1.rs
@@ -13,7 +13,7 @@ use embassy_hal_internal::drop::OnDrop;
 use embedded_hal_1::i2c::Operation;
 
 use super::*;
-use crate::dma::Transfer;
+use crate::mode::Mode as PeriMode;
 use crate::pac::i2c;
 
 // /!\                      /!\
@@ -41,7 +41,7 @@ pub unsafe fn on_interrupt<T: Instance>() {
     });
 }
 
-impl<'d, T: Instance, TXDMA, RXDMA> I2c<'d, T, TXDMA, RXDMA> {
+impl<'d, T: Instance, M: PeriMode> I2c<'d, T, M> {
     pub(crate) fn init(&mut self, freq: Hertz, _config: Config) {
         T::regs().cr1().modify(|reg| {
             reg.set_pe(false);
@@ -326,11 +326,10 @@ impl<'d, T: Instance, TXDMA, RXDMA> I2c<'d, T, TXDMA, RXDMA> {
             w.set_itevten(true);
         });
     }
+}
 
-    async fn write_frame(&mut self, address: u8, write: &[u8], frame: FrameOptions) -> Result<(), Error>
-    where
-        TXDMA: crate::i2c::TxDma<T>,
-    {
+impl<'d, T: Instance> I2c<'d, T, Async> {
+    async fn write_frame(&mut self, address: u8, write: &[u8], frame: FrameOptions) -> Result<(), Error> {
         T::regs().cr2().modify(|w| {
             // Note: Do not enable the ITBUFEN bit in the I2C_CR2 register if DMA is used for
             // reception.
@@ -415,9 +414,7 @@ impl<'d, T: Instance, TXDMA, RXDMA> I2c<'d, T, TXDMA, RXDMA> {
             // this address from the memory after each TxE event.
             let dst = T::regs().dr().as_ptr() as *mut u8;
 
-            let ch = &mut self.tx_dma;
-            let request = ch.request();
-            Transfer::new_write(ch, request, write, dst, Default::default())
+            self.tx_dma.as_mut().unwrap().write(write, dst, Default::default())
         };
 
         // Wait for bytes to be sent, or an error to occur.
@@ -479,10 +476,7 @@ impl<'d, T: Instance, TXDMA, RXDMA> I2c<'d, T, TXDMA, RXDMA> {
     }
 
     /// Write.
-    pub async fn write(&mut self, address: u8, write: &[u8]) -> Result<(), Error>
-    where
-        TXDMA: crate::i2c::TxDma<T>,
-    {
+    pub async fn write(&mut self, address: u8, write: &[u8]) -> Result<(), Error> {
         self.write_frame(address, write, FrameOptions::FirstAndLastFrame)
             .await?;
 
@@ -490,20 +484,14 @@ impl<'d, T: Instance, TXDMA, RXDMA> I2c<'d, T, TXDMA, RXDMA> {
     }
 
     /// Read.
-    pub async fn read(&mut self, address: u8, buffer: &mut [u8]) -> Result<(), Error>
-    where
-        RXDMA: crate::i2c::RxDma<T>,
-    {
+    pub async fn read(&mut self, address: u8, buffer: &mut [u8]) -> Result<(), Error> {
         self.read_frame(address, buffer, FrameOptions::FirstAndLastFrame)
             .await?;
 
         Ok(())
     }
 
-    async fn read_frame(&mut self, address: u8, buffer: &mut [u8], frame: FrameOptions) -> Result<(), Error>
-    where
-        RXDMA: crate::i2c::RxDma<T>,
-    {
+    async fn read_frame(&mut self, address: u8, buffer: &mut [u8], frame: FrameOptions) -> Result<(), Error> {
         if buffer.is_empty() {
             return Err(Error::Overrun);
         }
@@ -623,9 +611,7 @@ impl<'d, T: Instance, TXDMA, RXDMA> I2c<'d, T, TXDMA, RXDMA> {
             // from this address from the memory after each RxE event.
             let src = T::regs().dr().as_ptr() as *mut u8;
 
-            let ch = &mut self.rx_dma;
-            let request = ch.request();
-            Transfer::new_read(ch, request, src, buffer, Default::default())
+            self.rx_dma.as_mut().unwrap().read(src, buffer, Default::default())
         };
 
         // Wait for bytes to be received, or an error to occur.
@@ -664,11 +650,7 @@ impl<'d, T: Instance, TXDMA, RXDMA> I2c<'d, T, TXDMA, RXDMA> {
     }
 
     /// Write, restart, read.
-    pub async fn write_read(&mut self, address: u8, write: &[u8], read: &mut [u8]) -> Result<(), Error>
-    where
-        RXDMA: crate::i2c::RxDma<T>,
-        TXDMA: crate::i2c::TxDma<T>,
-    {
+    pub async fn write_read(&mut self, address: u8, write: &[u8], read: &mut [u8]) -> Result<(), Error> {
         // Check empty read buffer before starting transaction. Otherwise, we would not generate the
         // stop condition below.
         if read.is_empty() {
@@ -684,11 +666,7 @@ impl<'d, T: Instance, TXDMA, RXDMA> I2c<'d, T, TXDMA, RXDMA> {
     /// Consecutive operations of same type are merged. See [transaction contract] for details.
     ///
     /// [transaction contract]: embedded_hal_1::i2c::I2c::transaction
-    pub async fn transaction(&mut self, addr: u8, operations: &mut [Operation<'_>]) -> Result<(), Error>
-    where
-        RXDMA: crate::i2c::RxDma<T>,
-        TXDMA: crate::i2c::TxDma<T>,
-    {
+    pub async fn transaction(&mut self, addr: u8, operations: &mut [Operation<'_>]) -> Result<(), Error> {
         for (op, frame) in operation_frames(operations)? {
             match op {
                 Operation::Read(read) => self.read_frame(addr, read, frame).await?,
@@ -700,7 +678,7 @@ impl<'d, T: Instance, TXDMA, RXDMA> I2c<'d, T, TXDMA, RXDMA> {
     }
 }
 
-impl<'d, T: Instance, TXDMA, RXDMA> Drop for I2c<'d, T, TXDMA, RXDMA> {
+impl<'d, T: Instance, M: PeriMode> Drop for I2c<'d, T, M> {
     fn drop(&mut self) {
         T::disable();
     }
@@ -806,7 +784,7 @@ impl Timings {
     }
 }
 
-impl<'d, T: Instance> SetConfig for I2c<'d, T> {
+impl<'d, T: Instance, M: PeriMode> SetConfig for I2c<'d, T, M> {
     type Config = Hertz;
     type ConfigError = ();
     fn set_config(&mut self, config: &Self::Config) -> Result<(), ()> {
diff --git a/embassy-stm32/src/i2c/v2.rs b/embassy-stm32/src/i2c/v2.rs
index da3b0ee30..12df98534 100644
--- a/embassy-stm32/src/i2c/v2.rs
+++ b/embassy-stm32/src/i2c/v2.rs
@@ -7,7 +7,6 @@ use embassy_hal_internal::drop::OnDrop;
 use embedded_hal_1::i2c::Operation;
 
 use super::*;
-use crate::dma::Transfer;
 use crate::pac::i2c;
 
 pub(crate) unsafe fn on_interrupt<T: Instance>() {
@@ -24,7 +23,7 @@ pub(crate) unsafe fn on_interrupt<T: Instance>() {
     });
 }
 
-impl<'d, T: Instance, TXDMA, RXDMA> I2c<'d, T, TXDMA, RXDMA> {
+impl<'d, T: Instance, M: Mode> I2c<'d, T, M> {
     pub(crate) fn init(&mut self, freq: Hertz, _config: Config) {
         T::regs().cr1().modify(|reg| {
             reg.set_pe(false);
@@ -302,276 +301,6 @@ impl<'d, T: Instance, TXDMA, RXDMA> I2c<'d, T, TXDMA, RXDMA> {
         result
     }
 
-    async fn write_dma_internal(
-        &mut self,
-        address: u8,
-        write: &[u8],
-        first_slice: bool,
-        last_slice: bool,
-        timeout: Timeout,
-    ) -> Result<(), Error>
-    where
-        TXDMA: crate::i2c::TxDma<T>,
-    {
-        let total_len = write.len();
-
-        let dma_transfer = unsafe {
-            let regs = T::regs();
-            regs.cr1().modify(|w| {
-                w.set_txdmaen(true);
-                if first_slice {
-                    w.set_tcie(true);
-                }
-            });
-            let dst = regs.txdr().as_ptr() as *mut u8;
-
-            let ch = &mut self.tx_dma;
-            let request = ch.request();
-            Transfer::new_write(ch, request, write, dst, Default::default())
-        };
-
-        let state = T::state();
-        let mut remaining_len = total_len;
-
-        let on_drop = OnDrop::new(|| {
-            let regs = T::regs();
-            regs.cr1().modify(|w| {
-                if last_slice {
-                    w.set_txdmaen(false);
-                }
-                w.set_tcie(false);
-            })
-        });
-
-        poll_fn(|cx| {
-            state.waker.register(cx.waker());
-
-            let isr = T::regs().isr().read();
-            if remaining_len == total_len {
-                if first_slice {
-                    Self::master_write(
-                        address,
-                        total_len.min(255),
-                        Stop::Software,
-                        (total_len > 255) || !last_slice,
-                        timeout,
-                    )?;
-                } else {
-                    Self::master_continue(total_len.min(255), (total_len > 255) || !last_slice, timeout)?;
-                    T::regs().cr1().modify(|w| w.set_tcie(true));
-                }
-            } else if !(isr.tcr() || isr.tc()) {
-                // poll_fn was woken without an interrupt present
-                return Poll::Pending;
-            } else if remaining_len == 0 {
-                return Poll::Ready(Ok(()));
-            } else {
-                let last_piece = (remaining_len <= 255) && last_slice;
-
-                if let Err(e) = Self::master_continue(remaining_len.min(255), !last_piece, timeout) {
-                    return Poll::Ready(Err(e));
-                }
-                T::regs().cr1().modify(|w| w.set_tcie(true));
-            }
-
-            remaining_len = remaining_len.saturating_sub(255);
-            Poll::Pending
-        })
-        .await?;
-
-        dma_transfer.await;
-
-        if last_slice {
-            // This should be done already
-            self.wait_tc(timeout)?;
-            self.master_stop();
-        }
-
-        drop(on_drop);
-
-        Ok(())
-    }
-
-    async fn read_dma_internal(
-        &mut self,
-        address: u8,
-        buffer: &mut [u8],
-        restart: bool,
-        timeout: Timeout,
-    ) -> Result<(), Error>
-    where
-        RXDMA: crate::i2c::RxDma<T>,
-    {
-        let total_len = buffer.len();
-
-        let dma_transfer = unsafe {
-            let regs = T::regs();
-            regs.cr1().modify(|w| {
-                w.set_rxdmaen(true);
-                w.set_tcie(true);
-            });
-            let src = regs.rxdr().as_ptr() as *mut u8;
-
-            let ch = &mut self.rx_dma;
-            let request = ch.request();
-            Transfer::new_read(ch, request, src, buffer, Default::default())
-        };
-
-        let state = T::state();
-        let mut remaining_len = total_len;
-
-        let on_drop = OnDrop::new(|| {
-            let regs = T::regs();
-            regs.cr1().modify(|w| {
-                w.set_rxdmaen(false);
-                w.set_tcie(false);
-            })
-        });
-
-        poll_fn(|cx| {
-            state.waker.register(cx.waker());
-
-            let isr = T::regs().isr().read();
-            if remaining_len == total_len {
-                Self::master_read(
-                    address,
-                    total_len.min(255),
-                    Stop::Software,
-                    total_len > 255,
-                    restart,
-                    timeout,
-                )?;
-            } else if !(isr.tcr() || isr.tc()) {
-                // poll_fn was woken without an interrupt present
-                return Poll::Pending;
-            } else if remaining_len == 0 {
-                return Poll::Ready(Ok(()));
-            } else {
-                let last_piece = remaining_len <= 255;
-
-                if let Err(e) = Self::master_continue(remaining_len.min(255), !last_piece, timeout) {
-                    return Poll::Ready(Err(e));
-                }
-                T::regs().cr1().modify(|w| w.set_tcie(true));
-            }
-
-            remaining_len = remaining_len.saturating_sub(255);
-            Poll::Pending
-        })
-        .await?;
-
-        dma_transfer.await;
-
-        // This should be done already
-        self.wait_tc(timeout)?;
-        self.master_stop();
-
-        drop(on_drop);
-
-        Ok(())
-    }
-
-    // =========================
-    //  Async public API
-
-    /// Write.
-    pub async fn write(&mut self, address: u8, write: &[u8]) -> Result<(), Error>
-    where
-        TXDMA: crate::i2c::TxDma<T>,
-    {
-        let timeout = self.timeout();
-        if write.is_empty() {
-            self.write_internal(address, write, true, timeout)
-        } else {
-            timeout
-                .with(self.write_dma_internal(address, write, true, true, timeout))
-                .await
-        }
-    }
-
-    /// 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>
-    where
-        TXDMA: crate::i2c::TxDma<T>,
-    {
-        let timeout = self.timeout();
-
-        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();
-
-            let fut = self.write_dma_internal(address, c, first, is_last, timeout);
-            timeout.with(fut).await?;
-            first = false;
-            current = next;
-        }
-        Ok(())
-    }
-
-    /// Read.
-    pub async fn read(&mut self, address: u8, buffer: &mut [u8]) -> Result<(), Error>
-    where
-        RXDMA: crate::i2c::RxDma<T>,
-    {
-        let timeout = self.timeout();
-
-        if buffer.is_empty() {
-            self.read_internal(address, buffer, false, timeout)
-        } else {
-            let fut = self.read_dma_internal(address, buffer, false, timeout);
-            timeout.with(fut).await
-        }
-    }
-
-    /// Write, restart, read.
-    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 timeout = self.timeout();
-
-        if write.is_empty() {
-            self.write_internal(address, write, false, timeout)?;
-        } else {
-            let fut = self.write_dma_internal(address, write, true, true, timeout);
-            timeout.with(fut).await?;
-        }
-
-        if read.is_empty() {
-            self.read_internal(address, read, true, timeout)?;
-        } else {
-            let fut = self.read_dma_internal(address, read, true, timeout);
-            timeout.with(fut).await?;
-        }
-
-        Ok(())
-    }
-
-    /// Transaction with operations.
-    ///
-    /// Consecutive operations of same type are merged. See [transaction contract] for details.
-    ///
-    /// [transaction contract]: embedded_hal_1::i2c::I2c::transaction
-    pub async fn transaction(&mut self, addr: u8, operations: &mut [Operation<'_>]) -> Result<(), Error>
-    where
-        RXDMA: crate::i2c::RxDma<T>,
-        TXDMA: crate::i2c::TxDma<T>,
-    {
-        let _ = addr;
-        let _ = operations;
-        todo!()
-    }
-
     // =========================
     //  Blocking public API
 
@@ -684,7 +413,252 @@ impl<'d, T: Instance, TXDMA, RXDMA> I2c<'d, T, TXDMA, RXDMA> {
     }
 }
 
-impl<'d, T: Instance, TXDMA, RXDMA> Drop for I2c<'d, T, TXDMA, RXDMA> {
+impl<'d, T: Instance> I2c<'d, T, Async> {
+    async fn write_dma_internal(
+        &mut self,
+        address: u8,
+        write: &[u8],
+        first_slice: bool,
+        last_slice: bool,
+        timeout: Timeout,
+    ) -> Result<(), Error> {
+        let total_len = write.len();
+
+        let dma_transfer = unsafe {
+            let regs = T::regs();
+            regs.cr1().modify(|w| {
+                w.set_txdmaen(true);
+                if first_slice {
+                    w.set_tcie(true);
+                }
+            });
+            let dst = regs.txdr().as_ptr() as *mut u8;
+
+            self.tx_dma.as_mut().unwrap().write(write, dst, Default::default())
+        };
+
+        let state = T::state();
+        let mut remaining_len = total_len;
+
+        let on_drop = OnDrop::new(|| {
+            let regs = T::regs();
+            regs.cr1().modify(|w| {
+                if last_slice {
+                    w.set_txdmaen(false);
+                }
+                w.set_tcie(false);
+            })
+        });
+
+        poll_fn(|cx| {
+            state.waker.register(cx.waker());
+
+            let isr = T::regs().isr().read();
+            if remaining_len == total_len {
+                if first_slice {
+                    Self::master_write(
+                        address,
+                        total_len.min(255),
+                        Stop::Software,
+                        (total_len > 255) || !last_slice,
+                        timeout,
+                    )?;
+                } else {
+                    Self::master_continue(total_len.min(255), (total_len > 255) || !last_slice, timeout)?;
+                    T::regs().cr1().modify(|w| w.set_tcie(true));
+                }
+            } else if !(isr.tcr() || isr.tc()) {
+                // poll_fn was woken without an interrupt present
+                return Poll::Pending;
+            } else if remaining_len == 0 {
+                return Poll::Ready(Ok(()));
+            } else {
+                let last_piece = (remaining_len <= 255) && last_slice;
+
+                if let Err(e) = Self::master_continue(remaining_len.min(255), !last_piece, timeout) {
+                    return Poll::Ready(Err(e));
+                }
+                T::regs().cr1().modify(|w| w.set_tcie(true));
+            }
+
+            remaining_len = remaining_len.saturating_sub(255);
+            Poll::Pending
+        })
+        .await?;
+
+        dma_transfer.await;
+
+        if last_slice {
+            // This should be done already
+            self.wait_tc(timeout)?;
+            self.master_stop();
+        }
+
+        drop(on_drop);
+
+        Ok(())
+    }
+
+    async fn read_dma_internal(
+        &mut self,
+        address: u8,
+        buffer: &mut [u8],
+        restart: bool,
+        timeout: Timeout,
+    ) -> Result<(), Error> {
+        let total_len = buffer.len();
+
+        let dma_transfer = unsafe {
+            let regs = T::regs();
+            regs.cr1().modify(|w| {
+                w.set_rxdmaen(true);
+                w.set_tcie(true);
+            });
+            let src = regs.rxdr().as_ptr() as *mut u8;
+
+            self.rx_dma.as_mut().unwrap().read(src, buffer, Default::default())
+        };
+
+        let state = T::state();
+        let mut remaining_len = total_len;
+
+        let on_drop = OnDrop::new(|| {
+            let regs = T::regs();
+            regs.cr1().modify(|w| {
+                w.set_rxdmaen(false);
+                w.set_tcie(false);
+            })
+        });
+
+        poll_fn(|cx| {
+            state.waker.register(cx.waker());
+
+            let isr = T::regs().isr().read();
+            if remaining_len == total_len {
+                Self::master_read(
+                    address,
+                    total_len.min(255),
+                    Stop::Software,
+                    total_len > 255,
+                    restart,
+                    timeout,
+                )?;
+            } else if !(isr.tcr() || isr.tc()) {
+                // poll_fn was woken without an interrupt present
+                return Poll::Pending;
+            } else if remaining_len == 0 {
+                return Poll::Ready(Ok(()));
+            } else {
+                let last_piece = remaining_len <= 255;
+
+                if let Err(e) = Self::master_continue(remaining_len.min(255), !last_piece, timeout) {
+                    return Poll::Ready(Err(e));
+                }
+                T::regs().cr1().modify(|w| w.set_tcie(true));
+            }
+
+            remaining_len = remaining_len.saturating_sub(255);
+            Poll::Pending
+        })
+        .await?;
+
+        dma_transfer.await;
+
+        // This should be done already
+        self.wait_tc(timeout)?;
+        self.master_stop();
+
+        drop(on_drop);
+
+        Ok(())
+    }
+
+    // =========================
+    //  Async public API
+
+    /// Write.
+    pub async fn write(&mut self, address: u8, write: &[u8]) -> Result<(), Error> {
+        let timeout = self.timeout();
+        if write.is_empty() {
+            self.write_internal(address, write, true, timeout)
+        } else {
+            timeout
+                .with(self.write_dma_internal(address, write, true, true, timeout))
+                .await
+        }
+    }
+
+    /// 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> {
+        let timeout = self.timeout();
+
+        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();
+
+            let fut = self.write_dma_internal(address, c, first, is_last, timeout);
+            timeout.with(fut).await?;
+            first = false;
+            current = next;
+        }
+        Ok(())
+    }
+
+    /// Read.
+    pub async fn read(&mut self, address: u8, buffer: &mut [u8]) -> Result<(), Error> {
+        let timeout = self.timeout();
+
+        if buffer.is_empty() {
+            self.read_internal(address, buffer, false, timeout)
+        } else {
+            let fut = self.read_dma_internal(address, buffer, false, timeout);
+            timeout.with(fut).await
+        }
+    }
+
+    /// Write, restart, read.
+    pub async fn write_read(&mut self, address: u8, write: &[u8], read: &mut [u8]) -> Result<(), Error> {
+        let timeout = self.timeout();
+
+        if write.is_empty() {
+            self.write_internal(address, write, false, timeout)?;
+        } else {
+            let fut = self.write_dma_internal(address, write, true, true, timeout);
+            timeout.with(fut).await?;
+        }
+
+        if read.is_empty() {
+            self.read_internal(address, read, true, timeout)?;
+        } else {
+            let fut = self.read_dma_internal(address, read, true, timeout);
+            timeout.with(fut).await?;
+        }
+
+        Ok(())
+    }
+
+    /// Transaction with operations.
+    ///
+    /// Consecutive operations of same type are merged. See [transaction contract] for details.
+    ///
+    /// [transaction contract]: embedded_hal_1::i2c::I2c::transaction
+    pub async fn transaction(&mut self, addr: u8, operations: &mut [Operation<'_>]) -> Result<(), Error> {
+        let _ = addr;
+        let _ = operations;
+        todo!()
+    }
+}
+
+impl<'d, T: Instance, M: Mode> Drop for I2c<'d, T, M> {
     fn drop(&mut self) {
         T::disable();
     }
@@ -814,7 +788,7 @@ impl Timings {
     }
 }
 
-impl<'d, T: Instance> SetConfig for I2c<'d, T> {
+impl<'d, T: Instance, M: Mode> SetConfig for I2c<'d, T, M> {
     type Config = Hertz;
     type ConfigError = ();
     fn set_config(&mut self, config: &Self::Config) -> Result<(), ()> {
diff --git a/examples/stm32f4/src/bin/i2c.rs b/examples/stm32f4/src/bin/i2c.rs
index 4b5da774d..4a96357a4 100644
--- a/examples/stm32f4/src/bin/i2c.rs
+++ b/examples/stm32f4/src/bin/i2c.rs
@@ -3,35 +3,19 @@
 
 use defmt::*;
 use embassy_executor::Spawner;
-use embassy_stm32::dma::NoDma;
 use embassy_stm32::i2c::{Error, I2c};
 use embassy_stm32::time::Hertz;
-use embassy_stm32::{bind_interrupts, i2c, peripherals};
 use {defmt_rtt as _, panic_probe as _};
 
 const ADDRESS: u8 = 0x5F;
 const WHOAMI: u8 = 0x0F;
 
-bind_interrupts!(struct Irqs {
-    I2C2_EV => i2c::EventInterruptHandler<peripherals::I2C2>;
-    I2C2_ER => i2c::ErrorInterruptHandler<peripherals::I2C2>;
-});
-
 #[embassy_executor::main]
 async fn main(_spawner: Spawner) {
     info!("Hello world!");
     let p = embassy_stm32::init(Default::default());
 
-    let mut i2c = I2c::new(
-        p.I2C2,
-        p.PB10,
-        p.PB11,
-        Irqs,
-        NoDma,
-        NoDma,
-        Hertz(100_000),
-        Default::default(),
-    );
+    let mut i2c = I2c::new_blocking(p.I2C2, p.PB10, p.PB11, Hertz(100_000), Default::default());
 
     let mut data = [0u8; 1];
 
diff --git a/examples/stm32l4/src/bin/i2c.rs b/examples/stm32l4/src/bin/i2c.rs
index f553deb82..2861bc091 100644
--- a/examples/stm32l4/src/bin/i2c.rs
+++ b/examples/stm32l4/src/bin/i2c.rs
@@ -3,33 +3,17 @@
 
 use defmt::*;
 use embassy_executor::Spawner;
-use embassy_stm32::dma::NoDma;
 use embassy_stm32::i2c::I2c;
 use embassy_stm32::time::Hertz;
-use embassy_stm32::{bind_interrupts, i2c, peripherals};
 use {defmt_rtt as _, panic_probe as _};
 
 const ADDRESS: u8 = 0x5F;
 const WHOAMI: u8 = 0x0F;
 
-bind_interrupts!(struct Irqs {
-    I2C2_EV => i2c::EventInterruptHandler<peripherals::I2C2>;
-    I2C2_ER => i2c::ErrorInterruptHandler<peripherals::I2C2>;
-});
-
 #[embassy_executor::main]
 async fn main(_spawner: Spawner) {
     let p = embassy_stm32::init(Default::default());
-    let mut i2c = I2c::new(
-        p.I2C2,
-        p.PB10,
-        p.PB11,
-        Irqs,
-        NoDma,
-        NoDma,
-        Hertz(100_000),
-        Default::default(),
-    );
+    let mut i2c = I2c::new_blocking(p.I2C2, p.PB10, p.PB11, Hertz(100_000), Default::default());
 
     let mut data = [0u8; 1];
     unwrap!(i2c.blocking_write_read(ADDRESS, &[WHOAMI], &mut data));
diff --git a/examples/stm32l4/src/bin/i2c_blocking_async.rs b/examples/stm32l4/src/bin/i2c_blocking_async.rs
index 1b8652bcc..a014b23e0 100644
--- a/examples/stm32l4/src/bin/i2c_blocking_async.rs
+++ b/examples/stm32l4/src/bin/i2c_blocking_async.rs
@@ -4,34 +4,18 @@
 use defmt::*;
 use embassy_embedded_hal::adapter::BlockingAsync;
 use embassy_executor::Spawner;
-use embassy_stm32::dma::NoDma;
 use embassy_stm32::i2c::I2c;
 use embassy_stm32::time::Hertz;
-use embassy_stm32::{bind_interrupts, i2c, peripherals};
 use embedded_hal_async::i2c::I2c as I2cTrait;
 use {defmt_rtt as _, panic_probe as _};
 
 const ADDRESS: u8 = 0x5F;
 const WHOAMI: u8 = 0x0F;
 
-bind_interrupts!(struct Irqs {
-    I2C2_EV => i2c::EventInterruptHandler<peripherals::I2C2>;
-    I2C2_ER => i2c::ErrorInterruptHandler<peripherals::I2C2>;
-});
-
 #[embassy_executor::main]
 async fn main(_spawner: Spawner) {
     let p = embassy_stm32::init(Default::default());
-    let i2c = I2c::new(
-        p.I2C2,
-        p.PB10,
-        p.PB11,
-        Irqs,
-        NoDma,
-        NoDma,
-        Hertz(100_000),
-        Default::default(),
-    );
+    let i2c = I2c::new_blocking(p.I2C2, p.PB10, p.PB11, Hertz(100_000), Default::default());
     let mut i2c = BlockingAsync::new(i2c);
 
     let mut data = [0u8; 1];
diff --git a/examples/stm32l4/src/bin/spe_adin1110_http_server.rs b/examples/stm32l4/src/bin/spe_adin1110_http_server.rs
index a99d08924..694629ede 100644
--- a/examples/stm32l4/src/bin/spe_adin1110_http_server.rs
+++ b/examples/stm32l4/src/bin/spe_adin1110_http_server.rs
@@ -60,7 +60,7 @@ pub type SpeSpiCs = ExclusiveDevice<SpeSpi, Output<'static>, Delay>;
 pub type SpeInt = exti::ExtiInput<'static>;
 pub type SpeRst = Output<'static>;
 pub type Adin1110T = ADIN1110<SpeSpiCs>;
-pub type TempSensI2c = I2c<'static, peripherals::I2C3, peripherals::DMA1_CH6, peripherals::DMA1_CH7>;
+pub type TempSensI2c = I2c<'static, peripherals::I2C3, Async>;
 
 static TEMP: AtomicI32 = AtomicI32::new(0);
 
diff --git a/examples/stm32u5/src/bin/i2c.rs b/examples/stm32u5/src/bin/i2c.rs
index e376c6bc8..19a78eac9 100644
--- a/examples/stm32u5/src/bin/i2c.rs
+++ b/examples/stm32u5/src/bin/i2c.rs
@@ -3,33 +3,17 @@
 
 use defmt::{info, unwrap};
 use embassy_executor::Spawner;
-use embassy_stm32::dma::NoDma;
 use embassy_stm32::i2c::I2c;
 use embassy_stm32::time::Hertz;
-use embassy_stm32::{bind_interrupts, i2c, peripherals};
 use {defmt_rtt as _, panic_probe as _};
 
 const HTS221_ADDRESS: u8 = 0x5F;
 const WHOAMI: u8 = 0x0F;
 
-bind_interrupts!(struct Irqs {
-    I2C2_EV => i2c::EventInterruptHandler<peripherals::I2C2>;
-    I2C2_ER => i2c::ErrorInterruptHandler<peripherals::I2C2>;
-});
-
 #[embassy_executor::main]
 async fn main(_spawner: Spawner) {
     let p = embassy_stm32::init(Default::default());
-    let mut i2c = I2c::new(
-        p.I2C2,
-        p.PH4,
-        p.PH5,
-        Irqs,
-        NoDma,
-        NoDma,
-        Hertz(100_000),
-        Default::default(),
-    );
+    let mut i2c = I2c::new_blocking(p.I2C2, p.PH4, p.PH5, Hertz(100_000), Default::default());
 
     let mut data = [0u8; 1];
     unwrap!(i2c.blocking_write_read(HTS221_ADDRESS, &[WHOAMI], &mut data));