nanohub: i2c: detect and clock through stuck low sda

If the chip is reset in the middle of an i2c read, the
slave will still be waiting for additional pulses of sdc.
This prevents all chips on the stuck i2c bus from being
detected until a power cycle.
Detect that sda is stuck low and attempt to clock through
the issue.

Bug: 65966547
Test: run accel at a high rate over i2c and reset the stm32.
Confirm all chips on i2c are detected after reset.
Change-Id: I3a7ec24556a693b5506ba65f4eefa6e9f36e5818
Signed-off-by: Ben Fennema <fennema@google.com>
diff --git a/firmware/os/core/timer.c b/firmware/os/core/timer.c
index ace8072..f9c3352 100644
--- a/firmware/os/core/timer.c
+++ b/firmware/os/core/timer.c
@@ -62,6 +62,14 @@
     return platGetTicks();
 }
 
+void timDelay(uint32_t length)
+{
+    uint64_t curTime = timGetTime();
+
+    while (curTime + length > timGetTime())
+        ;
+}
+
 static struct Timer *timFindTimerById(uint32_t timId) /* no locks taken. be careful what you do with this */
 {
     uint32_t i;
diff --git a/firmware/os/inc/timer.h b/firmware/os/inc/timer.h
index abc5e19..30e359a 100644
--- a/firmware/os/inc/timer.h
+++ b/firmware/os/inc/timer.h
@@ -38,6 +38,7 @@
 
 
 uint64_t timGetTime(void);   /* Time since some stable reference point in nanoseconds */
+void timDelay(uint32_t length);
 
 uint32_t timTimerSet(uint64_t length, uint32_t jitterPpm, uint32_t driftPpm, TimTimerCbkF cbk, void* data, bool oneShot); /* return timer id or 0 if failed */
 uint32_t timTimerSetAsApp(uint64_t length, uint32_t jitterPpm, uint32_t driftPpm, uint32_t tid, void* data, bool oneShot); /* return timer id or 0 if failed */
diff --git a/firmware/os/platform/stm32/i2c.c b/firmware/os/platform/stm32/i2c.c
index 189e8f9..7ba7b7c 100644
--- a/firmware/os/platform/stm32/i2c.c
+++ b/firmware/os/platform/stm32/i2c.c
@@ -26,6 +26,7 @@
 #include <atomicBitset.h>
 #include <atomic.h>
 #include <platform.h>
+#include <timer.h>
 
 #include <plat/cmsis.h>
 #include <plat/dma.h>
@@ -775,6 +776,49 @@
     return gpio;
 }
 
+static int i2cMasterReset(uint32_t busId, uint32_t speed)
+{
+    struct Gpio *sda, *scl;
+    int cnt = 0;
+    uint32_t delay;
+
+    if (busId >= ARRAY_SIZE(mStmI2cDevs))
+        return -EINVAL;
+
+    const struct StmI2cBoardCfg *board = boardStmI2cCfg(busId);
+    if (!board)
+        return -EINVAL;
+
+    sda = gpioRequest(board->gpioSda.gpioNum);
+    gpioConfigOutput(sda, board->gpioSpeed, GPIO_PULL_NONE, GPIO_OUT_OPEN_DRAIN, 1);
+    if (gpioGet(sda) == 0) {
+        // 50% duty cycle for the clock
+        delay = 500000000UL/speed;
+
+        scl = gpioRequest(board->gpioScl.gpioNum);
+        gpioConfigOutput(scl, board->gpioSpeed, GPIO_PULL_NONE, GPIO_OUT_OPEN_DRAIN, 1);
+        do {
+            // generate clock pulse
+            gpioSet(scl, 1);
+            timDelay(delay);
+            gpioSet(scl, 0);
+            timDelay(delay);
+            cnt ++;
+        } while (gpioGet(sda) == 0 && cnt < 9);
+
+        // generate STOP condition
+        gpioSet(sda, 0);
+        gpioSet(scl, 1);
+        timDelay(delay);
+        gpioSet(sda, 1);
+        timDelay(delay);
+        gpioRelease(scl);
+    }
+    gpioRelease(sda);
+
+    return cnt;
+}
+
 int i2cMasterRequest(uint32_t busId, uint32_t speed)
 {
     if (busId >= ARRAY_SIZE(mStmI2cDevs))
@@ -797,6 +841,8 @@
         pdev->last = 1;
         atomicBitsetInit(mXfersValid, I2C_MAX_QUEUE_DEPTH);
 
+        i2cMasterReset(busId, speed);
+
         pdev->scl = stmI2cGpioInit(board, &board->gpioScl);
         pdev->sda = stmI2cGpioInit(board, &board->gpioSda);