From 9c1d941d2e22e6da1217efddfaee0edcac68e147 Mon Sep 17 00:00:00 2001
From: dteal <dteal@dteal.org>
Date: Thu, 20 Feb 2025 21:44:05 -0800
Subject: [PATCH] Fix PIO I2C race condition

---
 pio/i2c/i2c.pio   | 4 ++++
 pio/i2c/pio_i2c.c | 5 +++++
 2 files changed, 9 insertions(+)

diff --git a/pio/i2c/i2c.pio b/pio/i2c/i2c.pio
index 3808bccda..2d3984d65 100644
--- a/pio/i2c/i2c.pio
+++ b/pio/i2c/i2c.pio
@@ -48,6 +48,10 @@ do_nack:
 
 do_byte:
     set x, 7                   ; Loop 8 times
+    mov isr, null              ; Set ISR and input shift counter to zero. This
+                               ; helps fix a race condition when autopush is
+                               ; disabled and re-enabled, which can leave the
+                               ; counter in an inconsistent state.
 bitloop:
     out pindirs, 1         [7] ; Serialise write data (all-ones if reading)
     nop             side 1 [2] ; SCL rising edge
diff --git a/pio/i2c/pio_i2c.c b/pio/i2c/pio_i2c.c
index a3a19f576..da00ab616 100644
--- a/pio/i2c/pio_i2c.c
+++ b/pio/i2c/pio_i2c.c
@@ -22,6 +22,11 @@ void pio_i2c_resume_after_error(PIO pio, uint sm) {
     pio_interrupt_clear(pio, sm);
 }
 
+// Disable autopush of read I2C bytes, which is useful when only writing to
+// the I2C bus and we don't want to bother with cleaning the RX FIFO. But be
+// careful because this isn't synchronized to the state machine program and in
+// a race condition can leave its input shift counter in an unexpected state,
+// shifting any subsequently read bytes by an unexpected number of bits.
 void pio_i2c_rx_enable(PIO pio, uint sm, bool en) {
     if (en)
         hw_set_bits(&pio->sm[sm].shiftctrl, PIO_SM0_SHIFTCTRL_AUTOPUSH_BITS);