diff --git a/embassy-stm32/src/i2c/v2.rs b/embassy-stm32/src/i2c/v2.rs
index 54d2fe6ec..062a6225f 100644
--- a/embassy-stm32/src/i2c/v2.rs
+++ b/embassy-stm32/src/i2c/v2.rs
@@ -1,5 +1,5 @@
 use core::cmp;
-use core::future::{poll_fn, Future};
+use core::future::poll_fn;
 use core::marker::PhantomData;
 use core::task::Poll;
 
@@ -7,6 +7,8 @@ use embassy_embedded_hal::SetConfig;
 use embassy_hal_internal::drop::OnDrop;
 use embassy_hal_internal::{into_ref, PeripheralRef};
 use embassy_sync::waitqueue::AtomicWaker;
+#[cfg(feature = "time")]
+use embassy_time::{Duration, Instant};
 
 use crate::dma::{NoDma, Transfer};
 use crate::gpio::sealed::AFType;
@@ -17,75 +19,6 @@ use crate::pac::i2c;
 use crate::time::Hertz;
 use crate::{interrupt, Peripheral};
 
-/// # Async I2C Operations with Optional Timeouts
-///
-/// This module provides compatibility for async I2C operations with timeout-based APIs,
-/// even when the "time" feature is not enabled. In the absence of the "time" feature,
-/// operations effectively never time out.
-///
-/// ## Usage Scenario
-/// This is particularly useful in scenarios such as when using RTIC, where a user might
-/// have their own monotonic timer and thus choose not to enable the "time" feature.
-/// In such cases, this module allows the use of async I2C APIs without actual timeout
-/// handling.
-///
-/// ## Functionality
-/// - When the "time" feature is disabled, `Duration` and `Instant` types are provided
-///   as dummy implementations, and timeout functions do not perform real timing but
-///   simply mimic the required interfaces.
-/// - When the "time" feature is enabled, `Duration` and `Instant` from the `embassy_time`
-///   are used, and timeouts are handled as expected.
-#[cfg(not(feature = "time"))]
-mod dummy_time {
-    use core::ops::Sub;
-
-    use super::{Error, Future};
-
-    #[derive(Copy, Clone)]
-    pub struct Duration;
-
-    impl Duration {
-        pub fn dummy_duration() -> Duration {
-            Duration
-        }
-    }
-
-    pub struct Instant;
-
-    impl Instant {
-        pub fn now() -> Self {
-            Self
-        }
-
-        pub fn duration_since(&self, _since: Instant) -> Duration {
-            Duration
-        }
-    }
-
-    impl Sub for Duration {
-        type Output = Duration;
-
-        fn sub(self, _rhs: Duration) -> Duration {
-            Duration
-        }
-    }
-
-    /// Timeout that never times out.
-    pub fn timeout_fn(_timeout: Duration) -> impl Fn() -> Result<(), Error> {
-        move || Ok(())
-    }
-
-    /// This is compatible with `embassy_time::with_timeout` however it never times out.
-    pub async fn with_timeout<F: Future>(_timeout: Duration, fut: F) -> Result<F::Output, ()> {
-        Ok(fut.await)
-    }
-}
-
-#[cfg(not(feature = "time"))]
-use dummy_time::{timeout_fn, with_timeout, Duration, Instant};
-#[cfg(feature = "time")]
-use embassy_time::{Duration, Instant};
-
 #[cfg(feature = "time")]
 fn timeout_fn(timeout: Duration) -> impl Fn() -> Result<(), Error> {
     let deadline = Instant::now() + timeout;
@@ -98,9 +31,9 @@ fn timeout_fn(timeout: Duration) -> impl Fn() -> Result<(), Error> {
     }
 }
 
-#[cfg(feature = "time")]
-async fn with_timeout<F: Future>(timeout: Duration, fut: F) -> Result<F::Output, embassy_time::TimeoutError> {
-    embassy_time::with_timeout(timeout, fut).await
+#[cfg(not(feature = "time"))]
+pub fn no_timeout_fn() -> impl Fn() -> Result<(), Error> {
+    move || Ok(())
 }
 
 /// Interrupt handler.
@@ -162,6 +95,7 @@ pub struct I2c<'d, T: Instance, TXDMA = NoDma, RXDMA = NoDma> {
     tx_dma: PeripheralRef<'d, TXDMA>,
     #[allow(dead_code)]
     rx_dma: PeripheralRef<'d, RXDMA>,
+    #[cfg(feature = "time")]
     timeout: Duration,
 }
 
@@ -225,8 +159,6 @@ impl<'d, T: Instance, TXDMA, RXDMA> I2c<'d, T, TXDMA, RXDMA> {
             rx_dma,
             #[cfg(feature = "time")]
             timeout: config.transaction_timeout,
-            #[cfg(not(feature = "time"))]
-            timeout: Duration::dummy_duration(),
         }
     }
 
@@ -679,6 +611,7 @@ impl<'d, T: Instance, TXDMA, RXDMA> I2c<'d, T, TXDMA, RXDMA> {
 
     // =========================
     //  Async public API
+    #[cfg(feature = "time")]
     pub async fn write(&mut self, address: u8, write: &[u8]) -> Result<(), Error>
     where
         TXDMA: crate::i2c::TxDma<T>,
@@ -686,7 +619,7 @@ impl<'d, T: Instance, TXDMA, RXDMA> I2c<'d, T, TXDMA, RXDMA> {
         if write.is_empty() {
             self.write_internal(address, write, true, timeout_fn(self.timeout))
         } else {
-            with_timeout(
+            embassy_time::with_timeout(
                 self.timeout,
                 self.write_dma_internal(address, write, true, true, timeout_fn(self.timeout)),
             )
@@ -695,6 +628,20 @@ impl<'d, T: Instance, TXDMA, RXDMA> I2c<'d, T, TXDMA, RXDMA> {
         }
     }
 
+    #[cfg(not(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, no_timeout_fn())
+        } else {
+            self.write_dma_internal(address, write, true, true, no_timeout_fn())
+                .await
+        }
+    }
+
+    #[cfg(feature = "time")]
     pub async fn write_vectored(&mut self, address: u8, write: &[&[u8]]) -> Result<(), Error>
     where
         TXDMA: crate::i2c::TxDma<T>,
@@ -710,7 +657,7 @@ impl<'d, T: Instance, TXDMA, RXDMA> I2c<'d, T, TXDMA, RXDMA> {
             let next = iter.next();
             let is_last = next.is_none();
 
-            with_timeout(
+            embassy_time::with_timeout(
                 self.timeout,
                 self.write_dma_internal(address, c, first, is_last, timeout_fn(self.timeout)),
             )
@@ -722,6 +669,31 @@ impl<'d, T: Instance, TXDMA, RXDMA> I2c<'d, T, TXDMA, RXDMA> {
         Ok(())
     }
 
+    #[cfg(not(feature = "time"))]
+    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>
     where
         RXDMA: crate::i2c::RxDma<T>,
@@ -729,7 +701,7 @@ impl<'d, T: Instance, TXDMA, RXDMA> I2c<'d, T, TXDMA, RXDMA> {
         if buffer.is_empty() {
             self.read_internal(address, buffer, false, timeout_fn(self.timeout))
         } else {
-            with_timeout(
+            embassy_time::with_timeout(
                 self.timeout,
                 self.read_dma_internal(address, buffer, false, timeout_fn(self.timeout)),
             )
@@ -738,6 +710,19 @@ impl<'d, T: Instance, TXDMA, RXDMA> I2c<'d, T, TXDMA, RXDMA> {
         }
     }
 
+    #[cfg(not(feature = "time"))]
+    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>
     where
         TXDMA: super::TxDma<T>,
@@ -748,7 +733,7 @@ impl<'d, T: Instance, TXDMA, RXDMA> I2c<'d, T, TXDMA, RXDMA> {
         if write.is_empty() {
             self.write_internal(address, write, false, &check_timeout)?;
         } else {
-            with_timeout(
+            embassy_time::with_timeout(
                 self.timeout,
                 self.write_dma_internal(address, write, true, true, &check_timeout),
             )
@@ -761,7 +746,7 @@ impl<'d, T: Instance, TXDMA, RXDMA> I2c<'d, T, TXDMA, RXDMA> {
         if read.is_empty() {
             self.read_internal(address, read, true, &check_timeout)?;
         } else {
-            with_timeout(
+            embassy_time::with_timeout(
                 time_left_until_timeout,
                 self.read_dma_internal(address, read, true, &check_timeout),
             )
@@ -772,6 +757,28 @@ impl<'d, T: Instance, TXDMA, RXDMA> I2c<'d, T, TXDMA, RXDMA> {
         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() {
+            self.read_internal(address, read, true, &no_timeout)?;
+        } else {
+            self.read_dma_internal(address, read, true, &no_timeout).await?;
+        }
+
+        Ok(())
+    }
+
     // =========================
     //  Blocking public API