/****************************************************************************
 * drivers/video/video.c
 *
 * 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 <debug.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <fcntl.h>
#include <assert.h>
#include <errno.h>
#include <poll.h>

#include <nuttx/kmalloc.h>
#include <nuttx/mutex.h>

#include <nuttx/video/imgsensor.h>
#include <nuttx/video/imgdata.h>

#include "video_framebuff.h"

/****************************************************************************
 * Pre-processor Definitions
 ****************************************************************************/

#define MAX_VIDEO_FILE_PATH     (32)
#define MAX_VIDEO_FMT           (2)
#define VIDEO_FMT_MAIN          (0)
#define VIDEO_FMT_SUB           (1)

#define VIDEO_REMAINING_CAPNUM_INFINITY (-1)

#define VIDEO_ID(x, y) (((x) << 16) | (y))

/****************************************************************************
 * Private Types
 ****************************************************************************/

enum video_state_e
{
  VIDEO_STATE_STREAMOFF = 0, /* Capture trigger event is not received */
  VIDEO_STATE_STREAMON  = 1, /* Capture trigger event is received,
                              * but capture is not operated.
                              */
  VIDEO_STATE_CAPTURE   = 2, /* On capture */
};

enum video_state_transition_cause
{
  CAUSE_VIDEO_STOP  = 0,     /* Stop  capture event for video stream */
  CAUSE_VIDEO_START = 1,     /* Start capture event for video stream */
  CAUSE_VIDEO_DQBUF = 2,     /* DQBUF timing        for video stream */
  CAUSE_STILL_STOP  = 3,     /* Stop  capture event for still stream */
  CAUSE_STILL_START = 4,     /* Start capture event for still stream */
};

enum video_waitend_cause_e
{
  VIDEO_WAITEND_CAUSE_CAPTUREDONE = 0,
  VIDEO_WAITEND_CAUSE_DQCANCEL  = 1,
  VIDEO_WAITEND_CAUSE_STILLSTOP = 2,
};

struct video_wait_capture_s
{
  sem_t                dqbuf_wait_flg;

  /* Save container which capture is done */

  FAR vbuf_container_t *done_container;
  enum video_waitend_cause_e waitend_cause;
};

typedef struct video_wait_capture_s video_wait_capture_t;

struct video_format_s
{
  uint16_t width;
  uint16_t height;
  uint32_t pixelformat;
};

typedef struct video_format_s video_format_t;

struct video_type_inf_s
{
  mutex_t              lock_state;
  enum video_state_e   state;
  int32_t              remaining_capnum;
  video_wait_capture_t wait_capture;
  uint8_t              nr_fmt;
  video_format_t       fmt[MAX_VIDEO_FMT];
  struct v4l2_rect     clip;
  struct v4l2_fract    frame_interval;
  video_framebuff_t    bufinf;
  FAR uint8_t          *bufheap;   /* for V4L2_MEMORY_MMAP buffers */
  FAR struct pollfd    *fds;
  uint32_t             seqnum;
};

typedef struct video_type_inf_s video_type_inf_t;

struct video_scene_params_s
{
  uint8_t mode;   /* enum v4l2_scene_mode */

  int32_t brightness;
  int32_t contrast;
  int32_t saturation;
  int32_t hue;
  bool    awb;
  int32_t red;
  int32_t blue;
  int32_t gamma;
  uint32_t gamma_curve_sz;
  uint8_t *gamma_curve;
  int32_t ev;
  bool    hflip_video;
  bool    vflip_video;
  bool    hflip_still;
  bool    vflip_still;
  int32_t sharpness;
  enum v4l2_colorfx colorfx;
  bool    auto_brightness;
  int32_t rotate;
  enum  v4l2_exposure_auto_type ae;
  int32_t exposure_time;
  int32_t focus;
  bool    af;
  int32_t zoom;
  int32_t iris;
  enum v4l2_auto_n_preset_white_balance wb;
  int32_t wdr;
  bool    stabilization;
  enum v4l2_iso_sensitivity_auto_type iso_auto;
  int32_t iso;
  enum v4l2_exposure_metering meter;
  int32_t spot_pos;
  int32_t threea_lock;
  enum v4l2_flash_led_mode led;
  int32_t jpeg_quality;
};

typedef struct video_scene_params_s video_scene_params_t;

struct video_parameter_name_s
{
  uint32_t id;
  const char *name;
};

typedef struct video_parameter_name_s video_parameter_name_t;

struct video_mng_s
{
  /* Parameter of video_initialize() */

  FAR char                 *devpath;
  mutex_t                  lock_open_num;
  uint8_t                  open_num;
  video_type_inf_t         video_inf;
  video_type_inf_t         still_inf;
  FAR struct imgdata_s     *imgdata;
  FAR struct imgsensor_s   *imgsensor;
  enum v4l2_scene_mode     video_scene_mode;
  uint8_t                  video_scence_num;
  FAR video_scene_params_t *video_scene_param[V4L2_SCENE_MODE_MAX];
#ifndef CONFIG_DISABLE_PSEUDOFS_OPERATIONS
  bool                     unlinked;
#endif
};

typedef struct video_mng_s video_mng_t;

/****************************************************************************
 * Private Function Prototypes
 ****************************************************************************/

/* Character driver methods. */

static int video_open(FAR struct file *filep);
static int video_close(FAR struct file *filep);
static ssize_t video_read(FAR struct file *filep,
                          FAR char *buffer, size_t buflen);
static ssize_t video_write(FAR struct file *filep,
                           FAR const char *buffer, size_t buflen);
static int video_ioctl(FAR struct file *filep, int cmd, unsigned long arg);
static int video_mmap(FAR struct file *filep,
                      FAR struct mm_map_entry_s *map);
static int video_poll(FAR struct file *filep, FAR struct pollfd *fds,
                      bool setup);
#ifndef CONFIG_DISABLE_PSEUDOFS_OPERATIONS
static int video_unlink(FAR struct inode *inode);
#endif
/* Common function */

static FAR video_type_inf_t *
get_video_type_inf(FAR video_mng_t *vmng, uint8_t type);
static enum video_state_e
estimate_next_video_state(FAR video_mng_t                   *vmng,
                          enum video_state_transition_cause cause);
static void change_video_state(FAR video_mng_t    *vmng,
                               enum video_state_e next_state);
static bool is_taking_still_picture(FAR video_mng_t *vmng);
static bool is_bufsize_sufficient(FAR video_mng_t *vmng, uint32_t bufsize);
static void cleanup_resources(FAR video_mng_t *vmng);
static bool is_sem_waited(FAR sem_t *sem);
static int save_scene_param(FAR video_mng_t *vmng,
                            enum v4l2_scene_mode mode,
                            uint32_t id,
                            struct v4l2_ext_control *control);
static int video_complete_capture(uint8_t err_code, uint32_t datasize,
                                  FAR const struct timeval *ts,
                                  FAR void *arg);
static int validate_frame_setting(FAR video_mng_t *vmng,
                                  enum v4l2_buf_type type,
                                  uint8_t nr_fmt,
                                  FAR video_format_t *vfmt,
                                  FAR struct v4l2_rect *clip,
                                  FAR struct v4l2_fract *interval);
static size_t get_bufsize(FAR video_format_t *vf);

/* Internal function for each cmds of ioctl */

static int video_querycap(FAR video_mng_t *vmng,
                          FAR struct v4l2_capability *cap);
static int video_g_input(FAR int *num);
static int video_enum_input(FAR video_mng_t *vmng,
                            FAR struct v4l2_input *input);
static int video_reqbufs(FAR struct video_mng_s *vmng,
                         FAR struct v4l2_requestbuffers *reqbufs);
static int video_qbuf(FAR struct video_mng_s *vmng,
                      FAR struct v4l2_buffer *buf);
static int video_dqbuf(FAR struct video_mng_s *vmng,
                       FAR struct v4l2_buffer *buf,
                       int oflags);
static int video_cancel_dqbuf(FAR struct video_mng_s *vmng,
                              enum v4l2_buf_type type);
static int video_g_fmt(FAR struct video_mng_s *priv,
                       FAR struct v4l2_format *fmt);
static int video_s_fmt(FAR struct video_mng_s *priv,
                       FAR struct v4l2_format *fmt);
static int video_try_fmt(FAR struct video_mng_s *priv,
                         FAR struct v4l2_format *v4l2);
static int video_g_parm(FAR struct video_mng_s *priv,
                        FAR struct v4l2_streamparm *parm);
static int video_s_parm(FAR struct video_mng_s *priv,
                        FAR struct v4l2_streamparm *parm);
static int video_streamon(FAR struct video_mng_s *vmng,
                          FAR enum v4l2_buf_type *type);
static int video_streamoff(FAR struct video_mng_s *vmng,
                           FAR enum v4l2_buf_type *type);
static int video_do_halfpush(FAR struct video_mng_s *priv,
                             bool enable);
static int video_takepict_start(FAR struct video_mng_s *vmng,
                                int32_t                capture_num);
static int video_takepict_stop(FAR struct video_mng_s *vmng,
                               bool halfpush);
static int video_queryctrl(FAR video_mng_t *vmng,
                           FAR struct v4l2_queryctrl *ctrl);
static int video_query_ext_ctrl(FAR video_mng_t *vmng,
                                FAR struct v4l2_query_ext_ctrl *ctrl);
static int video_querymenu(FAR video_mng_t *vmng,
                           FAR struct v4l2_querymenu *menu);
static int video_g_ctrl(FAR struct video_mng_s *priv,
                        FAR struct v4l2_control *ctrl);
static int video_s_ctrl(FAR struct video_mng_s *priv,
                        FAR struct v4l2_control *ctrl);
static int video_g_ext_ctrls(FAR struct video_mng_s *priv,
                             FAR struct v4l2_ext_controls *ctrls);
static int video_s_ext_ctrls(FAR struct video_mng_s *priv,
                             FAR struct v4l2_ext_controls *ctrls);
static int video_query_ext_ctrl_scene(FAR video_mng_t *vmng,
                   FAR struct v4s_query_ext_ctrl_scene *ctrl);
static int video_querymenu_scene(FAR video_mng_t *vmng,
                                 FAR struct v4s_querymenu_scene *menu);
static int video_g_ext_ctrls_scene(FAR video_mng_t *vmng,
                                   FAR struct v4s_ext_controls_scene *ctrls);
static int video_s_ext_ctrls_scene(FAR video_mng_t *vmng,
                                   FAR struct v4s_ext_controls_scene *ctrls);

/****************************************************************************
 * Private Data
 ****************************************************************************/

static const struct file_operations g_video_fops =
{
  video_open,               /* open */
  video_close,              /* close */
  video_read,               /* read */
  video_write,              /* write */
  NULL,                     /* seek */
  video_ioctl,              /* ioctl */
  video_mmap,               /* mmap */
  NULL,                     /* truncate */
  video_poll,               /* poll */
#ifndef CONFIG_DISABLE_PSEUDOFS_OPERATIONS
  video_unlink,             /* unlink */
#endif
};

static const video_parameter_name_t g_video_parameter_name[] =
{
  {IMGSENSOR_ID_BRIGHTNESS,           "Brightness"},
  {IMGSENSOR_ID_CONTRAST,             "Contrast"},
  {IMGSENSOR_ID_SATURATION,           "Saturation"},
  {IMGSENSOR_ID_HUE,                  "Hue"},
  {IMGSENSOR_ID_AUTO_WHITE_BALANCE,   "Automatic white balance"},
  {IMGSENSOR_ID_RED_BALANCE,          "Red balance"},
  {IMGSENSOR_ID_BLUE_BALANCE,         "Blue balance"},
  {IMGSENSOR_ID_GAMMA,                "Gamma value"},
  {IMGSENSOR_ID_GAMMA_CURVE,          "Gamma adjustment(curve)"},
  {IMGSENSOR_ID_EXPOSURE,             "Exposure value"},
  {IMGSENSOR_ID_HFLIP_VIDEO,          "Mirror horizontally(VIDEO)"},
  {IMGSENSOR_ID_VFLIP_VIDEO,          "Mirror vertically(VIDEO)"},
  {IMGSENSOR_ID_HFLIP_STILL,          "Mirror horizontally(STILL)"},
  {IMGSENSOR_ID_VFLIP_STILL,          "Mirror vertically(STILL)"},
  {IMGSENSOR_ID_SHARPNESS,            "Sharpness"},
  {IMGSENSOR_ID_COLOR_KILLER,         "Color killer"},
  {IMGSENSOR_ID_COLORFX,              "Color effect"},
  {IMGSENSOR_ID_AUTOBRIGHTNESS,       "Auto brightness"},
  {IMGSENSOR_ID_ROTATE,               "Rotate"},
  {IMGSENSOR_ID_EXPOSURE_AUTO,        "Auto Exposure"},
  {IMGSENSOR_ID_EXPOSURE_ABSOLUTE,    "Exposure time(100 usec)"},
  {IMGSENSOR_ID_FOCUS_ABSOLUTE,       "Focus(absolute value)"},
  {IMGSENSOR_ID_FOCUS_RELATIVE,       "Focus(relative value)"},
  {IMGSENSOR_ID_FOCUS_AUTO,           "Continuous Auto Focus"},
  {IMGSENSOR_ID_ZOOM_ABSOLUTE,        "Zoom(absolute value)"},
  {IMGSENSOR_ID_ZOOM_RELATIVE,        "Zoom(relative value)"},
  {IMGSENSOR_ID_ZOOM_CONTINUOUS,      "Continuous zoom"},
  {IMGSENSOR_ID_IRIS_ABSOLUTE,        "Iris(absolute value)"},
  {IMGSENSOR_ID_IRIS_RELATIVE,        "Iris(relative value)"},
  {IMGSENSOR_ID_AUTO_N_PRESET_WB,     "Preset white balance"},
  {IMGSENSOR_ID_WIDE_DYNAMIC_RANGE,   "Wide dynamic range"},
  {IMGSENSOR_ID_IMG_STABILIZATION,    "Image stabilization"},
  {IMGSENSOR_ID_ISO_SENSITIVITY,      "ISO sensitivity"},
  {IMGSENSOR_ID_ISO_SENSITIVITY_AUTO, "Automatic ISO sensitivity"},
  {IMGSENSOR_ID_EXPOSURE_METERING,    "Photometry"},
  {IMGSENSOR_ID_SPOT_POSITION,        "Spot position"},
  {IMGSENSOR_ID_3A_LOCK,              "Lock AWB/AE"},
  {IMGSENSOR_ID_AUTO_FOCUS_START,     "Start single Auto Focus"},
  {IMGSENSOR_ID_AUTO_FOCUS_STOP,      "Stop single Auto Focus"},
  {IMGSENSOR_ID_3A_PARAMETER,         "3A parameter"},
  {IMGSENSOR_ID_3A_STATUS,            "3A status"},
  {IMGSENSOR_ID_FLASH_LED_MODE,       "LED mode"},
  {IMGSENSOR_ID_JPEG_QUALITY,         "JPEG compression quality"}
};

static FAR struct imgsensor_s **g_video_registered_sensor = NULL;
static size_t g_video_registered_sensor_num;
static FAR struct imgdata_s *g_video_data = NULL;

/****************************************************************************
 * Private Functions
 ****************************************************************************/

static FAR video_type_inf_t *
get_video_type_inf(FAR video_mng_t *vmng, uint8_t type)
{
  FAR video_type_inf_t *type_inf;

  switch (type)
    {
      case V4L2_BUF_TYPE_VIDEO_CAPTURE:
        type_inf = &vmng->video_inf;
        break;

      case V4L2_BUF_TYPE_STILL_CAPTURE:
        type_inf = &vmng->still_inf;
        break;

      default:  /* Error case */
        type_inf = NULL;
        break;
    }

  return type_inf;
}

static enum video_state_e
estimate_next_video_state(FAR video_mng_t                   *vmng,
                          enum video_state_transition_cause cause)
{
  enum video_state_e current_state = vmng->video_inf.state;

  switch (cause)
    {
      case CAUSE_VIDEO_STOP:
        return VIDEO_STATE_STREAMOFF;

      case CAUSE_VIDEO_START:
        if (is_taking_still_picture(vmng))
          {
            return VIDEO_STATE_STREAMON;
          }
        else
          {
            return VIDEO_STATE_CAPTURE;
          }

      case CAUSE_STILL_STOP:
        if (current_state == VIDEO_STATE_STREAMON)
          {
            return VIDEO_STATE_CAPTURE;
          }
        else
          {
            return current_state;
          }

      case CAUSE_STILL_START:
        if (current_state == VIDEO_STATE_CAPTURE)
          {
            return VIDEO_STATE_STREAMON;
          }
        else
          {
            return current_state;
          }

      case CAUSE_VIDEO_DQBUF:
        if (current_state == VIDEO_STATE_STREAMON &&
            !is_taking_still_picture(vmng))
          {
            return VIDEO_STATE_CAPTURE;
          }
        else
          {
            return current_state;
          }

      default:
        return current_state;
    }
}

static void convert_to_imgdatafmt(FAR video_format_t *video,
                                  FAR imgdata_format_t *data)
{
  ASSERT(video && data);

  data->width  = video->width;
  data->height = video->height;
  switch (video->pixelformat)
    {
      case V4L2_PIX_FMT_NV12:
        data->pixelformat = IMGDATA_PIX_FMT_NV12;
        break;

      case V4L2_PIX_FMT_YUV420:
        data->pixelformat = IMGDATA_PIX_FMT_YUV420P;
        break;

      case V4L2_PIX_FMT_YUYV:
        data->pixelformat = IMGDATA_PIX_FMT_YUYV;
        break;

      case V4L2_PIX_FMT_UYVY:
        data->pixelformat = IMGDATA_PIX_FMT_UYVY;
        break;

      case V4L2_PIX_FMT_RGB565:
        data->pixelformat = IMGDATA_PIX_FMT_RGB565;
        break;

      case V4L2_PIX_FMT_JPEG:
        data->pixelformat = IMGDATA_PIX_FMT_JPEG;
        break;

      default: /* V4L2_PIX_FMT_JPEG_WITH_SUBIMG */
        data->pixelformat = IMGDATA_PIX_FMT_JPEG_WITH_SUBIMG;
        break;
    }
}

static void convert_to_imgsensorfmt(FAR video_format_t *video,
                                    FAR imgsensor_format_t *sensor)
{
  ASSERT(video && sensor);

  sensor->width  = video->width;
  sensor->height = video->height;
  switch (video->pixelformat)
    {
      case V4L2_PIX_FMT_NV12:
        sensor->pixelformat = IMGSENSOR_PIX_FMT_NV12;
        break;

      case V4L2_PIX_FMT_YUV420:
        sensor->pixelformat = IMGSENSOR_PIX_FMT_YUV420P;
        break;

      case V4L2_PIX_FMT_YUYV:
        sensor->pixelformat = IMGSENSOR_PIX_FMT_YUYV;
        break;

      case V4L2_PIX_FMT_UYVY:
        sensor->pixelformat = IMGSENSOR_PIX_FMT_UYVY;
        break;

      case V4L2_PIX_FMT_RGB565:
        sensor->pixelformat = IMGSENSOR_PIX_FMT_RGB565;
        break;

      case V4L2_PIX_FMT_JPEG:
        sensor->pixelformat = IMGSENSOR_PIX_FMT_JPEG;
        break;

      default: /* V4L2_PIX_FMT_JPEG_WITH_SUBIMG */
        sensor->pixelformat = IMGSENSOR_PIX_FMT_JPEG_WITH_SUBIMG;
        break;
    }
}

static void convert_to_imgdatainterval(FAR struct v4l2_fract *video,
                                       FAR imgdata_interval_t *data)
{
  ASSERT(video && data);

  data->numerator   = video->numerator;
  data->denominator = video->denominator;
}

static void convert_to_imgsensorinterval(FAR struct v4l2_fract *video,
                                         FAR imgsensor_interval_t *sensor)
{
  ASSERT(video && sensor);

  sensor->numerator   = video->numerator;
  sensor->denominator = video->denominator;
}

static bool is_clipped(FAR struct v4l2_rect *clip)
{
  bool ret = false;

  if (clip)
    {
      if (clip->left  != 0 || clip->top    != 0 ||
          clip->width != 0 || clip->height != 0)
        {
          ret = true;
        }
    }

  return ret;
}

static void get_clipped_format(uint8_t              nr_fmt,
                               FAR video_format_t   *fmt,
                               FAR struct v4l2_rect *clip,
                               FAR video_format_t   *c_fmt)
{
  DEBUGASSERT(fmt && c_fmt);

  if (is_clipped(clip))
    {
      c_fmt[VIDEO_FMT_MAIN].width  = clip->width;
      c_fmt[VIDEO_FMT_MAIN].height = clip->height;
      c_fmt[VIDEO_FMT_MAIN].pixelformat = fmt[VIDEO_FMT_MAIN].pixelformat;

      if (nr_fmt > 1)
        {
          /* Clipped size of  thumbnail is
           * small as ratio of main size and thumbnal size.
           */

          memcpy(&c_fmt[VIDEO_FMT_SUB],
                 &fmt[VIDEO_FMT_SUB],
                 sizeof(video_format_t));

          c_fmt[VIDEO_FMT_SUB].width =
            (uint32_t)c_fmt[VIDEO_FMT_SUB].width *
            clip->width / fmt[VIDEO_FMT_MAIN].width;

          c_fmt[VIDEO_FMT_SUB].height =
            (uint32_t)c_fmt[VIDEO_FMT_SUB].height *
            clip->height / fmt[VIDEO_FMT_MAIN].height;
        }
    }
  else
    {
      memcpy(c_fmt, fmt, nr_fmt * sizeof(video_format_t));
    }
}

static int start_capture(FAR video_mng_t *vmng,
                         enum v4l2_buf_type type,
                         uint8_t nr_fmt,
                         FAR video_format_t *fmt,
                         FAR struct v4l2_rect *clip,
                         FAR struct v4l2_fract *interval,
                         uintptr_t bufaddr, uint32_t bufsize)
{
  video_format_t c_fmt[MAX_VIDEO_FMT];
  imgdata_format_t df[MAX_VIDEO_FMT];
  imgsensor_format_t sf[MAX_VIDEO_FMT];
  imgdata_interval_t di;
  imgsensor_interval_t si;

  ASSERT(fmt && interval && vmng->imgsensor && vmng->imgdata);

  get_clipped_format(nr_fmt, fmt, clip, c_fmt);

  convert_to_imgdatafmt(&c_fmt[VIDEO_FMT_MAIN], &df[IMGDATA_FMT_MAIN]);
  convert_to_imgdatafmt(&c_fmt[VIDEO_FMT_SUB], &df[IMGDATA_FMT_SUB]);
  convert_to_imgdatainterval(interval, &di);
  convert_to_imgsensorfmt(&fmt[VIDEO_FMT_MAIN], &sf[IMGSENSOR_FMT_MAIN]);
  convert_to_imgsensorfmt(&fmt[VIDEO_FMT_SUB], &sf[IMGSENSOR_FMT_SUB]);
  convert_to_imgsensorinterval(interval, &si);

  IMGSENSOR_START_CAPTURE(vmng->imgsensor,
     type == V4L2_BUF_TYPE_VIDEO_CAPTURE ?
     IMGSENSOR_STREAM_TYPE_VIDEO : IMGSENSOR_STREAM_TYPE_STILL,
     nr_fmt, sf, &si);
  IMGDATA_START_CAPTURE(vmng->imgdata,
     nr_fmt, df, &di, video_complete_capture, vmng);
  IMGDATA_SET_BUF(vmng->imgdata, (FAR uint8_t *)bufaddr, bufsize);
  return OK;
}

static void stop_capture(FAR video_mng_t *vmng, enum v4l2_buf_type type)
{
  ASSERT(vmng->imgsensor && vmng->imgdata);

  IMGDATA_STOP_CAPTURE(vmng->imgdata);
  IMGSENSOR_STOP_CAPTURE(vmng->imgsensor,
     type == V4L2_BUF_TYPE_VIDEO_CAPTURE ?
     IMGSENSOR_STREAM_TYPE_VIDEO : IMGSENSOR_STREAM_TYPE_STILL);
}

static void change_video_state(FAR video_mng_t    *vmng,
                               enum video_state_e next_state)
{
  enum video_state_e current_state = vmng->video_inf.state;
  enum video_state_e updated_next_state = next_state;

  if (current_state != VIDEO_STATE_CAPTURE &&
      next_state    == VIDEO_STATE_CAPTURE)
    {
      FAR vbuf_container_t *container =
              video_framebuff_get_vacant_container(&vmng->video_inf.bufinf);
      if (container != NULL)
        {
          vmng->video_inf.seqnum = 0;
          start_capture(vmng,
                        V4L2_BUF_TYPE_VIDEO_CAPTURE,
                        vmng->video_inf.nr_fmt,
                        vmng->video_inf.fmt,
                        &vmng->video_inf.clip,
                        &vmng->video_inf.frame_interval,
                        container->buf.m.userptr,
                        container->buf.length);
        }
      else
        {
          updated_next_state = VIDEO_STATE_STREAMON;
        }
    }
  else if (current_state == VIDEO_STATE_CAPTURE &&
           next_state    != VIDEO_STATE_CAPTURE)
    {
          stop_capture(vmng, V4L2_BUF_TYPE_VIDEO_CAPTURE);
    }

  vmng->video_inf.state = updated_next_state;
}

static bool is_taking_still_picture(FAR video_mng_t *vmng)
{
  return vmng->still_inf.state == VIDEO_STATE_STREAMON ||
         vmng->still_inf.state == VIDEO_STATE_CAPTURE;
}

static bool is_bufsize_sufficient(FAR video_mng_t *vmng, uint32_t bufsize)
{
  /* Depend on format, frame size, and JPEG compression quality */

  return true;
}

static void initialize_frame_setting(FAR uint8_t *nr_fmt,
                                     FAR video_format_t *fmt,
                                     FAR struct v4l2_fract *interval)
{
  ASSERT(nr_fmt && fmt && interval);

  /* Initial setting : QVGA YUV4:2:2 15FPS */

  *nr_fmt = 1;
  fmt[VIDEO_FMT_MAIN].width       = VIDEO_HSIZE_QVGA;
  fmt[VIDEO_FMT_MAIN].height      = VIDEO_VSIZE_QVGA;
  fmt[VIDEO_FMT_MAIN].pixelformat = V4L2_PIX_FMT_UYVY;
  interval->denominator = 15;
  interval->numerator   = 1;
}

static void initialize_streamresources(FAR video_type_inf_t *type_inf)
{
  memset(type_inf, 0, sizeof(video_type_inf_t));
  type_inf->remaining_capnum = VIDEO_REMAINING_CAPNUM_INFINITY;
  nxmutex_init(&type_inf->lock_state);
  nxsem_init(&type_inf->wait_capture.dqbuf_wait_flg, 0, 0);
  initialize_frame_setting(&type_inf->nr_fmt,
                           type_inf->fmt,
                           &type_inf->frame_interval);
  video_framebuff_init(&type_inf->bufinf);
}

static int32_t get_default_value(FAR video_mng_t *vmng, uint32_t id)
{
  imgsensor_supported_value_t value;
  int ret;

  if (vmng->imgsensor == NULL)
    {
      /* Don't care(unsupported parameter) */

      return 0;
    }

  ret = IMGSENSOR_GET_SUPPORTED_VALUE(vmng->imgsensor, id, &value);
  if (ret != OK)
    {
      /* Don't care(unsupported parameter) */

      return 0;
    }

  switch (value.type)
    {
      case IMGSENSOR_CTRL_TYPE_INTEGER_MENU:
        return value.u.discrete.default_value;

      case IMGSENSOR_CTRL_TYPE_U8:
      case IMGSENSOR_CTRL_TYPE_U16:
      case IMGSENSOR_CTRL_TYPE_U32:

        /* Don't care */

        return 0;

      default:
        return value.u.range.default_value;
    }
}

static int32_t initialize_scene_gamma(FAR video_mng_t *vmng, uint8_t **gamma)
{
  imgsensor_supported_value_t sup_val;
  imgsensor_value_t val;
  int32_t sz;
  int ret;

  *gamma = NULL;

  ASSERT(vmng->imgsensor);

  ret = IMGSENSOR_GET_SUPPORTED_VALUE(vmng->imgsensor,
          IMGSENSOR_ID_GAMMA_CURVE, &sup_val);
  if (ret != OK)
    {
      /* Unsupported parameter */

      return 0;
    }

  switch (sup_val.type)
    {
      case IMGSENSOR_CTRL_TYPE_U8:
        sz = sup_val.u.elems.nr_elems * sizeof(uint8_t);
        if (sz / sizeof(uint8_t) != sup_val.u.elems.nr_elems)
          {
            /* Multiplication overflow */

            return 0;
          }

        break;

      case IMGSENSOR_CTRL_TYPE_U16:
        sz = sup_val.u.elems.nr_elems * sizeof(uint16_t);
        if (sz / sizeof(uint16_t) != sup_val.u.elems.nr_elems)
          {
            /* Multiplication overflow */

            return 0;
          }

        break;

      default: /* IMGSENSOR_CTRL_TYPE_U32 */
        sz = sup_val.u.elems.nr_elems * sizeof(uint32_t);
        if (sz / sizeof(uint32_t) != sup_val.u.elems.nr_elems)
          {
            /* Multiplication overflow */

            return 0;
          }

        break;
    }

  *gamma = kmm_malloc(sz);
  val.p_u8 = (FAR uint8_t *)*gamma;
  IMGSENSOR_GET_VALUE(vmng->imgsensor, IMGSENSOR_ID_GAMMA_CURVE, sz, &val);
  return sz;
}

static int initialize_scene_parameter(FAR video_mng_t *vmng,
                                      enum v4l2_scene_mode mode,
                                      video_scene_params_t **vsp)
{
  FAR video_scene_params_t *sp = kmm_malloc(sizeof(video_scene_params_t));
  if (!sp)
    {
      return -ENOMEM;
    }

  sp->brightness      = get_default_value(vmng, IMGSENSOR_ID_BRIGHTNESS);
  sp->contrast        = get_default_value(vmng, IMGSENSOR_ID_CONTRAST);
  sp->saturation      = get_default_value(vmng, IMGSENSOR_ID_SATURATION);
  sp->hue             = get_default_value(vmng, IMGSENSOR_ID_HUE);
  sp->awb             = get_default_value(vmng,
                                          IMGSENSOR_ID_AUTO_WHITE_BALANCE);
  sp->red             = get_default_value(vmng, IMGSENSOR_ID_RED_BALANCE);
  sp->blue            = get_default_value(vmng, IMGSENSOR_ID_BLUE_BALANCE);
  sp->gamma           = get_default_value(vmng, IMGSENSOR_ID_GAMMA);
  sp->gamma_curve_sz  = initialize_scene_gamma(vmng, &sp->gamma_curve);
  sp->ev              = get_default_value(vmng, IMGSENSOR_ID_EXPOSURE);
  sp->hflip_video     = get_default_value(vmng, IMGSENSOR_ID_HFLIP_VIDEO);
  sp->vflip_video     = get_default_value(vmng, IMGSENSOR_ID_VFLIP_VIDEO);
  sp->hflip_still     = get_default_value(vmng, IMGSENSOR_ID_HFLIP_STILL);
  sp->vflip_still     = get_default_value(vmng, IMGSENSOR_ID_VFLIP_STILL);
  sp->sharpness       = get_default_value(vmng, IMGSENSOR_ID_SHARPNESS);
  sp->colorfx         = get_default_value(vmng, IMGSENSOR_ID_COLORFX);
  sp->auto_brightness = get_default_value(vmng, IMGSENSOR_ID_AUTOBRIGHTNESS);
  sp->rotate          = get_default_value(vmng, IMGSENSOR_ID_ROTATE);
  sp->ae              = get_default_value(vmng, IMGSENSOR_ID_EXPOSURE_AUTO);
  sp->exposure_time   = get_default_value(vmng,
                                         IMGSENSOR_ID_EXPOSURE_ABSOLUTE);
  sp->focus           = get_default_value(vmng, IMGSENSOR_ID_FOCUS_ABSOLUTE);
  sp->af              = get_default_value(vmng, IMGSENSOR_ID_FOCUS_AUTO);
  sp->zoom            = get_default_value(vmng, IMGSENSOR_ID_ZOOM_ABSOLUTE);
  sp->iris            = get_default_value(vmng, IMGSENSOR_ID_IRIS_ABSOLUTE);
  sp->wb              = get_default_value(vmng,
                                          IMGSENSOR_ID_AUTO_N_PRESET_WB);
  sp->wdr             = get_default_value(vmng,
                                          IMGSENSOR_ID_WIDE_DYNAMIC_RANGE);
  sp->stabilization   = get_default_value(vmng,
                                          IMGSENSOR_ID_IMG_STABILIZATION);
  sp->iso_auto        = get_default_value(vmng,
                                          IMGSENSOR_ID_ISO_SENSITIVITY_AUTO);
  sp->iso             = get_default_value(vmng,
                                          IMGSENSOR_ID_ISO_SENSITIVITY);
  sp->meter           = get_default_value(vmng,
                                          IMGSENSOR_ID_EXPOSURE_METERING);
  sp->threea_lock     = get_default_value(vmng, IMGSENSOR_ID_3A_LOCK);
  sp->led             = get_default_value(vmng, IMGSENSOR_ID_FLASH_LED_MODE);
  sp->jpeg_quality    = get_default_value(vmng, IMGSENSOR_ID_JPEG_QUALITY);

  *vsp = sp;

  return OK;
}

static void initialize_scenes_parameter(FAR video_mng_t *vmng)
{
  memset(vmng->video_scene_param,
         0, sizeof(vmng->video_scene_param));

  initialize_scene_parameter(vmng, V4L2_SCENE_MODE_NONE,
      &vmng->video_scene_param[vmng->video_scence_num++]);
#ifdef CONFIG_VIDEO_SCENE_BACKLIGHT
  initialize_scene_parameter(vmng, V4L2_SCENE_MODE_BACKLIGHT,
           &vmng->video_scene_param[vmng->video_scence_num++]);
#endif /* CONFIG_VIDEO_SCENE_BACKLIGHT */
#ifdef CONFIG_VIDEO_SCENE_BEACHSNOW
  initialize_scene_parameter(vmng, CONFIG_VIDEO_SCENE_BEACHSNOW,
              &vmng->video_scene_param[vmng->video_scence_num++]);
#endif /* CONFIG_VIDEO_SCENE_BEACHSNOW */
#ifdef CONFIG_VIDEO_SCENE_CANDLELIGHT
  initialize_scene_parameter(vmng, CONFIG_VIDEO_SCENE_CANDLELIGHT,
                &vmng->video_scene_param[vmng->video_scence_num++]);
#endif /* CONFIG_VIDEO_SCENE_CANDLELIGHT */
#ifdef CONFIG_VIDEO_SCENE_DAWNDUSK
  initialize_scene_parameter(vmng, CONFIG_VIDEO_SCENE_DAWNDUSK,
             &vmng->video_scene_param[vmng->video_scence_num++]);
#endif /* CONFIG_VIDEO_SCENE_DAWNDUSK */
#ifdef CONFIG_VIDEO_SCENE_FALLCOLORS
  initialize_scene_parameter(vmng, CONFIG_VIDEO_SCENE_FALLCOLORS,
               &vmng->video_scene_param[vmng->video_scence_num++]);
#endif /* CONFIG_VIDEO_SCENE_FALLCOLORS */
#ifdef CONFIG_VIDEO_SCENE_FIREWORKS
  initialize_scene_parameter(vmng, CONFIG_VIDEO_SCENE_FIREWORKS,
              &vmng->video_scene_param[vmng->video_scence_num++]);
#endif /* CONFIG_VIDEO_SCENE_FIREWORKS */
#ifdef CONFIG_VIDEO_SCENE_LANDSCAPE
  initialize_scene_parameter(vmng, CONFIG_VIDEO_SCENE_LANDSCAPE,
              &vmng->video_scene_param[vmng->video_scence_num++]);
#endif /* CONFIG_VIDEO_SCENE_LANDSCAPE */
#ifdef CONFIG_VIDEO_SCENE_NIGHT
  initialize_scene_parameter(vmng, CONFIG_VIDEO_SCENE_NIGHT,
          &vmng->video_scene_param[vmng->video_scence_num++]);
#endif /* CONFIG_VIDEO_SCENE_NIGHT */
#ifdef CONFIG_VIDEO_SCENE_PARTYINDOOR
  initialize_scene_parameter(vmng, CONFIG_VIDEO_SCENE_PARTYINDOOR,
                &vmng->video_scene_param[vmng->video_scence_num++]);
#endif /* CONFIG_VIDEO_SCENE_PARTYINDOOR */
#ifdef CONFIG_VIDEO_SCENE_PORTRAIT
  initialize_scene_parameter(vmng, CONFIG_VIDEO_SCENE_PORTRAIT,
             &vmng->video_scene_param[vmng->video_scence_num++]);
#endif /* CONFIG_VIDEO_SCENE_PORTRAIT */
#ifdef CONFIG_VIDEO_SCENE_SPORTS
  initialize_scene_parameter(vmng, CONFIG_VIDEO_SCENE_SPORTS,
           &vmng->video_scene_param[vmng->video_scence_num++]);
#endif /* CONFIG_VIDEO_SCENE_SPORTS */
#ifdef CONFIG_VIDEO_SCENE_SUNSET
  initialize_scene_parameter(vmng, CONFIG_VIDEO_SCENE_SUNSET,
           &vmng->video_scene_param[vmng->video_scence_num++]);
#endif /* CONFIG_VIDEO_SCENE_SUNSET */
#ifdef CONFIG_VIDEO_SCENE_TEXT
  initialize_scene_parameter(vmng, CONFIG_VIDEO_SCENE_TEXT,
         &vmng->video_scene_param[vmng->video_scence_num++]);
#endif /* CONFIG_VIDEO_SCENE_TEXT */
}

static void initialize_resources(FAR video_mng_t *vmng)
{
  initialize_streamresources(&vmng->video_inf);
  initialize_streamresources(&vmng->still_inf);
  initialize_scenes_parameter(vmng);
}

static void cleanup_streamresources(FAR video_type_inf_t *type_inf)
{
  video_framebuff_uninit(&type_inf->bufinf);
  nxsem_destroy(&type_inf->wait_capture.dqbuf_wait_flg);
  nxmutex_destroy(&type_inf->lock_state);
  if (type_inf->bufheap != NULL)
    {
      kumm_free(type_inf->bufheap);
      type_inf->bufheap = NULL;
    }
}

static void cleanup_scene_parameter(FAR video_scene_params_t **vsp)
{
  FAR video_scene_params_t *sp = *vsp;
  ASSERT(sp);

  if (sp->gamma_curve != NULL)
    {
      kmm_free(sp->gamma_curve);
      sp->gamma_curve = NULL;
      sp->gamma_curve_sz = 0;
    }

  kmm_free(sp);
  *vsp = NULL;
}

static void cleanup_scenes_parameter(FAR video_mng_t *vmng)
{
  int i;

  for (i = 0; i < vmng->video_scence_num; i++)
    {
      cleanup_scene_parameter(&vmng->video_scene_param[i]);
    }

  vmng->video_scence_num = 0;
}

static void cleanup_resources(FAR video_mng_t *vmng)
{
  /* If in capture, stop */

  if (vmng->video_inf.state == VIDEO_STATE_CAPTURE)
    {
      stop_capture(vmng, V4L2_BUF_TYPE_VIDEO_CAPTURE);
    }

  if (vmng->still_inf.state == VIDEO_STATE_CAPTURE)
    {
      stop_capture(vmng, V4L2_BUF_TYPE_STILL_CAPTURE);
    }

  /* Clean up resource */

  cleanup_streamresources(&vmng->video_inf);
  cleanup_streamresources(&vmng->still_inf);
  cleanup_scenes_parameter(vmng);
}

static bool is_sem_waited(FAR sem_t *sem)
{
  int semcount;

  return nxsem_get_value(sem, &semcount) == OK && semcount < 0;
}

static FAR struct imgsensor_s *
get_connected_imgsensor(FAR struct imgsensor_s **sensors,
                        size_t sensor_num)
{
  FAR struct imgsensor_s *sensor = NULL;
  int i;

  for (i = 0; i < sensor_num; i++)
    {
      if (sensors[i] &&
          IMGSENSOR_IS_AVAILABLE(sensors[i]))
        {
          sensor = sensors[i];
          break;
        }
    }

  return sensor;
}

static int video_open(FAR struct file *filep)
{
  FAR struct inode *inode = filep->f_inode;
  FAR video_mng_t  *priv  = inode->i_private;
  int ret = OK;

  nxmutex_lock(&priv->lock_open_num);
  if (priv->open_num == 0)
    {
      /* Only in first execution, open device */

      ret = IMGSENSOR_INIT(priv->imgsensor);
      if (ret == OK)
        {
          ret = IMGDATA_INIT(priv->imgdata);
          if (ret == OK)
            {
              initialize_resources(priv);
            }
        }
      else
        {
          ret = -ENODEV;
        }
    }

  /* In second or later execution, ret is initial value(=OK) */

  if (ret == OK)
    {
      priv->open_num++;
    }

  nxmutex_unlock(&priv->lock_open_num);
  return ret;
}

static int video_close(FAR struct file *filep)
{
  FAR struct inode *inode = filep->f_inode;
  FAR video_mng_t  *priv  = inode->i_private;

  nxmutex_lock(&priv->lock_open_num);

  if (--priv->open_num == 0)
    {
      cleanup_resources(priv);
      IMGSENSOR_UNINIT(priv->imgsensor);
      IMGDATA_UNINIT(priv->imgdata);
#ifndef CONFIG_DISABLE_PSEUDOFS_OPERATIONS
      if (priv->unlinked)
        {
          nxmutex_unlock(&priv->lock_open_num);
          nxmutex_destroy(&priv->lock_open_num);
          kmm_free(priv->devpath);
          kmm_free(priv);
          inode->i_private = NULL;
          return OK;
        }

#endif
    }

  nxmutex_unlock(&priv->lock_open_num);
  return OK;
}

static ssize_t video_read(FAR struct file *filep,
                          FAR char *buffer, size_t buflen)
{
  return -ENOTSUP;
}

static ssize_t video_write(FAR struct file *filep,
                           FAR const char *buffer, size_t buflen)
{
  return -ENOTSUP;
}

#ifndef CONFIG_DISABLE_PSEUDOFS_OPERATIONS
static int video_unlink(FAR struct inode *inode)
{
  FAR video_mng_t *priv = inode->i_private;
  nxmutex_lock(&priv->lock_open_num);
  if (priv->open_num == 0)
    {
      nxmutex_unlock(&priv->lock_open_num);
      nxmutex_destroy(&priv->lock_open_num);
      kmm_free(priv->devpath);
      kmm_free(priv);
      inode->i_private = NULL;
    }
  else
    {
      priv->unlinked = true;
      nxmutex_unlock(&priv->lock_open_num);
    }

  return OK;
}
#endif

static int video_querycap(FAR video_mng_t *vmng,
                          FAR struct v4l2_capability *cap)
{
  FAR const char *name;

  ASSERT(vmng->imgsensor);

  if (cap == NULL)
    {
      return -EINVAL;
    }

  name = IMGSENSOR_GET_DRIVER_NAME(vmng->imgsensor);
  if (name == NULL)
    {
      return -ENOTTY;
    }

  memset(cap, 0, sizeof(struct v4l2_capability));

  /* cap->driver needs to be NULL-terminated. */

  strlcpy((FAR char *)cap->driver, name, sizeof(cap->driver));
  cap->capabilities = V4L2_CAP_VIDEO_CAPTURE | V4L2_CAP_STREAMING;

  return OK;
}

static int video_g_input(FAR int *num)
{
  *num = 0;
  return OK;
}

static int video_enum_input(FAR video_mng_t *vmng,
                            FAR struct v4l2_input *input)
{
  FAR const char *name;

  ASSERT(vmng->imgsensor);

  if (input->index > 0)
    {
      return -EINVAL;
    }

  name = IMGSENSOR_GET_DRIVER_NAME(vmng->imgsensor);
  if (name == NULL)
    {
      return -ENOTTY;
    }

  memset(input, 0, sizeof(struct v4l2_input));
  strlcpy((FAR char *)input->name, name, sizeof(input->name));
  input->type = V4L2_INPUT_TYPE_CAMERA;

  return OK;
}

static int video_reqbufs(FAR struct video_mng_s         *vmng,
                         FAR struct v4l2_requestbuffers *reqbufs)
{
  FAR video_type_inf_t *type_inf;
  irqstate_t flags;
  int ret = OK;

  if (vmng == NULL || reqbufs == NULL)
    {
      return -EINVAL;
    }

  type_inf = get_video_type_inf(vmng, reqbufs->type);
  if (type_inf == NULL)
    {
      return -EINVAL;
    }

  flags = enter_critical_section();

  if (type_inf->state == VIDEO_STATE_CAPTURE)
    {
      /* In capture, REQBUFS is not permitted */

      ret = -EPERM;
    }
  else
    {
      if (reqbufs->count > V4L2_REQBUFS_COUNT_MAX)
        {
          reqbufs->count = V4L2_REQBUFS_COUNT_MAX;
        }

      video_framebuff_change_mode(&type_inf->bufinf, reqbufs->mode);
      ret = video_framebuff_realloc_container(&type_inf->bufinf,
                                              reqbufs->count);
      if (ret == OK && reqbufs->memory == V4L2_MEMORY_MMAP)
        {
          if (type_inf->bufheap != NULL)
            {
              kumm_free(type_inf->bufheap);
            }

          type_inf->bufheap = kumm_memalign(32,
            reqbufs->count * get_bufsize(&type_inf->fmt[VIDEO_FMT_MAIN]));
          if (type_inf->bufheap == NULL)
            {
              ret = -ENOMEM;
            }
        }
    }

  leave_critical_section(flags);
  return ret;
}

static int video_querybuf(FAR struct video_mng_s *vmng,
                          FAR struct v4l2_buffer *buf)
{
  FAR video_type_inf_t *type_inf;

  if (vmng == NULL || buf == NULL || buf->memory != V4L2_MEMORY_MMAP)
    {
      return -EINVAL;
    }

  type_inf = get_video_type_inf(vmng, buf->type);
  if (type_inf == NULL)
    {
      return -EINVAL;
    }

  if (buf->index >= type_inf->bufinf.container_size)
    {
      return -EINVAL;
    }

  buf->length = get_bufsize(&type_inf->fmt[VIDEO_FMT_MAIN]);
  buf->m.offset = buf->length * buf->index;

  return OK;
}

static int video_qbuf(FAR struct video_mng_s *vmng,
                      FAR struct v4l2_buffer *buf)
{
  FAR video_type_inf_t *type_inf;
  FAR vbuf_container_t *container;
  enum video_state_e   next_video_state;
  irqstate_t           flags;

  if (vmng == NULL || buf == NULL)
    {
      return -EINVAL;
    }

  type_inf = get_video_type_inf(vmng, buf->type);
  if (type_inf == NULL)
    {
      return -EINVAL;
    }

  if (!is_bufsize_sufficient(vmng, buf->length))
    {
      return -EINVAL;
    }

  container = video_framebuff_get_container(&type_inf->bufinf);
  if (container == NULL)
    {
      return -ENOMEM;
    }

  memcpy(&container->buf, buf, sizeof(struct v4l2_buffer));
  if (buf->memory == V4L2_MEMORY_MMAP)
    {
      /* only use userptr inside the container */

      container->buf.length = get_bufsize(&type_inf->fmt[VIDEO_FMT_MAIN]);
      container->buf.m.userptr = (unsigned long)(type_inf->bufheap +
                                 container->buf.length * buf->index);
    }

  video_framebuff_queue_container(&type_inf->bufinf, container);

  nxmutex_lock(&type_inf->lock_state);
  flags = enter_critical_section();
  if (type_inf->state == VIDEO_STATE_STREAMON)
    {
      leave_critical_section(flags);

      if (buf->type == V4L2_BUF_TYPE_VIDEO_CAPTURE)
        {
          nxmutex_lock(&vmng->still_inf.lock_state);
          next_video_state =
            estimate_next_video_state(vmng, CAUSE_VIDEO_START);
          change_video_state(vmng, next_video_state);
          nxmutex_unlock(&vmng->still_inf.lock_state);
        }
      else
        {
          container =
            video_framebuff_get_vacant_container(&type_inf->bufinf);
          if (container != NULL)
            {
              type_inf->seqnum = 0;
              start_capture(vmng,
                            buf->type,
                            type_inf->nr_fmt,
                            type_inf->fmt,
                            &type_inf->clip,
                            &type_inf->frame_interval,
                            container->buf.m.userptr,
                            container->buf.length);
              type_inf->state = VIDEO_STATE_CAPTURE;
            }
        }
    }
  else
    {
      leave_critical_section(flags);
    }

  nxmutex_unlock(&type_inf->lock_state);
  return OK;
}

static int video_dqbuf(FAR struct video_mng_s *vmng,
                       FAR struct v4l2_buffer *buf,
                       int oflags)
{
  irqstate_t           flags;
  FAR video_type_inf_t *type_inf;
  FAR vbuf_container_t *container;
  FAR sem_t            *dqbuf_wait_flg;
  enum video_state_e   next_video_state;

  if (vmng == NULL || buf == NULL)
    {
      return -EINVAL;
    }

  type_inf = get_video_type_inf(vmng, buf->type);
  if (type_inf == NULL)
    {
      return -EINVAL;
    }

  container = video_framebuff_dq_valid_container(&type_inf->bufinf);
  if (container == NULL)
    {
      if (oflags & O_NONBLOCK)
        {
          return -EAGAIN;
        }

      /* Not yet done capture. Wait done */

      dqbuf_wait_flg = &type_inf->wait_capture.dqbuf_wait_flg;

      /* Loop until semaphore is unlocked by capture done or DQCANCEL */

      do
        {
          if (buf->type == V4L2_BUF_TYPE_VIDEO_CAPTURE)
            {
              /* If start capture condition is satisfied, start capture */

              flags = enter_critical_section();
              next_video_state =
                estimate_next_video_state(vmng, CAUSE_VIDEO_DQBUF);
              change_video_state(vmng, next_video_state);
              leave_critical_section(flags);
            }

          nxsem_wait_uninterruptible(dqbuf_wait_flg);
        }
      while (type_inf->wait_capture.waitend_cause ==
             VIDEO_WAITEND_CAUSE_STILLSTOP);

      container = type_inf->wait_capture.done_container;
      if (container == NULL)
        {
          /* Waking up without captured data means abort.
           * Therefore, Check cause.
           */

          if (type_inf->wait_capture.waitend_cause ==
              VIDEO_WAITEND_CAUSE_DQCANCEL)
            {
              return -ECANCELED;
            }
        }

      type_inf->wait_capture.done_container = NULL;
    }

  memcpy(buf, &container->buf, sizeof(struct v4l2_buffer));
  video_framebuff_free_container(&type_inf->bufinf, container);

  return OK;
}

static int video_cancel_dqbuf(FAR struct video_mng_s *vmng,
                              enum v4l2_buf_type type)
{
  FAR video_type_inf_t *type_inf;

  type_inf = get_video_type_inf(vmng, type);
  if (type_inf == NULL)
    {
      return -EINVAL;
    }

  if (!is_sem_waited(&type_inf->wait_capture.dqbuf_wait_flg))
    {
      /* In not waiting DQBUF case, return OK */

      return OK;
    }

  type_inf->wait_capture.waitend_cause = VIDEO_WAITEND_CAUSE_DQCANCEL;

  /* If capture is done before nxsem_post, cause is overwritten */

  return nxsem_post(&type_inf->wait_capture.dqbuf_wait_flg);
}

static bool validate_clip_range(int32_t pos, uint32_t c_sz, uint16_t frm_sz)
{
  return pos >= 0 && c_sz <= frm_sz && pos + c_sz <= frm_sz;
}

static bool validate_clip_setting(FAR struct v4l2_rect *clip,
                                  FAR video_format_t *fmt)
{
  DEBUGASSERT(clip && fmt);

  /* Not permit the setting which do not fit inside frame size. */

  return validate_clip_range(clip->left, clip->width,  fmt->width) &&
         validate_clip_range(clip->top,  clip->height, fmt->height);
}

static int video_s_selection(FAR struct video_mng_s    *vmng,
                             FAR struct v4l2_selection *clip)
{
  FAR video_type_inf_t *type_inf;
  uint32_t p_u32[IMGSENSOR_CLIP_NELEM];
  imgsensor_value_t val;
  int32_t id;
  int ret;

  ASSERT(vmng && vmng->imgsensor);

  if (clip == NULL)
    {
      return -EINVAL;
    }

  type_inf = get_video_type_inf(vmng, clip->type);
  if (type_inf == NULL)
    {
      return -EINVAL;
    }

  if (type_inf->state != VIDEO_STATE_STREAMOFF)
    {
      return -EBUSY;
    }

  if (!validate_clip_setting(&clip->r, type_inf->fmt))
    {
      return -EINVAL;
    }

  ret = validate_frame_setting(vmng,
                               clip->type,
                               type_inf->nr_fmt,
                               type_inf->fmt,
                               &clip->r,
                               &type_inf->frame_interval);
  if (ret != OK)
    {
      return ret;
    }

  id = clip->type == V4L2_BUF_TYPE_VIDEO_CAPTURE ?
       IMGSENSOR_ID_CLIP_VIDEO : IMGSENSOR_ID_CLIP_STILL;

  p_u32[IMGSENSOR_CLIP_INDEX_LEFT]   = clip->r.left;
  p_u32[IMGSENSOR_CLIP_INDEX_TOP]    = clip->r.top;
  p_u32[IMGSENSOR_CLIP_INDEX_WIDTH]  = clip->r.width;
  p_u32[IMGSENSOR_CLIP_INDEX_HEIGHT] = clip->r.height;

  val.p_u32 = p_u32;
  ret = IMGSENSOR_SET_VALUE(vmng->imgsensor, id, sizeof(p_u32), val);
  if (ret != OK)
    {
      return ret;
    }

  memcpy(&type_inf->clip, &clip->r, sizeof(struct v4l2_rect));
  return ret;
}

static int video_g_selection(FAR struct video_mng_s    *vmng,
                             FAR struct v4l2_selection *clip)
{
  FAR video_type_inf_t *type_inf;

  ASSERT(vmng);

  if (clip == NULL)
    {
      return -EINVAL;
    }

  type_inf = get_video_type_inf(vmng, clip->type);
  if (type_inf == NULL)
    {
      return -EINVAL;
    }

  memcpy(&clip->r, &type_inf->clip, sizeof(struct v4l2_rect));
  return OK;
}

static int validate_frame_setting(FAR video_mng_t *vmng,
                                  enum v4l2_buf_type type,
                                  uint8_t nr_fmt,
                                  FAR video_format_t *vfmt,
                                  FAR struct v4l2_rect *clip,
                                  FAR struct v4l2_fract *interval)
{
  video_format_t c_fmt[MAX_VIDEO_FMT];
  imgdata_format_t df[MAX_VIDEO_FMT];
  imgsensor_format_t sf[MAX_VIDEO_FMT];
  imgdata_interval_t di;
  imgsensor_interval_t si;
  int ret;

  ASSERT(vfmt && interval && vmng->imgsensor && vmng->imgdata);

  /* Return OK only in case both image data driver and
   * image sensor driver support.
   */

  get_clipped_format(nr_fmt, vfmt, clip, c_fmt);

  convert_to_imgdatafmt(&c_fmt[VIDEO_FMT_MAIN], &df[IMGDATA_FMT_MAIN]);
  convert_to_imgdatafmt(&c_fmt[VIDEO_FMT_SUB], &df[IMGDATA_FMT_SUB]);
  convert_to_imgdatainterval(interval, &di);
  convert_to_imgsensorfmt(&vfmt[VIDEO_FMT_MAIN], &sf[IMGSENSOR_FMT_MAIN]);
  convert_to_imgsensorfmt(&vfmt[VIDEO_FMT_SUB], &sf[IMGSENSOR_FMT_SUB]);
  convert_to_imgsensorinterval(interval, &si);

  ret = IMGSENSOR_VALIDATE_FRAME_SETTING(vmng->imgsensor,
            type == V4L2_BUF_TYPE_VIDEO_CAPTURE ?
              IMGSENSOR_STREAM_TYPE_VIDEO : IMGSENSOR_STREAM_TYPE_STILL,
            nr_fmt, sf, &si);
  if (ret != OK)
    {
      return ret;
    }

  return IMGDATA_VALIDATE_FRAME_SETTING(vmng->imgdata, nr_fmt, df, &di);
}

static size_t get_bufsize(FAR video_format_t *vf)
{
  size_t ret = vf->width * vf->height;
  switch (vf->pixelformat)
    {
      case V4L2_PIX_FMT_NV12:
      case V4L2_PIX_FMT_YUV420:
        return ret * 3 / 2;
      case V4L2_PIX_FMT_YUYV:
      case V4L2_PIX_FMT_UYVY:
      case V4L2_PIX_FMT_RGB565:
      case V4L2_PIX_FMT_JPEG:
      default:
        return ret * 2;
    }
}

static size_t get_heapsize(FAR video_type_inf_t *type_inf)
{
  return type_inf->bufinf.container_size *
         get_bufsize(&type_inf->fmt[VIDEO_FMT_MAIN]);
}

static int video_try_fmt(FAR struct video_mng_s *priv,
                         FAR struct v4l2_format *v4l2)
{
  FAR video_type_inf_t *type_inf;
  video_format_t vf[MAX_VIDEO_FMT];
  uint8_t nr_fmt;

  ASSERT(priv && priv->imgsensor && priv->imgdata);

  if (v4l2 == NULL)
    {
      return -EINVAL;
    }

  type_inf = get_video_type_inf(priv, v4l2->type);
  if (type_inf == NULL)
    {
      return -EINVAL;
    }

  switch (v4l2->fmt.pix.pixelformat)
    {
      case V4L2_PIX_FMT_SUBIMG_UYVY:
      case V4L2_PIX_FMT_SUBIMG_RGB565:
        if (type_inf->fmt[VIDEO_FMT_MAIN].pixelformat !=
            V4L2_PIX_FMT_JPEG_WITH_SUBIMG)
          {
            return -EPERM;
          }

        /* Validate both main image and subimage. */

        nr_fmt = 2;
        memcpy(&vf[VIDEO_FMT_MAIN],
               &type_inf->fmt[VIDEO_FMT_MAIN],
               sizeof(video_format_t));
        vf[VIDEO_FMT_SUB].width       = v4l2->fmt.pix.width;
        vf[VIDEO_FMT_SUB].height      = v4l2->fmt.pix.height;
        vf[VIDEO_FMT_SUB].pixelformat =
            v4l2->fmt.pix.pixelformat == V4L2_PIX_FMT_SUBIMG_UYVY ?
              V4L2_PIX_FMT_UYVY : V4L2_PIX_FMT_RGB565;
        break;
      case V4L2_PIX_FMT_NV12:
      case V4L2_PIX_FMT_YUV420:
      case V4L2_PIX_FMT_YUYV:
      case V4L2_PIX_FMT_UYVY:
      case V4L2_PIX_FMT_RGB565:
      case V4L2_PIX_FMT_JPEG:
      case V4L2_PIX_FMT_JPEG_WITH_SUBIMG:
        nr_fmt = 1;
        vf[VIDEO_FMT_MAIN].width       = v4l2->fmt.pix.width;
        vf[VIDEO_FMT_MAIN].height      = v4l2->fmt.pix.height;
        vf[VIDEO_FMT_MAIN].pixelformat = v4l2->fmt.pix.pixelformat;
        break;

      default:
        return -EINVAL;
    }

  return validate_frame_setting(priv,
                                v4l2->type,
                                nr_fmt,
                                vf,
                                &type_inf->clip,
                                &type_inf->frame_interval);
}

static int video_g_fmt(FAR struct video_mng_s *priv,
                       FAR struct v4l2_format *fmt)
{
  FAR video_type_inf_t *type_inf;

  type_inf = get_video_type_inf(priv, fmt->type);
  if (type_inf == NULL)
    {
      return -EINVAL;
    }

  memset(&fmt->fmt, 0, sizeof(fmt->fmt));
  fmt->fmt.pix.width = type_inf->fmt[VIDEO_FMT_MAIN].width;
  fmt->fmt.pix.height = type_inf->fmt[VIDEO_FMT_MAIN].height;
  fmt->fmt.pix.pixelformat = type_inf->fmt[VIDEO_FMT_MAIN].pixelformat;

  return OK;
}

static int video_s_fmt(FAR struct video_mng_s *priv,
                       FAR struct v4l2_format *fmt)
{
  FAR video_type_inf_t *type_inf;
  int ret;

  ret = video_try_fmt(priv, fmt);
  if (ret != 0)
    {
      return ret;
    }

  type_inf = get_video_type_inf(priv, fmt->type);
  if (type_inf == NULL)
    {
      return -EINVAL;
    }

  if (type_inf->state != VIDEO_STATE_STREAMOFF)
    {
      return -EBUSY;
    }

  switch (fmt->fmt.pix.pixelformat)
    {
      case V4L2_PIX_FMT_SUBIMG_UYVY:
      case V4L2_PIX_FMT_SUBIMG_RGB565:
        if (type_inf->fmt[VIDEO_FMT_MAIN].pixelformat !=
            V4L2_PIX_FMT_JPEG_WITH_SUBIMG)
          {
            return -EPERM;
          }

        type_inf->fmt[VIDEO_FMT_SUB].width  = fmt->fmt.pix.width;
        type_inf->fmt[VIDEO_FMT_SUB].height = fmt->fmt.pix.height;
        type_inf->fmt[VIDEO_FMT_SUB].pixelformat =
            fmt->fmt.pix.pixelformat == V4L2_PIX_FMT_SUBIMG_UYVY ?
              V4L2_PIX_FMT_UYVY : V4L2_PIX_FMT_RGB565;
        type_inf->nr_fmt = 2;
        break;

      default:
        type_inf->fmt[VIDEO_FMT_MAIN].width  = fmt->fmt.pix.width;
        type_inf->fmt[VIDEO_FMT_MAIN].height = fmt->fmt.pix.height;
        type_inf->fmt[VIDEO_FMT_MAIN].pixelformat = fmt->fmt.pix.pixelformat;
        type_inf->nr_fmt = 1;
        break;
    }

  return OK;
}

static int video_s_parm(FAR struct video_mng_s *priv,
                        FAR struct v4l2_streamparm *parm)
{
  FAR video_type_inf_t *type_inf;
  int ret;

  ASSERT(priv->imgsensor && priv->imgdata);

  type_inf = get_video_type_inf(priv, parm->type);
  if (type_inf == NULL)
    {
      return -EINVAL;
    }

  if (type_inf->state != VIDEO_STATE_STREAMOFF)
    {
      return -EBUSY;
    }

  ret = validate_frame_setting(priv,
                               parm->type,
                               type_inf->nr_fmt,
                               type_inf->fmt,
                               &type_inf->clip,
                               &parm->parm.capture.timeperframe);
  if (ret != OK)
    {
      return ret;
    }

  memcpy(&type_inf->frame_interval,
         &parm->parm.capture.timeperframe,
         sizeof(struct v4l2_fract));

  return ret;
}

static int video_g_parm(FAR struct video_mng_s *vmng,
                        FAR struct v4l2_streamparm *parm)
{
  FAR video_type_inf_t *type_inf;
  int ret = -EINVAL;

  DEBUGASSERT(vmng && vmng->imgsensor);

  type_inf = get_video_type_inf(vmng, parm->type);
  if (type_inf == NULL)
    {
      return -EINVAL;
    }

  memset(&parm->parm, 0, sizeof(parm->parm));

  if (type_inf->state == VIDEO_STATE_CAPTURE)
    {
      /* If capture is started and lower driver has the get_frame_interval(),
       * query lower driver.
       */

      ret = IMGSENSOR_GET_FRAME_INTERVAL(vmng->imgsensor, parm->type,
              (imgsensor_interval_t *)&parm->parm.capture.timeperframe);
    }

  if (ret != OK)
    {
      /* In no capture state or error case, return stored value. */

      memcpy(&parm->parm.capture.timeperframe,
             &type_inf->frame_interval,
             sizeof(struct v4l2_fract));
    }

  parm->parm.capture.capability = V4L2_CAP_TIMEPERFRAME;
  return OK;
}

static int video_streamon(FAR struct video_mng_s *vmng,
                          FAR enum v4l2_buf_type *type)
{
  FAR video_type_inf_t *type_inf;
  enum video_state_e   next_video_state;
  int                  ret = OK;

  if (vmng == NULL || type == NULL)
    {
      return -EINVAL;
    }

  type_inf = get_video_type_inf(vmng, *type);
  if (type_inf == NULL)
    {
      return -EINVAL;
    }

  if (*type != V4L2_BUF_TYPE_VIDEO_CAPTURE)
    {
      /* No procedure for VIDIOC_STREAMON(STILL_CAPTURE) */

      return OK;
    }

  nxmutex_lock(&type_inf->lock_state);

  if (type_inf->state != VIDEO_STATE_STREAMOFF)
    {
      ret = -EPERM;
    }
  else
    {
      next_video_state =
        estimate_next_video_state(vmng, CAUSE_VIDEO_START);
      change_video_state(vmng, next_video_state);
    }

  nxmutex_unlock(&type_inf->lock_state);
  return ret;
}

static int video_streamoff(FAR struct video_mng_s *vmng,
                           FAR enum v4l2_buf_type *type)
{
  FAR video_type_inf_t *type_inf;
  enum video_state_e   next_video_state;
  irqstate_t           flags;
  int                  ret = OK;

  if (vmng == NULL || type == NULL)
    {
      return -EINVAL;
    }

  type_inf = get_video_type_inf(vmng, *type);
  if (type_inf == NULL)
    {
      return -EINVAL;
    }

  if (*type != V4L2_BUF_TYPE_VIDEO_CAPTURE)
    {
      /* No procedure for VIDIOC_STREAMOFF(STILL_CAPTURE) */

      return OK;
    }

  flags = enter_critical_section();

  if (type_inf->state == VIDEO_STATE_STREAMOFF)
    {
      ret = -EPERM;
    }
  else
    {
      next_video_state =
        estimate_next_video_state(vmng, CAUSE_VIDEO_STOP);
      change_video_state(vmng, next_video_state);
    }

  leave_critical_section(flags);

  return ret;
}

static int video_do_halfpush(FAR struct video_mng_s *priv, bool enable)
{
  struct v4l2_ext_controls ext_controls;
  struct v4l2_ext_control  control[2];

  /* Replace to VIDIOC_S_EXT_CTRLS format */

  control[0].id    = V4L2_CID_3A_LOCK;
  control[0].value = enable ?
                     V4L2_LOCK_EXPOSURE | V4L2_LOCK_WHITE_BALANCE : 0;
  control[1].id    = V4L2_CID_AUTO_FOCUS_START;
  control[1].value = enable ? true : false;

  ext_controls.ctrl_class = V4L2_CTRL_CLASS_CAMERA;
  ext_controls.count      = 2;
  ext_controls.controls   = control;

  /* Execute VIDIOC_S_EXT_CTRLS */

  return video_s_ext_ctrls(priv, &ext_controls);
}

static int video_takepict_start(FAR struct video_mng_s *vmng,
                                int32_t capture_num)
{
  enum video_state_e   next_video_state;
  FAR vbuf_container_t *container;
  irqstate_t           flags;
  int                  ret = OK;

  if (vmng == NULL)
    {
      return -EINVAL;
    }

  nxmutex_lock(&vmng->still_inf.lock_state);

  if (vmng->still_inf.state != VIDEO_STATE_STREAMOFF)
    {
      ret = -EPERM;
    }
  else
    {
      if (capture_num > 0)
        {
          vmng->still_inf.remaining_capnum = capture_num;
        }
      else
        {
          vmng->still_inf.remaining_capnum = VIDEO_REMAINING_CAPNUM_INFINITY;
        }

      /* Control video stream prior to still stream */

      flags = enter_critical_section();

      next_video_state = estimate_next_video_state(vmng,
                                                   CAUSE_STILL_START);
      change_video_state(vmng, next_video_state);

      leave_critical_section(flags);

      container =
        video_framebuff_get_vacant_container(&vmng->still_inf.bufinf);
      if (container != NULL)
        {
          /* Start still stream capture */

          start_capture(vmng,
                        V4L2_BUF_TYPE_STILL_CAPTURE,
                        vmng->still_inf.nr_fmt,
                        vmng->still_inf.fmt,
                        &vmng->still_inf.clip,
                        &vmng->still_inf.frame_interval,
                        container->buf.m.userptr,
                        container->buf.length);

          vmng->still_inf.state = VIDEO_STATE_CAPTURE;
        }
      else
        {
          vmng->still_inf.state = VIDEO_STATE_STREAMON;
        }
    }

  nxmutex_unlock(&vmng->still_inf.lock_state);
  return ret;
}

static int video_takepict_stop(FAR struct video_mng_s *vmng, bool halfpush)
{
  enum video_state_e next_video_state;
  irqstate_t flags;
  int ret = OK;

  if (vmng == NULL)
    {
      return -EINVAL;
    }

  nxmutex_lock(&vmng->still_inf.lock_state);

  if (vmng->still_inf.state == VIDEO_STATE_STREAMOFF &&
      vmng->still_inf.remaining_capnum == VIDEO_REMAINING_CAPNUM_INFINITY)
    {
      ret = -EPERM;
    }
  else
    {
      flags = enter_critical_section();
      if (vmng->still_inf.state == VIDEO_STATE_CAPTURE)
        {
          stop_capture(vmng, V4L2_BUF_TYPE_STILL_CAPTURE);
        }

      leave_critical_section(flags);

      vmng->still_inf.state = VIDEO_STATE_STREAMOFF;
      vmng->still_inf.remaining_capnum = VIDEO_REMAINING_CAPNUM_INFINITY;

      /* Control video stream */

      nxmutex_lock(&vmng->video_inf.lock_state);
      next_video_state = estimate_next_video_state(vmng,
                                                   CAUSE_STILL_STOP);
      change_video_state(vmng, next_video_state);
      nxmutex_unlock(&vmng->video_inf.lock_state);
    }

  nxmutex_unlock(&vmng->still_inf.lock_state);
  return ret;
}

static int video_queryctrl(FAR video_mng_t *vmng,
                           FAR struct v4l2_queryctrl *ctrl)
{
  struct v4l2_query_ext_ctrl ext_ctrl;
  int                        ret;

  if (ctrl == NULL)
    {
      return -EINVAL;
    }

  /* Replace to VIDIOC_QUERY_EXT_CTRL format */

  ext_ctrl.ctrl_class = ctrl->ctrl_class;
  ext_ctrl.id         = ctrl->id;

  ret = video_query_ext_ctrl(vmng, &ext_ctrl);
  if (ret != OK)
    {
      return ret;
    }

  if (ext_ctrl.type == V4L2_CTRL_TYPE_INTEGER64 ||
      ext_ctrl.type == V4L2_CTRL_TYPE_U8 ||
      ext_ctrl.type == V4L2_CTRL_TYPE_U16 ||
      ext_ctrl.type == V4L2_CTRL_TYPE_U32)
    {
      /* Unsupported type in VIDIOC_QUERYCTRL */

      return -EINVAL;
    }

  /* Replace gotten value to VIDIOC_QUERYCTRL */

  ctrl->type          = ext_ctrl.type;
  ctrl->minimum       = ext_ctrl.minimum;
  ctrl->maximum       = ext_ctrl.maximum;
  ctrl->step          = ext_ctrl.step;
  ctrl->default_value = ext_ctrl.default_value;
  ctrl->flags         = ext_ctrl.flags;
  strlcpy(ctrl->name, ext_ctrl.name, sizeof(ctrl->name));

  return OK;
}

static void set_parameter_name(uint32_t id, char *name)
{
  int size =
    sizeof(g_video_parameter_name) / sizeof(video_parameter_name_t);
  int cnt;

  for (cnt = 0; cnt < size; cnt++)
    {
      if (g_video_parameter_name[cnt].id == id)
        {
          break;
        }
    }

  ASSERT(cnt < size);

  /* copy size = 32 is due to V4L2 specification. */

  strlcpy(name, g_video_parameter_name[cnt].name, 32);
}

static int video_query_ext_ctrl(FAR struct video_mng_s *vmng,
                                FAR struct v4l2_query_ext_ctrl *attr)
{
  imgsensor_supported_value_t value;
  imgsensor_capability_range_t *range = &value.u.range;
  imgsensor_capability_discrete_t *disc = &value.u.discrete;
  imgsensor_capability_elems_t *elem = &value.u.elems;
  int ret;

  ASSERT(vmng->imgsensor);

  if (attr == NULL)
    {
      return -EINVAL;
    }

  attr->flags      = 0;
  attr->elem_size  = 0;
  attr->nr_of_dims = 0;
  memset(attr->dims, 0, sizeof(attr->dims));

  if (attr->ctrl_class == V4L2_CTRL_CLASS_CAMERA &&
      attr->id == V4L2_CID_SCENE_MODE)
    {
      /* Scene mode is processed in only video driver. */

      attr->type          = V4L2_CTRL_TYPE_INTEGER_MENU;
      attr->minimum       = 0;
      attr->maximum       = vmng->video_scence_num - 1;
      attr->step          = 1;
      attr->default_value = 0;
      attr->flags         = 0;
      strlcpy(attr->name, "Scene Mode", 32);
    }
  else
    {
      ret = IMGSENSOR_GET_SUPPORTED_VALUE(vmng->imgsensor,
              VIDEO_ID(attr->ctrl_class, attr->id),
              &value);
      if (ret < 0)
        {
          return ret;
        }

      attr->type = value.type;
      attr->flags = 0;

      switch (value.type)
        {
          case IMGSENSOR_CTRL_TYPE_INTEGER_MENU:
            attr->minimum       = 0;
            attr->maximum       = disc->nr_values - 1;
            attr->step          = 1;
            attr->default_value = disc->default_value;
            break;

          case IMGSENSOR_CTRL_TYPE_U8:
          case IMGSENSOR_CTRL_TYPE_U16:
          case IMGSENSOR_CTRL_TYPE_U32:
            attr->minimum = elem->minimum;
            attr->maximum = elem->maximum;
            attr->step    = elem->step;
            attr->elems   = elem->nr_elems;
            break;

          default:
            attr->minimum       = range->minimum;
            attr->maximum       = range->maximum;
            attr->step          = range->step;
            attr->default_value = range->default_value;
            break;
        }

      set_parameter_name(VIDEO_ID(attr->ctrl_class, attr->id),
                         attr->name);
    }

  return OK;
}

static int video_querymenu(FAR video_mng_t *vmng,
                           FAR struct v4l2_querymenu *menu)
{
  imgsensor_supported_value_t value;
  int ret;

  ASSERT(vmng->imgsensor);

  if (menu == NULL)
    {
      return -EINVAL;
    }

  if (menu->ctrl_class == V4L2_CTRL_CLASS_CAMERA &&
      menu->id == V4L2_CID_SCENE_MODE)
    {
      /* Scene mode is processed in only video driver. */

      if (menu->index > vmng->video_scence_num - 1)
        {
          return -EINVAL;
        }

      menu->value = vmng->video_scene_param[menu->index]->mode;
    }
  else
    {
      ret = IMGSENSOR_GET_SUPPORTED_VALUE(vmng->imgsensor,
              VIDEO_ID(menu->ctrl_class, menu->id),
              &value);
      if (ret < 0)
        {
          return ret;
        }

      if (value.type != IMGSENSOR_CTRL_TYPE_INTEGER_MENU)
        {
          /* VIDIOC_QUERYMENU is used only for
           * IMGSENSOR_CTRL_TYPE_INTEGER_MENU.
           */

          return -EINVAL;
        }

      if (menu->index >= value.u.discrete.nr_values)
        {
          return -EINVAL;
        }

      menu->value = value.u.discrete.values[menu->index];
    }

  return OK;
}

static int video_g_ctrl(FAR struct video_mng_s *priv,
                        FAR struct v4l2_control *ctrl)
{
  struct v4l2_ext_controls ext_controls;
  struct v4l2_ext_control  control;
  int                      ret;

  memset(&ext_controls, 0, sizeof(struct v4l2_ext_controls));
  memset(&control, 0, sizeof(struct v4l2_ext_control));

  if (ctrl == NULL)
    {
      return -EINVAL;
    }

  /* Replace to VIDIOC_G_EXT_CTRLS format */

  control.id = ctrl->id;

  ext_controls.ctrl_class = V4L2_CTRL_CLASS_USER;
  ext_controls.count      = 1;
  ext_controls.controls   = &control;

  /* Execute VIDIOC_G_EXT_CTRLS */

  ret = video_g_ext_ctrls(priv, &ext_controls);
  if (ret == OK)
    {
      /* Replace gotten value to VIDIOC_G_CTRL parameter */

      ctrl->value = control.value;
    }

  return ret;
}

static int video_s_ctrl(FAR struct video_mng_s *priv,
                        FAR struct v4l2_control *ctrl)
{
  struct v4l2_ext_controls ext_controls;
  struct v4l2_ext_control  control;

  if (ctrl == NULL)
    {
      return -EINVAL;
    }

  /* Replace to VIDIOC_S_EXT_CTRLS format */

  control.id    = ctrl->id;
  control.value = ctrl->value;

  ext_controls.ctrl_class = V4L2_CTRL_CLASS_USER;
  ext_controls.count      = 1;
  ext_controls.controls   = &control;

  /* Execute VIDIOC_S_EXT_CTRLS */

  return video_s_ext_ctrls(priv, &ext_controls);
}

static int video_g_ext_ctrls(FAR struct video_mng_s *priv,
                             FAR struct v4l2_ext_controls *ctrls)
{
  FAR struct v4l2_ext_control *control;
  int ret = OK;
  int cnt;

  ASSERT(priv->imgsensor);

  if (priv == NULL || ctrls == NULL)
    {
      return -EINVAL;
    }

  for (cnt = 0, control = ctrls->controls;
       cnt < ctrls->count;
       cnt++, control++)
    {
      ret = IMGSENSOR_GET_VALUE(priv->imgsensor,
              VIDEO_ID(ctrls->ctrl_class, control->id),
              control->size,
              (imgsensor_value_t *)&control->value64);
      if (ret < 0)
        {
          /* Set cnt in that error occurred */

          ctrls->error_idx = cnt;
          return ret;
        }
    }

  return ret;
}

static int set_intvalue(FAR video_mng_t *vmng,
                        uint32_t id, int32_t value32)
{
  imgsensor_value_t value;

  ASSERT(vmng->imgsensor);

  value.value32 = value32;
  return IMGSENSOR_SET_VALUE(vmng->imgsensor, id, sizeof(int32_t), value);
}

static int set_pvalue(FAR video_mng_t *vmng,
                      uint32_t id, int size, void *pval)
{
  imgsensor_value_t value;

  ASSERT(vmng->imgsensor);

  value.p_u8 = (FAR uint8_t *)pval;
  return IMGSENSOR_SET_VALUE(vmng->imgsensor, id, size, value);
}

static video_scene_params_t *search_scene_param(FAR video_mng_t *vmng,
                                                enum v4l2_scene_mode mode)
{
  int i;

  for (i = 0; i < vmng->video_scence_num; i++)
    {
      if (vmng->video_scene_param[i]->mode == mode)
        {
          return vmng->video_scene_param[i];
        }
    }

  return NULL;
}

static int reflect_scene_parameter(FAR video_mng_t *vmng,
                                   enum v4l2_scene_mode mode)
{
  video_scene_params_t *sp;

  sp = search_scene_param(vmng, mode);
  if (sp == NULL)
    {
      /* Unsupported scene mode */

      return -EINVAL;
    }

  set_intvalue(vmng, IMGSENSOR_ID_BRIGHTNESS, sp->brightness);
  set_intvalue(vmng, IMGSENSOR_ID_CONTRAST, sp->contrast);
  set_intvalue(vmng, IMGSENSOR_ID_SATURATION, sp->saturation);
  set_intvalue(vmng, IMGSENSOR_ID_HUE , sp->hue);
  set_intvalue(vmng, IMGSENSOR_ID_AUTO_WHITE_BALANCE, sp->awb);
  set_intvalue(vmng, IMGSENSOR_ID_RED_BALANCE , sp->red);
  set_intvalue(vmng, IMGSENSOR_ID_BLUE_BALANCE, sp->blue);
  set_intvalue(vmng, IMGSENSOR_ID_GAMMA, sp->gamma);
  set_pvalue(vmng, IMGSENSOR_ID_GAMMA_CURVE,
             sp->gamma_curve_sz, sp->gamma_curve);
  set_intvalue(vmng, IMGSENSOR_ID_EXPOSURE, sp->ev);
  set_intvalue(vmng, IMGSENSOR_ID_HFLIP_VIDEO, sp->hflip_video);
  set_intvalue(vmng, IMGSENSOR_ID_VFLIP_VIDEO, sp->vflip_video);
  set_intvalue(vmng, IMGSENSOR_ID_HFLIP_STILL, sp->hflip_still);
  set_intvalue(vmng, IMGSENSOR_ID_VFLIP_STILL, sp->vflip_still);
  set_intvalue(vmng, IMGSENSOR_ID_SHARPNESS, sp->sharpness);
  set_intvalue(vmng, IMGSENSOR_ID_COLORFX, sp->colorfx);
  set_intvalue(vmng, IMGSENSOR_ID_AUTOBRIGHTNESS, sp->auto_brightness);
  set_intvalue(vmng, IMGSENSOR_ID_ROTATE, sp->rotate);
  set_intvalue(vmng, IMGSENSOR_ID_EXPOSURE_AUTO, sp->ae);
  if (sp->ae == V4L2_EXPOSURE_MANUAL ||
      sp->ae == V4L2_EXPOSURE_SHUTTER_PRIORITY)
    {
      set_intvalue(vmng, IMGSENSOR_ID_EXPOSURE_ABSOLUTE, sp->exposure_time);
    }

  set_intvalue(vmng, IMGSENSOR_ID_FOCUS_ABSOLUTE, sp->focus);
  set_intvalue(vmng, IMGSENSOR_ID_FOCUS_AUTO, sp->af);
  set_intvalue(vmng, IMGSENSOR_ID_ZOOM_ABSOLUTE, sp->zoom);
  if (sp->ae == V4L2_EXPOSURE_MANUAL ||
      sp->ae == V4L2_EXPOSURE_APERTURE_PRIORITY)
    {
      set_intvalue(vmng, IMGSENSOR_ID_IRIS_ABSOLUTE, sp->iris);
    }

  set_intvalue(vmng, IMGSENSOR_ID_AUTO_N_PRESET_WB, sp->wb);
  set_intvalue(vmng, IMGSENSOR_ID_WIDE_DYNAMIC_RANGE, sp->wdr);
  set_intvalue(vmng, IMGSENSOR_ID_IMG_STABILIZATION, sp->stabilization);
  set_intvalue(vmng, IMGSENSOR_ID_ISO_SENSITIVITY_AUTO, sp->iso_auto);
  if (sp->iso_auto == V4L2_ISO_SENSITIVITY_MANUAL)
    {
      set_intvalue(vmng, IMGSENSOR_ID_ISO_SENSITIVITY, sp->iso);
    }

  set_intvalue(vmng, IMGSENSOR_ID_EXPOSURE_METERING, sp->meter);
  set_intvalue(vmng, IMGSENSOR_ID_3A_LOCK, sp->threea_lock);
  set_intvalue(vmng, IMGSENSOR_ID_FLASH_LED_MODE, sp->led);
  set_intvalue(vmng, IMGSENSOR_ID_JPEG_QUALITY, sp->jpeg_quality);

  vmng->video_scene_mode = mode;
  return OK;
}

static int video_s_ext_ctrls(FAR struct video_mng_s *priv,
                             FAR struct v4l2_ext_controls *ctrls)
{
  FAR struct v4l2_ext_control *control;
  int ret = OK;
  int cnt;

  ASSERT(priv->imgsensor);

  if (priv == NULL || ctrls == NULL)
    {
      return -EINVAL;
    }

  for (cnt = 0, control = ctrls->controls;
       cnt < ctrls->count;
       cnt++, control++)
    {
      if (ctrls->ctrl_class == V4L2_CTRL_CLASS_CAMERA &&
          control->id == V4L2_CID_SCENE_MODE)
        {
          ret = reflect_scene_parameter(priv, control->value);
        }
      else
        {
          ret = IMGSENSOR_SET_VALUE(priv->imgsensor,
                  VIDEO_ID(ctrls->ctrl_class, control->id),
                  control->size,
                  (imgsensor_value_t)control->value64);
          if (ret == 0)
            {
              if (priv->video_scene_mode == V4L2_SCENE_MODE_NONE)
                {
                  save_scene_param(priv, V4L2_SCENE_MODE_NONE,
                    VIDEO_ID(ctrls->ctrl_class, control->id),
                    control);
                }
            }
        }

      if (ret < 0)
        {
          /* Set cnt in that error occurred */

          ctrls->error_idx = cnt;
          return ret;
        }
    }

  return ret;
}

static int video_query_ext_ctrl_scene(FAR video_mng_t *vmng,
             FAR struct v4s_query_ext_ctrl_scene *attr)
{
  if (attr == NULL)
    {
      return -EINVAL;
    }

  return video_query_ext_ctrl(vmng, &attr->control);
}

static int video_querymenu_scene(FAR video_mng_t *vmng,
                                 FAR struct v4s_querymenu_scene *menu)
{
  if (menu == NULL)
    {
      return -EINVAL;
    }

  return video_querymenu(vmng, &menu->menu);
}

static int read_scene_param(FAR struct video_mng_s *vmng,
                            enum v4l2_scene_mode mode,
                            uint32_t id,
                            struct v4l2_ext_control *control)
{
  imgsensor_supported_value_t value;
  video_scene_params_t *sp;
  int ret = OK;

  ASSERT(vmng->imgsensor);

  if (control == NULL)
    {
      return -EINVAL;
    }

  sp = search_scene_param(vmng, mode);
  if (sp == NULL)
    {
      /* Unsupported scene mode */

      return -EINVAL;
    }

  ret = IMGSENSOR_GET_SUPPORTED_VALUE(vmng->imgsensor, id, &value);
  if (ret < 0)
    {
      /* Unsupported camera parameter */

      return ret;
    }

  switch (id)
    {
      case IMGSENSOR_ID_BRIGHTNESS:
        control->value = sp->brightness;
        break;

      case IMGSENSOR_ID_CONTRAST:
        control->value = sp->contrast;
        break;

      case IMGSENSOR_ID_SATURATION:
        control->value = sp->saturation;
        break;

      case IMGSENSOR_ID_HUE:
        control->value = sp->hue;
        break;

      case IMGSENSOR_ID_AUTO_WHITE_BALANCE:
        control->value = sp->awb;
        break;

      case IMGSENSOR_ID_RED_BALANCE:
        control->value = sp->red;
        break;

      case IMGSENSOR_ID_BLUE_BALANCE:
        control->value = sp->blue;
        break;

      case IMGSENSOR_ID_GAMMA:
        control->value = sp->gamma;
        break;

      case IMGSENSOR_ID_GAMMA_CURVE:
        memcpy(control->p_u8,
               sp->gamma_curve,
               sp->gamma_curve_sz);
        break;

      case IMGSENSOR_ID_EXPOSURE:
        control->value = sp->ev;
        break;

      case IMGSENSOR_ID_HFLIP_VIDEO:
        control->value = sp->hflip_video;
        break;

      case IMGSENSOR_ID_VFLIP_VIDEO:
        control->value = sp->vflip_video;
        break;

      case IMGSENSOR_ID_HFLIP_STILL:
        control->value = sp->hflip_still;
        break;

      case IMGSENSOR_ID_VFLIP_STILL:
        control->value = sp->vflip_still;
        break;

      case IMGSENSOR_ID_SHARPNESS:
        control->value = sp->sharpness;
        break;

      case IMGSENSOR_ID_COLOR_KILLER:
        control->value = sp->colorfx == V4L2_COLORFX_BW;
        break;

      case IMGSENSOR_ID_COLORFX:
        control->value = sp->colorfx;
        break;

      case IMGSENSOR_ID_AUTOBRIGHTNESS:
        control->value = sp->auto_brightness;
        break;

      case IMGSENSOR_ID_ROTATE:
        control->value = sp->rotate;
        break;

      case IMGSENSOR_ID_EXPOSURE_AUTO:
        control->value = sp->ae;
        break;

      case IMGSENSOR_ID_EXPOSURE_ABSOLUTE:
        control->value = sp->exposure_time;
        break;

      case IMGSENSOR_ID_FOCUS_ABSOLUTE:
        control->value = sp->focus;
        break;

      case IMGSENSOR_ID_FOCUS_AUTO:
        control->value = sp->af;
        break;

      case IMGSENSOR_ID_ZOOM_ABSOLUTE:
        control->value = sp->zoom;
        break;

      case IMGSENSOR_ID_IRIS_ABSOLUTE:
        control->value = sp->iris;
        break;

      case IMGSENSOR_ID_AUTO_N_PRESET_WB:
        control->value = sp->wb;
        break;

      case IMGSENSOR_ID_WIDE_DYNAMIC_RANGE:
        control->value = sp->wdr;
        break;

      case IMGSENSOR_ID_IMG_STABILIZATION:
        control->value = sp->stabilization;
        break;

      case IMGSENSOR_ID_ISO_SENSITIVITY:
        control->value = sp->iso;
        break;

      case IMGSENSOR_ID_ISO_SENSITIVITY_AUTO:
        control->value = sp->iso_auto;
        break;

      case IMGSENSOR_ID_EXPOSURE_METERING:
        control->value = sp->meter;
        break;

      case IMGSENSOR_ID_SPOT_POSITION:
        control->value = sp->spot_pos;
        break;

      case IMGSENSOR_ID_3A_LOCK:
        control->value = sp->threea_lock;
        break;

      case IMGSENSOR_ID_FLASH_LED_MODE:
        control->value = sp->led;
        break;

      case IMGSENSOR_ID_JPEG_QUALITY:
        control->value = sp->jpeg_quality;
        break;

      default:
        ret = -EINVAL;
        break;
    }

  return ret;
}

static int video_g_ext_ctrls_scene(FAR video_mng_t *vmng,
                                   FAR struct v4s_ext_controls_scene *ctrls)
{
  FAR struct v4l2_ext_control *control;
  int ret = OK;
  int cnt;

  if (ctrls == NULL)
    {
      return -EINVAL;
    }

  for (cnt = 0, control = ctrls->control.controls;
       cnt < ctrls->control.count;
       cnt++, control++)
    {
      ret = read_scene_param(vmng, ctrls->mode,
               VIDEO_ID(ctrls->control.ctrl_class, control->id),
               control);
      if (ret != OK)
        {
          ctrls->control.error_idx = cnt;
          return ret;
        }
    }

  return ret;
}

static int check_range(int64_t value,
                       int64_t min,
                       int64_t max,
                       uint64_t step)
{
  if (value < min || value > max ||
      (value - min) % step != 0)
    {
      return -EINVAL;
    }

  return OK;
}

static int save_scene_param(FAR video_mng_t *vmng,
                            enum v4l2_scene_mode mode,
                            uint32_t id,
                            struct v4l2_ext_control *control)
{
  imgsensor_supported_value_t value;
  imgsensor_capability_range_t *range = &value.u.range;
  imgsensor_capability_discrete_t *disc = &value.u.discrete;
  imgsensor_capability_elems_t *elem = &value.u.elems;
  video_scene_params_t *sp;
  int ret;
  int i;

  ASSERT(vmng->imgsensor);

  sp = search_scene_param(vmng, mode);
  if (sp == NULL)
    {
      /* Unsupported scene mode */

      return -EINVAL;
    }

  ret = IMGSENSOR_GET_SUPPORTED_VALUE(vmng->imgsensor, id, &value);
  if (ret < 0)
    {
      /* Unsupported camera parameter */

      return ret;
    }

  switch (value.type)
    {
      case IMGSENSOR_CTRL_TYPE_INTEGER_MENU:
        for (i = 0; i < disc->nr_values; i++)
          {
            if (control->value == disc->values[i])
              {
                break;
              }
          }

        if (i >= disc->nr_values)
          {
            return -EINVAL;
          }

        break;

      case IMGSENSOR_CTRL_TYPE_U8:
        if (control->size < elem->nr_elems * sizeof(uint8_t))
          {
            return -EINVAL;
          }

        for (i = 0; i < elem->nr_elems; i++)
          {
            ret = check_range(control->p_u8[i],
                              elem->minimum,
                              elem->maximum,
                              elem->step);
            if (ret != OK)
              {
                return ret;
              }
          }

        break;

      case IMGSENSOR_CTRL_TYPE_U16:
        if (control->size < elem->nr_elems * sizeof(uint16_t))
          {
            return -EINVAL;
          }

        for (i = 0; i < elem->nr_elems; i++)
          {
            ret = check_range(control->p_u16[i],
                              elem->minimum,
                              elem->maximum,
                              elem->step);
            if (ret != OK)
              {
                return ret;
              }
          }

        break;

      case IMGSENSOR_CTRL_TYPE_U32:
        if (control->size < elem->nr_elems * sizeof(uint32_t))
          {
            return -EINVAL;
          }

        for (i = 0; i < elem->nr_elems; i++)
          {
            ret = check_range(control->p_u32[i],
                              elem->minimum,
                              elem->maximum,
                              elem->step);
            if (ret != OK)
              {
                return ret;
              }
          }

        break;

      default:
        ret = check_range(control->value,
                          range->minimum,
                          range->maximum,
                          range->step);
        if (ret != OK)
          {
            return ret;
          }

        break;
    }

  switch (id)
    {
      case IMGSENSOR_ID_BRIGHTNESS:
        sp->brightness = control->value;
        break;

      case IMGSENSOR_ID_CONTRAST:
        sp->contrast = control->value;
        break;

      case IMGSENSOR_ID_SATURATION:
        sp->saturation = control->value;
        break;

      case IMGSENSOR_ID_HUE:
        sp->hue = control->value;
        break;

      case IMGSENSOR_ID_AUTO_WHITE_BALANCE:
        sp->awb = control->value;
        break;

      case IMGSENSOR_ID_RED_BALANCE:
        sp->red = control->value;
        break;

      case IMGSENSOR_ID_BLUE_BALANCE:
        sp->blue = control->value;
        break;

      case IMGSENSOR_ID_GAMMA:
        sp->gamma = control->value;
        break;

      case IMGSENSOR_ID_GAMMA_CURVE:
        memcpy(sp->gamma_curve,
               control->p_u8,
               sp->gamma_curve_sz);
        break;

      case IMGSENSOR_ID_EXPOSURE:
        sp->ev = control->value;
        break;

      case IMGSENSOR_ID_HFLIP_VIDEO:
        sp->hflip_video = control->value;
        break;

      case IMGSENSOR_ID_VFLIP_VIDEO:
        sp->vflip_video = control->value;
        break;

      case IMGSENSOR_ID_HFLIP_STILL:
        sp->hflip_still = control->value;
        break;

      case IMGSENSOR_ID_VFLIP_STILL:
        sp->vflip_still = control->value;
        break;

      case IMGSENSOR_ID_SHARPNESS:
        sp->sharpness = control->value;
        break;

      case IMGSENSOR_ID_COLOR_KILLER:
        sp->colorfx = control->value ? V4L2_COLORFX_BW : V4L2_COLORFX_NONE;
        break;

      case IMGSENSOR_ID_COLORFX:
        sp->colorfx = control->value;
        break;

      case IMGSENSOR_ID_AUTOBRIGHTNESS:
        sp->auto_brightness = control->value;
        break;

      case IMGSENSOR_ID_ROTATE:
        sp->rotate = control->value;
        break;

      case IMGSENSOR_ID_EXPOSURE_AUTO:
        sp->ae = control->value;
        break;

      case IMGSENSOR_ID_EXPOSURE_ABSOLUTE:
        sp->exposure_time = control->value;
        break;

      case IMGSENSOR_ID_FOCUS_ABSOLUTE:
        sp->focus = control->value;
        break;

      case IMGSENSOR_ID_FOCUS_AUTO:
        sp->af = control->value;
        break;

      case IMGSENSOR_ID_ZOOM_ABSOLUTE:
        sp->zoom = control->value;
        break;

      case IMGSENSOR_ID_IRIS_ABSOLUTE:
        sp->iris = control->value;
        break;

      case IMGSENSOR_ID_AUTO_N_PRESET_WB:
        sp->wb = control->value;
        break;

      case IMGSENSOR_ID_WIDE_DYNAMIC_RANGE:
        sp->wdr = control->value;
        break;

      case IMGSENSOR_ID_IMG_STABILIZATION:
        sp->stabilization = control->value;
        break;

      case IMGSENSOR_ID_ISO_SENSITIVITY:
        sp->iso = control->value;
        break;

      case IMGSENSOR_ID_ISO_SENSITIVITY_AUTO:
        sp->iso_auto = control->value;
        break;

      case IMGSENSOR_ID_EXPOSURE_METERING:
        sp->meter = control->value;
        break;

      case IMGSENSOR_ID_SPOT_POSITION:
        sp->spot_pos = control->value;
        break;

      case IMGSENSOR_ID_3A_LOCK:
        sp->threea_lock = control->value;
        break;

      case IMGSENSOR_ID_FLASH_LED_MODE:
        sp->led = control->value;
        break;

      case IMGSENSOR_ID_JPEG_QUALITY:
        sp->jpeg_quality = control->value;
        break;

      default:
        return -EINVAL;
    }

  return OK;
}

static int video_s_ext_ctrls_scene(FAR struct video_mng_s *vmng,
                                   FAR struct v4s_ext_controls_scene *ctrls)
{
  FAR struct v4l2_ext_control *control;
  int ret = OK;
  int cnt;

  if (ctrls == NULL)
    {
      return -EINVAL;
    }

  for (cnt = 0, control = ctrls->control.controls;
       cnt < ctrls->control.count;
       cnt++, control++)
    {
      ret = save_scene_param(vmng, ctrls->mode,
               VIDEO_ID(ctrls->control.ctrl_class, control->id),
               control);
      if (ret != OK)
        {
          ctrls->control.error_idx = cnt;
          return ret;
        }
    }

  return ret;
}

/****************************************************************************
 * Name: video_ioctl
 *
 * Description:
 *   Standard character driver ioctl method.
 *
 ****************************************************************************/

static int video_ioctl(FAR struct file *filep, int cmd, unsigned long arg)
{
  FAR struct inode *inode = filep->f_inode;
  FAR video_mng_t  *priv  = inode->i_private;
  int ret = OK;

  switch (cmd)
    {
      case VIDIOC_QUERYCAP:
        ret = video_querycap(priv, (FAR struct v4l2_capability *)arg);
        break;

      case VIDIOC_G_INPUT:
        ret = video_g_input((FAR int *)arg);
        break;

      case VIDIOC_ENUMINPUT:
        ret = video_enum_input(priv, (FAR struct v4l2_input *)arg);
        break;

      case VIDIOC_REQBUFS:
        ret = video_reqbufs(priv, (FAR struct v4l2_requestbuffers *)arg);
        break;

      case VIDIOC_QUERYBUF:
        ret = video_querybuf(priv, (FAR struct v4l2_buffer *)arg);

        break;

      case VIDIOC_QBUF:
        ret = video_qbuf(priv, (FAR struct v4l2_buffer *)arg);
        break;

      case VIDIOC_DQBUF:
        ret = video_dqbuf(priv, (FAR struct v4l2_buffer *)arg,
                          filep->f_oflags);
        break;

      case VIDIOC_CANCEL_DQBUF:
        ret = video_cancel_dqbuf(priv, (FAR enum v4l2_buf_type)arg);
        break;

      case VIDIOC_STREAMON:
        ret = video_streamon(priv, (FAR enum v4l2_buf_type *)arg);
        break;

      case VIDIOC_STREAMOFF:
        ret = video_streamoff(priv, (FAR enum v4l2_buf_type *)arg);
        break;

      case VIDIOC_DO_HALFPUSH:
        ret = video_do_halfpush(priv, arg);
        break;

      case VIDIOC_TAKEPICT_START:
        ret = video_takepict_start(priv, (int32_t)arg);
        break;

      case VIDIOC_TAKEPICT_STOP:
        ret = video_takepict_stop(priv, arg);
        break;

      case VIDIOC_S_SELECTION:
        ret = video_s_selection(priv, (FAR struct v4l2_selection *)arg);
        break;

      case VIDIOC_G_SELECTION:
        ret = video_g_selection(priv, (FAR struct v4l2_selection *)arg);
        break;

      case VIDIOC_TRY_FMT:
        ret = video_try_fmt(priv, (FAR struct v4l2_format *)arg);
        break;

      case VIDIOC_G_FMT:
        ret = video_g_fmt(priv, (FAR struct v4l2_format *)arg);
        break;

      case VIDIOC_S_FMT:
        ret = video_s_fmt(priv, (FAR struct v4l2_format *)arg);
        break;

      case VIDIOC_S_PARM:
        ret = video_s_parm(priv, (FAR struct v4l2_streamparm *)arg);
        break;

      case VIDIOC_G_PARM:
        ret = video_g_parm(priv, (FAR struct v4l2_streamparm *)arg);
        break;

      case VIDIOC_QUERYCTRL:
        ret = video_queryctrl(priv, (FAR struct v4l2_queryctrl *)arg);
        break;

      case VIDIOC_QUERY_EXT_CTRL:
        ret = video_query_ext_ctrl(priv,
                                   (FAR struct v4l2_query_ext_ctrl *)arg);
        break;

      case VIDIOC_QUERYMENU:
        ret = video_querymenu(priv, (FAR struct v4l2_querymenu *)arg);
        break;

      case VIDIOC_G_CTRL:
        ret = video_g_ctrl(priv, (FAR struct v4l2_control *)arg);
        break;

      case VIDIOC_S_CTRL:
        ret = video_s_ctrl(priv, (FAR struct v4l2_control *)arg);
        break;

      case VIDIOC_G_EXT_CTRLS:
        ret = video_g_ext_ctrls(priv, (FAR struct v4l2_ext_controls *)arg);
        break;

      case VIDIOC_S_EXT_CTRLS:
        ret = video_s_ext_ctrls(priv, (FAR struct v4l2_ext_controls *)arg);
        break;

      case VIDIOC_G_STD:
        ret = -ENODATA;
        break;

      case VIDIOC_S_STD:
        ret = -EINVAL;
        break;

      case V4SIOC_QUERY_EXT_CTRL_SCENE:
        ret = video_query_ext_ctrl_scene(priv,
                (FAR struct v4s_query_ext_ctrl_scene *)arg);
        break;

      case V4SIOC_QUERYMENU_SCENE:
        ret = video_querymenu_scene(priv,
                (FAR struct v4s_querymenu_scene *)arg);
        break;

      case V4SIOC_G_EXT_CTRLS_SCENE:
        ret = video_g_ext_ctrls_scene(priv,
                (FAR struct v4s_ext_controls_scene *)arg);
        break;

      case V4SIOC_S_EXT_CTRLS_SCENE:
        ret = video_s_ext_ctrls_scene(priv,
                (FAR struct v4s_ext_controls_scene *)arg);
        break;

      default:
        verr("Unrecognized cmd: %d\n", cmd);
        ret = - ENOTTY;
        break;
    }

  return ret;
}

static int video_mmap(FAR struct file *filep, FAR struct mm_map_entry_s *map)
{
  FAR struct inode     *inode    = filep->f_inode;
  FAR video_mng_t      *priv     = inode->i_private;
  FAR video_type_inf_t *type_inf = &priv->video_inf;
  size_t                heapsize = get_heapsize(type_inf);
  int                   ret      = -EINVAL;

  if (map->offset >= 0 && map->offset < heapsize &&
      map->length && map->offset + map->length <= heapsize)
    {
      map->vaddr = type_inf->bufheap + map->offset;
      ret = OK;
    }

  return ret;
}

static int video_poll(FAR struct file *filep, struct pollfd *fds, bool setup)
{
  FAR struct inode     *inode = filep->f_inode;
  FAR video_mng_t      *priv  = inode->i_private;
  FAR video_type_inf_t *type_inf;
  enum v4l2_buf_type   buf_type;
  irqstate_t           flags;

  buf_type = priv->still_inf.state == VIDEO_STATE_CAPTURE ?
                V4L2_BUF_TYPE_STILL_CAPTURE : V4L2_BUF_TYPE_VIDEO_CAPTURE;

  type_inf = get_video_type_inf(priv, buf_type);
  if (type_inf == NULL)
    {
      return -EINVAL;
    }

  flags = enter_critical_section();

  if (setup)
    {
      if (type_inf->fds == NULL)
        {
          type_inf->fds = fds;
          fds->priv     = &type_inf->fds;
          if (!video_framebuff_is_empty(&type_inf->bufinf))
            {
              poll_notify(&type_inf->fds, 1, POLLIN);
            }
        }
      else
        {
          leave_critical_section(flags);
          return -EBUSY;
        }
    }
  else if (fds->priv)
    {
      type_inf->fds = NULL;
      fds->priv     = NULL;
    }

  leave_critical_section(flags);

  return OK;
}

/* Callback function which device driver call when capture has done. */

static int video_complete_capture(uint8_t err_code, uint32_t datasize,
                                  FAR const struct timeval *ts,
                                  FAR void *arg)
{
  FAR video_mng_t      *vmng = (FAR video_mng_t *)arg;
  FAR video_type_inf_t *type_inf;
  FAR vbuf_container_t *container = NULL;
  enum v4l2_buf_type buf_type;
  irqstate_t           flags;

  flags = enter_critical_section();

  buf_type = vmng->still_inf.state == VIDEO_STATE_CAPTURE ?
                V4L2_BUF_TYPE_STILL_CAPTURE : V4L2_BUF_TYPE_VIDEO_CAPTURE;

  type_inf = get_video_type_inf(vmng, buf_type);
  if (type_inf == NULL)
    {
      leave_critical_section(flags);
      return -EINVAL;
    }

  poll_notify(&type_inf->fds, 1, POLLIN);

  if (err_code == 0)
    {
      type_inf->bufinf.vbuf_curr->buf.flags = 0;
      if (type_inf->remaining_capnum > 0)
        {
          type_inf->remaining_capnum--;
        }
    }
  else
    {
      type_inf->bufinf.vbuf_curr->buf.flags = V4L2_BUF_FLAG_ERROR;
    }

  type_inf->bufinf.vbuf_curr->buf.bytesused = datasize;
  if (ts != NULL)
    {
      type_inf->bufinf.vbuf_curr->buf.timestamp = *ts;
    }

  video_framebuff_capture_done(&type_inf->bufinf);

  if (is_sem_waited(&type_inf->wait_capture.dqbuf_wait_flg))
    {
      /* If waiting capture in DQBUF,
       * get/save container and unlock wait
       */

      type_inf->wait_capture.done_container =
        video_framebuff_pop_curr_container(&type_inf->bufinf);
      type_inf->wait_capture.waitend_cause =
        VIDEO_WAITEND_CAUSE_CAPTUREDONE;
      nxsem_post(&type_inf->wait_capture.dqbuf_wait_flg);
    }

  if (type_inf->remaining_capnum == 0)
    {
      stop_capture(vmng, buf_type);
      type_inf->state = VIDEO_STATE_STREAMOFF;

      /* If stop still stream, notify it to video stream */

      if (buf_type == V4L2_BUF_TYPE_STILL_CAPTURE &&
          is_sem_waited(&vmng->video_inf.wait_capture.dqbuf_wait_flg))
        {
          vmng->video_inf.wait_capture.waitend_cause =
            VIDEO_WAITEND_CAUSE_STILLSTOP;
          nxsem_post(&vmng->video_inf.wait_capture.dqbuf_wait_flg);
        }
    }
  else
    {
      container = video_framebuff_get_vacant_container(&type_inf->bufinf);
      if (container == NULL)
        {
          stop_capture(vmng, buf_type);
          type_inf->state = VIDEO_STATE_STREAMON;
        }
      else
        {
          IMGDATA_SET_BUF(vmng->imgdata,
            (FAR uint8_t *)container->buf.m.userptr,
            container->buf.length);
          container->buf.sequence = type_inf->seqnum++;
        }
    }

  leave_critical_section(flags);
  return OK;
}

/****************************************************************************
 * Public Functions
 ****************************************************************************/

int video_initialize(FAR const char *devpath)
{
  return video_register(devpath,
                       g_video_data,
                       g_video_registered_sensor,
                       g_video_registered_sensor_num);
}

int video_uninitialize(FAR const char *devpath)
{
  return video_unregister(devpath);
}

int video_register(FAR const char *devpath,
                   FAR struct imgdata_s *data,
                   FAR struct imgsensor_s **sensors,
                   size_t sensor_num)
{
  FAR video_mng_t *priv;
  size_t allocsize;
  int    ret;

  /* Input devpath Error Check */

  if (devpath == NULL || data == NULL)
    {
      return -EINVAL;
    }

  allocsize = strnlen(devpath, MAX_VIDEO_FILE_PATH - 1/* Space for '\0' */);
  if (allocsize < 2     ||
      devpath[0] != '/' ||
      (allocsize == MAX_VIDEO_FILE_PATH - 1 &&
       devpath[MAX_VIDEO_FILE_PATH] != '\0'))
    {
      return -EINVAL;
    }

  /* Initialize video device structure */

  priv = kmm_zalloc(sizeof(video_mng_t));
  if (priv == NULL)
    {
      verr("Failed to allocate instance\n");
      return -ENOMEM;
    }

  priv->imgsensor = get_connected_imgsensor(sensors, sensor_num);
  if (priv->imgsensor == NULL)
    {
      kmm_free(priv);
      return -EINVAL;
    }

  /* Save device path */

  priv->devpath = kmm_malloc(allocsize + 1);
  if (priv->devpath == NULL)
    {
      kmm_free(priv);
      return -ENOMEM;
    }

  memcpy(priv->devpath, devpath, allocsize);
  priv->devpath[allocsize] = '\0';
  priv->imgdata = data;

  /* Initialize mutex */

  nxmutex_init(&priv->lock_open_num);

  /* Register the character driver */

  ret = register_driver(priv->devpath, &g_video_fops, 0666, priv);
  if (ret < 0)
    {
      verr("Failed to register driver: %d\n", ret);
      nxmutex_destroy(&priv->lock_open_num);
      kmm_free(priv->devpath);
      kmm_free(priv);
      return ret;
    }

  return OK;
}

int video_unregister(FAR const char *devpath)
{
  return unregister_driver(devpath);
}

int imgsensor_register(FAR struct imgsensor_s *sensor)
{
  FAR struct imgsensor_s **new_addr;
  int ret = -ENOMEM;

  new_addr = kmm_realloc(g_video_registered_sensor, sizeof(sensor) *
                         (g_video_registered_sensor_num + 1));
  if (new_addr != NULL)
    {
      new_addr[g_video_registered_sensor_num++] = sensor;
      g_video_registered_sensor = new_addr;
      ret = OK;
    }

  return ret;
}

void imgdata_register(FAR struct imgdata_s *data)
{
  g_video_data = data;
}