From 83532a157bdec93d08a16ea8c69ba7dde4095cbd Mon Sep 17 00:00:00 2001
From: Benjamin Koch <bbbsnowball@gmail.com>
Date: Sat, 13 May 2023 06:53:47 +0200
Subject: [PATCH] implement recovery for bus errors

I don't expect that we will need it but it is good practice to do it.
---
 firmware/rust1/src/bin/heizung.rs | 31 +++++++++++++++++++++++++++++--
 1 file changed, 29 insertions(+), 2 deletions(-)

diff --git a/firmware/rust1/src/bin/heizung.rs b/firmware/rust1/src/bin/heizung.rs
index 2ce1775..bb32d66 100644
--- a/firmware/rust1/src/bin/heizung.rs
+++ b/firmware/rust1/src/bin/heizung.rs
@@ -151,8 +151,35 @@ async fn i2c_write_zero_bytes<SCL: gpio::Pin, SDA: gpio::Pin, const FREQ : u64>(
     scl.set_low();
 
     Timer::after(delay).await;
-    if sda.is_low() || scl.is_low() {
-        return Err(Abort(ArbitrationLoss))
+
+    // abort if SCL is stuck low
+    handle_clock_stretching(&mut scl).await?;
+
+    // handle SDA being stuck low
+    // (This can happen for very simple I2C devices that only use SCL and don't have any other clocks that
+    // they can use to handle timeouts, e.g. simple port expanders.)
+    if sda.is_low() {
+        // I think I have read that DW_apb_i2c does nine clocks. This seems resonable so let's do the same.
+        for _ in 0..9 {
+            scl.set_as_output();
+            Timer::after(delay).await;
+            scl.set_as_input();
+            Timer::after(delay).await;
+
+            if sda.is_high() {
+                break;
+            }
+        }
+
+        if sda.is_low() || scl.is_low() {
+            // There is isn't much that we can do here. A start condition would reset the state but we cannot
+            // generate it while one of the lines is stuck low.
+            return Err(Abort(ArbitrationLoss))
+        }
+
+        // SDA is high, again. Good. We will generate a start condition soon. This should abort any
+        // partial transfers that some devices might think are ongoing. We have already waited for half
+        // a clock so we are good to go.
     }
 
     let result = i2c_write_zero_bytes_middle_part::<_, _, FREQ>(addr, delay, &mut scl, &mut sda).await;
-- 
GitLab