xtensa/esp32s3: fix esp32s3_cam uninit/stop_capture bugs

Fix several issues in the ESP32-S3 CAM driver:

- stop_capture: reset DMA channel, CAM module and AFIFO under
  spinlock to fully quiesce hardware before returning. Clear
  pending VSYNC interrupt to prevent stale ISR firing.

- uninit: reset CAM/AFIFO before releasing DMA to prevent
  in-flight transfers after channel detach. Use esp_teardown_irq
  with correct peripheral ID (ESP32S3_PERIPH_LCD_CAM) instead of
  irq_detach which corrupts the shared IRQ mapping table. Mask
  interrupts and clear pending flags under spinlock before
  detaching handler.

- uninit: preserve XCLK output so the sensor remains accessible
  via I2C for subsequent re-initialization.

- set_buf/uninit: track driver-allocated vs user-provided frame
  buffers with fb_allocated flag to prevent double-free when
  using V4L2 USERPTR mode.

Signed-off-by: wangjianyu3 <wangjianyu3@xiaomi.com>
This commit is contained in:
wangjianyu3
2026-04-02 10:50:52 +08:00
committed by simbit18
parent bebfaf8bb4
commit 929acd26e0
+86 -12
View File
@@ -94,6 +94,7 @@ struct esp32s3_cam_s
uint8_t *fb; /* Frame buffer */ uint8_t *fb; /* Frame buffer */
uint32_t fb_size; /* Frame buffer size */ uint32_t fb_size; /* Frame buffer size */
uint32_t fb_pos; /* Current write position */ uint32_t fb_pos; /* Current write position */
bool fb_allocated; /* true if driver allocated fb */
uint8_t vsync_cnt; /* VSYNC counter for frame sync */ uint8_t vsync_cnt; /* VSYNC counter for frame sync */
imgdata_capture_t cb; /* Capture done callback */ imgdata_capture_t cb; /* Capture done callback */
@@ -559,6 +560,7 @@ static int esp32s3_cam_uninit(struct imgdata_s *data)
{ {
struct esp32s3_cam_s *priv = (struct esp32s3_cam_s *)data; struct esp32s3_cam_s *priv = (struct esp32s3_cam_s *)data;
uint32_t regval; uint32_t regval;
irqstate_t flags;
/* Stop capture */ /* Stop capture */
@@ -566,20 +568,59 @@ static int esp32s3_cam_uninit(struct imgdata_s *data)
regval &= ~LCD_CAM_CAM_START_M; regval &= ~LCD_CAM_CAM_START_M;
putreg32(regval, LCD_CAM_CAM_CTRL1_REG); putreg32(regval, LCD_CAM_CAM_CTRL1_REG);
/* Disable interrupt */ /* Reset CAM module and AFIFO to stop all hardware activity */
up_disable_irq(ESP32S3_IRQ_LCD_CAM); regval = getreg32(LCD_CAM_CAM_CTRL1_REG);
irq_detach(ESP32S3_IRQ_LCD_CAM); regval |= LCD_CAM_CAM_RESET_M;
esp_teardown_irq(ESP32S3_IRQ_LCD_CAM, priv->cpuint); putreg32(regval, LCD_CAM_CAM_CTRL1_REG);
regval &= ~LCD_CAM_CAM_RESET_M;
putreg32(regval, LCD_CAM_CAM_CTRL1_REG);
/* Release DMA */ regval |= LCD_CAM_CAM_AFIFO_RESET_M;
putreg32(regval, LCD_CAM_CAM_CTRL1_REG);
regval &= ~LCD_CAM_CAM_AFIFO_RESET_M;
putreg32(regval, LCD_CAM_CAM_CTRL1_REG);
/* Keep XCLK running so the sensor stays accessible via I2C for
* subsequent re-initialization. VSYNC generation is already
* stopped by the CAM_RESET above. Disable only the CAM_START
* bit in CAM_CTRL to stop the capture engine while preserving
* the clock divider configuration.
*/
regval = getreg32(LCD_CAM_CAM_CTRL_REG);
regval &= ~LCD_CAM_CAM_UPDATE_REG_M;
putreg32(regval, LCD_CAM_CAM_CTRL_REG);
/* Stop and release DMA before tearing down the CPU-level IRQ.
* esp32s3_dma_release() detaches the GDMA channel interrupt;
* if DMA is still active it may fire after detach -> irq_unexpected.
*/
if (priv->dma_channel >= 0) if (priv->dma_channel >= 0)
{ {
esp32s3_dma_reset_channel(priv->dma_channel, false);
esp32s3_dma_release(priv->dma_channel); esp32s3_dma_release(priv->dma_channel);
priv->dma_channel = -1; priv->dma_channel = -1;
} }
/* Mask CPU interrupts so no new peripheral interrupt can be
* delivered between clearing the pending flag and detaching
* the handler. XCLK is still running (kept for I2C access),
* so a VSYNC edge that arrived before the CAM_RESET could
* still be latched in the interrupt controller.
*/
flags = spin_lock_irqsave(&priv->lock);
putreg32(0, LCD_CAM_LC_DMA_INT_ENA_REG);
putreg32(0xffffffff, LCD_CAM_LC_DMA_INT_CLR_REG);
up_disable_irq(ESP32S3_IRQ_LCD_CAM);
esp_teardown_irq(ESP32S3_PERIPH_LCD_CAM, priv->cpuint);
spin_unlock_irqrestore(&priv->lock, flags);
/* Free DMA descriptors */ /* Free DMA descriptors */
if (priv->dmadesc) if (priv->dmadesc)
@@ -588,14 +629,16 @@ static int esp32s3_cam_uninit(struct imgdata_s *data)
priv->dmadesc = NULL; priv->dmadesc = NULL;
} }
/* Free frame buffer */ /* Free frame buffer only if driver allocated it */
if (priv->fb) if (priv->fb && priv->fb_allocated)
{ {
kmm_free(priv->fb); kmm_free(priv->fb);
priv->fb = NULL;
} }
priv->fb = NULL;
priv->fb_allocated = false;
priv->capturing = false; priv->capturing = false;
return OK; return OK;
@@ -622,6 +665,7 @@ static int esp32s3_cam_set_buf(struct imgdata_s *data,
{ {
priv->fb = addr; priv->fb = addr;
priv->fb_size = size; priv->fb_size = size;
priv->fb_allocated = false;
} }
else else
{ {
@@ -636,6 +680,8 @@ static int esp32s3_cam_set_buf(struct imgdata_s *data,
snerr("ERROR: Failed to allocate frame buffer\n"); snerr("ERROR: Failed to allocate frame buffer\n");
return -ENOMEM; return -ENOMEM;
} }
priv->fb_allocated = true;
} }
memset(priv->fb, 0, priv->fb_size); memset(priv->fb, 0, priv->fb_size);
@@ -768,20 +814,48 @@ static int esp32s3_cam_stop_capture(struct imgdata_s *data)
{ {
struct esp32s3_cam_s *priv = (struct esp32s3_cam_s *)data; struct esp32s3_cam_s *priv = (struct esp32s3_cam_s *)data;
uint32_t regval; uint32_t regval;
irqstate_t flags;
/* Stop capture */ flags = spin_lock_irqsave(&priv->lock);
/* Mark not capturing first so ISR won't process further VSYNCs */
priv->capturing = false;
priv->cb = NULL;
priv->cb_arg = NULL;
/* Stop capture engine */
regval = getreg32(LCD_CAM_CAM_CTRL1_REG); regval = getreg32(LCD_CAM_CAM_CTRL1_REG);
regval &= ~LCD_CAM_CAM_START_M; regval &= ~LCD_CAM_CAM_START_M;
putreg32(regval, LCD_CAM_CAM_CTRL1_REG); putreg32(regval, LCD_CAM_CAM_CTRL1_REG);
/* Reset CAM + AFIFO to fully quiesce hardware */
regval |= LCD_CAM_CAM_RESET_M;
putreg32(regval, LCD_CAM_CAM_CTRL1_REG);
regval &= ~LCD_CAM_CAM_RESET_M;
putreg32(regval, LCD_CAM_CAM_CTRL1_REG);
regval |= LCD_CAM_CAM_AFIFO_RESET_M;
putreg32(regval, LCD_CAM_CAM_CTRL1_REG);
regval &= ~LCD_CAM_CAM_AFIFO_RESET_M;
putreg32(regval, LCD_CAM_CAM_CTRL1_REG);
regval = getreg32(LCD_CAM_CAM_CTRL_REG); regval = getreg32(LCD_CAM_CAM_CTRL_REG);
regval |= LCD_CAM_CAM_UPDATE_REG_M; regval |= LCD_CAM_CAM_UPDATE_REG_M;
putreg32(regval, LCD_CAM_CAM_CTRL_REG); putreg32(regval, LCD_CAM_CAM_CTRL_REG);
priv->capturing = false; /* Reset DMA channel to abort any in-flight transfer */
priv->cb = NULL;
priv->cb_arg = NULL; esp32s3_dma_reset_channel(priv->dma_channel, false);
/* Clear any pending VSYNC interrupt */
putreg32(LCD_CAM_CAM_VSYNC_INT_CLR_M,
LCD_CAM_LC_DMA_INT_CLR_REG);
spin_unlock_irqrestore(&priv->lock, flags);
return OK; return OK;
} }