From c8c7f972900e573ae2f05fffc93b7421afeffa09 Mon Sep 17 00:00:00 2001
From: Our Air Quality <info@ourairquality.org>
Date: Fri, 15 Jun 2018 08:22:29 +1000
Subject: [PATCH] i2c: increase the default clock strech timeout to 250msec.

This also redefines the timeout in FreeRTOS clock ticks, and implements a two
stage wait: firstly spinning sampling frequently, and then falling back to a
longer wait while sampling less frequently and yielding.
---
 extras/i2c/i2c.c | 54 ++++++++++++++++++++++++++++++++----------------
 extras/i2c/i2c.h | 11 +++++-----
 2 files changed, 42 insertions(+), 23 deletions(-)

diff --git a/extras/i2c/i2c.c b/extras/i2c/i2c.c
index 204bc98..faa245a 100644
--- a/extras/i2c/i2c.c
+++ b/extras/i2c/i2c.c
@@ -66,7 +66,7 @@ typedef struct i2c_bus_description
   bool started;
   bool flag;
   bool force;
-  uint32_t clk_stretch;
+  TickType_t clk_stretch;
 } i2c_bus_description_t;
 
 static i2c_bus_description_t i2c_bus[I2C_MAX_BUS];
@@ -183,7 +183,7 @@ int i2c_set_frequency_hz(uint8_t bus, uint32_t freq)
     return not_ok ? -EINVAL : 0;
 }
 
-void i2c_set_clock_stretch(uint8_t bus, uint32_t clk_stretch)
+void i2c_set_clock_stretch(uint8_t bus, TickType_t clk_stretch)
 {
     i2c_bus[bus].clk_stretch = clk_stretch;
 }
@@ -235,13 +235,46 @@ static inline void clear_sda(uint8_t bus)
 #endif
 }
 
-static inline void set_scl(uint8_t bus)
+#define I2C_CLK_STRETCH_SPIN 1024
+
+static void set_scl(uint8_t bus)
 {
 #if I2C_USE_GPIO16 == 1
     gpio_write(i2c_bus[bus].g_scl_pin, 1);
 #else
     GPIO.OUT_SET = i2c_bus[bus].g_scl_mask;
 #endif
+
+    // Clock stretching.
+
+    // Spin sampling frequently.
+    uint32_t clk_stretch_spin = I2C_CLK_STRETCH_SPIN;
+    do {
+        if (read_scl(bus)) {
+            return;
+        }
+
+        clk_stretch_spin--;
+    } while (clk_stretch_spin);
+
+    // Fall back to a longer wait, sampling less frequently.
+    TickType_t clk_stretch = i2c_bus[bus].clk_stretch;
+    TickType_t start = xTaskGetTickCount();
+
+    do {
+        vTaskDelay(20 / portTICK_PERIOD_MS);
+
+        if (read_scl(bus)) {
+            return;
+        }
+
+        TickType_t elapsed = xTaskGetTickCount() - start;
+        if (elapsed > clk_stretch) {
+            break;
+        }
+    } while (1);
+
+    debug("bus %u clock stretch timeout", bus);
 }
 
 static inline void set_sda(uint8_t bus)
@@ -265,10 +298,7 @@ void i2c_start(uint8_t bus)
         // Set SDA to 1
         set_sda(bus);
         i2c_delay(bus);
-        uint32_t clk_stretch = i2c_bus[bus].clk_stretch;
         set_scl(bus);
-        while (read_scl(bus) == 0 && clk_stretch--)
-            ;
         // Repeated start setup time, minimum 4.7us
         i2c_delay(bus);
     }
@@ -286,14 +316,10 @@ void i2c_start(uint8_t bus)
 // Output stop condition
 bool i2c_stop(uint8_t bus)
 {
-    uint32_t clk_stretch = i2c_bus[bus].clk_stretch;
     // Set SDA to 0
     clear_sda(bus);
     i2c_delay(bus);
-    // Clock stretching
     set_scl(bus);
-    while (read_scl(bus) == 0 && clk_stretch--)
-        ;
     // Stop bit setup time, minimum 4us
     i2c_delay(bus);
     // SCL is high, set SDA from 0 to 1
@@ -315,17 +341,13 @@ bool i2c_stop(uint8_t bus)
 // Write a bit to I2C bus
 static void i2c_write_bit(uint8_t bus, bool bit)
 {
-    uint32_t clk_stretch = i2c_bus[bus].clk_stretch;
     if (bit) {
         set_sda(bus);
     } else {
         clear_sda(bus);
     }
     i2c_delay(bus);
-    // Clock stretching
     set_scl(bus);
-    while (read_scl(bus) == 0 && clk_stretch--)
-        ;
     // SCL is high, now data is valid
     // If SDA is high, check that nobody else is driving SDA
     if (bit && read_sda(bus) == 0) {
@@ -338,15 +360,11 @@ static void i2c_write_bit(uint8_t bus, bool bit)
 // Read a bit from I2C bus
 static bool i2c_read_bit(uint8_t bus)
 {
-    uint32_t clk_stretch = i2c_bus[bus].clk_stretch;
     bool bit;
     // Let the slave drive data
     set_sda(bus);
     i2c_delay(bus);
     set_scl(bus);
-    // Clock stretching
-    while (read_scl(bus) == 0 && clk_stretch--)
-        ;
     // SCL is high, now data is valid
     bit = read_sda(bus);
     i2c_delay(bus);
diff --git a/extras/i2c/i2c.h b/extras/i2c/i2c.h
index 46f8468..9cd55f2 100644
--- a/extras/i2c/i2c.h
+++ b/extras/i2c/i2c.h
@@ -32,6 +32,8 @@
 #include <stdint.h>
 #include <stdbool.h>
 #include <errno.h>
+#include <FreeRTOS.h>
+#include <task.h>
 
 #ifdef	__cplusplus
 extern "C" {
@@ -51,7 +53,8 @@ extern "C" {
     #define I2C_USE_GPIO16 0
 #endif
 
-#define I2C_DEFAULT_CLK_STRETCH (10)
+/* Default clock strech waiting time, 250 msec. */
+#define I2C_DEFAULT_CLK_STRETCH (250 / portTICK_PERIOD_MS)
 
 /* SCL speed settings. 160 MHz sysclk frequency will result in improved
  * timing accuracy. Greater bitrates will have poorer accuracy. 1000K is the
@@ -86,7 +89,6 @@ typedef struct i2c_dev
  * @param scl_pin SCL pin for I2C
  * @param sda_pin SDA pin for I2C
  * @param freq frequency of bus (ex : I2C_FREQ_400K)
- * @param clk_stretch I2C clock stretch. I2C_DEFAULT_CLK_STRETCH would be good in most cases
  * @return Non-zero if error occured
  */
 int i2c_init(uint8_t bus, uint8_t scl_pin, uint8_t sda_pin, i2c_freq_t freq);
@@ -97,7 +99,6 @@ int i2c_init(uint8_t bus, uint8_t scl_pin, uint8_t sda_pin, i2c_freq_t freq);
  * @param scl_pin SCL pin for I2C
  * @param sda_pin SDA pin for I2C
  * @param freq frequency of bus in hertz
- * @param clk_stretch I2C clock stretch. I2C_DEFAULT_CLK_STRETCH would be good in most cases
  * @return Non-zero if error occured
  */
 int i2c_init_hz(uint8_t bus, uint8_t scl_pin, uint8_t sda_pin, uint32_t freq);
@@ -119,9 +120,9 @@ int i2c_set_frequency_hz(uint8_t bus, uint32_t freq);
 /**
  * Change clock stretch
  * @param bus I2C bus
- * @param clk_stretch I2C clock stretch. I2C_DEFAULT_CLK_STRETCH by default
+ * @param clk_stretch I2C clock stretch, in ticks. I2C_DEFAULT_CLK_STRETCH by default
  */
-void i2c_set_clock_stretch(uint8_t bus, uint32_t clk_stretch);
+void i2c_set_clock_stretch(uint8_t bus, TickType_t clk_stretch);
 
 /**
  * Write a byte to I2C bus.