From 9de08d56a087d946bb768ed164aed459b0a2ce3c Mon Sep 17 00:00:00 2001
From: Priit Laes <plaes@plaes.org>
Date: Wed, 6 Sep 2023 23:15:33 +0300
Subject: [PATCH] nrf: spim: Anomaly 109 workaround for SPIM peripheral (#460)

This implements SPIM TX workaround suggested from section 3.8.1
from Anomaly 109 addendum.

In workaround case we first keep track of original maxcnt values,
then initiate "fake" transfer with zero-length maxcnt values.
Once the "fake" transfer is triggered, we handle it, fill in the
original maxcnt values and restart the transmission.
---
 embassy-nrf/src/spim.rs | 60 ++++++++++++++++++++++++++++++++++++++---
 1 file changed, 56 insertions(+), 4 deletions(-)

diff --git a/embassy-nrf/src/spim.rs b/embassy-nrf/src/spim.rs
index d131a43dd..a468bc302 100644
--- a/embassy-nrf/src/spim.rs
+++ b/embassy-nrf/src/spim.rs
@@ -68,6 +68,28 @@ impl<T: Instance> interrupt::typelevel::Handler<T::Interrupt> for InterruptHandl
         let r = T::regs();
         let s = T::state();
 
+        #[cfg(feature = "nrf52832")]
+        // NRF32 Anomaly 109 workaround... NRF52832
+        if r.intenset.read().started().is_enabled() && r.events_started.read().bits() != 0 {
+            // Handle the first "fake" transmission
+            r.events_started.reset();
+            r.events_end.reset();
+
+            // Update DMA registers with correct rx/tx buffer sizes
+            r.rxd
+                .maxcnt
+                .write(|w| unsafe { w.maxcnt().bits(s.rx.load(Ordering::Relaxed)) });
+            r.txd
+                .maxcnt
+                .write(|w| unsafe { w.maxcnt().bits(s.tx.load(Ordering::Relaxed)) });
+
+            // Disable interrupt for STARTED event...
+            r.intenclr.write(|w| w.started().clear());
+            // ... and start actual, hopefully glitch-free transmission
+            r.tasks_start.write(|w| unsafe { w.bits(1) });
+            return;
+        }
+
         if r.events_end.read().bits() != 0 {
             s.end_waker.wake();
             r.intenclr.write(|w| w.end().clear());
@@ -223,14 +245,33 @@ impl<'d, T: Instance> Spim<'d, T> {
         let r = T::regs();
 
         // Set up the DMA write.
-        let (ptr, len) = slice_ptr_parts(tx);
+        let (ptr, tx_len) = slice_ptr_parts(tx);
         r.txd.ptr.write(|w| unsafe { w.ptr().bits(ptr as _) });
-        r.txd.maxcnt.write(|w| unsafe { w.maxcnt().bits(len as _) });
+        r.txd.maxcnt.write(|w| unsafe { w.maxcnt().bits(tx_len as _) });
 
         // Set up the DMA read.
-        let (ptr, len) = slice_ptr_parts_mut(rx);
+        let (ptr, rx_len) = slice_ptr_parts_mut(rx);
         r.rxd.ptr.write(|w| unsafe { w.ptr().bits(ptr as _) });
-        r.rxd.maxcnt.write(|w| unsafe { w.maxcnt().bits(len as _) });
+        r.rxd.maxcnt.write(|w| unsafe { w.maxcnt().bits(rx_len as _) });
+
+        // ANOMALY 109 workaround
+        #[cfg(feature = "nrf52832")]
+        {
+            let s = T::state();
+
+            r.events_started.reset();
+
+            // Set rx/tx buffer lengths to 0...
+            r.txd.maxcnt.reset();
+            r.rxd.maxcnt.reset();
+
+            // ...and keep track of original buffer lengths...
+            s.tx.store(tx_len as _, Ordering::Relaxed);
+            s.rx.store(rx_len as _, Ordering::Relaxed);
+
+            // ...signalling the start of the fake transfer.
+            r.intenset.write(|w| w.started().bit(true));
+        }
 
         // Reset and enable the event
         r.events_end.reset();
@@ -386,18 +427,29 @@ impl<'d, T: Instance> Drop for Spim<'d, T> {
 }
 
 pub(crate) mod sealed {
+    #[cfg(feature = "nrf52832")]
+    use core::sync::atomic::AtomicU8;
+
     use embassy_sync::waitqueue::AtomicWaker;
 
     use super::*;
 
     pub struct State {
         pub end_waker: AtomicWaker,
+        #[cfg(feature = "nrf52832")]
+        pub rx: AtomicU8,
+        #[cfg(feature = "nrf52832")]
+        pub tx: AtomicU8,
     }
 
     impl State {
         pub const fn new() -> Self {
             Self {
                 end_waker: AtomicWaker::new(),
+                #[cfg(feature = "nrf52832")]
+                rx: AtomicU8::new(0),
+                #[cfg(feature = "nrf52832")]
+                tx: AtomicU8::new(0),
             }
         }
     }