Quellcodebibliothek Statistik Leitseite products/Sources/formale Sprachen/C/Linux/drivers/media/platform/nxp/imx8-isi/   (Open Source Betriebssystem Version 6.17.9©)  Datei vom 24.10.2025 mit Größe 40 kB image not shown  

Quelle  imx8-isi-video.c   Sprache: C

 
// SPDX-License-Identifier: GPL-2.0
/*
 * V4L2 Capture ISI subdev driver for i.MX8QXP/QM platform
 *
 * ISI is a Image Sensor Interface of i.MX8QXP/QM platform, which
 * used to process image from camera sensor to memory or DC
 *
 * Copyright (c) 2019 NXP Semiconductor
 */


#include <linux/device.h>
#include <linux/dma-mapping.h>
#include <linux/errno.h>
#include <linux/kernel.h>
#include <linux/media-bus-format.h>
#include <linux/minmax.h>
#include <linux/pm_runtime.h>
#include <linux/string.h>
#include <linux/types.h>
#include <linux/videodev2.h>

#include <media/media-entity.h>
#include <media/v4l2-ctrls.h>
#include <media/v4l2-dev.h>
#include <media/v4l2-event.h>
#include <media/v4l2-fh.h>
#include <media/v4l2-ioctl.h>
#include <media/v4l2-subdev.h>
#include <media/videobuf2-core.h>
#include <media/videobuf2-dma-contig.h>
#include <media/videobuf2-v4l2.h>

#include "imx8-isi-core.h"
#include "imx8-isi-regs.h"

/* Keep the first entry matching MXC_ISI_DEF_PIXEL_FORMAT */
static const struct mxc_isi_format_info mxc_isi_formats[] = {
 /* YUV formats */
 {
  .mbus_code = MEDIA_BUS_FMT_YUV8_1X24,
  .fourcc  = V4L2_PIX_FMT_YUYV,
  .type  = MXC_ISI_VIDEO_CAP | MXC_ISI_VIDEO_M2M_OUT
    | MXC_ISI_VIDEO_M2M_CAP,
  .isi_in_format = CHNL_MEM_RD_CTRL_IMG_TYPE_YUV422_1P8P,
  .isi_out_format = CHNL_IMG_CTRL_FORMAT_YUV422_1P8P,
  .mem_planes = 1,
  .color_planes = 1,
  .depth  = { 16 },
  .encoding = MXC_ISI_ENC_YUV,
 }, {
  .mbus_code = MEDIA_BUS_FMT_YUV8_1X24,
  .fourcc  = V4L2_PIX_FMT_YUVA32,
  .type  = MXC_ISI_VIDEO_CAP | MXC_ISI_VIDEO_M2M_CAP,
  .isi_out_format = CHNL_IMG_CTRL_FORMAT_YUV444_1P8,
  .mem_planes = 1,
  .color_planes = 1,
  .depth  = { 32 },
  .encoding = MXC_ISI_ENC_YUV,
 }, {
  .mbus_code = MEDIA_BUS_FMT_YUV8_1X24,
  .fourcc  = V4L2_PIX_FMT_NV12,
  .type  = MXC_ISI_VIDEO_CAP | MXC_ISI_VIDEO_M2M_CAP,
  .isi_out_format = CHNL_IMG_CTRL_FORMAT_YUV420_2P8P,
  .color_planes = 2,
  .mem_planes = 1,
  .depth  = { 8, 16 },
  .hsub  = 2,
  .vsub  = 2,
  .encoding = MXC_ISI_ENC_YUV,
 }, {
  .mbus_code = MEDIA_BUS_FMT_YUV8_1X24,
  .fourcc  = V4L2_PIX_FMT_NV12M,
  .type  = MXC_ISI_VIDEO_CAP | MXC_ISI_VIDEO_M2M_CAP,
  .isi_out_format = CHNL_IMG_CTRL_FORMAT_YUV420_2P8P,
  .mem_planes = 2,
  .color_planes = 2,
  .depth  = { 8, 16 },
  .hsub  = 2,
  .vsub  = 2,
  .encoding = MXC_ISI_ENC_YUV,
 }, {
  .mbus_code = MEDIA_BUS_FMT_YUV8_1X24,
  .fourcc  = V4L2_PIX_FMT_NV16,
  .type  = MXC_ISI_VIDEO_CAP | MXC_ISI_VIDEO_M2M_CAP,
  .isi_out_format = CHNL_IMG_CTRL_FORMAT_YUV422_2P8P,
  .color_planes = 2,
  .mem_planes = 1,
  .depth  = { 8, 16 },
  .hsub  = 2,
  .vsub  = 1,
  .encoding = MXC_ISI_ENC_YUV,
 }, {
  .mbus_code = MEDIA_BUS_FMT_YUV8_1X24,
  .fourcc  = V4L2_PIX_FMT_NV16M,
  .type  = MXC_ISI_VIDEO_CAP | MXC_ISI_VIDEO_M2M_CAP,
  .isi_out_format = CHNL_IMG_CTRL_FORMAT_YUV422_2P8P,
  .mem_planes = 2,
  .color_planes = 2,
  .depth  = { 8, 16 },
  .hsub  = 2,
  .vsub  = 1,
  .encoding = MXC_ISI_ENC_YUV,
 }, {
  .mbus_code = MEDIA_BUS_FMT_YUV8_1X24,
  .fourcc  = V4L2_PIX_FMT_YUV444M,
  .type  = MXC_ISI_VIDEO_CAP | MXC_ISI_VIDEO_M2M_CAP,
  .isi_out_format = CHNL_IMG_CTRL_FORMAT_YUV444_3P8P,
  .mem_planes = 3,
  .color_planes = 3,
  .depth  = { 8, 8, 8 },
  .hsub  = 1,
  .vsub  = 1,
  .encoding = MXC_ISI_ENC_YUV,
 },
 /* RGB formats */
 {
  .mbus_code = MEDIA_BUS_FMT_RGB888_1X24,
  .fourcc  = V4L2_PIX_FMT_RGB565,
  .type  = MXC_ISI_VIDEO_CAP | MXC_ISI_VIDEO_M2M_OUT
    | MXC_ISI_VIDEO_M2M_CAP,
  .isi_in_format = CHNL_MEM_RD_CTRL_IMG_TYPE_RGB565,
  .isi_out_format = CHNL_IMG_CTRL_FORMAT_RGB565,
  .mem_planes = 1,
  .color_planes = 1,
  .depth  = { 16 },
  .encoding = MXC_ISI_ENC_RGB,
 }, {
  .mbus_code = MEDIA_BUS_FMT_RGB888_1X24,
  .fourcc  = V4L2_PIX_FMT_RGB24,
  .type  = MXC_ISI_VIDEO_CAP | MXC_ISI_VIDEO_M2M_OUT
    | MXC_ISI_VIDEO_M2M_CAP,
  .isi_in_format = CHNL_MEM_RD_CTRL_IMG_TYPE_BGR8P,
  .isi_out_format = CHNL_IMG_CTRL_FORMAT_BGR888P,
  .mem_planes = 1,
  .color_planes = 1,
  .depth  = { 24 },
  .encoding = MXC_ISI_ENC_RGB,
 }, {
  .mbus_code = MEDIA_BUS_FMT_RGB888_1X24,
  .fourcc  = V4L2_PIX_FMT_BGR24,
  .type  = MXC_ISI_VIDEO_CAP | MXC_ISI_VIDEO_M2M_OUT
    | MXC_ISI_VIDEO_M2M_CAP,
  .isi_in_format = CHNL_MEM_RD_CTRL_IMG_TYPE_RGB8P,
  .isi_out_format = CHNL_IMG_CTRL_FORMAT_RGB888P,
  .mem_planes = 1,
  .color_planes = 1,
  .depth  = { 24 },
  .encoding = MXC_ISI_ENC_RGB,
 }, {
  .mbus_code = MEDIA_BUS_FMT_RGB888_1X24,
  .fourcc  = V4L2_PIX_FMT_XBGR32,
  .type  = MXC_ISI_VIDEO_CAP | MXC_ISI_VIDEO_M2M_OUT
    | MXC_ISI_VIDEO_M2M_CAP,
  .isi_in_format = CHNL_MEM_RD_CTRL_IMG_TYPE_XBGR8,
  .isi_out_format = CHNL_IMG_CTRL_FORMAT_XRGB888,
  .mem_planes = 1,
  .color_planes = 1,
  .depth  = { 32 },
  .encoding = MXC_ISI_ENC_RGB,
 }, {
  .mbus_code = MEDIA_BUS_FMT_RGB888_1X24,
  .fourcc  = V4L2_PIX_FMT_ABGR32,
  .type  = MXC_ISI_VIDEO_CAP | MXC_ISI_VIDEO_M2M_CAP,
  .isi_out_format = CHNL_IMG_CTRL_FORMAT_ARGB8888,
  .mem_planes = 1,
  .color_planes = 1,
  .depth  = { 32 },
  .encoding = MXC_ISI_ENC_RGB,
 },
 /*
 * RAW formats
 *
 * The ISI shifts the 10-bit and 12-bit formats left by 6 and 4 bits
 * when using CHNL_IMG_CTRL_FORMAT_RAW10 or MXC_ISI_OUT_FMT_RAW12
 * respectively, to align the bits to the left and pad with zeros in
 * the LSBs. The corresponding V4L2 formats are however right-aligned,
 * we have to use CHNL_IMG_CTRL_FORMAT_RAW16 to avoid the left shift.
 */

 {
  .mbus_code = MEDIA_BUS_FMT_Y8_1X8,
  .fourcc  = V4L2_PIX_FMT_GREY,
  .type  = MXC_ISI_VIDEO_CAP,
  .isi_out_format = CHNL_IMG_CTRL_FORMAT_RAW8,
  .mem_planes = 1,
  .color_planes = 1,
  .depth  = { 8 },
  .encoding = MXC_ISI_ENC_RAW,
 }, {
  .mbus_code = MEDIA_BUS_FMT_Y10_1X10,
  .fourcc  = V4L2_PIX_FMT_Y10,
  .type  = MXC_ISI_VIDEO_CAP,
  .isi_out_format = CHNL_IMG_CTRL_FORMAT_RAW16,
  .mem_planes = 1,
  .color_planes = 1,
  .depth  = { 16 },
  .encoding = MXC_ISI_ENC_RAW,
 }, {
  .mbus_code = MEDIA_BUS_FMT_Y12_1X12,
  .fourcc  = V4L2_PIX_FMT_Y12,
  .type  = MXC_ISI_VIDEO_CAP,
  .isi_out_format = CHNL_IMG_CTRL_FORMAT_RAW16,
  .mem_planes = 1,
  .color_planes = 1,
  .depth  = { 16 },
  .encoding = MXC_ISI_ENC_RAW,
 }, {
  .mbus_code = MEDIA_BUS_FMT_Y14_1X14,
  .fourcc  = V4L2_PIX_FMT_Y14,
  .type  = MXC_ISI_VIDEO_CAP,
  .isi_out_format = CHNL_IMG_CTRL_FORMAT_RAW16,
  .mem_planes = 1,
  .color_planes = 1,
  .depth  = { 16 },
  .encoding = MXC_ISI_ENC_RAW,
 }, {
  .mbus_code = MEDIA_BUS_FMT_SBGGR8_1X8,
  .fourcc  = V4L2_PIX_FMT_SBGGR8,
  .type  = MXC_ISI_VIDEO_CAP,
  .isi_out_format = CHNL_IMG_CTRL_FORMAT_RAW8,
  .mem_planes = 1,
  .color_planes = 1,
  .depth  = { 8 },
  .encoding = MXC_ISI_ENC_RAW,
 }, {
  .mbus_code = MEDIA_BUS_FMT_SGBRG8_1X8,
  .fourcc  = V4L2_PIX_FMT_SGBRG8,
  .type  = MXC_ISI_VIDEO_CAP,
  .isi_out_format = CHNL_IMG_CTRL_FORMAT_RAW8,
  .mem_planes = 1,
  .color_planes = 1,
  .depth  = { 8 },
  .encoding = MXC_ISI_ENC_RAW,
 }, {
  .mbus_code = MEDIA_BUS_FMT_SGRBG8_1X8,
  .fourcc  = V4L2_PIX_FMT_SGRBG8,
  .type  = MXC_ISI_VIDEO_CAP,
  .isi_out_format = CHNL_IMG_CTRL_FORMAT_RAW8,
  .mem_planes = 1,
  .color_planes = 1,
  .depth  = { 8 },
  .encoding = MXC_ISI_ENC_RAW,
 }, {
  .mbus_code = MEDIA_BUS_FMT_SRGGB8_1X8,
  .fourcc  = V4L2_PIX_FMT_SRGGB8,
  .type  = MXC_ISI_VIDEO_CAP,
  .isi_out_format = CHNL_IMG_CTRL_FORMAT_RAW8,
  .mem_planes = 1,
  .color_planes = 1,
  .depth  = { 8 },
  .encoding = MXC_ISI_ENC_RAW,
 }, {
  .mbus_code = MEDIA_BUS_FMT_SBGGR10_1X10,
  .fourcc  = V4L2_PIX_FMT_SBGGR10,
  .type  = MXC_ISI_VIDEO_CAP,
  .isi_out_format = CHNL_IMG_CTRL_FORMAT_RAW16,
  .mem_planes = 1,
  .color_planes = 1,
  .depth  = { 16 },
  .encoding = MXC_ISI_ENC_RAW,
 }, {
  .mbus_code = MEDIA_BUS_FMT_SGBRG10_1X10,
  .fourcc  = V4L2_PIX_FMT_SGBRG10,
  .type  = MXC_ISI_VIDEO_CAP,
  .isi_out_format = CHNL_IMG_CTRL_FORMAT_RAW16,
  .mem_planes = 1,
  .color_planes = 1,
  .depth  = { 16 },
  .encoding = MXC_ISI_ENC_RAW,
 }, {
  .mbus_code = MEDIA_BUS_FMT_SGRBG10_1X10,
  .fourcc  = V4L2_PIX_FMT_SGRBG10,
  .type  = MXC_ISI_VIDEO_CAP,
  .isi_out_format = CHNL_IMG_CTRL_FORMAT_RAW16,
  .mem_planes = 1,
  .color_planes = 1,
  .depth  = { 16 },
  .encoding = MXC_ISI_ENC_RAW,
 }, {
  .mbus_code = MEDIA_BUS_FMT_SRGGB10_1X10,
  .fourcc  = V4L2_PIX_FMT_SRGGB10,
  .type  = MXC_ISI_VIDEO_CAP,
  .isi_out_format = CHNL_IMG_CTRL_FORMAT_RAW16,
  .mem_planes = 1,
  .color_planes = 1,
  .depth  = { 16 },
  .encoding = MXC_ISI_ENC_RAW,
 }, {
  .mbus_code = MEDIA_BUS_FMT_SBGGR12_1X12,
  .fourcc  = V4L2_PIX_FMT_SBGGR12,
  .type  = MXC_ISI_VIDEO_CAP,
  .isi_out_format = CHNL_IMG_CTRL_FORMAT_RAW16,
  .mem_planes = 1,
  .color_planes = 1,
  .depth  = { 16 },
  .encoding = MXC_ISI_ENC_RAW,
 }, {
  .mbus_code = MEDIA_BUS_FMT_SGBRG12_1X12,
  .fourcc  = V4L2_PIX_FMT_SGBRG12,
  .type  = MXC_ISI_VIDEO_CAP,
  .isi_out_format = CHNL_IMG_CTRL_FORMAT_RAW16,
  .mem_planes = 1,
  .color_planes = 1,
  .depth  = { 16 },
  .encoding = MXC_ISI_ENC_RAW,
 }, {
  .mbus_code = MEDIA_BUS_FMT_SGRBG12_1X12,
  .fourcc  = V4L2_PIX_FMT_SGRBG12,
  .type  = MXC_ISI_VIDEO_CAP,
  .isi_out_format = CHNL_IMG_CTRL_FORMAT_RAW16,
  .mem_planes = 1,
  .color_planes = 1,
  .depth  = { 16 },
  .encoding = MXC_ISI_ENC_RAW,
 }, {
  .mbus_code = MEDIA_BUS_FMT_SRGGB12_1X12,
  .fourcc  = V4L2_PIX_FMT_SRGGB12,
  .type  = MXC_ISI_VIDEO_CAP,
  .isi_out_format = CHNL_IMG_CTRL_FORMAT_RAW16,
  .mem_planes = 1,
  .color_planes = 1,
  .depth  = { 16 },
  .encoding = MXC_ISI_ENC_RAW,
 }, {
  .mbus_code = MEDIA_BUS_FMT_SBGGR14_1X14,
  .fourcc  = V4L2_PIX_FMT_SBGGR14,
  .type  = MXC_ISI_VIDEO_CAP,
  .isi_out_format = CHNL_IMG_CTRL_FORMAT_RAW16,
  .mem_planes = 1,
  .color_planes = 1,
  .depth  = { 16 },
  .encoding = MXC_ISI_ENC_RAW,
 }, {
  .mbus_code = MEDIA_BUS_FMT_SGBRG14_1X14,
  .fourcc  = V4L2_PIX_FMT_SGBRG14,
  .type  = MXC_ISI_VIDEO_CAP,
  .isi_out_format = CHNL_IMG_CTRL_FORMAT_RAW16,
  .mem_planes = 1,
  .color_planes = 1,
  .depth  = { 16 },
  .encoding = MXC_ISI_ENC_RAW,
 }, {
  .mbus_code = MEDIA_BUS_FMT_SGRBG14_1X14,
  .fourcc  = V4L2_PIX_FMT_SGRBG14,
  .type  = MXC_ISI_VIDEO_CAP,
  .isi_out_format = CHNL_IMG_CTRL_FORMAT_RAW16,
  .mem_planes = 1,
  .color_planes = 1,
  .depth  = { 16 },
  .encoding = MXC_ISI_ENC_RAW,
 }, {
  .mbus_code = MEDIA_BUS_FMT_SRGGB14_1X14,
  .fourcc  = V4L2_PIX_FMT_SRGGB14,
  .type  = MXC_ISI_VIDEO_CAP,
  .isi_out_format = CHNL_IMG_CTRL_FORMAT_RAW16,
  .mem_planes = 1,
  .color_planes = 1,
  .depth  = { 16 },
  .encoding = MXC_ISI_ENC_RAW,
 },
 /* JPEG */
 {
  .mbus_code = MEDIA_BUS_FMT_JPEG_1X8,
  .fourcc  = V4L2_PIX_FMT_MJPEG,
  .type  = MXC_ISI_VIDEO_CAP,
  .isi_out_format = CHNL_IMG_CTRL_FORMAT_RAW8,
  .mem_planes = 1,
  .color_planes = 1,
  .depth  = { 8 },
  .encoding = MXC_ISI_ENC_RAW,
 }
};

const struct mxc_isi_format_info *
mxc_isi_format_by_fourcc(u32 fourcc, enum mxc_isi_video_type type)
{
 unsigned int i;

 for (i = 0; i < ARRAY_SIZE(mxc_isi_formats); i++) {
  const struct mxc_isi_format_info *fmt = &mxc_isi_formats[i];

  if (fmt->fourcc == fourcc && fmt->type & type)
   return fmt;
 }

 return NULL;
}

const struct mxc_isi_format_info *
mxc_isi_format_enum(unsigned int index, enum mxc_isi_video_type type)
{
 unsigned int i;

 for (i = 0; i < ARRAY_SIZE(mxc_isi_formats); i++) {
  const struct mxc_isi_format_info *fmt = &mxc_isi_formats[i];

  if (!(fmt->type & type))
   continue;

  if (!index)
   return fmt;

  index--;
 }

 return NULL;
}

const struct mxc_isi_format_info *
mxc_isi_format_try(struct mxc_isi_pipe *pipe, struct v4l2_pix_format_mplane *pix,
     enum mxc_isi_video_type type)
{
 const struct mxc_isi_format_info *fmt;
 unsigned int max_width;
 unsigned int i;

 max_width = pipe->id == pipe->isi->pdata->num_channels - 1
    ? MXC_ISI_MAX_WIDTH_UNCHAINED
    : MXC_ISI_MAX_WIDTH_CHAINED;

 fmt = mxc_isi_format_by_fourcc(pix->pixelformat, type);
 if (!fmt)
  fmt = &mxc_isi_formats[0];

 pix->width = clamp(pix->width, MXC_ISI_MIN_WIDTH, max_width);
 pix->height = clamp(pix->height, MXC_ISI_MIN_HEIGHT, MXC_ISI_MAX_HEIGHT);
 pix->pixelformat = fmt->fourcc;
 pix->field = V4L2_FIELD_NONE;

 if (pix->colorspace == V4L2_COLORSPACE_DEFAULT) {
  pix->colorspace = MXC_ISI_DEF_COLOR_SPACE;
  pix->ycbcr_enc = MXC_ISI_DEF_YCBCR_ENC;
  pix->quantization = MXC_ISI_DEF_QUANTIZATION;
  pix->xfer_func = MXC_ISI_DEF_XFER_FUNC;
 }

 if (pix->ycbcr_enc == V4L2_YCBCR_ENC_DEFAULT)
  pix->ycbcr_enc = V4L2_MAP_YCBCR_ENC_DEFAULT(pix->colorspace);
 if (pix->quantization == V4L2_QUANTIZATION_DEFAULT) {
  bool is_rgb = fmt->encoding == MXC_ISI_ENC_RGB;

  pix->quantization =
   V4L2_MAP_QUANTIZATION_DEFAULT(is_rgb, pix->colorspace,
            pix->ycbcr_enc);
 }
 if (pix->xfer_func == V4L2_XFER_FUNC_DEFAULT)
  pix->xfer_func = V4L2_MAP_XFER_FUNC_DEFAULT(pix->colorspace);

 pix->num_planes = fmt->mem_planes;

 for (i = 0; i < fmt->color_planes; ++i) {
  struct v4l2_plane_pix_format *plane = &pix->plane_fmt[i];
  unsigned int bpl;

  /* The pitch must be identical for all planes. */
  if (i == 0)
   bpl = clamp(plane->bytesperline,
        pix->width * fmt->depth[0] / 8,
        65535U);
  else
   bpl = pix->plane_fmt[0].bytesperline;

  plane->bytesperline = bpl;

  plane->sizeimage = plane->bytesperline * pix->height;
  if (i >= 1)
   plane->sizeimage /= fmt->vsub;
 }

 /*
 * For single-planar pixel formats with multiple color planes,
 * concatenate the size of all planes and clear all planes but the
 * first one.
 */

 if (fmt->color_planes != fmt->mem_planes) {
  for (i = 1; i < fmt->color_planes; ++i) {
   struct v4l2_plane_pix_format *plane = &pix->plane_fmt[i];

   pix->plane_fmt[0].sizeimage += plane->sizeimage;
   plane->bytesperline = 0;
   plane->sizeimage = 0;
  }
 }

 return fmt;
}

/* -----------------------------------------------------------------------------
 * videobuf2 queue operations
 */


static void mxc_isi_video_frame_write_done(struct mxc_isi_pipe *pipe,
        u32 status)
{
 struct mxc_isi_video *video = &pipe->video;
 struct device *dev = pipe->isi->dev;
 struct mxc_isi_buffer *next_buf;
 struct mxc_isi_buffer *buf;
 enum mxc_isi_buf_id buf_id;

 spin_lock(&video->buf_lock);

 /*
 * The ISI hardware handles buffers using a ping-pong mechanism with
 * two sets of destination addresses (with shadow registers to allow
 * programming addresses for all planes atomically) named BUF1 and
 * BUF2. Addresses can be loaded and copied to shadow registers at any
 * at any time.
 *
 * The hardware keeps track of which buffer is being written to and
 * automatically switches to the other buffer at frame end, copying the
 * corresponding address to another set of shadow registers that track
 * the address being written to. The active buffer tracking bits are
 * accessible through the CHNL_STS register.
 *
 *  BUF1        BUF2  |   Event   | Action
 *                    |           |
 *                    |           | Program initial buffers
 *                    |           | B0 in BUF1, B1 in BUF2
 *                    | Start ISI |
 * +----+             |           |
 * | B0 |             |           |
 * +----+             |           |
 *             +----+ | FRM IRQ 0 | B0 complete, BUF2 now active
 *             | B1 | |           | Program B2 in BUF1
 *             +----+ |           |
 * +----+             | FRM IRQ 1 | B1 complete, BUF1 now active
 * | B2 |             |           | Program B3 in BUF2
 * +----+             |           |
 *             +----+ | FRM IRQ 2 | B2 complete, BUF2 now active
 *             | B3 | |           | Program B4 in BUF1
 *             +----+ |           |
 * +----+             | FRM IRQ 3 | B3 complete, BUF1 now active
 * | B4 |             |           | Program B5 in BUF2
 * +----+             |           |
 *        ...         |           |
 *
 * Races between address programming and buffer switching can be
 * detected by checking if a frame end interrupt occurred after
 * programming the addresses.
 *
 * As none of the shadow registers are accessible, races can occur
 * between address programming and buffer switching. It is possible to
 * detect the race condition by checking if a frame end interrupt
 * occurred after programming the addresses, but impossible to
 * determine if the race has been won or lost.
 *
 * In addition to this, we need to use discard buffers if no pending
 * buffers are available. To simplify handling of discard buffer, we
 * need to allocate three of them, as two can be active concurrently
 * and we need to still be able to get hold of a next buffer. The logic
 * could be improved to use two buffers only, but as all discard
 * buffers share the same memory, an additional buffer is cheap.
 */


 /* Check which buffer has just completed. */
 buf_id = pipe->isi->pdata->buf_active_reverse
        ? (status & CHNL_STS_BUF1_ACTIVE ? MXC_ISI_BUF2 : MXC_ISI_BUF1)
        : (status & CHNL_STS_BUF1_ACTIVE ? MXC_ISI_BUF1 : MXC_ISI_BUF2);

 buf = list_first_entry_or_null(&video->out_active,
           struct mxc_isi_buffer, list);

 /* Safety check, this should really never happen. */
 if (!buf) {
  dev_warn(dev, "trying to access empty active list\n");
  goto done;
 }

 /*
 * If the buffer that has completed doesn't match the buffer on the
 * front of the active list, it means we have lost one frame end
 * interrupt (or possibly a large odd number of interrupts, although
 * quite unlikely).
 *
 * For instance, if IRQ1 is lost and we handle IRQ2, both B1 and B2
 * have been completed, but B3 hasn't been programmed, BUF2 still
 * addresses B1 and the ISI is now writing in B1 instead of B3. We
 * can't complete B2 as that would result in out-of-order completion.
 *
 * The only option is to ignore this interrupt and try again. When IRQ3
 * will be handled, we will complete B1 and be in sync again.
 */

 if (buf->id != buf_id) {
  dev_dbg(dev, "buffer ID mismatch (expected %u, got %u), skipping\n",
   buf->id, buf_id);

  /*
 * Increment the frame count by two to account for the missed
 * and the ignored interrupts.
 */

  video->frame_count += 2;
  goto done;
 }

 /* Pick the next buffer and queue it to the hardware. */
 next_buf = list_first_entry_or_null(&video->out_pending,
         struct mxc_isi_buffer, list);
 if (!next_buf) {
  next_buf = list_first_entry_or_null(&video->out_discard,
          struct mxc_isi_buffer, list);

  /* Safety check, this should never happen. */
  if (!next_buf) {
   dev_warn(dev, "trying to access empty discard list\n");
   goto done;
  }
 }

 mxc_isi_channel_set_outbuf(pipe, next_buf->dma_addrs, buf_id);
 next_buf->id = buf_id;

 /*
 * Check if we have raced with the end of frame interrupt. If so, we
 * can't tell if the ISI has recorded the new address, or is still
 * using the previous buffer. We must assume the latter as that is the
 * worst case.
 *
 * For instance, if we are handling IRQ1 and now detect the FRM
 * interrupt, assume B2 has completed and the ISI has switched to BUF2
 * using B1 just before we programmed B3. Unlike in the previous race
 * condition, B3 has been programmed and will be written to the next
 * time the ISI switches to BUF2. We can however handle this exactly as
 * the first race condition, as we'll program B3 (still at the head of
 * the pending list) when handling IRQ3.
 */

 status = mxc_isi_channel_irq_status(pipe, false);
 if (status & CHNL_STS_FRM_STRD) {
  dev_dbg(dev, "raced with frame end interrupt\n");
  video->frame_count += 2;
  goto done;
 }

 /*
 * The next buffer has been queued successfully, move it to the active
 * list, and complete the current buffer.
 */

 list_move_tail(&next_buf->list, &video->out_active);

 if (!buf->discard) {
  list_del_init(&buf->list);
  buf->v4l2_buf.sequence = video->frame_count;
  buf->v4l2_buf.vb2_buf.timestamp = ktime_get_ns();
  vb2_buffer_done(&buf->v4l2_buf.vb2_buf, VB2_BUF_STATE_DONE);
 } else {
  list_move_tail(&buf->list, &video->out_discard);
 }

 video->frame_count++;

done:
 spin_unlock(&video->buf_lock);
}

static void mxc_isi_video_free_discard_buffers(struct mxc_isi_video *video)
{
 unsigned int i;

 for (i = 0; i < video->pix.num_planes; i++) {
  struct mxc_isi_dma_buffer *buf = &video->discard_buffer[i];

  if (!buf->addr)
   continue;

  dma_free_coherent(video->pipe->isi->dev, buf->size, buf->addr,
      buf->dma);
  buf->addr = NULL;
 }
}

static int mxc_isi_video_alloc_discard_buffers(struct mxc_isi_video *video)
{
 unsigned int i, j;

 /* Allocate memory for each plane. */
 for (i = 0; i < video->pix.num_planes; i++) {
  struct mxc_isi_dma_buffer *buf = &video->discard_buffer[i];

  buf->size = PAGE_ALIGN(video->pix.plane_fmt[i].sizeimage);
  buf->addr = dma_alloc_coherent(video->pipe->isi->dev, buf->size,
            &buf->dma, GFP_DMA | GFP_KERNEL);
  if (!buf->addr) {
   mxc_isi_video_free_discard_buffers(video);
   return -ENOMEM;
  }

  dev_dbg(video->pipe->isi->dev,
   "discard buffer plane %u: %zu bytes @%pad (CPU address %p)\n",
   i, buf->size, &buf->dma, buf->addr);
 }

 /* Fill the DMA addresses in the discard buffers. */
 for (i = 0; i < ARRAY_SIZE(video->buf_discard); ++i) {
  struct mxc_isi_buffer *buf = &video->buf_discard[i];

  buf->discard = true;

  for (j = 0; j < video->pix.num_planes; ++j)
   buf->dma_addrs[j] = video->discard_buffer[j].dma;
 }

 return 0;
}

static int mxc_isi_video_validate_format(struct mxc_isi_video *video)
{
 const struct v4l2_mbus_framefmt *format;
 const struct mxc_isi_format_info *info;
 struct v4l2_subdev_state *state;
 struct v4l2_subdev *sd = &video->pipe->sd;
 int ret = 0;

 state = v4l2_subdev_lock_and_get_active_state(sd);

 info = mxc_isi_format_by_fourcc(video->pix.pixelformat,
     MXC_ISI_VIDEO_CAP);
 format = v4l2_subdev_state_get_format(state, MXC_ISI_PIPE_PAD_SOURCE);

 if (format->code != info->mbus_code ||
     format->width != video->pix.width ||
     format->height != video->pix.height) {
  dev_dbg(video->pipe->isi->dev,
   "%s: configuration mismatch, 0x%04x/%ux%u != 0x%04x/%ux%u\n",
   __func__, format->code, format->width, format->height,
   info->mbus_code, video->pix.width, video->pix.height);
  ret = -EINVAL;
 }

 v4l2_subdev_unlock_state(state);

 return ret;
}

static void mxc_isi_video_return_buffers(struct mxc_isi_video *video,
      enum vb2_buffer_state state)
{
 struct mxc_isi_buffer *buf;

 spin_lock_irq(&video->buf_lock);

 while (!list_empty(&video->out_active)) {
  buf = list_first_entry(&video->out_active,
           struct mxc_isi_buffer, list);
  list_del_init(&buf->list);
  if (buf->discard)
   continue;

  vb2_buffer_done(&buf->v4l2_buf.vb2_buf, state);
 }

 while (!list_empty(&video->out_pending)) {
  buf = list_first_entry(&video->out_pending,
           struct mxc_isi_buffer, list);
  list_del_init(&buf->list);
  vb2_buffer_done(&buf->v4l2_buf.vb2_buf, state);
 }

 while (!list_empty(&video->out_discard)) {
  buf = list_first_entry(&video->out_discard,
           struct mxc_isi_buffer, list);
  list_del_init(&buf->list);
 }

 INIT_LIST_HEAD(&video->out_active);
 INIT_LIST_HEAD(&video->out_pending);
 INIT_LIST_HEAD(&video->out_discard);

 spin_unlock_irq(&video->buf_lock);
}

static void mxc_isi_video_queue_first_buffers(struct mxc_isi_video *video)
{
 unsigned int discard;
 unsigned int i;

 lockdep_assert_held(&video->buf_lock);

 /*
 * Queue two ISI channel output buffers. We are not guaranteed to have
 * any buffer in the pending list when this function is called from the
 * system resume handler. Use pending buffers as much as possible, and
 * use discard buffers to fill the remaining slots.
 */


 /* How many discard buffers do we need to queue first ? */
 discard = list_empty(&video->out_pending) ? 2
  : list_is_singular(&video->out_pending) ? 1
  : 0;

 for (i = 0; i < 2; ++i) {
  enum mxc_isi_buf_id buf_id = i == 0 ? MXC_ISI_BUF1
        : MXC_ISI_BUF2;
  struct mxc_isi_buffer *buf;
  struct list_head *list;

  list = i < discard ? &video->out_discard : &video->out_pending;
  buf = list_first_entry(list, struct mxc_isi_buffer, list);

  mxc_isi_channel_set_outbuf(video->pipe, buf->dma_addrs, buf_id);
  buf->id = buf_id;
  list_move_tail(&buf->list, &video->out_active);
 }
}

static inline struct mxc_isi_buffer *to_isi_buffer(struct vb2_v4l2_buffer *v4l2_buf)
{
 return container_of(v4l2_buf, struct mxc_isi_buffer, v4l2_buf);
}

int mxc_isi_video_queue_setup(const struct v4l2_pix_format_mplane *format,
         const struct mxc_isi_format_info *info,
         unsigned int *num_buffers,
         unsigned int *num_planes, unsigned int sizes[])
{
 unsigned int i;

 if (*num_planes) {
  if (*num_planes != info->mem_planes)
   return -EINVAL;

  for (i = 0; i < info->mem_planes; ++i) {
   if (sizes[i] < format->plane_fmt[i].sizeimage)
    return -EINVAL;
  }

  return 0;
 }

 *num_planes = info->mem_planes;

 for (i = 0; i < info->mem_planes; ++i)
  sizes[i] = format->plane_fmt[i].sizeimage;

 return 0;
}

void mxc_isi_video_buffer_init(struct vb2_buffer *vb2, dma_addr_t dma_addrs[3],
          const struct mxc_isi_format_info *info,
          const struct v4l2_pix_format_mplane *pix)
{
 unsigned int i;

 for (i = 0; i < info->mem_planes; ++i)
  dma_addrs[i] = vb2_dma_contig_plane_dma_addr(vb2, i);

 /*
 * For single-planar pixel formats with multiple color planes, split
 * the buffer into color planes.
 */

 if (info->color_planes != info->mem_planes) {
  unsigned int size = pix->plane_fmt[0].bytesperline * pix->height;

  for (i = 1; i < info->color_planes; ++i) {
   unsigned int vsub = i > 1 ? info->vsub : 1;

   dma_addrs[i] = dma_addrs[i - 1] + size / vsub;
  }
 }
}

int mxc_isi_video_buffer_prepare(struct mxc_isi_dev *isi, struct vb2_buffer *vb2,
     const struct mxc_isi_format_info *info,
     const struct v4l2_pix_format_mplane *pix)
{
 struct vb2_v4l2_buffer *v4l2_buf = to_vb2_v4l2_buffer(vb2);
 unsigned int i;

 for (i = 0; i < info->mem_planes; i++) {
  unsigned long size = pix->plane_fmt[i].sizeimage;

  if (vb2_plane_size(vb2, i) < size) {
   dev_err(isi->dev, "User buffer too small (%ld < %ld)\n",
    vb2_plane_size(vb2, i), size);
   return -EINVAL;
  }

  vb2_set_plane_payload(vb2, i, size);
 }

 v4l2_buf->field = pix->field;

 return 0;
}

static int mxc_isi_vb2_queue_setup(struct vb2_queue *q,
       unsigned int *num_buffers,
       unsigned int *num_planes,
       unsigned int sizes[],
       struct device *alloc_devs[])
{
 struct mxc_isi_video *video = vb2_get_drv_priv(q);

 return mxc_isi_video_queue_setup(&video->pix, video->fmtinfo,
      num_buffers, num_planes, sizes);
}

static int mxc_isi_vb2_buffer_init(struct vb2_buffer *vb2)
{
 struct mxc_isi_buffer *buf = to_isi_buffer(to_vb2_v4l2_buffer(vb2));
 struct mxc_isi_video *video = vb2_get_drv_priv(vb2->vb2_queue);

 mxc_isi_video_buffer_init(vb2, buf->dma_addrs, video->fmtinfo,
      &video->pix);

 return 0;
}

static int mxc_isi_vb2_buffer_prepare(struct vb2_buffer *vb2)
{
 struct mxc_isi_video *video = vb2_get_drv_priv(vb2->vb2_queue);

 return mxc_isi_video_buffer_prepare(video->pipe->isi, vb2,
         video->fmtinfo, &video->pix);
}

static void mxc_isi_vb2_buffer_queue(struct vb2_buffer *vb2)
{
 struct vb2_v4l2_buffer *v4l2_buf = to_vb2_v4l2_buffer(vb2);
 struct mxc_isi_buffer *buf = to_isi_buffer(v4l2_buf);
 struct mxc_isi_video *video = vb2_get_drv_priv(vb2->vb2_queue);

 spin_lock_irq(&video->buf_lock);
 list_add_tail(&buf->list, &video->out_pending);
 spin_unlock_irq(&video->buf_lock);
}

static void mxc_isi_video_init_channel(struct mxc_isi_video *video)
{
 struct mxc_isi_pipe *pipe = video->pipe;

 mxc_isi_channel_get(pipe);

 mutex_lock(video->ctrls.handler.lock);
 mxc_isi_channel_set_alpha(pipe, video->ctrls.alpha);
 mxc_isi_channel_set_flip(pipe, video->ctrls.hflip, video->ctrls.vflip);
 mutex_unlock(video->ctrls.handler.lock);

 mxc_isi_channel_set_output_format(pipe, video->fmtinfo, &video->pix);
}

static int mxc_isi_vb2_prepare_streaming(struct vb2_queue *q)
{
 struct mxc_isi_video *video = vb2_get_drv_priv(q);
 struct media_device *mdev = &video->pipe->isi->media_dev;
 struct media_pipeline *pipe;
 int ret;

 /* Get a pipeline for the video node and start it. */
 scoped_guard(mutex, &mdev->graph_mutex) {
  ret = mxc_isi_pipe_acquire(video->pipe,
        &mxc_isi_video_frame_write_done);
  if (ret)
   return ret;

  pipe = media_entity_pipeline(&video->vdev.entity)
       ? : &video->pipe->pipe;

  ret = __video_device_pipeline_start(&video->vdev, pipe);
  if (ret)
   goto err_release;
 }

 /* Verify that the video format matches the output of the subdev. */
 ret = mxc_isi_video_validate_format(video);
 if (ret)
  goto err_stop;

 /* Allocate buffers for discard operation. */
 ret = mxc_isi_video_alloc_discard_buffers(video);
 if (ret)
  goto err_stop;

 video->is_streaming = true;

 return 0;

err_stop:
 video_device_pipeline_stop(&video->vdev);
err_release:
 mxc_isi_pipe_release(video->pipe);
 return ret;
}

static int mxc_isi_vb2_start_streaming(struct vb2_queue *q, unsigned int count)
{
 struct mxc_isi_video *video = vb2_get_drv_priv(q);
 unsigned int i;
 int ret;

 /* Initialize the ISI channel. */
 mxc_isi_video_init_channel(video);

 spin_lock_irq(&video->buf_lock);

 /* Add the discard buffers to the out_discard list. */
 for (i = 0; i < ARRAY_SIZE(video->buf_discard); ++i) {
  struct mxc_isi_buffer *buf = &video->buf_discard[i];

  list_add_tail(&buf->list, &video->out_discard);
 }

 /* Queue the first buffers. */
 mxc_isi_video_queue_first_buffers(video);

 /* Clear frame count */
 video->frame_count = 0;

 spin_unlock_irq(&video->buf_lock);

 ret = mxc_isi_pipe_enable(video->pipe);
 if (ret)
  goto error;

 return 0;

error:
 mxc_isi_channel_put(video->pipe);
 mxc_isi_video_return_buffers(video, VB2_BUF_STATE_QUEUED);
 return ret;
}

static void mxc_isi_vb2_stop_streaming(struct vb2_queue *q)
{
 struct mxc_isi_video *video = vb2_get_drv_priv(q);

 mxc_isi_pipe_disable(video->pipe);
 mxc_isi_channel_put(video->pipe);

 mxc_isi_video_return_buffers(video, VB2_BUF_STATE_ERROR);
}

static void mxc_isi_vb2_unprepare_streaming(struct vb2_queue *q)
{
 struct mxc_isi_video *video = vb2_get_drv_priv(q);

 mxc_isi_video_free_discard_buffers(video);
 video_device_pipeline_stop(&video->vdev);
 mxc_isi_pipe_release(video->pipe);

 video->is_streaming = false;
}

static const struct vb2_ops mxc_isi_vb2_qops = {
 .queue_setup  = mxc_isi_vb2_queue_setup,
 .buf_init  = mxc_isi_vb2_buffer_init,
 .buf_prepare  = mxc_isi_vb2_buffer_prepare,
 .buf_queue  = mxc_isi_vb2_buffer_queue,
 .prepare_streaming = mxc_isi_vb2_prepare_streaming,
 .start_streaming = mxc_isi_vb2_start_streaming,
 .stop_streaming  = mxc_isi_vb2_stop_streaming,
 .unprepare_streaming = mxc_isi_vb2_unprepare_streaming,
};

/* -----------------------------------------------------------------------------
 * V4L2 controls
 */


static inline struct mxc_isi_video *ctrl_to_isi_video(struct v4l2_ctrl *ctrl)
{
 return container_of(ctrl->handler, struct mxc_isi_video, ctrls.handler);
}

static int mxc_isi_video_s_ctrl(struct v4l2_ctrl *ctrl)
{
 struct mxc_isi_video *video = ctrl_to_isi_video(ctrl);

 switch (ctrl->id) {
 case V4L2_CID_ALPHA_COMPONENT:
  video->ctrls.alpha = ctrl->val;
  break;
 case V4L2_CID_VFLIP:
  video->ctrls.vflip = ctrl->val;
  break;
 case V4L2_CID_HFLIP:
  video->ctrls.hflip = ctrl->val;
  break;
 }

 return 0;
}

static const struct v4l2_ctrl_ops mxc_isi_video_ctrl_ops = {
 .s_ctrl = mxc_isi_video_s_ctrl,
};

static int mxc_isi_video_ctrls_create(struct mxc_isi_video *video)
{
 struct v4l2_ctrl_handler *handler = &video->ctrls.handler;
 int ret;

 v4l2_ctrl_handler_init(handler, 3);

 v4l2_ctrl_new_std(handler, &mxc_isi_video_ctrl_ops,
     V4L2_CID_ALPHA_COMPONENT, 0, 255, 1, 0);

 v4l2_ctrl_new_std(handler, &mxc_isi_video_ctrl_ops,
     V4L2_CID_VFLIP, 0, 1, 1, 0);

 v4l2_ctrl_new_std(handler, &mxc_isi_video_ctrl_ops,
     V4L2_CID_HFLIP, 0, 1, 1, 0);

 if (handler->error) {
  ret = handler->error;
  v4l2_ctrl_handler_free(handler);
  return ret;
 }

 video->vdev.ctrl_handler = handler;

 return 0;
}

static void mxc_isi_video_ctrls_delete(struct mxc_isi_video *video)
{
 v4l2_ctrl_handler_free(&video->ctrls.handler);
}

/* -----------------------------------------------------------------------------
 * V4L2 ioctls
 */


static int mxc_isi_video_querycap(struct file *file, void *priv,
      struct v4l2_capability *cap)
{
 strscpy(cap->driver, MXC_ISI_DRIVER_NAME, sizeof(cap->driver));
 strscpy(cap->card, MXC_ISI_CAPTURE, sizeof(cap->card));

 return 0;
}

static int mxc_isi_video_enum_fmt(struct file *file, void *priv,
      struct v4l2_fmtdesc *f)
{
 const struct mxc_isi_format_info *fmt;
 unsigned int index = f->index;
 unsigned int i;

 if (f->mbus_code) {
  /*
 * If a media bus code is specified, only enumerate formats
 * compatible with it.
 */

  for (i = 0; i < ARRAY_SIZE(mxc_isi_formats); i++) {
   fmt = &mxc_isi_formats[i];
   if (fmt->mbus_code != f->mbus_code)
    continue;

   if (index == 0)
    break;

   index--;
  }

  if (i == ARRAY_SIZE(mxc_isi_formats))
   return -EINVAL;
 } else {
  /* Otherwise, enumerate all formatS. */
  if (f->index >= ARRAY_SIZE(mxc_isi_formats))
   return -EINVAL;

  fmt = &mxc_isi_formats[f->index];
 }

 f->pixelformat = fmt->fourcc;
 f->flags |= V4L2_FMT_FLAG_CSC_COLORSPACE | V4L2_FMT_FLAG_CSC_YCBCR_ENC
   |  V4L2_FMT_FLAG_CSC_QUANTIZATION | V4L2_FMT_FLAG_CSC_XFER_FUNC;

 return 0;
}

static int mxc_isi_video_g_fmt(struct file *file, void *fh,
          struct v4l2_format *f)
{
 struct mxc_isi_video *video = video_drvdata(file);

 f->fmt.pix_mp = video->pix;

 return 0;
}

static int mxc_isi_video_try_fmt(struct file *file, void *fh,
     struct v4l2_format *f)
{
 struct mxc_isi_video *video = video_drvdata(file);

 mxc_isi_format_try(video->pipe, &f->fmt.pix_mp, MXC_ISI_VIDEO_CAP);
 return 0;
}

static int mxc_isi_video_s_fmt(struct file *file, void *priv,
          struct v4l2_format *f)
{
 struct mxc_isi_video *video = video_drvdata(file);
 struct v4l2_pix_format_mplane *pix = &f->fmt.pix_mp;

 if (vb2_is_busy(&video->vb2_q))
  return -EBUSY;

 video->fmtinfo = mxc_isi_format_try(video->pipe, pix, MXC_ISI_VIDEO_CAP);
 video->pix = *pix;

 return 0;
}

static int mxc_isi_video_enum_framesizes(struct file *file, void *priv,
      struct v4l2_frmsizeenum *fsize)
{
 struct mxc_isi_video *video = video_drvdata(file);
 const struct mxc_isi_format_info *info;
 unsigned int max_width;
 unsigned int h_align;
 unsigned int v_align;

 if (fsize->index)
  return -EINVAL;

 info = mxc_isi_format_by_fourcc(fsize->pixel_format, MXC_ISI_VIDEO_CAP);
 if (!info)
  return -EINVAL;

 h_align = max_t(unsigned int, info->hsub, 1);
 v_align = max_t(unsigned int, info->vsub, 1);

 max_width = video->pipe->id == video->pipe->isi->pdata->num_channels - 1
    ? MXC_ISI_MAX_WIDTH_UNCHAINED
    : MXC_ISI_MAX_WIDTH_CHAINED;

 fsize->type = V4L2_FRMSIZE_TYPE_STEPWISE;
 fsize->stepwise.min_width = ALIGN(MXC_ISI_MIN_WIDTH, h_align);
 fsize->stepwise.min_height = ALIGN(MXC_ISI_MIN_HEIGHT, v_align);
 fsize->stepwise.max_width = ALIGN_DOWN(max_width, h_align);
 fsize->stepwise.max_height = ALIGN_DOWN(MXC_ISI_MAX_HEIGHT, v_align);
 fsize->stepwise.step_width = h_align;
 fsize->stepwise.step_height = v_align;

 /*
 * The width can be further restricted due to line buffer sharing
 * between pipelines when scaling, but we have no way to know here if
 * the scaler will be used.
 */


 return 0;
}

static const struct v4l2_ioctl_ops mxc_isi_video_ioctl_ops = {
 .vidioc_querycap  = mxc_isi_video_querycap,

 .vidioc_enum_fmt_vid_cap = mxc_isi_video_enum_fmt,
 .vidioc_try_fmt_vid_cap_mplane = mxc_isi_video_try_fmt,
 .vidioc_s_fmt_vid_cap_mplane = mxc_isi_video_s_fmt,
 .vidioc_g_fmt_vid_cap_mplane = mxc_isi_video_g_fmt,

 .vidioc_reqbufs   = vb2_ioctl_reqbufs,
 .vidioc_querybuf  = vb2_ioctl_querybuf,
 .vidioc_qbuf   = vb2_ioctl_qbuf,
 .vidioc_dqbuf   = vb2_ioctl_dqbuf,
 .vidioc_expbuf   = vb2_ioctl_expbuf,
 .vidioc_prepare_buf  = vb2_ioctl_prepare_buf,
 .vidioc_create_bufs  = vb2_ioctl_create_bufs,
 .vidioc_streamon  = vb2_ioctl_streamon,
 .vidioc_streamoff  = vb2_ioctl_streamoff,

 .vidioc_enum_framesizes  = mxc_isi_video_enum_framesizes,

 .vidioc_subscribe_event  = v4l2_ctrl_subscribe_event,
 .vidioc_unsubscribe_event = v4l2_event_unsubscribe,
};

/* -----------------------------------------------------------------------------
 * Video device file operations
 */


static int mxc_isi_video_open(struct file *file)
{
 struct mxc_isi_video *video = video_drvdata(file);
 int ret;

 ret = v4l2_fh_open(file);
 if (ret)
  return ret;

 ret = pm_runtime_resume_and_get(video->pipe->isi->dev);
 if (ret) {
  v4l2_fh_release(file);
  return ret;
 }

 return 0;
}

static int mxc_isi_video_release(struct file *file)
{
 struct mxc_isi_video *video = video_drvdata(file);
 int ret;

 ret = vb2_fop_release(file);
 if (ret)
  dev_err(video->pipe->isi->dev, "%s fail\n", __func__);

 pm_runtime_put(video->pipe->isi->dev);
 return ret;
}

static const struct v4l2_file_operations mxc_isi_video_fops = {
 .owner  = THIS_MODULE,
 .open  = mxc_isi_video_open,
 .release = mxc_isi_video_release,
 .poll  = vb2_fop_poll,
 .unlocked_ioctl = video_ioctl2,
 .mmap  = vb2_fop_mmap,
};

/* -----------------------------------------------------------------------------
 * Suspend & resume
 */


void mxc_isi_video_suspend(struct mxc_isi_pipe *pipe)
{
 struct mxc_isi_video *video = &pipe->video;

 if (!video->is_streaming)
  return;

 mxc_isi_pipe_disable(pipe);
 mxc_isi_channel_put(pipe);

 spin_lock_irq(&video->buf_lock);

 /*
 * Move the active buffers back to the pending or discard list. We must
 * iterate the active list backward and move the buffers to the head of
 * the pending list to preserve the buffer queueing order.
 */

 while (!list_empty(&video->out_active)) {
  struct mxc_isi_buffer *buf =
   list_last_entry(&video->out_active,
     struct mxc_isi_buffer, list);

  if (buf->discard)
   list_move(&buf->list, &video->out_discard);
  else
   list_move(&buf->list, &video->out_pending);
 }

 spin_unlock_irq(&video->buf_lock);
}

int mxc_isi_video_resume(struct mxc_isi_pipe *pipe)
{
 struct mxc_isi_video *video = &pipe->video;

 if (!video->is_streaming)
  return 0;

 mxc_isi_video_init_channel(video);

 spin_lock_irq(&video->buf_lock);
 mxc_isi_video_queue_first_buffers(video);
 spin_unlock_irq(&video->buf_lock);

 return mxc_isi_pipe_enable(pipe);
}

/* -----------------------------------------------------------------------------
 * Registration
 */


int mxc_isi_video_register(struct mxc_isi_pipe *pipe,
      struct v4l2_device *v4l2_dev)
{
 struct mxc_isi_video *video = &pipe->video;
 struct v4l2_pix_format_mplane *pix = &video->pix;
 struct video_device *vdev = &video->vdev;
 struct vb2_queue *q = &video->vb2_q;
 int ret = -ENOMEM;

 video->pipe = pipe;

 mutex_init(&video->lock);
 spin_lock_init(&video->buf_lock);

 pix->width = MXC_ISI_DEF_WIDTH;
 pix->height = MXC_ISI_DEF_HEIGHT;
 pix->pixelformat = MXC_ISI_DEF_PIXEL_FORMAT;
 pix->colorspace = MXC_ISI_DEF_COLOR_SPACE;
 pix->ycbcr_enc = MXC_ISI_DEF_YCBCR_ENC;
 pix->quantization = MXC_ISI_DEF_QUANTIZATION;
 pix->xfer_func = MXC_ISI_DEF_XFER_FUNC;
 video->fmtinfo = mxc_isi_format_try(video->pipe, pix, MXC_ISI_VIDEO_CAP);

 memset(vdev, 0, sizeof(*vdev));
 snprintf(vdev->name, sizeof(vdev->name), "mxc_isi.%d.capture", pipe->id);

 vdev->fops = &mxc_isi_video_fops;
 vdev->ioctl_ops = &mxc_isi_video_ioctl_ops;
 vdev->v4l2_dev = v4l2_dev;
 vdev->minor = -1;
 vdev->release = video_device_release_empty;
 vdev->queue = q;
 vdev->lock = &video->lock;

 vdev->device_caps = V4L2_CAP_STREAMING | V4L2_CAP_VIDEO_CAPTURE_MPLANE
     | V4L2_CAP_IO_MC;
 video_set_drvdata(vdev, video);

 INIT_LIST_HEAD(&video->out_pending);
 INIT_LIST_HEAD(&video->out_active);
 INIT_LIST_HEAD(&video->out_discard);

 memset(q, 0, sizeof(*q));
 q->type = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE;
 q->io_modes = VB2_MMAP | VB2_DMABUF;
 q->drv_priv = video;
 q->ops = &mxc_isi_vb2_qops;
 q->mem_ops = &vb2_dma_contig_memops;
 q->buf_struct_size = sizeof(struct mxc_isi_buffer);
 q->timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_MONOTONIC;
 q->min_queued_buffers = 2;
 q->lock = &video->lock;
 q->dev = pipe->isi->dev;

 ret = vb2_queue_init(q);
 if (ret)
  goto err_free_ctx;

 video->pad.flags = MEDIA_PAD_FL_SINK;
 vdev->entity.function = MEDIA_ENT_F_PROC_VIDEO_SCALER;
 ret = media_entity_pads_init(&vdev->entity, 1, &video->pad);
 if (ret)
  goto err_free_ctx;

 ret = mxc_isi_video_ctrls_create(video);
 if (ret)
  goto err_me_cleanup;

 ret = video_register_device(vdev, VFL_TYPE_VIDEO, -1);
 if (ret)
  goto err_ctrl_free;

 ret = media_create_pad_link(&pipe->sd.entity,
        MXC_ISI_PIPE_PAD_SOURCE,
        &vdev->entity, 0,
        MEDIA_LNK_FL_IMMUTABLE |
        MEDIA_LNK_FL_ENABLED);
 if (ret)
  goto err_video_unreg;

 return 0;

err_video_unreg:
 video_unregister_device(vdev);
err_ctrl_free:
 mxc_isi_video_ctrls_delete(video);
err_me_cleanup:
 media_entity_cleanup(&vdev->entity);
err_free_ctx:
 return ret;
}

void mxc_isi_video_unregister(struct mxc_isi_pipe *pipe)
{
 struct mxc_isi_video *video = &pipe->video;
 struct video_device *vdev = &video->vdev;

 mutex_lock(&video->lock);

 if (video_is_registered(vdev)) {
  video_unregister_device(vdev);
  mxc_isi_video_ctrls_delete(video);
  media_entity_cleanup(&vdev->entity);
 }

 mutex_unlock(&video->lock);
}

Messung V0.5
C=98 H=92 G=94

¤ Dauer der Verarbeitung: 0.5 Sekunden  (vorverarbeitet)  ¤

*© Formatika GbR, Deutschland






Wurzel

Suchen

Beweissystem der NASA

Beweissystem Isabelle

NIST Cobol Testsuite

Cephes Mathematical Library

Wiener Entwicklungsmethode

Haftungshinweis

Die Informationen auf dieser Webseite wurden nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit, noch Qualität der bereit gestellten Informationen zugesichert.

Bemerkung:

Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.