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

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

commit 370921aebac362ac34049ee2f04c7a78a2546c65
Author: wangjianyu3 <[email protected]>
AuthorDate: Mon Mar 16 11:49:53 2026 +0800

    arch/xtensa/esp32s3: add CAM DVP imgdata driver
    
    Add ESP32-S3 CAM controller driver implementing the NuttX imgdata
    interface. Supports 8-bit DVP camera input with DMA-based frame
    capture, VSYNC interrupt handling, and PSRAM frame buffer allocation.
    
    The driver is sensor-agnostic: resolution and pixel format are
    negotiated at runtime through the V4L2 pipeline. XCLK output
    (24 MHz) is started during initialization for sensor communication.
    
    Signed-off-by: wangjianyu3 <[email protected]>
---
 arch/xtensa/src/esp32s3/Kconfig       |  61 +++
 arch/xtensa/src/esp32s3/Make.defs     |   4 +
 arch/xtensa/src/esp32s3/esp32s3_cam.c | 808 ++++++++++++++++++++++++++++++++++
 arch/xtensa/src/esp32s3/esp32s3_cam.h |  50 +++
 4 files changed, 923 insertions(+)

diff --git a/arch/xtensa/src/esp32s3/Kconfig b/arch/xtensa/src/esp32s3/Kconfig
index e70abbe56ff..edac2d873c7 100644
--- a/arch/xtensa/src/esp32s3/Kconfig
+++ b/arch/xtensa/src/esp32s3/Kconfig
@@ -931,6 +931,14 @@ config ESP32S3_LCD
        ---help---
                LCD controller that outputs parallel data and supports RGB 
interface.
 
+config ESP32S3_CAM
+       bool "Camera (DVP)"
+       default n
+       select ESP32S3_DMA
+       select DRIVERS_VIDEO
+       ---help---
+               Camera controller that receives parallel DVP data from image 
sensors.
+
 config ESP32S3_AES_ACCELERATOR
        bool "AES Accelerator"
        default n
@@ -2683,6 +2691,59 @@ config ESP32S3_LCD_REGDEBUG
 
 endmenu
 
+menu "Camera (DVP) Configuration"
+        depends on ESP32S3_CAM
+
+config ESP32S3_CAM_XCLK_PIN
+        int "CAM XCLK Output Pin"
+        default 5
+
+config ESP32S3_CAM_PCLK_PIN
+        int "CAM PCLK Input Pin"
+        default 7
+
+config ESP32S3_CAM_VSYNC_PIN
+        int "CAM VSYNC Input Pin"
+        default 3
+
+config ESP32S3_CAM_HREF_PIN
+        int "CAM HREF Input Pin"
+        default 46
+
+config ESP32S3_CAM_D0_PIN
+        int "CAM Data Bit-0 Pin"
+        default 16
+
+config ESP32S3_CAM_D1_PIN
+        int "CAM Data Bit-1 Pin"
+        default 18
+
+config ESP32S3_CAM_D2_PIN
+        int "CAM Data Bit-2 Pin"
+        default 8
+
+config ESP32S3_CAM_D3_PIN
+        int "CAM Data Bit-3 Pin"
+        default 17
+
+config ESP32S3_CAM_D4_PIN
+        int "CAM Data Bit-4 Pin"
+        default 15
+
+config ESP32S3_CAM_D5_PIN
+        int "CAM Data Bit-5 Pin"
+        default 6
+
+config ESP32S3_CAM_D6_PIN
+        int "CAM Data Bit-6 Pin"
+        default 4
+
+config ESP32S3_CAM_D7_PIN
+        int "CAM Data Bit-7 Pin"
+        default 9
+
+endmenu
+
 menu "SD/MMC Configuration"
 
 config ESP32S3_SDMMC
diff --git a/arch/xtensa/src/esp32s3/Make.defs 
b/arch/xtensa/src/esp32s3/Make.defs
index be3c080838d..6126987f6fa 100644
--- a/arch/xtensa/src/esp32s3/Make.defs
+++ b/arch/xtensa/src/esp32s3/Make.defs
@@ -173,6 +173,10 @@ ifeq ($(CONFIG_ESP32S3_LCD),y)
 CHIP_CSRCS += esp32s3_lcd.c
 endif
 
+ifeq ($(CONFIG_ESP32S3_CAM),y)
+CHIP_CSRCS += esp32s3_cam.c
+endif
+
 ifeq ($(CONFIG_ESP32S3_SDMMC),y)
 CHIP_CSRCS += esp32s3_sdmmc.c
 endif
diff --git a/arch/xtensa/src/esp32s3/esp32s3_cam.c 
b/arch/xtensa/src/esp32s3/esp32s3_cam.c
new file mode 100644
index 00000000000..f89fbb19de8
--- /dev/null
+++ b/arch/xtensa/src/esp32s3/esp32s3_cam.c
@@ -0,0 +1,808 @@
+/****************************************************************************
+ * arch/xtensa/src/esp32s3/esp32s3_cam.c
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.  The
+ * ASF licenses this file to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance with the
+ * License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ *
+ ****************************************************************************/
+
+/****************************************************************************
+ * Included Files
+ ****************************************************************************/
+
+#include <nuttx/config.h>
+
+#include <inttypes.h>
+#include <stdint.h>
+#include <string.h>
+#include <assert.h>
+#include <errno.h>
+#include <debug.h>
+
+#include <nuttx/spinlock.h>
+#include <nuttx/kmalloc.h>
+#include <nuttx/semaphore.h>
+#include <nuttx/video/imgdata.h>
+
+#include <arch/board/board.h>
+
+#include "esp32s3_clockconfig.h"
+#include "esp32s3_gpio.h"
+#include "esp32s3_dma.h"
+#include "esp32s3_irq.h"
+
+#include "xtensa.h"
+#include "hardware/esp32s3_system.h"
+#include "hardware/esp32s3_gpio_sigmap.h"
+#include "hardware/esp32s3_lcd_cam.h"
+
+#include "periph_ctrl.h"
+
+/****************************************************************************
+ * Pre-processor Definitions
+ ****************************************************************************/
+
+/* Use PLL=160MHz as clock resource for CAM */
+
+#define ESP32S3_CAM_CLK_SEL       2
+#define ESP32S3_CAM_CLK_MHZ       160
+
+/* XCLK = 160 / (6 + 2/3) = 24 MHz */
+
+#define ESP32S3_CAM_CLKM_DIV_NUM  6
+#define ESP32S3_CAM_CLKM_DIV_A    3
+#define ESP32S3_CAM_CLKM_DIV_B    2
+
+/* DMA buffer configuration */
+
+#define ESP32S3_CAM_DMA_BUFLEN    4096
+#define ESP32S3_CAM_DMADESC_NUM   40
+
+/* VSYNC filter threshold */
+
+#define ESP32S3_CAM_VSYNC_FILTER  4
+
+/****************************************************************************
+ * Private Types
+ ****************************************************************************/
+
+struct esp32s3_cam_s
+{
+  struct imgdata_s data;
+
+  bool capturing;
+
+  int cpuint;
+  uint8_t cpu;
+  int32_t dma_channel;
+
+  struct esp32s3_dmadesc_s dmadesc[ESP32S3_CAM_DMADESC_NUM];
+
+  uint8_t *fb;                    /* Frame buffer */
+  uint32_t fb_size;               /* Frame buffer size */
+  uint32_t fb_pos;                /* Current write position */
+  uint8_t vsync_cnt;              /* VSYNC counter for frame sync */
+
+  imgdata_capture_t cb;           /* Capture done callback */
+  void *cb_arg;                   /* Callback argument */
+
+  uint16_t width;
+  uint16_t height;
+  uint32_t pixfmt;
+
+  spinlock_t lock;
+};
+
+/****************************************************************************
+ * Private Function Prototypes
+ ****************************************************************************/
+
+static int esp32s3_cam_init(struct imgdata_s *data);
+static int esp32s3_cam_uninit(struct imgdata_s *data);
+static int esp32s3_cam_set_buf(struct imgdata_s *data,
+                               uint8_t nr_datafmts,
+                               imgdata_format_t *datafmts,
+                               uint8_t *addr, uint32_t size);
+static int esp32s3_cam_validate_frame_setting(struct imgdata_s *data,
+                               uint8_t nr_datafmts,
+                               imgdata_format_t *datafmts,
+                               imgdata_interval_t *interval);
+static int esp32s3_cam_start_capture(struct imgdata_s *data,
+                               uint8_t nr_datafmts,
+                               imgdata_format_t *datafmts,
+                               imgdata_interval_t *interval,
+                               imgdata_capture_t callback,
+                               void *arg);
+static int esp32s3_cam_stop_capture(struct imgdata_s *data);
+
+/****************************************************************************
+ * Private Data
+ ****************************************************************************/
+
+static const struct imgdata_ops_s g_cam_ops =
+{
+  .init                   = esp32s3_cam_init,
+  .uninit                 = esp32s3_cam_uninit,
+  .set_buf                = esp32s3_cam_set_buf,
+  .validate_frame_setting = esp32s3_cam_validate_frame_setting,
+  .start_capture          = esp32s3_cam_start_capture,
+  .stop_capture           = esp32s3_cam_stop_capture,
+};
+
+static struct esp32s3_cam_s g_cam_priv =
+{
+  .data =
+    {
+      .ops = &g_cam_ops,
+    },
+};
+
+/****************************************************************************
+ * Private Functions
+ ****************************************************************************/
+
+/****************************************************************************
+ * Name: cam_interrupt
+ *
+ * Description:
+ *   CAM VSYNC interrupt handler. Called when a frame is complete.
+ *
+ ****************************************************************************/
+
+static int IRAM_ATTR cam_interrupt(int irq, void *context, void *arg)
+{
+  struct esp32s3_cam_s *priv = (struct esp32s3_cam_s *)arg;
+  uint32_t status = getreg32(LCD_CAM_LC_DMA_INT_ST_REG);
+
+  /* Only handle CAM VSYNC interrupt */
+
+  if (status & LCD_CAM_CAM_VSYNC_INT_ST_M)
+    {
+      /* Clear interrupt */
+
+      putreg32(LCD_CAM_CAM_VSYNC_INT_CLR_M,
+               LCD_CAM_LC_DMA_INT_CLR_REG);
+
+      if (priv->capturing)
+        {
+          priv->vsync_cnt++;
+
+          /* First VSYNC = start of frame (DMA begins filling buffer).
+           * Second VSYNC = end of frame (buffer is complete).
+           */
+
+          if (priv->vsync_cnt == 1)
+            {
+              /* Frame capture just started, DMA is now receiving data.
+               * Nothing to do here — wait for next VSYNC.
+               */
+            }
+          else if (priv->vsync_cnt >= 2 && priv->cb)
+            {
+              struct timeval ts;
+              uint32_t regval;
+
+              /* Stop capture */
+
+              regval = getreg32(LCD_CAM_CAM_CTRL1_REG);
+              regval &= ~LCD_CAM_CAM_START_M;
+              putreg32(regval, LCD_CAM_CAM_CTRL1_REG);
+
+              regval = getreg32(LCD_CAM_CAM_CTRL_REG);
+              regval |= LCD_CAM_CAM_UPDATE_REG_M;
+              putreg32(regval, LCD_CAM_CAM_CTRL_REG);
+
+              gettimeofday(&ts, NULL);
+
+              /* Notify frame complete */
+
+              priv->cb(0, priv->fb_size, &ts, priv->cb_arg);
+
+              /* Restart capture for next frame */
+
+              priv->vsync_cnt = 0;
+
+              /* Reset DMA */
+
+              esp32s3_dma_reset_channel(priv->dma_channel, false);
+
+              /* Reset CAM + AFIFO */
+
+              regval = getreg32(LCD_CAM_CAM_CTRL1_REG);
+              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);
+
+              /* Re-set REC_DATA_BYTELEN after reset */
+
+              regval &= ~LCD_CAM_CAM_REC_DATA_BYTELEN_M;
+              regval |= ((ESP32S3_CAM_DMA_BUFLEN - 1)
+                         << LCD_CAM_CAM_REC_DATA_BYTELEN_S);
+              putreg32(regval, LCD_CAM_CAM_CTRL1_REG);
+
+              /* Reload DMA descriptors */
+
+              esp32s3_dma_load(priv->dmadesc, priv->dma_channel, false);
+              esp32s3_dma_enable(priv->dma_channel, false);
+
+              /* Restart */
+
+              regval = getreg32(LCD_CAM_CAM_CTRL1_REG);
+              regval |= LCD_CAM_CAM_START_M;
+              putreg32(regval, LCD_CAM_CAM_CTRL1_REG);
+
+              regval = getreg32(LCD_CAM_CAM_CTRL_REG);
+              regval |= LCD_CAM_CAM_UPDATE_REG_M;
+              putreg32(regval, LCD_CAM_CAM_CTRL_REG);
+            }
+        }
+    }
+
+  /* Clear any CAM HS interrupt too */
+
+  if (status & LCD_CAM_CAM_HS_INT_ST_M)
+    {
+      putreg32(LCD_CAM_CAM_HS_INT_CLR_M,
+               LCD_CAM_LC_DMA_INT_CLR_REG);
+    }
+
+  return 0;
+}
+
+/****************************************************************************
+ * Name: esp32s3_cam_gpio_config
+ *
+ * Description:
+ *   Configure GPIO pins for DVP camera interface.
+ *
+ ****************************************************************************/
+
+static void esp32s3_cam_gpio_config(void)
+{
+  /* Data pins D0-D7 as input via GPIO matrix */
+
+  esp32s3_configgpio(CONFIG_ESP32S3_CAM_D0_PIN, INPUT);
+  esp32s3_gpio_matrix_in(CONFIG_ESP32S3_CAM_D0_PIN,
+                         CAM_DATA_IN0_IDX, false);
+
+  esp32s3_configgpio(CONFIG_ESP32S3_CAM_D1_PIN, INPUT);
+  esp32s3_gpio_matrix_in(CONFIG_ESP32S3_CAM_D1_PIN,
+                         CAM_DATA_IN1_IDX, false);
+
+  esp32s3_configgpio(CONFIG_ESP32S3_CAM_D2_PIN, INPUT);
+  esp32s3_gpio_matrix_in(CONFIG_ESP32S3_CAM_D2_PIN,
+                         CAM_DATA_IN2_IDX, false);
+
+  esp32s3_configgpio(CONFIG_ESP32S3_CAM_D3_PIN, INPUT);
+  esp32s3_gpio_matrix_in(CONFIG_ESP32S3_CAM_D3_PIN,
+                         CAM_DATA_IN3_IDX, false);
+
+  esp32s3_configgpio(CONFIG_ESP32S3_CAM_D4_PIN, INPUT);
+  esp32s3_gpio_matrix_in(CONFIG_ESP32S3_CAM_D4_PIN,
+                         CAM_DATA_IN4_IDX, false);
+
+  esp32s3_configgpio(CONFIG_ESP32S3_CAM_D5_PIN, INPUT);
+  esp32s3_gpio_matrix_in(CONFIG_ESP32S3_CAM_D5_PIN,
+                         CAM_DATA_IN5_IDX, false);
+
+  esp32s3_configgpio(CONFIG_ESP32S3_CAM_D6_PIN, INPUT);
+  esp32s3_gpio_matrix_in(CONFIG_ESP32S3_CAM_D6_PIN,
+                         CAM_DATA_IN6_IDX, false);
+
+  esp32s3_configgpio(CONFIG_ESP32S3_CAM_D7_PIN, INPUT);
+  esp32s3_gpio_matrix_in(CONFIG_ESP32S3_CAM_D7_PIN,
+                         CAM_DATA_IN7_IDX, false);
+
+  /* PCLK input */
+
+  esp32s3_configgpio(CONFIG_ESP32S3_CAM_PCLK_PIN, INPUT);
+  esp32s3_gpio_matrix_in(CONFIG_ESP32S3_CAM_PCLK_PIN,
+                         CAM_PCLK_IDX, false);
+
+  /* VSYNC input */
+
+  esp32s3_configgpio(CONFIG_ESP32S3_CAM_VSYNC_PIN, INPUT);
+  esp32s3_gpio_matrix_in(CONFIG_ESP32S3_CAM_VSYNC_PIN,
+                         CAM_V_SYNC_IDX, false);
+
+  /* HREF (H_ENABLE) input */
+
+  esp32s3_configgpio(CONFIG_ESP32S3_CAM_HREF_PIN, INPUT);
+  esp32s3_gpio_matrix_in(CONFIG_ESP32S3_CAM_HREF_PIN,
+                         CAM_H_ENABLE_IDX, false);
+
+  /* XCLK output to sensor */
+
+  esp32s3_configgpio(CONFIG_ESP32S3_CAM_XCLK_PIN, OUTPUT);
+  esp32s3_gpio_matrix_out(CONFIG_ESP32S3_CAM_XCLK_PIN,
+                          CAM_CLK_IDX, false, false);
+}
+
+/****************************************************************************
+ * Name: esp32s3_cam_enableclk
+ *
+ * Description:
+ *   Enable CAM clock and configure XCLK output.
+ *
+ ****************************************************************************/
+
+static void esp32s3_cam_enableclk(void)
+{
+  uint32_t regval;
+
+  /* Enable LCD_CAM peripheral clock (shared with LCD) */
+
+  periph_module_enable(PERIPH_LCD_CAM_MODULE);
+
+  /* Configure CAM clock:
+   *   CLK_SEL = 3 (PLL 160MHz)
+   *   DIV_NUM = 6, DIV_A = 3, DIV_B = 2
+   *   XCLK = 160 / (6 + 2/3) = 24 MHz
+   */
+
+  regval = (ESP32S3_CAM_CLK_SEL << LCD_CAM_CAM_CLK_SEL_S) |
+           (ESP32S3_CAM_CLKM_DIV_A << LCD_CAM_CAM_CLKM_DIV_A_S) |
+           (ESP32S3_CAM_CLKM_DIV_B << LCD_CAM_CAM_CLKM_DIV_B_S) |
+           (ESP32S3_CAM_CLKM_DIV_NUM << LCD_CAM_CAM_CLKM_DIV_NUM_S) |
+           LCD_CAM_CAM_VS_EOF_EN_M |  /* VSYNC triggers DMA EOF */
+           (ESP32S3_CAM_VSYNC_FILTER << LCD_CAM_CAM_VSYNC_FILTER_THRES_S);
+  putreg32(regval, LCD_CAM_CAM_CTRL_REG);
+}
+
+/****************************************************************************
+ * Name: esp32s3_cam_dmasetup
+ *
+ * Description:
+ *   Configure DMA for camera RX.
+ *
+ ****************************************************************************/
+
+static int esp32s3_cam_dmasetup(struct esp32s3_cam_s *priv)
+{
+  priv->dma_channel = esp32s3_dma_request(ESP32S3_DMA_PERIPH_LCDCAM,
+                                          1, 10, true);
+  if (priv->dma_channel < 0)
+    {
+      snerr("ERROR: Failed to allocate GDMA channel\n");
+      return -ENOMEM;
+    }
+
+  esp32s3_dma_set_ext_memblk(priv->dma_channel,
+                             false,
+                             ESP32S3_DMA_EXT_MEMBLK_64B);
+
+  return OK;
+}
+
+/****************************************************************************
+ * Name: esp32s3_cam_config
+ *
+ * Description:
+ *   Configure CAM controller registers and interrupts.
+ *
+ ****************************************************************************/
+
+static int esp32s3_cam_config(struct esp32s3_cam_s *priv)
+{
+  int ret;
+  uint32_t regval;
+  irqstate_t flags;
+
+  /* Reset CAM module and AFIFO first */
+
+  regval = LCD_CAM_CAM_RESET_M | LCD_CAM_CAM_AFIFO_RESET_M;
+  putreg32(regval, LCD_CAM_CAM_CTRL1_REG);
+
+  /* Configure CAM_CTRL1 after reset:
+   *   - VSYNC filter enable
+   *   - REC_DATA_BYTELEN = DMA node size - 1 (controls in_suc_eof)
+   *   Note: 8-bit DVP sensor outputs BE RGB565 (high byte first).
+   *         CAM_BYTE_ORDER only reverses bits within a byte in
+   *         1-byte mode, so HW byte swap is not available here.
+   *         Application must swap bytes if LE RGB565 is needed.
+   */
+
+  regval = LCD_CAM_CAM_VSYNC_FILTER_EN_M |
+           ((ESP32S3_CAM_DMA_BUFLEN - 1) << LCD_CAM_CAM_REC_DATA_BYTELEN_S);
+  putreg32(regval, LCD_CAM_CAM_CTRL1_REG);
+
+  /* Update registers */
+
+  regval = getreg32(LCD_CAM_CAM_CTRL_REG);
+  regval |= LCD_CAM_CAM_UPDATE_REG_M;
+  putreg32(regval, LCD_CAM_CAM_CTRL_REG);
+
+  /* Bypass RGB/YUV conversion (bit=0 means bypass) */
+
+  putreg32(0, LCD_CAM_CAM_RGB_YUV_REG);
+
+  /* Setup DMA */
+
+  if (esp32s3_cam_dmasetup(priv) != OK)
+    {
+      return -ENOMEM;
+    }
+
+  /* Disable all LCD_CAM interrupts and clear pending */
+
+  putreg32(0, LCD_CAM_LC_DMA_INT_ENA_REG);
+  putreg32(0xffffffff, LCD_CAM_LC_DMA_INT_CLR_REG);
+
+  /* Setup interrupt with CPU interrupts disabled.
+   * Order matters:
+   *   1) setup_irq creates cpuint → peripheral IRQ mapping
+   *   2) irq_attach registers the handler for that IRQ
+   * Since spin_lock_irqsave masks CPU interrupts, even if the
+   * peripheral line is asserted after setup_irq, the interrupt
+   * won't be serviced until after irq_attach completes.
+   */
+
+  flags = spin_lock_irqsave(&priv->lock);
+
+  priv->cpu = this_cpu();
+
+  /* 1) Map peripheral → CPU interrupt (creates IRQ mapping) */
+
+  priv->cpuint = esp32s3_setup_irq(priv->cpu,
+                                   ESP32S3_PERIPH_LCD_CAM,
+                                   ESP32S3_INT_PRIO_DEF,
+                                   ESP32S3_CPUINT_LEVEL);
+  DEBUGASSERT(priv->cpuint >= 0);
+
+  /* 2) Attach handler (mapping now exists, so IRQ is valid) */
+
+  ret = irq_attach(ESP32S3_IRQ_LCD_CAM, cam_interrupt, priv);
+  DEBUGASSERT(ret == 0);
+  UNUSED(ret);
+
+  /* 3) Clear any spurious interrupt that fired during setup */
+
+  putreg32(0xffffffff, LCD_CAM_LC_DMA_INT_CLR_REG);
+
+  spin_unlock_irqrestore(&priv->lock, flags);
+
+  up_enable_irq(ESP32S3_IRQ_LCD_CAM);
+
+  /* Enable CAM VSYNC interrupt in hardware */
+
+  regval = getreg32(LCD_CAM_LC_DMA_INT_ENA_REG);
+  regval |= LCD_CAM_CAM_VSYNC_INT_ENA_M;
+  putreg32(regval, LCD_CAM_LC_DMA_INT_ENA_REG);
+
+  return OK;
+}
+
+/****************************************************************************
+ * Name: esp32s3_cam_init
+ ****************************************************************************/
+
+static int esp32s3_cam_init(struct imgdata_s *data)
+{
+  struct esp32s3_cam_s *priv = (struct esp32s3_cam_s *)data;
+
+  priv->capturing = false;
+  priv->cb = NULL;
+  priv->cb_arg = NULL;
+  priv->fb = NULL;
+  priv->fb_size = 0;
+  priv->fb_pos = 0;
+  priv->width = 0;
+  priv->height = 0;
+  priv->pixfmt = 0;
+
+  spin_lock_init(&priv->lock);
+
+  /* Configure GPIO pins */
+
+  esp32s3_cam_gpio_config();
+
+  /* Enable clock and configure XCLK */
+
+  esp32s3_cam_enableclk();
+
+  /* Configure CAM controller */
+
+  return esp32s3_cam_config(priv);
+}
+
+/****************************************************************************
+ * Name: esp32s3_cam_uninit
+ ****************************************************************************/
+
+static int esp32s3_cam_uninit(struct imgdata_s *data)
+{
+  struct esp32s3_cam_s *priv = (struct esp32s3_cam_s *)data;
+  uint32_t regval;
+
+  /* Stop capture */
+
+  regval = getreg32(LCD_CAM_CAM_CTRL1_REG);
+  regval &= ~LCD_CAM_CAM_START_M;
+  putreg32(regval, LCD_CAM_CAM_CTRL1_REG);
+
+  /* Disable interrupt */
+
+  up_disable_irq(ESP32S3_IRQ_LCD_CAM);
+  irq_detach(ESP32S3_IRQ_LCD_CAM);
+  esp32s3_teardown_irq(priv->cpu, ESP32S3_PERIPH_LCD_CAM, priv->cpuint);
+
+  /* Release DMA */
+
+  if (priv->dma_channel >= 0)
+    {
+      esp32s3_dma_release(priv->dma_channel);
+      priv->dma_channel = -1;
+    }
+
+  /* Free frame buffer */
+
+  if (priv->fb)
+    {
+      kmm_free(priv->fb);
+      priv->fb = NULL;
+    }
+
+  priv->capturing = false;
+
+  return OK;
+}
+
+/****************************************************************************
+ * Name: esp32s3_cam_set_buf
+ ****************************************************************************/
+
+static int esp32s3_cam_set_buf(struct imgdata_s *data,
+                               uint8_t nr_datafmts,
+                               imgdata_format_t *datafmts,
+                               uint8_t *addr, uint32_t size)
+{
+  struct esp32s3_cam_s *priv = (struct esp32s3_cam_s *)data;
+
+  /* Store negotiated format from upper layer */
+
+  priv->width  = datafmts[IMGDATA_FMT_MAIN].width;
+  priv->height = datafmts[IMGDATA_FMT_MAIN].height;
+  priv->pixfmt = datafmts[IMGDATA_FMT_MAIN].pixelformat;
+
+  if (addr != NULL && size > 0)
+    {
+      priv->fb = addr;
+      priv->fb_size = size;
+    }
+  else
+    {
+      /* Allocate frame buffer in PSRAM if available.
+       * 8-bit DVP formats (RGB565, YUV422) are all 2 bytes per pixel.
+       */
+
+      priv->fb_size = priv->width * priv->height * 2;
+      priv->fb = kmm_memalign(64, priv->fb_size);
+      if (!priv->fb)
+        {
+          snerr("ERROR: Failed to allocate frame buffer\n");
+          return -ENOMEM;
+        }
+    }
+
+  memset(priv->fb, 0, priv->fb_size);
+
+  /* Setup DMA descriptors for RX into frame buffer */
+
+  esp32s3_dma_setup(priv->dmadesc,
+                    ESP32S3_CAM_DMADESC_NUM,
+                    priv->fb,
+                    priv->fb_size,
+                    false,  /* RX */
+                    priv->dma_channel);
+
+  return OK;
+}
+
+/****************************************************************************
+ * Name: esp32s3_cam_validate_frame_setting
+ ****************************************************************************/
+
+static int esp32s3_cam_validate_frame_setting(struct imgdata_s *data,
+                               uint8_t nr_datafmts,
+                               imgdata_format_t *datafmts,
+                               imgdata_interval_t *interval)
+{
+  if (nr_datafmts < 1 || !datafmts)
+    {
+      return -EINVAL;
+    }
+
+  /* Pixel format is validated by the sensor driver (imgsensor);
+   * imgdata only checks that width and height are non-zero.
+   */
+
+  if (datafmts[IMGDATA_FMT_MAIN].width == 0 ||
+      datafmts[IMGDATA_FMT_MAIN].height == 0)
+    {
+      return -EINVAL;
+    }
+
+  return OK;
+}
+
+/****************************************************************************
+ * Name: esp32s3_cam_start_capture
+ ****************************************************************************/
+
+static int esp32s3_cam_start_capture(struct imgdata_s *data,
+                               uint8_t nr_datafmts,
+                               imgdata_format_t *datafmts,
+                               imgdata_interval_t *interval,
+                               imgdata_capture_t callback,
+                               void *arg)
+{
+  struct esp32s3_cam_s *priv = (struct esp32s3_cam_s *)data;
+  uint32_t regval;
+
+  if (priv->capturing)
+    {
+      return -EBUSY;
+    }
+
+  if (!priv->fb)
+    {
+      return -EINVAL;
+    }
+
+  priv->cb = callback;
+  priv->cb_arg = arg;
+  priv->fb_pos = 0;
+  priv->vsync_cnt = 0;
+
+  /* Stop CAM first */
+
+  regval = getreg32(LCD_CAM_CAM_CTRL1_REG);
+  regval &= ~LCD_CAM_CAM_START_M;
+  putreg32(regval, LCD_CAM_CAM_CTRL1_REG);
+
+  /* Reset CAM module + AFIFO (match esp32-camera ll_cam_start) */
+
+  regval = getreg32(LCD_CAM_CAM_CTRL1_REG);
+  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);
+
+  /* Reset DMA channel */
+
+  esp32s3_dma_reset_channel(priv->dma_channel, false);
+
+  /* Re-set REC_DATA_BYTELEN after reset (reference does this) */
+
+  regval = getreg32(LCD_CAM_CAM_CTRL1_REG);
+  regval &= ~LCD_CAM_CAM_REC_DATA_BYTELEN_M;
+  regval |= ((ESP32S3_CAM_DMA_BUFLEN - 1) << LCD_CAM_CAM_REC_DATA_BYTELEN_S);
+  putreg32(regval, LCD_CAM_CAM_CTRL1_REG);
+
+  /* Load DMA descriptors */
+
+  esp32s3_dma_load(priv->dmadesc, priv->dma_channel, false);
+  esp32s3_dma_enable(priv->dma_channel, false);
+
+  /* Start capture */
+
+  regval = getreg32(LCD_CAM_CAM_CTRL1_REG);
+  regval |= LCD_CAM_CAM_START_M;
+  putreg32(regval, LCD_CAM_CAM_CTRL1_REG);
+
+  /* Update registers */
+
+  regval = getreg32(LCD_CAM_CAM_CTRL_REG);
+  regval |= LCD_CAM_CAM_UPDATE_REG_M;
+  putreg32(regval, LCD_CAM_CAM_CTRL_REG);
+
+  priv->capturing = true;
+
+  syslog(LOG_INFO, "CAM start: CTRL=0x%08lx CTRL1=0x%08lx ENA=0x%08lx\n",
+         (unsigned long)getreg32(LCD_CAM_CAM_CTRL_REG),
+         (unsigned long)getreg32(LCD_CAM_CAM_CTRL1_REG),
+         (unsigned long)getreg32(LCD_CAM_LC_DMA_INT_ENA_REG));
+
+  /* Debug: poll RAW register to see if VSYNC appears */
+
+  for (int i = 0; i < 50; i++)
+    {
+      up_mdelay(20);
+      uint32_t raw = getreg32(LCD_CAM_LC_DMA_INT_RAW_REG);
+      uint32_t st  = getreg32(LCD_CAM_LC_DMA_INT_ST_REG);
+      if (raw || st)
+        {
+          syslog(LOG_INFO, "CAM poll[%d]: raw=0x%08lx st=0x%08lx\n",
+                 i, (unsigned long)raw, (unsigned long)st);
+          break;
+        }
+
+      if (i == 49)
+        {
+          syslog(LOG_INFO, "CAM poll: no VSYNC after 1s\n");
+        }
+    }
+
+  return OK;
+}
+
+/****************************************************************************
+ * Name: esp32s3_cam_stop_capture
+ ****************************************************************************/
+
+static int esp32s3_cam_stop_capture(struct imgdata_s *data)
+{
+  struct esp32s3_cam_s *priv = (struct esp32s3_cam_s *)data;
+  uint32_t regval;
+
+  /* Stop capture */
+
+  regval = getreg32(LCD_CAM_CAM_CTRL1_REG);
+  regval &= ~LCD_CAM_CAM_START_M;
+  putreg32(regval, LCD_CAM_CAM_CTRL1_REG);
+
+  regval = getreg32(LCD_CAM_CAM_CTRL_REG);
+  regval |= LCD_CAM_CAM_UPDATE_REG_M;
+  putreg32(regval, LCD_CAM_CAM_CTRL_REG);
+
+  priv->capturing = false;
+  priv->cb = NULL;
+  priv->cb_arg = NULL;
+
+  return OK;
+}
+
+/****************************************************************************
+ * Public Functions
+ ****************************************************************************/
+
+/****************************************************************************
+ * Name: esp32s3_cam_initialize
+ *
+ * Description:
+ *   Initialize the ESP32-S3 CAM imgdata driver.
+ *   Starts XCLK output for sensor communication.
+ *
+ * Returned Value:
+ *   Pointer to imgdata_s on success; NULL on failure.
+ *
+ ****************************************************************************/
+
+struct imgdata_s *esp32s3_cam_initialize(void)
+{
+  /* Start XCLK output to sensor — must be running before
+   * sensor I2C communication (e.g. gc0308_initialize).
+   */
+
+  esp32s3_configgpio(CONFIG_ESP32S3_CAM_XCLK_PIN, OUTPUT);
+  esp32s3_gpio_matrix_out(CONFIG_ESP32S3_CAM_XCLK_PIN,
+                          CAM_CLK_IDX, false, false);
+  esp32s3_cam_enableclk();
+
+  return &g_cam_priv.data;
+}
diff --git a/arch/xtensa/src/esp32s3/esp32s3_cam.h 
b/arch/xtensa/src/esp32s3/esp32s3_cam.h
new file mode 100644
index 00000000000..d9a769866e6
--- /dev/null
+++ b/arch/xtensa/src/esp32s3/esp32s3_cam.h
@@ -0,0 +1,50 @@
+/****************************************************************************
+ * arch/xtensa/src/esp32s3/esp32s3_cam.h
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.  The
+ * ASF licenses this file to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance with the
+ * License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ *
+ ****************************************************************************/
+
+#ifndef __ARCH_XTENSA_SRC_ESP32S3_ESP32S3_CAM_H
+#define __ARCH_XTENSA_SRC_ESP32S3_ESP32S3_CAM_H
+
+/****************************************************************************
+ * Included Files
+ ****************************************************************************/
+
+#include <nuttx/video/imgdata.h>
+
+/****************************************************************************
+ * Public Function Prototypes
+ ****************************************************************************/
+
+/****************************************************************************
+ * Name: esp32s3_cam_initialize
+ *
+ * Description:
+ *   Initialize the ESP32-S3 CAM imgdata driver.
+ *   Starts XCLK output for sensor communication.
+ *
+ * Returned Value:
+ *   Pointer to imgdata_s on success; NULL on failure.
+ *
+ ****************************************************************************/
+
+struct imgdata_s *esp32s3_cam_initialize(void);
+
+#endif /* __ARCH_XTENSA_SRC_ESP32S3_ESP32S3_CAM_H */

Reply via email to