From 9c1d941d2e22e6da1217efddfaee0edcac68e147 Mon Sep 17 00:00:00 2001 From: dteal 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);