diff options
Diffstat (limited to 'drivers/media/platform/nxp/imx8-isi/imx8-isi-video.c')
-rw-r--r-- | drivers/media/platform/nxp/imx8-isi/imx8-isi-video.c | 1512 |
1 files changed, 1512 insertions, 0 deletions
diff --git a/drivers/media/platform/nxp/imx8-isi/imx8-isi-video.c b/drivers/media/platform/nxp/imx8-isi/imx8-isi-video.c new file mode 100644 index 0000000000..10840c9a09 --- /dev/null +++ b/drivers/media/platform/nxp/imx8-isi/imx8-isi-video.c @@ -0,0 +1,1512 @@ +// 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_get_try_format(sd, 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) +{ + 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); + } + + 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_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 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, + .wait_prepare = vb2_ops_wait_prepare, + .wait_finish = vb2_ops_wait_finish, + .start_streaming = mxc_isi_vb2_start_streaming, + .stop_streaming = mxc_isi_vb2_stop_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_streamon(struct file *file, void *priv, + enum v4l2_buf_type type) +{ + struct mxc_isi_video *video = video_drvdata(file); + struct media_device *mdev = &video->pipe->isi->media_dev; + struct media_pipeline *pipe; + int ret; + + if (vb2_queue_is_busy(&video->vb2_q, file)) + return -EBUSY; + + /* + * Get a pipeline for the video node and start it. This must be done + * here and not in the queue .start_streaming() handler, so that + * pipeline start errors can be reported from VIDIOC_STREAMON and not + * delayed until subsequent VIDIOC_QBUF calls. + */ + mutex_lock(&mdev->graph_mutex); + + ret = mxc_isi_pipe_acquire(video->pipe, &mxc_isi_video_frame_write_done); + if (ret) { + mutex_unlock(&mdev->graph_mutex); + return ret; + } + + pipe = media_entity_pipeline(&video->vdev.entity) ? : &video->pipe->pipe; + + ret = __video_device_pipeline_start(&video->vdev, pipe); + if (ret) { + mutex_unlock(&mdev->graph_mutex); + goto err_release; + } + + mutex_unlock(&mdev->graph_mutex); + + /* 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; + + ret = vb2_streamon(&video->vb2_q, type); + if (ret) + goto err_free; + + video->is_streaming = true; + + return 0; + +err_free: + mxc_isi_video_free_discard_buffers(video); +err_stop: + video_device_pipeline_stop(&video->vdev); +err_release: + mxc_isi_pipe_release(video->pipe); + return ret; +} + +static void mxc_isi_video_cleanup_streaming(struct mxc_isi_video *video) +{ + lockdep_assert_held(&video->lock); + + if (!video->is_streaming) + return; + + mxc_isi_video_free_discard_buffers(video); + video_device_pipeline_stop(&video->vdev); + mxc_isi_pipe_release(video->pipe); + + video->is_streaming = false; +} + +static int mxc_isi_video_streamoff(struct file *file, void *priv, + enum v4l2_buf_type type) +{ + struct mxc_isi_video *video = video_drvdata(file); + int ret; + + ret = vb2_ioctl_streamoff(file, priv, type); + if (ret) + return ret; + + mxc_isi_video_cleanup_streaming(video); + + 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 = mxc_isi_video_streamon, + .vidioc_streamoff = mxc_isi_video_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__); + + mutex_lock(&video->lock); + mxc_isi_video_cleanup_streaming(video); + mutex_unlock(&video->lock); + + 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_buffers_needed = 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); +} |