/****************************************************************************
 * drivers/video/v4l2_m2m.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 <debug.h>
#include <stdio.h>
#include <fcntl.h>
#include <poll.h>

#include <nuttx/video/v4l2_m2m.h>
#include <nuttx/video/video.h>

#include "video_framebuff.h"

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

/* Offset base for buffers on the destination queue - used to distinguish
 * between source and destination buffers when mmapping - they receive the
 * same offsets but for different queues.
 */

#define CAPTURE_BUF_OFFSET (1 << 30)
#define CODEC_EVENT_COUNT  6

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

struct codec_event_s
{
  sq_entry_t        entry;
  struct v4l2_event event;
};

typedef struct codec_event_s codec_event_t;

struct codec_type_inf_s
{
  video_framebuff_t bufinf;
  FAR uint8_t       *bufheap;   /* for V4L2_MEMORY_MMAP buffers */
  bool              buflast;
};

typedef struct codec_type_inf_s codec_type_inf_t;

struct codec_file_s
{
  codec_type_inf_t  capture_inf;
  codec_type_inf_t  output_inf;
  sq_queue_t        event_avail;
  sq_queue_t        event_free;
  codec_event_t     event_pool[CODEC_EVENT_COUNT];
  FAR struct pollfd *fds;
  FAR void          *priv;
};

typedef struct codec_file_s codec_file_t;

struct codec_mng_s
{
  struct v4l2_s v4l2;
  FAR struct codec_s *codec;
};

typedef struct codec_mng_s codec_mng_t;

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

/* Character driver methods. */

static int codec_open(FAR struct file *filep);
static int codec_close(FAR struct file *filep);
static int codec_mmap(FAR struct file *filep,
                      FAR struct mm_map_entry_s *map);
static int codec_poll(FAR struct file *filep,
                      FAR struct pollfd *fds, bool setup);

/* Common function */

static FAR codec_type_inf_t *
codec_get_type_inf(FAR struct codec_file_s *cfile, int type);

/* ioctl function for each cmds of ioctl */

static int codec_querycap(FAR struct file *filep,
                          FAR struct v4l2_capability *cap);
static int codec_reqbufs(FAR struct file *filep,
                         FAR struct v4l2_requestbuffers *reqbufs);
static int codec_querybuf(FAR struct file *filep,
                          FAR struct v4l2_buffer *buf);
static int codec_qbuf(FAR struct file *filep,
                      FAR struct v4l2_buffer *buf);
static int codec_dqbuf(FAR struct file *filep,
                       FAR struct v4l2_buffer *buf);
static int codec_g_fmt(FAR struct file *filep,
                       FAR struct v4l2_format *fmt);
static int codec_s_fmt(FAR struct file *filep,
                       FAR struct v4l2_format *fmt);
static int codec_try_fmt(FAR struct file *filep,
                         FAR struct v4l2_format *fmt);
static int codec_g_parm(FAR struct file *filep,
                        FAR struct v4l2_streamparm *parm);
static int codec_s_parm(FAR struct file *filep,
                        FAR struct v4l2_streamparm *parm);
static int codec_streamon(FAR struct file *filep,
                          FAR enum v4l2_buf_type *type);
static int codec_streamoff(FAR struct file *filep,
                           FAR enum v4l2_buf_type *type);
static int codec_g_selection(FAR struct file *filep,
                             FAR struct v4l2_selection *clip);
static int codec_s_selection(FAR struct file *filep,
                             FAR struct v4l2_selection *clip);
static int codec_g_ext_ctrls(FAR struct file *filep,
                             FAR struct v4l2_ext_controls *ctrls);
static int codec_s_ext_ctrls(FAR struct file *filep,
                             FAR struct v4l2_ext_controls *ctrls);
static int codec_enum_fmt(FAR struct file *filep,
                          FAR struct v4l2_fmtdesc *fmt);
static int codec_cropcap(FAR struct file *filep,
                         FAR struct v4l2_cropcap *cropcap);
static int codec_dqevent(FAR struct file *filep,
                         FAR struct v4l2_event *event);
static int codec_subscribe_event(FAR struct file *filep,
                                 FAR struct v4l2_event_subscription *sub);
static int codec_decoder_cmd(FAR struct file *filep,
                             FAR struct v4l2_decoder_cmd *cmd);
static int codec_encoder_cmd(FAR struct file *filep,
                             FAR struct v4l2_encoder_cmd *cmd);

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

static const struct v4l2_ops_s g_codec_vops =
{
  codec_querycap,        /* querycap */
  NULL,                  /* g_input */
  NULL,                  /* enum_input */
  codec_reqbufs,         /* reqbufs */
  codec_querybuf,        /* querybuf */
  codec_qbuf,            /* qbuf */
  codec_dqbuf,           /* dqbuf */
  NULL,                  /* cancel_dqbuf */
  codec_g_fmt,           /* g_fmt */
  codec_s_fmt,           /* s_fmt */
  codec_try_fmt,         /* try_fmt */
  codec_g_parm,          /* g_parm */
  codec_s_parm,          /* s_parm */
  codec_streamon,        /* streamon */
  codec_streamoff,       /* streamoff */
  NULL,                  /* do_halfpush */
  NULL,                  /* takepict_start */
  NULL,                  /* takepict_stop */
  codec_s_selection,     /* s_selection */
  codec_g_selection,     /* g_selection */
  NULL,                  /* queryctrl */
  NULL,                  /* query_ext_ctrl */
  NULL,                  /* querymenu */
  NULL,                  /* g_ctrl */
  NULL,                  /* s_ctrl */
  codec_g_ext_ctrls,     /* g_ext_ctrls */
  codec_s_ext_ctrls,     /* s_ext_ctrls */
  NULL,                  /* query_ext_ctrl_scene */
  NULL,                  /* querymenu_scene */
  NULL,                  /* g_ext_ctrls_scene */
  NULL,                  /* s_ext_ctrls_scene */
  codec_enum_fmt,        /* enum_fmt */
  NULL,                  /* enum_frminterval */
  NULL,                  /* enum_frmsize */
  codec_cropcap,         /* cropcap */
  codec_dqevent,         /* dqevent */
  codec_subscribe_event, /* subscribe_event */
  codec_decoder_cmd,     /* decoder_cmd */
  codec_encoder_cmd      /* encoder_cmd */
};

static const struct file_operations g_codec_fops =
{
  codec_open,            /* open */
  codec_close,           /* close */
  NULL,                  /* read */
  NULL,                  /* write */
  NULL,                  /* seek */
  NULL,                  /* ioctl */
  codec_mmap,            /* mmap */
  NULL,                  /* truncate */
  codec_poll,            /* poll */
};

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

static FAR codec_type_inf_t *
codec_get_type_inf(FAR struct codec_file_s *cfile, int type)
{
  if (V4L2_TYPE_IS_OUTPUT(type))
    {
      return &cfile->output_inf;
    }
  else
    {
      return &cfile->capture_inf;
    }
}

static int codec_querycap(FAR struct file *filep,
                          FAR struct v4l2_capability *cap)
{
  FAR struct inode *inode = filep->f_inode;
  FAR codec_mng_t *cmng = inode->i_private;
  FAR codec_file_t *cfile = filep->f_priv;

  return CODEC_QUERYCAP(cmng->codec, cfile->priv, cap);
}

static int codec_enum_fmt(FAR struct file *filep,
                          FAR struct v4l2_fmtdesc *fmt)
{
  FAR struct inode *inode = filep->f_inode;
  FAR codec_mng_t *cmng = inode->i_private;
  FAR codec_file_t *cfile = filep->f_priv;

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

  if (V4L2_TYPE_IS_OUTPUT(fmt->type))
    {
      return CODEC_OUTPUT_ENUM_FMT(cmng->codec, cfile->priv, fmt);
    }
  else
    {
      return CODEC_CAPTURE_ENUM_FMT(cmng->codec, cfile->priv, fmt);
    }
}

static int codec_reqbufs(FAR struct file *filep,
                         FAR struct v4l2_requestbuffers *reqbufs)
{
  FAR struct inode *inode = filep->f_inode;
  FAR codec_mng_t *cmng = inode->i_private;
  FAR codec_file_t *cfile = filep->f_priv;
  FAR codec_type_inf_t *type_inf;
  irqstate_t flags;
  size_t buf_size;
  int ret = OK;

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

  reqbufs->mode = V4L2_BUF_MODE_FIFO;
  if (reqbufs->count > V4L2_REQBUFS_COUNT_MAX)
    {
      reqbufs->count = V4L2_REQBUFS_COUNT_MAX;
    }

  if (V4L2_TYPE_IS_OUTPUT(reqbufs->type))
    {
      buf_size = CODEC_OUTPUT_G_BUFSIZE(cmng->codec, cfile->priv);
    }
  else
    {
      buf_size = CODEC_CAPTURE_G_BUFSIZE(cmng->codec, cfile->priv);
    }

  if (buf_size == 0)
    {
      return -EINVAL;
    }

  flags = enter_critical_section();

  type_inf = codec_get_type_inf(cfile, reqbufs->type);
  video_framebuff_change_mode(&type_inf->bufinf, reqbufs->mode);
  ret = video_framebuff_realloc_container(&type_inf->bufinf,
                                          reqbufs->count);
  if (ret == 0 && reqbufs->memory == V4L2_MEMORY_MMAP)
    {
      kumm_free(type_inf->bufheap);
      type_inf->bufheap = kumm_memalign(32, reqbufs->count * buf_size);
      if (type_inf->bufheap == NULL)
        {
          ret = -ENOMEM;
        }
    }

  leave_critical_section(flags);
  return ret;
}

static int codec_querybuf(FAR struct file *filep,
                          FAR struct v4l2_buffer *buf)
{
  FAR struct inode *inode = filep->f_inode;
  FAR codec_mng_t *cmng = inode->i_private;
  FAR codec_file_t *cfile = filep->f_priv;
  FAR codec_type_inf_t *type_inf;

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

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

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

  if (V4L2_TYPE_IS_OUTPUT(buf->type))
    {
      buf->length   = CODEC_OUTPUT_G_BUFSIZE(cmng->codec, cfile->priv);
      buf->m.offset = buf->length * buf->index;
    }
  else
    {
      buf->length   = CODEC_CAPTURE_G_BUFSIZE(cmng->codec, cfile->priv);
      buf->m.offset = buf->length * buf->index + CAPTURE_BUF_OFFSET;
    }

  if (buf->length == 0)
    {
      return -EINVAL;
    }

  return OK;
}

static int codec_qbuf(FAR struct file *filep,
                      FAR struct v4l2_buffer *buf)
{
  FAR struct inode *inode = filep->f_inode;
  FAR codec_mng_t *cmng = inode->i_private;
  FAR codec_file_t *cfile = filep->f_priv;
  FAR codec_type_inf_t *type_inf;
  FAR vbuf_container_t *container;
  size_t buf_size;

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

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

  container = video_framebuff_get_container(&type_inf->bufinf);
  if (container == NULL)
    {
      vwarn("get container failed\n");
      return -EAGAIN;
    }

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

      if (V4L2_TYPE_IS_OUTPUT(buf->type))
        {
          buf_size = CODEC_OUTPUT_G_BUFSIZE(cmng->codec, cfile->priv);
        }
      else
        {
          buf_size = CODEC_CAPTURE_G_BUFSIZE(cmng->codec, cfile->priv);
        }

      if (buf_size == 0)
        {
          return -EINVAL;
        }

      container->buf.length    = buf_size;
      container->buf.m.userptr = (unsigned long)(type_inf->bufheap +
                                 container->buf.length * buf->index);
    }

  video_framebuff_queue_container(&type_inf->bufinf, container);

  if (V4L2_TYPE_IS_OUTPUT(buf->type))
    {
      return CODEC_OUTPUT_AVAILABLE(cmng->codec, cfile->priv);
    }
  else
    {
      return CODEC_CAPTURE_AVAILABLE(cmng->codec, cfile->priv);
    }
}

static int codec_dqbuf(FAR struct file *filep,
                       FAR struct v4l2_buffer *buf)
{
  FAR codec_file_t *cfile = filep->f_priv;
  FAR codec_type_inf_t *type_inf;
  FAR vbuf_container_t *container;
  irqstate_t flags;

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

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

  flags = enter_critical_section();

  if (video_framebuff_is_empty(&type_inf->bufinf))
    {
      leave_critical_section(flags);
      return -EAGAIN;
    }

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

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

  vinfo("%s dequeue done\n", V4L2_TYPE_IS_OUTPUT(buf->type) ?
                             "output" : "capture");

  leave_critical_section(flags);
  return OK;
}

static int codec_s_selection(FAR struct file *filep,
                             FAR struct v4l2_selection *clip)
{
  FAR struct inode *inode = filep->f_inode;
  FAR codec_mng_t *cmng = inode->i_private;
  FAR codec_file_t *cfile = filep->f_priv;

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

  if (V4L2_TYPE_IS_OUTPUT(clip->type))
    {
      return CODEC_OUTPUT_S_SELECTION(cmng->codec, cfile->priv, clip);
    }
  else
    {
      return CODEC_CAPTURE_S_SELECTION(cmng->codec, cfile->priv, clip);
    }
}

static int codec_g_selection(FAR struct file *filep,
                             FAR struct v4l2_selection *clip)
{
  FAR struct inode *inode = filep->f_inode;
  FAR codec_mng_t *cmng = inode->i_private;
  FAR codec_file_t *cfile = filep->f_priv;

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

  if (V4L2_TYPE_IS_OUTPUT(clip->type))
    {
      return CODEC_OUTPUT_G_SELECTION(cmng->codec, cfile->priv, clip);
    }
  else
    {
      return CODEC_CAPTURE_G_SELECTION(cmng->codec, cfile->priv, clip);
    }
}

static int codec_g_ext_ctrls(FAR struct file *filep,
                             FAR struct v4l2_ext_controls *ctrls)
{
  FAR struct inode *inode = filep->f_inode;
  FAR codec_mng_t *cmng = inode->i_private;
  FAR codec_file_t *cfile = filep->f_priv;

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

  return CODEC_G_EXT_CTRLS(cmng->codec, cfile->priv, ctrls);
}

static int codec_s_ext_ctrls(FAR struct file *filep,
                             FAR struct v4l2_ext_controls *ctrls)
{
  FAR struct inode *inode = filep->f_inode;
  FAR codec_mng_t *cmng = inode->i_private;
  FAR codec_file_t *cfile = filep->f_priv;

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

  return CODEC_S_EXT_CTRLS(cmng->codec, cfile->priv, ctrls);
}

static int codec_try_fmt(FAR struct file *filep,
                         FAR struct v4l2_format *fmt)
{
  FAR struct inode *inode = filep->f_inode;
  FAR codec_mng_t *cmng = inode->i_private;
  FAR codec_file_t *cfile = filep->f_priv;

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

  if (V4L2_TYPE_IS_OUTPUT(fmt->type))
    {
      return CODEC_OUTPUT_TRY_FMT(cmng->codec, cfile->priv, fmt);
    }
  else
    {
      return CODEC_CAPTURE_TRY_FMT(cmng->codec, cfile->priv, fmt);
    }
}

static int codec_g_fmt(FAR struct file *filep,
                       FAR struct v4l2_format *fmt)
{
  FAR struct inode *inode = filep->f_inode;
  FAR codec_mng_t *cmng = inode->i_private;
  FAR codec_file_t *cfile = filep->f_priv;

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

  if (V4L2_TYPE_IS_OUTPUT(fmt->type))
    {
      return CODEC_OUTPUT_G_FMT(cmng->codec, cfile->priv, fmt);
    }
  else
    {
      return CODEC_CAPTURE_G_FMT(cmng->codec, cfile->priv, fmt);
    }
}

static int codec_s_fmt(FAR struct file *filep,
                       FAR struct v4l2_format *fmt)
{
  FAR struct inode *inode = filep->f_inode;
  FAR codec_mng_t *cmng = inode->i_private;
  FAR codec_file_t *cfile = filep->f_priv;

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

  if (V4L2_TYPE_IS_OUTPUT(fmt->type))
    {
      return CODEC_OUTPUT_S_FMT(cmng->codec, cfile->priv, fmt);
    }
  else
    {
      return CODEC_CAPTURE_S_FMT(cmng->codec, cfile->priv, fmt);
    }
}

static int codec_g_parm(FAR struct file *filep,
                        FAR struct v4l2_streamparm *parm)
{
  FAR struct inode *inode = filep->f_inode;
  FAR codec_mng_t *cmng = inode->i_private;
  FAR codec_file_t *cfile = filep->f_priv;

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

  if (V4L2_TYPE_IS_OUTPUT(parm->type))
    {
      return CODEC_OUTPUT_G_PARM(cmng->codec, cfile->priv, parm);
    }
  else
    {
      return CODEC_CAPTURE_G_PARM(cmng->codec, cfile->priv, parm);
    }
}

static int codec_s_parm(FAR struct file *filep,
                          FAR struct v4l2_streamparm *parm)
{
  FAR struct inode *inode = filep->f_inode;
  FAR codec_mng_t *cmng = inode->i_private;
  FAR codec_file_t *cfile = filep->f_priv;

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

  if (V4L2_TYPE_IS_OUTPUT(parm->type))
    {
      return CODEC_OUTPUT_S_PARM(cmng->codec, cfile->priv, parm);
    }
  else
    {
      return CODEC_CAPTURE_S_PARM(cmng->codec, cfile->priv, parm);
    }
}

static int codec_streamon(FAR struct file *filep,
                          FAR enum v4l2_buf_type *type)
{
  FAR struct inode *inode = filep->f_inode;
  FAR codec_mng_t *cmng = inode->i_private;
  FAR codec_file_t *cfile = filep->f_priv;
  FAR codec_type_inf_t *type_inf;

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

  type_inf = codec_get_type_inf(cfile, *type);
  if (type_inf == NULL)
    {
      return -EINVAL;
    }

  type_inf->buflast = false;

  if (V4L2_TYPE_IS_OUTPUT(*type))
    {
      return CODEC_OUTPUT_STREAMON(cmng->codec, cfile->priv);
    }
  else
    {
      return  CODEC_CAPTURE_STREAMON(cmng->codec, cfile->priv);
    }
}

static int codec_streamoff(FAR struct file *filep,
                           FAR enum v4l2_buf_type *type)
{
  FAR struct inode *inode = filep->f_inode;
  FAR codec_mng_t *cmng = inode->i_private;
  FAR codec_file_t *cfile = filep->f_priv;
  FAR codec_type_inf_t *type_inf;

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

  type_inf = codec_get_type_inf(cfile, *type);
  if (type_inf == NULL)
    {
      return -EINVAL;
    }

  if (V4L2_TYPE_IS_OUTPUT(*type))
    {
      return CODEC_OUTPUT_STREAMOFF(cmng->codec, cfile->priv);
    }
  else
    {
      return CODEC_CAPTURE_STREAMOFF(cmng->codec, cfile->priv);
    }
}

int codec_cropcap(FAR struct file *filep,
                  FAR struct v4l2_cropcap *cropcap)
{
  FAR struct inode *inode = filep->f_inode;
  FAR codec_mng_t *cmng = inode->i_private;
  FAR codec_file_t *cfile = filep->f_priv;

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

  if (V4L2_TYPE_IS_OUTPUT(cropcap->type))
    {
      return CODEC_OUTPUT_CROPCAP(cmng->codec, cfile->priv, cropcap);
    }
  else
    {
      return CODEC_CAPTURE_CROPCAP(cmng->codec, cfile->priv, cropcap);
    }
}

int codec_dqevent(FAR struct file *filep,
                  FAR struct v4l2_event *event)
{
  FAR codec_file_t *cfile = filep->f_priv;
  FAR codec_event_t *cevt;
  irqstate_t flags;

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

  flags = enter_critical_section();

  if (sq_empty(&cfile->event_avail))
    {
      leave_critical_section(flags);
      return -ENOENT;
    }

  cevt = (FAR codec_event_t *)sq_remfirst(&cfile->event_avail);
  memcpy(event, &cevt->event, sizeof(struct v4l2_event));
  sq_addlast((FAR sq_entry_t *)cevt, &cfile->event_free);

  leave_critical_section(flags);
  return OK;
}

int codec_subscribe_event(FAR struct file *filep,
                          FAR struct v4l2_event_subscription *sub)
{
  FAR struct inode *inode = filep->f_inode;
  FAR codec_mng_t *cmng = inode->i_private;
  FAR codec_file_t *cfile = filep->f_priv;

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

  return CODEC_SUBSCRIBE_EVENT(cmng->codec, cfile->priv, sub);
}

int codec_decoder_cmd(FAR struct file *filep,
                      FAR struct v4l2_decoder_cmd *cmd)
{
  FAR struct inode *inode = filep->f_inode;
  FAR codec_mng_t *cmng = inode->i_private;
  FAR codec_file_t *cfile = filep->f_priv;

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

  return CODEC_DECODER_CMD(cmng->codec, cfile->priv, cmd);
}

int codec_encoder_cmd(FAR struct file *filep,
                      FAR struct v4l2_encoder_cmd *cmd)
{
  FAR struct inode *inode = filep->f_inode;
  FAR codec_mng_t *cmng = inode->i_private;
  FAR codec_file_t *cfile = filep->f_priv;

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

  return CODEC_ENCODER_CMD(cmng->codec, cfile->priv, cmd);
}

/* file operations */

static int codec_open(FAR struct file *filep)
{
  FAR struct inode *inode = filep->f_inode;
  FAR struct codec_mng_s *cmng = inode->i_private;
  FAR struct codec_file_s *cfile;
  int ret;
  int i;

  cfile = kmm_zalloc(sizeof(struct codec_file_s));
  if (cfile == NULL)
    {
      return -ENOMEM;
    }

  filep->f_priv = cfile;

  ret = CODEC_OPEN(cmng->codec, cfile, &cfile->priv);
  if (ret != OK)
    {
      kmm_free(cfile);
      return ret;
    }

  sq_init(&cfile->event_avail);
  sq_init(&cfile->event_free);

  for (i = 0; i < CODEC_EVENT_COUNT; i++)
    {
      sq_addlast((FAR sq_entry_t *)&cfile->event_pool[i],
                 &cfile->event_free);
    }

  video_framebuff_init(&cfile->capture_inf.bufinf);
  video_framebuff_init(&cfile->output_inf.bufinf);

  return OK;
}

static int codec_close(FAR struct file *filep)
{
  FAR struct inode *inode = filep->f_inode;
  FAR codec_mng_t *cmng = inode->i_private;
  FAR codec_file_t *cfile = filep->f_priv;

  CODEC_CLOSE(cmng->codec, cfile->priv);

  video_framebuff_uninit(&cfile->capture_inf.bufinf);
  video_framebuff_uninit(&cfile->output_inf.bufinf);
  kumm_free(cfile->capture_inf.bufheap);
  kumm_free(cfile->output_inf.bufheap);
  kmm_free(cfile);

  return OK;
}

static int codec_munmap(FAR struct task_group_s *group,
                        FAR struct mm_map_entry_s *entry,
                        FAR void *start, size_t length)
{
  return mm_map_remove(get_group_mm(group), entry);
}

static int codec_mmap(FAR struct file *filep,
                      FAR struct mm_map_entry_s *map)
{
  FAR struct inode *inode = filep->f_inode;
  FAR codec_mng_t *cmng = inode->i_private;
  FAR codec_file_t *cfile = filep->f_priv;
  FAR codec_type_inf_t *type_inf;
  int ret = -EINVAL;
  size_t total_size;
  size_t buf_size;

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

  if (map->offset < CAPTURE_BUF_OFFSET)
    {
      type_inf = &cfile->output_inf;
      buf_size = CODEC_OUTPUT_G_BUFSIZE(cmng->codec, cfile->priv);
    }
  else
    {
      type_inf     = &cfile->capture_inf;
      map->offset -= CAPTURE_BUF_OFFSET;
      buf_size     = CODEC_CAPTURE_G_BUFSIZE(cmng->codec, cfile->priv);
    }

  if (buf_size == 0)
    {
      return -EINVAL;
    }

  total_size = type_inf->bufinf.container_size * buf_size;
  if (map->offset >= 0 && map->offset < total_size &&
      map->length && map->offset + map->length <= total_size)
    {
      map->vaddr  = type_inf->bufheap + map->offset;
      map->munmap = codec_munmap;
      ret = mm_map_add(get_current_mm(), map);
    }

  return ret;
}

static int codec_poll(FAR struct file *filep,
                      FAR struct pollfd *fds, bool setup)
{
  FAR codec_file_t *cfile = filep->f_priv;
  pollevent_t eventset = 0;
  irqstate_t flags;

  flags = enter_critical_section();

  if (setup)
    {
      if (cfile->fds == NULL)
        {
          cfile->fds = fds;
          fds->priv  = &cfile->fds;

          if (!video_framebuff_is_empty(&cfile->output_inf.bufinf))
            {
              eventset |= POLLOUT;
            }

          if (cfile->capture_inf.buflast ||
              !video_framebuff_is_empty(&cfile->capture_inf.bufinf))
            {
              eventset |= POLLIN;
            }

          if (!sq_empty(&cfile->event_avail))
            {
              eventset |= POLLPRI;
            }

          if (eventset > 0)
            {
              poll_notify(&cfile->fds, 1, eventset);
            }
        }
      else
        {
          leave_critical_section(flags);
          return -EBUSY;
        }
    }
  else if (fds->priv)
    {
      cfile->fds = NULL;
      fds->priv  = NULL;
    }

  leave_critical_section(flags);
  return OK;
}

static FAR struct v4l2_buffer *codec_get_buf(FAR codec_type_inf_t *type_inf)
{
  FAR vbuf_container_t *container;

  container = video_framebuff_get_vacant_container(&type_inf->bufinf);
  if (container == NULL)
    {
      vinfo("No buffer available\n");
      return NULL;
    }

  return &container->buf;
}

static int codec_put_buf(FAR codec_file_t *cfile,
                         FAR codec_type_inf_t *type_inf,
                         FAR struct v4l2_buffer *buf)
{
  if (cfile == NULL || type_inf == NULL || buf == NULL)
    {
      return -EINVAL;
    }

  if (buf->flags & V4L2_BUF_FLAG_LAST)
    {
      type_inf->buflast = true;
    }

  video_framebuff_capture_done(&type_inf->bufinf);
  poll_notify(&cfile->fds, 1,
              V4L2_TYPE_IS_OUTPUT(buf->type) ? POLLOUT : POLLIN);

  return OK;
}

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

int codec_register(FAR const char *devpath, FAR struct codec_s *codec)
{
  FAR struct codec_mng_s *cmng;
  int ret;

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

  cmng = kmm_zalloc(sizeof(struct codec_mng_s));
  if (cmng == NULL)
    {
      verr("Failed to allocate codec instance\n");
      return -ENOMEM;
    }

  cmng->v4l2.vops = &g_codec_vops;
  cmng->v4l2.fops = &g_codec_fops;
  cmng->codec     = codec;

  /* Register the character driver */

  ret = video_register(devpath, (FAR struct v4l2_s *)cmng);
  if (ret < 0)
    {
      verr("Failed to register driver: %d\n", ret);
      kmm_free(cmng);
      return ret;
    }

  return OK;
}

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

FAR struct v4l2_buffer *codec_output_get_buf(void *cookie)
{
  FAR codec_file_t *cfile = cookie;

  return codec_get_buf(&cfile->output_inf);
}

FAR struct v4l2_buffer *codec_capture_get_buf(void *cookie)
{
  FAR codec_file_t *cfile = cookie;

  return codec_get_buf(&cfile->capture_inf);
}

int codec_output_put_buf(FAR void *cookie, FAR struct v4l2_buffer *buf)
{
  FAR codec_file_t *cfile = cookie;

  return codec_put_buf(cfile, &cfile->output_inf, buf);
}

int codec_capture_put_buf(FAR void *cookie, FAR struct v4l2_buffer *buf)
{
  FAR codec_file_t *cfile = cookie;

  return codec_put_buf(cfile, &cfile->capture_inf, buf);
}

int codec_queue_event(FAR void *cookie, FAR struct v4l2_event *evt)
{
  FAR codec_file_t *cfile = cookie;
  FAR codec_event_t *cevt;
  irqstate_t flags;

  flags = enter_critical_section();

  cevt = (FAR codec_event_t *)sq_remfirst(&cfile->event_free);
  if (cevt == NULL)
    {
      leave_critical_section(flags);
      return -EINVAL;
    }

  memcpy(&cevt->event, evt, sizeof(struct v4l2_event));
  sq_addlast((FAR sq_entry_t *)cevt, &cfile->event_avail);

  poll_notify(&cfile->fds, 1, POLLPRI);
  leave_critical_section(flags);

  return OK;
}