diff --git a/embassy-nrf/src/spim.rs b/embassy-nrf/src/spim.rs
index 8937159df..2d70732a8 100644
--- a/embassy-nrf/src/spim.rs
+++ b/embassy-nrf/src/spim.rs
@@ -17,7 +17,7 @@ use crate::chip::{EASY_DMA_SIZE, FORCE_COPY_BUFFER_SIZE};
 use crate::gpio::sealed::Pin as _;
 use crate::gpio::{self, AnyPin, Pin as GpioPin, PselBits};
 use crate::interrupt::typelevel::Interrupt;
-use crate::util::{slice_in_ram_or, slice_ptr_parts, slice_ptr_parts_mut};
+use crate::util::{slice_in_ram_or, slice_ptr_len, slice_ptr_parts, slice_ptr_parts_mut};
 use crate::{interrupt, pac, Peripheral};
 
 /// SPIM error
@@ -25,10 +25,6 @@ use crate::{interrupt, pac, Peripheral};
 #[cfg_attr(feature = "defmt", derive(defmt::Format))]
 #[non_exhaustive]
 pub enum Error {
-    /// Supplied TX buffer overflows EasyDMA transmit buffer
-    TxBufferTooLong,
-    /// Supplied RX buffer overflows EasyDMA receive buffer
-    RxBufferTooLong,
     /// EasyDMA can only read from data memory, read only buffers in flash will fail.
     BufferNotInRAM,
 }
@@ -74,9 +70,13 @@ impl<T: Instance> interrupt::typelevel::Handler<T::Interrupt> for InterruptHandl
         let s = T::state();
 
         #[cfg(feature = "_nrf52832_anomaly_109")]
-        if r.events_started.read().bits() != 0 {
-            s.waker.wake();
-            r.intenclr.write(|w| w.started().clear());
+        {
+            // Ideally we should call this only during the first chunk transfer,
+            // but so far calling this every time doesn't seem to be causing any issues.
+            if r.events_started.read().bits() != 0 {
+                s.waker.wake();
+                r.intenclr.write(|w| w.started().clear());
+            }
         }
 
         if r.events_end.read().bits() != 0 {
@@ -209,35 +209,39 @@ impl<'d, T: Instance> Spim<'d, T> {
         spim
     }
 
-    fn prepare(&mut self, rx: *mut [u8], tx: *const [u8]) -> Result<(), Error> {
-        slice_in_ram_or(tx, Error::BufferNotInRAM)?;
-        // NOTE: RAM slice check for rx is not necessary, as a mutable
-        // slice can only be built from data located in RAM.
-
+    fn prepare_dma_transfer(&mut self, rx: *mut [u8], tx: *const [u8], offset: usize, length: usize) {
         compiler_fence(Ordering::SeqCst);
 
         let r = T::regs();
 
-        // Set up the DMA write.
-        let (ptr, tx_len) = slice_ptr_parts(tx);
-        if tx_len > EASY_DMA_SIZE {
-            return Err(Error::TxBufferTooLong);
+        fn xfer_params(ptr: u32, total: usize, offset: usize, length: usize) -> (u32, usize) {
+            if total > offset {
+                (ptr.wrapping_add(offset as _), core::cmp::min(total - offset, length))
+            } else {
+                (ptr, 0)
+            }
         }
 
-        r.txd.ptr.write(|w| unsafe { w.ptr().bits(ptr as _) });
-        r.txd.maxcnt.write(|w| unsafe { w.maxcnt().bits(tx_len as _) });
-
         // Set up the DMA read.
-        let (ptr, rx_len) = slice_ptr_parts_mut(rx);
-        if rx_len > EASY_DMA_SIZE {
-            return Err(Error::RxBufferTooLong);
-        }
-
-        r.rxd.ptr.write(|w| unsafe { w.ptr().bits(ptr as _) });
+        let (ptr, len) = slice_ptr_parts_mut(rx);
+        let (rx_ptr, rx_len) = xfer_params(ptr as _, len as _, offset, length);
+        r.rxd.ptr.write(|w| unsafe { w.ptr().bits(rx_ptr) });
         r.rxd.maxcnt.write(|w| unsafe { w.maxcnt().bits(rx_len as _) });
 
+        // Set up the DMA write.
+        let (ptr, len) = slice_ptr_parts(tx);
+        let (tx_ptr, tx_len) = xfer_params(ptr as _, len as _, offset, length);
+        r.txd.ptr.write(|w| unsafe { w.ptr().bits(tx_ptr) });
+        r.txd.maxcnt.write(|w| unsafe { w.maxcnt().bits(tx_len as _) });
+
+        /*
+        trace!("XFER: offset: {}, length: {}", offset, length);
+        trace!("RX(len: {}, ptr: {=u32:02x})", rx_len, rx_ptr as u32);
+        trace!("TX(len: {}, ptr: {=u32:02x})", tx_len, tx_ptr as u32);
+        */
+
         #[cfg(feature = "_nrf52832_anomaly_109")]
-        {
+        if offset == 0 {
             let s = T::state();
 
             r.events_started.reset();
@@ -260,21 +264,32 @@ impl<'d, T: Instance> Spim<'d, T> {
 
         // Start SPI transaction.
         r.tasks_start.write(|w| unsafe { w.bits(1) });
-
-        Ok(())
     }
 
-    fn blocking_inner_from_ram(&mut self, rx: *mut [u8], tx: *const [u8]) -> Result<(), Error> {
-        self.prepare(rx, tx)?;
+    fn blocking_inner_from_ram_chunk(&mut self, rx: *mut [u8], tx: *const [u8], offset: usize, length: usize) {
+        self.prepare_dma_transfer(rx, tx, offset, length);
 
         #[cfg(feature = "_nrf52832_anomaly_109")]
-        while let Poll::Pending = self.nrf52832_dma_workaround_status() {}
+        if offset == 0 {
+            while self.nrf52832_dma_workaround_status().is_pending() {}
+        }
 
         // Wait for 'end' event.
         while T::regs().events_end.read().bits() == 0 {}
 
         compiler_fence(Ordering::SeqCst);
+    }
 
+    fn blocking_inner_from_ram(&mut self, rx: *mut [u8], tx: *const [u8]) -> Result<(), Error> {
+        slice_in_ram_or(tx, Error::BufferNotInRAM)?;
+        // NOTE: RAM slice check for rx is not necessary, as a mutable
+        // slice can only be built from data located in RAM.
+
+        let xfer_len = core::cmp::max(slice_ptr_len(rx), slice_ptr_len(tx));
+        for offset in (0..xfer_len).step_by(EASY_DMA_SIZE) {
+            let length = core::cmp::min(xfer_len - offset, EASY_DMA_SIZE);
+            self.blocking_inner_from_ram_chunk(rx, tx, offset, length);
+        }
         Ok(())
     }
 
@@ -287,22 +302,23 @@ impl<'d, T: Instance> Spim<'d, T> {
                 tx_ram_buf.copy_from_slice(tx);
                 self.blocking_inner_from_ram(rx, tx_ram_buf)
             }
-            Err(error) => Err(error),
         }
     }
 
-    async fn async_inner_from_ram(&mut self, rx: *mut [u8], tx: *const [u8]) -> Result<(), Error> {
-        self.prepare(rx, tx)?;
+    async fn async_inner_from_ram_chunk(&mut self, rx: *mut [u8], tx: *const [u8], offset: usize, length: usize) {
+        self.prepare_dma_transfer(rx, tx, offset, length);
 
         #[cfg(feature = "_nrf52832_anomaly_109")]
-        poll_fn(|cx| {
-            let s = T::state();
+        if offset == 0 {
+            poll_fn(|cx| {
+                let s = T::state();
 
-            s.waker.register(cx.waker());
+                s.waker.register(cx.waker());
 
-            self.nrf52832_dma_workaround_status()
-        })
-        .await;
+                self.nrf52832_dma_workaround_status()
+            })
+            .await;
+        }
 
         // Wait for 'end' event.
         poll_fn(|cx| {
@@ -316,7 +332,18 @@ impl<'d, T: Instance> Spim<'d, T> {
         .await;
 
         compiler_fence(Ordering::SeqCst);
+    }
 
+    async fn async_inner_from_ram(&mut self, rx: *mut [u8], tx: *const [u8]) -> Result<(), Error> {
+        slice_in_ram_or(tx, Error::BufferNotInRAM)?;
+        // NOTE: RAM slice check for rx is not necessary, as a mutable
+        // slice can only be built from data located in RAM.
+
+        let xfer_len = core::cmp::max(slice_ptr_len(rx), slice_ptr_len(tx));
+        for offset in (0..xfer_len).step_by(EASY_DMA_SIZE) {
+            let length = core::cmp::min(xfer_len - offset, EASY_DMA_SIZE);
+            self.async_inner_from_ram_chunk(rx, tx, offset, length).await;
+        }
         Ok(())
     }
 
@@ -329,7 +356,6 @@ impl<'d, T: Instance> Spim<'d, T> {
                 tx_ram_buf.copy_from_slice(tx);
                 self.async_inner_from_ram(rx, tx_ram_buf).await
             }
-            Err(error) => Err(error),
         }
     }
 
@@ -528,8 +554,6 @@ mod eh02 {
 impl embedded_hal_1::spi::Error for Error {
     fn kind(&self) -> embedded_hal_1::spi::ErrorKind {
         match *self {
-            Self::TxBufferTooLong => embedded_hal_1::spi::ErrorKind::Other,
-            Self::RxBufferTooLong => embedded_hal_1::spi::ErrorKind::Other,
             Self::BufferNotInRAM => embedded_hal_1::spi::ErrorKind::Other,
         }
     }
diff --git a/embassy-nrf/src/util.rs b/embassy-nrf/src/util.rs
index b408c517b..6cdb97f08 100644
--- a/embassy-nrf/src/util.rs
+++ b/embassy-nrf/src/util.rs
@@ -4,6 +4,19 @@ use core::mem;
 const SRAM_LOWER: usize = 0x2000_0000;
 const SRAM_UPPER: usize = 0x3000_0000;
 
+// #![feature(const_slice_ptr_len)]
+// https://github.com/rust-lang/rust/issues/71146
+pub(crate) fn slice_ptr_len<T>(ptr: *const [T]) -> usize {
+    use core::ptr::NonNull;
+    let ptr = ptr.cast_mut();
+    if let Some(ptr) = NonNull::new(ptr) {
+        ptr.len()
+    } else {
+        // We know ptr is null, so we know ptr.wrapping_byte_add(1) is not null.
+        NonNull::new(ptr.wrapping_byte_add(1)).unwrap().len()
+    }
+}
+
 // TODO: replace transmutes with core::ptr::metadata once it's stable
 pub(crate) fn slice_ptr_parts<T>(slice: *const [T]) -> (*const T, usize) {
     unsafe { mem::transmute(slice) }