This is an automated email from the ASF dual-hosted git repository.

xiaoxiang781216 pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/nuttx.git


The following commit(s) were added to refs/heads/master by this push:
     new 66831010e5d esp32s3/esp32s3_i2c.c: hold off light sleep for the 
duration of an I2C transfer
66831010e5d is described below

commit 66831010e5dd2564b5687eaddc927526731287b9
Author: Felipe Moura <[email protected]>
AuthorDate: Sat Sep 19 17:57:37 2026 -0300

    esp32s3/esp32s3_i2c.c: hold off light sleep for the duration of an I2C 
transfer
    
    Light sleep gates the APB clock the I2C peripheral runs on.  A transfer
    in flight stops mid-message and never raises its completion interrupt, so
    the caller blocks in i2c_sem_waitdone() until ESP32S3_I2CTIMEOTICKS
    expires and gets -ETIMEDOUT for a bus that was working perfectly.
    
    The caller is what causes it.  Blocking in i2c_sem_waitdone() is exactly
    what makes the idle task runnable, and the idle task is what decides to
    sleep -- so the longer the transfer, the likelier it is to be cut in half
    by its own wait.  Nothing about this is driver-specific.
    
    Seen on an esp32s3-xiao reading an LSM6DS3TR-C FIFO: 6000 bytes in one
    transaction, some 135 ms of bus time at 400 kHz, failing with -110 over
    and over.  A WHO_AM_I probe and the FIFO status read, both short, never
    failed once in the same runs -- only the long burst did.
    
    The consequences went well past one failed read.  With the FIFO left
    undrained the sensor's level-triggered INT1 stayed asserted, the worker
    was re-entered the moment the IRQ was re-enabled, and that hot loop
    starved every other task until the board wedged with no console output
    and no crash dump.
    
    pm_stay(PM_IDLE_DOMAIN, PM_IDLE) is the lightest lock that suffices:
    greedy_governor_checkstate() walks up from PM_NORMAL and stops at the
    first state holding a wakelock, so a stay at PM_IDLE keeps the domain out
    of PM_STANDBY and PM_SLEEP while still allowing the plain WFI idle.
    There is no early return between the stay and the relax.
    
    Validated over 3 h 45 of continuous acquisition across two sessions:
    wakes and drains stayed 1:1 (302/302, then 375/375), zero I2C failures of
    any kind, and light sleep itself unaffected -- 11.8% of wall time asleep
    in both, median sleep 2.08 s.
    
    Signed-off-by: Felipe Moura <[email protected]>
    Assisted-by: Claude:claude-opus-5
---
 arch/xtensa/src/esp32s3/esp32s3_i2c.c | 24 ++++++++++++++++++++++++
 1 file changed, 24 insertions(+)

diff --git a/arch/xtensa/src/esp32s3/esp32s3_i2c.c 
b/arch/xtensa/src/esp32s3/esp32s3_i2c.c
index 6a77aa195af..7963d73b003 100644
--- a/arch/xtensa/src/esp32s3/esp32s3_i2c.c
+++ b/arch/xtensa/src/esp32s3/esp32s3_i2c.c
@@ -42,6 +42,7 @@
 #include <nuttx/irq.h>
 #include <nuttx/i2c/i2c_master.h>
 #include <nuttx/mutex.h>
+#include <nuttx/power/pm.h>
 #include <nuttx/semaphore.h>
 
 #include <arch/board/board.h>
@@ -88,6 +89,19 @@
 #define ESP32S3_I2CTIMEOTICKS \
     (SEC2TICK(CONFIG_ESP32S3_I2CTIMEOSEC) + 
MSEC2TICK(CONFIG_ESP32S3_I2CTIMEOMS))
 
+/* Light sleep gates the APB clock mid-transfer, so an in-flight transfer
+ * never completes and times out.  PM_IDLE is the lightest stay that keeps
+ * the domain out of PM_STANDBY/PM_SLEEP without blocking plain WFI idle.
+ */
+
+#ifdef CONFIG_PM
+#  define i2c_pm_stay()  pm_stay(PM_IDLE_DOMAIN, PM_IDLE)
+#  define i2c_pm_relax() pm_relax(PM_IDLE_DOMAIN, PM_IDLE)
+#else
+#  define i2c_pm_stay()
+#  define i2c_pm_relax()
+#endif
+
 /* Default option */
 
 #define I2C_FIFO_SIZE (32)
@@ -779,6 +793,7 @@ static void i2c_init_clock(struct esp32s3_i2c_priv_s *priv,
 static void i2c_init(struct esp32s3_i2c_priv_s *priv)
 {
   const struct esp32s3_i2c_config_s *config = priv->config;
+
   if (priv->id != ESP32S3_RTC_I2C)
     {
       esp_gpiowrite(config->scl_pin, 1);
@@ -1136,6 +1151,12 @@ static int i2c_transfer(struct i2c_master_s *dev, struct 
i2c_msg_s *msgs,
       return ret;
     }
 
+  /* Hold the domain out of light sleep for the whole transfer -- see the
+   * comment on i2c_pm_stay() above.
+   */
+
+  i2c_pm_stay();
+
   /* If previous state is different than idle,
    * reset the FSMC to the idle state.
    */
@@ -1256,6 +1277,7 @@ static int i2c_transfer(struct i2c_master_s *dev, struct 
i2c_msg_s *msgs,
   /* Dump the trace result */
 
   i2c_tracedump(priv);
+  i2c_pm_relax();
   nxmutex_unlock(&priv->lock);
 
   return ret;
@@ -1490,6 +1512,7 @@ static void i2c_tracedump(struct esp32s3_i2c_priv_s *priv)
   for (int i = 0; i < priv->tndx; i++)
     {
       struct esp32s3_trace_s *trace = &priv->trace[i];
+
       syslog(LOG_DEBUG,
              "%2d. STATUS: %08" PRIx32 " COUNT: %3" PRIu32 " EVENT: %s(%2d)"
              " PARM: %08" PRIx32 " TIME: %" PRId64 "\n",
@@ -1527,6 +1550,7 @@ static int i2c_irq(int cpuint, void *context, void *arg)
    */
 
   uint32_t irq_status = getreg32(I2C_INT_STATUS_REG(priv->id));
+
   putreg32(irq_status, I2C_INT_CLR_REG(priv->id));
 
   i2c_process(priv, irq_status);

Reply via email to