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);