162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0
262306a36Sopenharmony_ci/*
362306a36Sopenharmony_ci * ISI V4L2 memory to memory driver for i.MX8QXP/QM platform
462306a36Sopenharmony_ci *
562306a36Sopenharmony_ci * ISI is a Image Sensor Interface of i.MX8QXP/QM platform, which
662306a36Sopenharmony_ci * used to process image from camera sensor or memory to memory or DC
762306a36Sopenharmony_ci *
862306a36Sopenharmony_ci * Copyright (c) 2019 NXP Semiconductor
962306a36Sopenharmony_ci */
1062306a36Sopenharmony_ci
1162306a36Sopenharmony_ci#include <linux/container_of.h>
1262306a36Sopenharmony_ci#include <linux/device.h>
1362306a36Sopenharmony_ci#include <linux/errno.h>
1462306a36Sopenharmony_ci#include <linux/kernel.h>
1562306a36Sopenharmony_ci#include <linux/limits.h>
1662306a36Sopenharmony_ci#include <linux/minmax.h>
1762306a36Sopenharmony_ci#include <linux/mutex.h>
1862306a36Sopenharmony_ci#include <linux/pm_runtime.h>
1962306a36Sopenharmony_ci#include <linux/slab.h>
2062306a36Sopenharmony_ci#include <linux/spinlock.h>
2162306a36Sopenharmony_ci#include <linux/string.h>
2262306a36Sopenharmony_ci#include <linux/types.h>
2362306a36Sopenharmony_ci#include <linux/videodev2.h>
2462306a36Sopenharmony_ci
2562306a36Sopenharmony_ci#include <media/media-entity.h>
2662306a36Sopenharmony_ci#include <media/v4l2-ctrls.h>
2762306a36Sopenharmony_ci#include <media/v4l2-device.h>
2862306a36Sopenharmony_ci#include <media/v4l2-event.h>
2962306a36Sopenharmony_ci#include <media/v4l2-fh.h>
3062306a36Sopenharmony_ci#include <media/v4l2-ioctl.h>
3162306a36Sopenharmony_ci#include <media/v4l2-mem2mem.h>
3262306a36Sopenharmony_ci#include <media/videobuf2-core.h>
3362306a36Sopenharmony_ci#include <media/videobuf2-dma-contig.h>
3462306a36Sopenharmony_ci
3562306a36Sopenharmony_ci#include "imx8-isi-core.h"
3662306a36Sopenharmony_ci
3762306a36Sopenharmony_cistruct mxc_isi_m2m_buffer {
3862306a36Sopenharmony_ci	struct v4l2_m2m_buffer buf;
3962306a36Sopenharmony_ci	dma_addr_t dma_addrs[3];
4062306a36Sopenharmony_ci};
4162306a36Sopenharmony_ci
4262306a36Sopenharmony_cistruct mxc_isi_m2m_ctx_queue_data {
4362306a36Sopenharmony_ci	struct v4l2_pix_format_mplane format;
4462306a36Sopenharmony_ci	const struct mxc_isi_format_info *info;
4562306a36Sopenharmony_ci	u32 sequence;
4662306a36Sopenharmony_ci};
4762306a36Sopenharmony_ci
4862306a36Sopenharmony_cistruct mxc_isi_m2m_ctx {
4962306a36Sopenharmony_ci	struct v4l2_fh fh;
5062306a36Sopenharmony_ci	struct mxc_isi_m2m *m2m;
5162306a36Sopenharmony_ci
5262306a36Sopenharmony_ci	/* Protects the m2m vb2 queues */
5362306a36Sopenharmony_ci	struct mutex vb2_lock;
5462306a36Sopenharmony_ci
5562306a36Sopenharmony_ci	struct {
5662306a36Sopenharmony_ci		struct mxc_isi_m2m_ctx_queue_data out;
5762306a36Sopenharmony_ci		struct mxc_isi_m2m_ctx_queue_data cap;
5862306a36Sopenharmony_ci	} queues;
5962306a36Sopenharmony_ci
6062306a36Sopenharmony_ci	struct {
6162306a36Sopenharmony_ci		struct v4l2_ctrl_handler handler;
6262306a36Sopenharmony_ci		unsigned int alpha;
6362306a36Sopenharmony_ci		bool hflip;
6462306a36Sopenharmony_ci		bool vflip;
6562306a36Sopenharmony_ci	} ctrls;
6662306a36Sopenharmony_ci
6762306a36Sopenharmony_ci	bool chained;
6862306a36Sopenharmony_ci};
6962306a36Sopenharmony_ci
7062306a36Sopenharmony_cistatic inline struct mxc_isi_m2m_buffer *
7162306a36Sopenharmony_cito_isi_m2m_buffer(struct vb2_v4l2_buffer *buf)
7262306a36Sopenharmony_ci{
7362306a36Sopenharmony_ci	return container_of(buf, struct mxc_isi_m2m_buffer, buf.vb);
7462306a36Sopenharmony_ci}
7562306a36Sopenharmony_ci
7662306a36Sopenharmony_cistatic inline struct mxc_isi_m2m_ctx *to_isi_m2m_ctx(struct v4l2_fh *fh)
7762306a36Sopenharmony_ci{
7862306a36Sopenharmony_ci	return container_of(fh, struct mxc_isi_m2m_ctx, fh);
7962306a36Sopenharmony_ci}
8062306a36Sopenharmony_ci
8162306a36Sopenharmony_cistatic inline struct mxc_isi_m2m_ctx_queue_data *
8262306a36Sopenharmony_cimxc_isi_m2m_ctx_qdata(struct mxc_isi_m2m_ctx *ctx, enum v4l2_buf_type type)
8362306a36Sopenharmony_ci{
8462306a36Sopenharmony_ci	if (V4L2_TYPE_IS_OUTPUT(type))
8562306a36Sopenharmony_ci		return &ctx->queues.out;
8662306a36Sopenharmony_ci	else
8762306a36Sopenharmony_ci		return &ctx->queues.cap;
8862306a36Sopenharmony_ci}
8962306a36Sopenharmony_ci
9062306a36Sopenharmony_ci/* -----------------------------------------------------------------------------
9162306a36Sopenharmony_ci * V4L2 M2M device operations
9262306a36Sopenharmony_ci */
9362306a36Sopenharmony_ci
9462306a36Sopenharmony_cistatic void mxc_isi_m2m_frame_write_done(struct mxc_isi_pipe *pipe, u32 status)
9562306a36Sopenharmony_ci{
9662306a36Sopenharmony_ci	struct mxc_isi_m2m *m2m = &pipe->isi->m2m;
9762306a36Sopenharmony_ci	struct vb2_v4l2_buffer *src_vbuf, *dst_vbuf;
9862306a36Sopenharmony_ci	struct mxc_isi_m2m_ctx *ctx;
9962306a36Sopenharmony_ci
10062306a36Sopenharmony_ci	ctx = v4l2_m2m_get_curr_priv(m2m->m2m_dev);
10162306a36Sopenharmony_ci	if (!ctx) {
10262306a36Sopenharmony_ci		dev_err(m2m->isi->dev,
10362306a36Sopenharmony_ci			"Instance released before the end of transaction\n");
10462306a36Sopenharmony_ci		return;
10562306a36Sopenharmony_ci	}
10662306a36Sopenharmony_ci
10762306a36Sopenharmony_ci	src_vbuf = v4l2_m2m_src_buf_remove(ctx->fh.m2m_ctx);
10862306a36Sopenharmony_ci	dst_vbuf = v4l2_m2m_dst_buf_remove(ctx->fh.m2m_ctx);
10962306a36Sopenharmony_ci
11062306a36Sopenharmony_ci	v4l2_m2m_buf_copy_metadata(src_vbuf, dst_vbuf, false);
11162306a36Sopenharmony_ci
11262306a36Sopenharmony_ci	src_vbuf->sequence = ctx->queues.out.sequence++;
11362306a36Sopenharmony_ci	dst_vbuf->sequence = ctx->queues.cap.sequence++;
11462306a36Sopenharmony_ci
11562306a36Sopenharmony_ci	v4l2_m2m_buf_done(src_vbuf, VB2_BUF_STATE_DONE);
11662306a36Sopenharmony_ci	v4l2_m2m_buf_done(dst_vbuf, VB2_BUF_STATE_DONE);
11762306a36Sopenharmony_ci
11862306a36Sopenharmony_ci	v4l2_m2m_job_finish(m2m->m2m_dev, ctx->fh.m2m_ctx);
11962306a36Sopenharmony_ci}
12062306a36Sopenharmony_ci
12162306a36Sopenharmony_cistatic void mxc_isi_m2m_device_run(void *priv)
12262306a36Sopenharmony_ci{
12362306a36Sopenharmony_ci	struct mxc_isi_m2m_ctx *ctx = priv;
12462306a36Sopenharmony_ci	struct mxc_isi_m2m *m2m = ctx->m2m;
12562306a36Sopenharmony_ci	struct vb2_v4l2_buffer *src_vbuf, *dst_vbuf;
12662306a36Sopenharmony_ci	struct mxc_isi_m2m_buffer *src_buf, *dst_buf;
12762306a36Sopenharmony_ci
12862306a36Sopenharmony_ci	mxc_isi_channel_disable(m2m->pipe);
12962306a36Sopenharmony_ci
13062306a36Sopenharmony_ci	mutex_lock(&m2m->lock);
13162306a36Sopenharmony_ci
13262306a36Sopenharmony_ci	/* If the context has changed, reconfigure the channel. */
13362306a36Sopenharmony_ci	if (m2m->last_ctx != ctx) {
13462306a36Sopenharmony_ci		const struct v4l2_area in_size = {
13562306a36Sopenharmony_ci			.width = ctx->queues.out.format.width,
13662306a36Sopenharmony_ci			.height = ctx->queues.out.format.height,
13762306a36Sopenharmony_ci		};
13862306a36Sopenharmony_ci		const struct v4l2_area scale = {
13962306a36Sopenharmony_ci			.width = ctx->queues.cap.format.width,
14062306a36Sopenharmony_ci			.height = ctx->queues.cap.format.height,
14162306a36Sopenharmony_ci		};
14262306a36Sopenharmony_ci		const struct v4l2_rect crop = {
14362306a36Sopenharmony_ci			.width = ctx->queues.cap.format.width,
14462306a36Sopenharmony_ci			.height = ctx->queues.cap.format.height,
14562306a36Sopenharmony_ci		};
14662306a36Sopenharmony_ci
14762306a36Sopenharmony_ci		mxc_isi_channel_config(m2m->pipe, MXC_ISI_INPUT_MEM,
14862306a36Sopenharmony_ci				       &in_size, &scale, &crop,
14962306a36Sopenharmony_ci				       ctx->queues.out.info->encoding,
15062306a36Sopenharmony_ci				       ctx->queues.cap.info->encoding);
15162306a36Sopenharmony_ci		mxc_isi_channel_set_input_format(m2m->pipe,
15262306a36Sopenharmony_ci						 ctx->queues.out.info,
15362306a36Sopenharmony_ci						 &ctx->queues.out.format);
15462306a36Sopenharmony_ci		mxc_isi_channel_set_output_format(m2m->pipe,
15562306a36Sopenharmony_ci						  ctx->queues.cap.info,
15662306a36Sopenharmony_ci						  &ctx->queues.cap.format);
15762306a36Sopenharmony_ci
15862306a36Sopenharmony_ci		m2m->last_ctx = ctx;
15962306a36Sopenharmony_ci	}
16062306a36Sopenharmony_ci
16162306a36Sopenharmony_ci	mutex_unlock(&m2m->lock);
16262306a36Sopenharmony_ci
16362306a36Sopenharmony_ci	mutex_lock(ctx->ctrls.handler.lock);
16462306a36Sopenharmony_ci	mxc_isi_channel_set_alpha(m2m->pipe, ctx->ctrls.alpha);
16562306a36Sopenharmony_ci	mxc_isi_channel_set_flip(m2m->pipe, ctx->ctrls.hflip, ctx->ctrls.vflip);
16662306a36Sopenharmony_ci	mutex_unlock(ctx->ctrls.handler.lock);
16762306a36Sopenharmony_ci
16862306a36Sopenharmony_ci	src_vbuf = v4l2_m2m_next_src_buf(ctx->fh.m2m_ctx);
16962306a36Sopenharmony_ci	dst_vbuf = v4l2_m2m_next_dst_buf(ctx->fh.m2m_ctx);
17062306a36Sopenharmony_ci
17162306a36Sopenharmony_ci	src_buf = to_isi_m2m_buffer(src_vbuf);
17262306a36Sopenharmony_ci	dst_buf = to_isi_m2m_buffer(dst_vbuf);
17362306a36Sopenharmony_ci
17462306a36Sopenharmony_ci	mxc_isi_channel_set_inbuf(m2m->pipe, src_buf->dma_addrs[0]);
17562306a36Sopenharmony_ci	mxc_isi_channel_set_outbuf(m2m->pipe, dst_buf->dma_addrs, MXC_ISI_BUF1);
17662306a36Sopenharmony_ci	mxc_isi_channel_set_outbuf(m2m->pipe, dst_buf->dma_addrs, MXC_ISI_BUF2);
17762306a36Sopenharmony_ci
17862306a36Sopenharmony_ci	mxc_isi_channel_enable(m2m->pipe);
17962306a36Sopenharmony_ci
18062306a36Sopenharmony_ci	mxc_isi_channel_m2m_start(m2m->pipe);
18162306a36Sopenharmony_ci}
18262306a36Sopenharmony_ci
18362306a36Sopenharmony_cistatic const struct v4l2_m2m_ops mxc_isi_m2m_ops = {
18462306a36Sopenharmony_ci	.device_run = mxc_isi_m2m_device_run,
18562306a36Sopenharmony_ci};
18662306a36Sopenharmony_ci
18762306a36Sopenharmony_ci/* -----------------------------------------------------------------------------
18862306a36Sopenharmony_ci * videobuf2 queue operations
18962306a36Sopenharmony_ci */
19062306a36Sopenharmony_ci
19162306a36Sopenharmony_cistatic int mxc_isi_m2m_vb2_queue_setup(struct vb2_queue *q,
19262306a36Sopenharmony_ci				       unsigned int *num_buffers,
19362306a36Sopenharmony_ci				       unsigned int *num_planes,
19462306a36Sopenharmony_ci				       unsigned int sizes[],
19562306a36Sopenharmony_ci				       struct device *alloc_devs[])
19662306a36Sopenharmony_ci{
19762306a36Sopenharmony_ci	struct mxc_isi_m2m_ctx *ctx = vb2_get_drv_priv(q);
19862306a36Sopenharmony_ci	const struct mxc_isi_m2m_ctx_queue_data *qdata =
19962306a36Sopenharmony_ci		mxc_isi_m2m_ctx_qdata(ctx, q->type);
20062306a36Sopenharmony_ci
20162306a36Sopenharmony_ci	return mxc_isi_video_queue_setup(&qdata->format, qdata->info,
20262306a36Sopenharmony_ci					 num_buffers, num_planes, sizes);
20362306a36Sopenharmony_ci}
20462306a36Sopenharmony_ci
20562306a36Sopenharmony_cistatic int mxc_isi_m2m_vb2_buffer_init(struct vb2_buffer *vb2)
20662306a36Sopenharmony_ci{
20762306a36Sopenharmony_ci	struct vb2_queue *vq = vb2->vb2_queue;
20862306a36Sopenharmony_ci	struct mxc_isi_m2m_buffer *buf = to_isi_m2m_buffer(to_vb2_v4l2_buffer(vb2));
20962306a36Sopenharmony_ci	struct mxc_isi_m2m_ctx *ctx = vb2_get_drv_priv(vb2->vb2_queue);
21062306a36Sopenharmony_ci	const struct mxc_isi_m2m_ctx_queue_data *qdata =
21162306a36Sopenharmony_ci		mxc_isi_m2m_ctx_qdata(ctx, vq->type);
21262306a36Sopenharmony_ci
21362306a36Sopenharmony_ci	mxc_isi_video_buffer_init(vb2, buf->dma_addrs, qdata->info,
21462306a36Sopenharmony_ci				  &qdata->format);
21562306a36Sopenharmony_ci
21662306a36Sopenharmony_ci	return 0;
21762306a36Sopenharmony_ci}
21862306a36Sopenharmony_ci
21962306a36Sopenharmony_cistatic int mxc_isi_m2m_vb2_buffer_prepare(struct vb2_buffer *vb2)
22062306a36Sopenharmony_ci{
22162306a36Sopenharmony_ci	struct vb2_queue *vq = vb2->vb2_queue;
22262306a36Sopenharmony_ci	struct mxc_isi_m2m_ctx *ctx = vb2_get_drv_priv(vq);
22362306a36Sopenharmony_ci	const struct mxc_isi_m2m_ctx_queue_data *qdata =
22462306a36Sopenharmony_ci		mxc_isi_m2m_ctx_qdata(ctx, vq->type);
22562306a36Sopenharmony_ci
22662306a36Sopenharmony_ci	return mxc_isi_video_buffer_prepare(ctx->m2m->isi, vb2, qdata->info,
22762306a36Sopenharmony_ci					    &qdata->format);
22862306a36Sopenharmony_ci}
22962306a36Sopenharmony_ci
23062306a36Sopenharmony_cistatic void mxc_isi_m2m_vb2_buffer_queue(struct vb2_buffer *vb2)
23162306a36Sopenharmony_ci{
23262306a36Sopenharmony_ci	struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb2);
23362306a36Sopenharmony_ci	struct mxc_isi_m2m_ctx *ctx = vb2_get_drv_priv(vb2->vb2_queue);
23462306a36Sopenharmony_ci
23562306a36Sopenharmony_ci	v4l2_m2m_buf_queue(ctx->fh.m2m_ctx, vbuf);
23662306a36Sopenharmony_ci}
23762306a36Sopenharmony_ci
23862306a36Sopenharmony_cistatic int mxc_isi_m2m_vb2_start_streaming(struct vb2_queue *q,
23962306a36Sopenharmony_ci					   unsigned int count)
24062306a36Sopenharmony_ci{
24162306a36Sopenharmony_ci	struct mxc_isi_m2m_ctx *ctx = vb2_get_drv_priv(q);
24262306a36Sopenharmony_ci	struct mxc_isi_m2m_ctx_queue_data *qdata =
24362306a36Sopenharmony_ci		mxc_isi_m2m_ctx_qdata(ctx, q->type);
24462306a36Sopenharmony_ci
24562306a36Sopenharmony_ci	qdata->sequence = 0;
24662306a36Sopenharmony_ci
24762306a36Sopenharmony_ci	return 0;
24862306a36Sopenharmony_ci}
24962306a36Sopenharmony_ci
25062306a36Sopenharmony_cistatic void mxc_isi_m2m_vb2_stop_streaming(struct vb2_queue *q)
25162306a36Sopenharmony_ci{
25262306a36Sopenharmony_ci	struct mxc_isi_m2m_ctx *ctx = vb2_get_drv_priv(q);
25362306a36Sopenharmony_ci	struct vb2_v4l2_buffer *vbuf;
25462306a36Sopenharmony_ci
25562306a36Sopenharmony_ci	for (;;) {
25662306a36Sopenharmony_ci		if (q->type == V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE)
25762306a36Sopenharmony_ci			vbuf = v4l2_m2m_src_buf_remove(ctx->fh.m2m_ctx);
25862306a36Sopenharmony_ci		else
25962306a36Sopenharmony_ci			vbuf = v4l2_m2m_dst_buf_remove(ctx->fh.m2m_ctx);
26062306a36Sopenharmony_ci		if (!vbuf)
26162306a36Sopenharmony_ci			break;
26262306a36Sopenharmony_ci
26362306a36Sopenharmony_ci		v4l2_m2m_buf_done(vbuf, VB2_BUF_STATE_ERROR);
26462306a36Sopenharmony_ci	}
26562306a36Sopenharmony_ci}
26662306a36Sopenharmony_ci
26762306a36Sopenharmony_cistatic const struct vb2_ops mxc_isi_m2m_vb2_qops = {
26862306a36Sopenharmony_ci	.queue_setup		= mxc_isi_m2m_vb2_queue_setup,
26962306a36Sopenharmony_ci	.buf_init		= mxc_isi_m2m_vb2_buffer_init,
27062306a36Sopenharmony_ci	.buf_prepare		= mxc_isi_m2m_vb2_buffer_prepare,
27162306a36Sopenharmony_ci	.buf_queue		= mxc_isi_m2m_vb2_buffer_queue,
27262306a36Sopenharmony_ci	.wait_prepare		= vb2_ops_wait_prepare,
27362306a36Sopenharmony_ci	.wait_finish		= vb2_ops_wait_finish,
27462306a36Sopenharmony_ci	.start_streaming	= mxc_isi_m2m_vb2_start_streaming,
27562306a36Sopenharmony_ci	.stop_streaming		= mxc_isi_m2m_vb2_stop_streaming,
27662306a36Sopenharmony_ci};
27762306a36Sopenharmony_ci
27862306a36Sopenharmony_cistatic int mxc_isi_m2m_queue_init(void *priv, struct vb2_queue *src_vq,
27962306a36Sopenharmony_ci				  struct vb2_queue *dst_vq)
28062306a36Sopenharmony_ci{
28162306a36Sopenharmony_ci	struct mxc_isi_m2m_ctx *ctx = priv;
28262306a36Sopenharmony_ci	struct mxc_isi_m2m *m2m = ctx->m2m;
28362306a36Sopenharmony_ci	int ret;
28462306a36Sopenharmony_ci
28562306a36Sopenharmony_ci	src_vq->type = V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE;
28662306a36Sopenharmony_ci	src_vq->io_modes = VB2_MMAP | VB2_DMABUF;
28762306a36Sopenharmony_ci	src_vq->drv_priv = ctx;
28862306a36Sopenharmony_ci	src_vq->buf_struct_size = sizeof(struct mxc_isi_m2m_buffer);
28962306a36Sopenharmony_ci	src_vq->ops = &mxc_isi_m2m_vb2_qops;
29062306a36Sopenharmony_ci	src_vq->mem_ops = &vb2_dma_contig_memops;
29162306a36Sopenharmony_ci	src_vq->timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_COPY;
29262306a36Sopenharmony_ci	src_vq->lock = &ctx->vb2_lock;
29362306a36Sopenharmony_ci	src_vq->dev = m2m->isi->dev;
29462306a36Sopenharmony_ci
29562306a36Sopenharmony_ci	ret = vb2_queue_init(src_vq);
29662306a36Sopenharmony_ci	if (ret)
29762306a36Sopenharmony_ci		return ret;
29862306a36Sopenharmony_ci
29962306a36Sopenharmony_ci	dst_vq->type = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE;
30062306a36Sopenharmony_ci	dst_vq->io_modes = VB2_MMAP | VB2_DMABUF;
30162306a36Sopenharmony_ci	dst_vq->drv_priv = ctx;
30262306a36Sopenharmony_ci	dst_vq->buf_struct_size = sizeof(struct mxc_isi_m2m_buffer);
30362306a36Sopenharmony_ci	dst_vq->ops = &mxc_isi_m2m_vb2_qops;
30462306a36Sopenharmony_ci	dst_vq->mem_ops = &vb2_dma_contig_memops;
30562306a36Sopenharmony_ci	dst_vq->timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_COPY;
30662306a36Sopenharmony_ci	dst_vq->lock = &ctx->vb2_lock;
30762306a36Sopenharmony_ci	dst_vq->dev = m2m->isi->dev;
30862306a36Sopenharmony_ci
30962306a36Sopenharmony_ci	return vb2_queue_init(dst_vq);
31062306a36Sopenharmony_ci}
31162306a36Sopenharmony_ci
31262306a36Sopenharmony_ci/* -----------------------------------------------------------------------------
31362306a36Sopenharmony_ci * V4L2 controls
31462306a36Sopenharmony_ci */
31562306a36Sopenharmony_ci
31662306a36Sopenharmony_cistatic inline struct mxc_isi_m2m_ctx *
31762306a36Sopenharmony_cictrl_to_mxc_isi_m2m_ctx(struct v4l2_ctrl *ctrl)
31862306a36Sopenharmony_ci{
31962306a36Sopenharmony_ci	return container_of(ctrl->handler, struct mxc_isi_m2m_ctx, ctrls.handler);
32062306a36Sopenharmony_ci}
32162306a36Sopenharmony_ci
32262306a36Sopenharmony_cistatic int mxc_isi_m2m_ctx_s_ctrl(struct v4l2_ctrl *ctrl)
32362306a36Sopenharmony_ci{
32462306a36Sopenharmony_ci	struct mxc_isi_m2m_ctx *ctx = ctrl_to_mxc_isi_m2m_ctx(ctrl);
32562306a36Sopenharmony_ci
32662306a36Sopenharmony_ci	switch (ctrl->id) {
32762306a36Sopenharmony_ci	case V4L2_CID_HFLIP:
32862306a36Sopenharmony_ci		ctx->ctrls.hflip = ctrl->val;
32962306a36Sopenharmony_ci		break;
33062306a36Sopenharmony_ci
33162306a36Sopenharmony_ci	case V4L2_CID_VFLIP:
33262306a36Sopenharmony_ci		ctx->ctrls.vflip = ctrl->val;
33362306a36Sopenharmony_ci		break;
33462306a36Sopenharmony_ci
33562306a36Sopenharmony_ci	case V4L2_CID_ALPHA_COMPONENT:
33662306a36Sopenharmony_ci		ctx->ctrls.alpha = ctrl->val;
33762306a36Sopenharmony_ci		break;
33862306a36Sopenharmony_ci	}
33962306a36Sopenharmony_ci
34062306a36Sopenharmony_ci	return 0;
34162306a36Sopenharmony_ci}
34262306a36Sopenharmony_ci
34362306a36Sopenharmony_cistatic const struct v4l2_ctrl_ops mxc_isi_m2m_ctx_ctrl_ops = {
34462306a36Sopenharmony_ci	.s_ctrl = mxc_isi_m2m_ctx_s_ctrl,
34562306a36Sopenharmony_ci};
34662306a36Sopenharmony_ci
34762306a36Sopenharmony_cistatic int mxc_isi_m2m_ctx_ctrls_create(struct mxc_isi_m2m_ctx *ctx)
34862306a36Sopenharmony_ci{
34962306a36Sopenharmony_ci	struct v4l2_ctrl_handler *handler = &ctx->ctrls.handler;
35062306a36Sopenharmony_ci	int ret;
35162306a36Sopenharmony_ci
35262306a36Sopenharmony_ci	v4l2_ctrl_handler_init(handler, 3);
35362306a36Sopenharmony_ci
35462306a36Sopenharmony_ci	v4l2_ctrl_new_std(handler, &mxc_isi_m2m_ctx_ctrl_ops,
35562306a36Sopenharmony_ci			  V4L2_CID_ALPHA_COMPONENT, 0, 255, 1, 0);
35662306a36Sopenharmony_ci	v4l2_ctrl_new_std(handler, &mxc_isi_m2m_ctx_ctrl_ops,
35762306a36Sopenharmony_ci			  V4L2_CID_HFLIP, 0, 1, 1, 0);
35862306a36Sopenharmony_ci	v4l2_ctrl_new_std(handler, &mxc_isi_m2m_ctx_ctrl_ops,
35962306a36Sopenharmony_ci			  V4L2_CID_VFLIP, 0, 1, 1, 0);
36062306a36Sopenharmony_ci
36162306a36Sopenharmony_ci	if (handler->error) {
36262306a36Sopenharmony_ci		ret = handler->error;
36362306a36Sopenharmony_ci		v4l2_ctrl_handler_free(handler);
36462306a36Sopenharmony_ci		return ret;
36562306a36Sopenharmony_ci	}
36662306a36Sopenharmony_ci
36762306a36Sopenharmony_ci	ctx->fh.ctrl_handler = handler;
36862306a36Sopenharmony_ci
36962306a36Sopenharmony_ci	return 0;
37062306a36Sopenharmony_ci}
37162306a36Sopenharmony_ci
37262306a36Sopenharmony_cistatic void mxc_isi_m2m_ctx_ctrls_delete(struct mxc_isi_m2m_ctx *ctx)
37362306a36Sopenharmony_ci{
37462306a36Sopenharmony_ci	v4l2_ctrl_handler_free(&ctx->ctrls.handler);
37562306a36Sopenharmony_ci}
37662306a36Sopenharmony_ci
37762306a36Sopenharmony_ci/* -----------------------------------------------------------------------------
37862306a36Sopenharmony_ci * V4L2 ioctls
37962306a36Sopenharmony_ci */
38062306a36Sopenharmony_ci
38162306a36Sopenharmony_cistatic int mxc_isi_m2m_querycap(struct file *file, void *fh,
38262306a36Sopenharmony_ci				struct v4l2_capability *cap)
38362306a36Sopenharmony_ci{
38462306a36Sopenharmony_ci	strscpy(cap->driver, MXC_ISI_DRIVER_NAME, sizeof(cap->driver));
38562306a36Sopenharmony_ci	strscpy(cap->card, MXC_ISI_M2M, sizeof(cap->card));
38662306a36Sopenharmony_ci	cap->device_caps = V4L2_CAP_STREAMING | V4L2_CAP_VIDEO_M2M_MPLANE;
38762306a36Sopenharmony_ci	cap->capabilities = cap->device_caps | V4L2_CAP_DEVICE_CAPS;
38862306a36Sopenharmony_ci
38962306a36Sopenharmony_ci	return 0;
39062306a36Sopenharmony_ci}
39162306a36Sopenharmony_ci
39262306a36Sopenharmony_cistatic int mxc_isi_m2m_enum_fmt_vid(struct file *file, void *fh,
39362306a36Sopenharmony_ci				    struct v4l2_fmtdesc *f)
39462306a36Sopenharmony_ci{
39562306a36Sopenharmony_ci	const enum mxc_isi_video_type type =
39662306a36Sopenharmony_ci		f->type == V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE ?
39762306a36Sopenharmony_ci		MXC_ISI_VIDEO_M2M_OUT : MXC_ISI_VIDEO_M2M_CAP;
39862306a36Sopenharmony_ci	const struct mxc_isi_format_info *info;
39962306a36Sopenharmony_ci
40062306a36Sopenharmony_ci	info = mxc_isi_format_enum(f->index, type);
40162306a36Sopenharmony_ci	if (!info)
40262306a36Sopenharmony_ci		return -EINVAL;
40362306a36Sopenharmony_ci
40462306a36Sopenharmony_ci	f->pixelformat = info->fourcc;
40562306a36Sopenharmony_ci	f->flags |= V4L2_FMT_FLAG_CSC_COLORSPACE | V4L2_FMT_FLAG_CSC_YCBCR_ENC
40662306a36Sopenharmony_ci		 |  V4L2_FMT_FLAG_CSC_QUANTIZATION | V4L2_FMT_FLAG_CSC_XFER_FUNC;
40762306a36Sopenharmony_ci
40862306a36Sopenharmony_ci	return 0;
40962306a36Sopenharmony_ci}
41062306a36Sopenharmony_ci
41162306a36Sopenharmony_cistatic const struct mxc_isi_format_info *
41262306a36Sopenharmony_ci__mxc_isi_m2m_try_fmt_vid(struct mxc_isi_m2m_ctx *ctx,
41362306a36Sopenharmony_ci			  struct v4l2_pix_format_mplane *pix,
41462306a36Sopenharmony_ci			  const enum mxc_isi_video_type type)
41562306a36Sopenharmony_ci{
41662306a36Sopenharmony_ci	if (type == MXC_ISI_VIDEO_M2M_CAP) {
41762306a36Sopenharmony_ci		/* Downscaling only  */
41862306a36Sopenharmony_ci		pix->width = min(pix->width, ctx->queues.out.format.width);
41962306a36Sopenharmony_ci		pix->height = min(pix->height, ctx->queues.out.format.height);
42062306a36Sopenharmony_ci	}
42162306a36Sopenharmony_ci
42262306a36Sopenharmony_ci	return mxc_isi_format_try(ctx->m2m->pipe, pix, type);
42362306a36Sopenharmony_ci}
42462306a36Sopenharmony_ci
42562306a36Sopenharmony_cistatic int mxc_isi_m2m_try_fmt_vid(struct file *file, void *fh,
42662306a36Sopenharmony_ci				   struct v4l2_format *f)
42762306a36Sopenharmony_ci{
42862306a36Sopenharmony_ci	const enum mxc_isi_video_type type =
42962306a36Sopenharmony_ci		f->type == V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE ?
43062306a36Sopenharmony_ci		MXC_ISI_VIDEO_M2M_OUT : MXC_ISI_VIDEO_M2M_CAP;
43162306a36Sopenharmony_ci	struct mxc_isi_m2m_ctx *ctx = to_isi_m2m_ctx(fh);
43262306a36Sopenharmony_ci
43362306a36Sopenharmony_ci	__mxc_isi_m2m_try_fmt_vid(ctx, &f->fmt.pix_mp, type);
43462306a36Sopenharmony_ci
43562306a36Sopenharmony_ci	return 0;
43662306a36Sopenharmony_ci}
43762306a36Sopenharmony_ci
43862306a36Sopenharmony_cistatic int mxc_isi_m2m_g_fmt_vid(struct file *file, void *fh,
43962306a36Sopenharmony_ci				 struct v4l2_format *f)
44062306a36Sopenharmony_ci{
44162306a36Sopenharmony_ci	struct mxc_isi_m2m_ctx *ctx = to_isi_m2m_ctx(fh);
44262306a36Sopenharmony_ci	const struct mxc_isi_m2m_ctx_queue_data *qdata =
44362306a36Sopenharmony_ci		mxc_isi_m2m_ctx_qdata(ctx, f->type);
44462306a36Sopenharmony_ci
44562306a36Sopenharmony_ci	f->fmt.pix_mp = qdata->format;
44662306a36Sopenharmony_ci
44762306a36Sopenharmony_ci	return 0;
44862306a36Sopenharmony_ci}
44962306a36Sopenharmony_ci
45062306a36Sopenharmony_cistatic int mxc_isi_m2m_s_fmt_vid(struct file *file, void *fh,
45162306a36Sopenharmony_ci				 struct v4l2_format *f)
45262306a36Sopenharmony_ci{
45362306a36Sopenharmony_ci	const enum mxc_isi_video_type type =
45462306a36Sopenharmony_ci		f->type == V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE ?
45562306a36Sopenharmony_ci		MXC_ISI_VIDEO_M2M_OUT : MXC_ISI_VIDEO_M2M_CAP;
45662306a36Sopenharmony_ci	struct mxc_isi_m2m_ctx *ctx = to_isi_m2m_ctx(fh);
45762306a36Sopenharmony_ci	struct v4l2_pix_format_mplane *pix = &f->fmt.pix_mp;
45862306a36Sopenharmony_ci	const struct mxc_isi_format_info *info;
45962306a36Sopenharmony_ci	struct vb2_queue *vq;
46062306a36Sopenharmony_ci
46162306a36Sopenharmony_ci	vq = v4l2_m2m_get_vq(ctx->fh.m2m_ctx, f->type);
46262306a36Sopenharmony_ci	if (!vq)
46362306a36Sopenharmony_ci		return -EINVAL;
46462306a36Sopenharmony_ci
46562306a36Sopenharmony_ci	if (vb2_is_busy(vq))
46662306a36Sopenharmony_ci		return -EBUSY;
46762306a36Sopenharmony_ci
46862306a36Sopenharmony_ci	info = __mxc_isi_m2m_try_fmt_vid(ctx, pix, type);
46962306a36Sopenharmony_ci
47062306a36Sopenharmony_ci	if (f->type == V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE) {
47162306a36Sopenharmony_ci		ctx->queues.out.format = *pix;
47262306a36Sopenharmony_ci		ctx->queues.out.info = info;
47362306a36Sopenharmony_ci	}
47462306a36Sopenharmony_ci
47562306a36Sopenharmony_ci	/*
47662306a36Sopenharmony_ci	 * Always set the format on the capture side, due to either format
47762306a36Sopenharmony_ci	 * propagation or direct setting.
47862306a36Sopenharmony_ci	 */
47962306a36Sopenharmony_ci	ctx->queues.cap.format = *pix;
48062306a36Sopenharmony_ci	ctx->queues.cap.info = info;
48162306a36Sopenharmony_ci
48262306a36Sopenharmony_ci	return 0;
48362306a36Sopenharmony_ci}
48462306a36Sopenharmony_ci
48562306a36Sopenharmony_cistatic int mxc_isi_m2m_streamon(struct file *file, void *fh,
48662306a36Sopenharmony_ci				enum v4l2_buf_type type)
48762306a36Sopenharmony_ci{
48862306a36Sopenharmony_ci	struct mxc_isi_m2m_ctx *ctx = to_isi_m2m_ctx(fh);
48962306a36Sopenharmony_ci	const struct v4l2_pix_format_mplane *out_pix = &ctx->queues.out.format;
49062306a36Sopenharmony_ci	const struct v4l2_pix_format_mplane *cap_pix = &ctx->queues.cap.format;
49162306a36Sopenharmony_ci	const struct mxc_isi_format_info *cap_info = ctx->queues.cap.info;
49262306a36Sopenharmony_ci	const struct mxc_isi_format_info *out_info = ctx->queues.out.info;
49362306a36Sopenharmony_ci	struct mxc_isi_m2m *m2m = ctx->m2m;
49462306a36Sopenharmony_ci	bool bypass;
49562306a36Sopenharmony_ci
49662306a36Sopenharmony_ci	int ret;
49762306a36Sopenharmony_ci
49862306a36Sopenharmony_ci	mutex_lock(&m2m->lock);
49962306a36Sopenharmony_ci
50062306a36Sopenharmony_ci	if (m2m->usage_count == INT_MAX) {
50162306a36Sopenharmony_ci		ret = -EOVERFLOW;
50262306a36Sopenharmony_ci		goto unlock;
50362306a36Sopenharmony_ci	}
50462306a36Sopenharmony_ci
50562306a36Sopenharmony_ci	bypass = cap_pix->width == out_pix->width &&
50662306a36Sopenharmony_ci		 cap_pix->height == out_pix->height &&
50762306a36Sopenharmony_ci		 cap_info->encoding == out_info->encoding;
50862306a36Sopenharmony_ci
50962306a36Sopenharmony_ci	/*
51062306a36Sopenharmony_ci	 * Acquire the pipe and initialize the channel with the first user of
51162306a36Sopenharmony_ci	 * the M2M device.
51262306a36Sopenharmony_ci	 */
51362306a36Sopenharmony_ci	if (m2m->usage_count == 0) {
51462306a36Sopenharmony_ci		ret = mxc_isi_channel_acquire(m2m->pipe,
51562306a36Sopenharmony_ci					      &mxc_isi_m2m_frame_write_done,
51662306a36Sopenharmony_ci					      bypass);
51762306a36Sopenharmony_ci		if (ret)
51862306a36Sopenharmony_ci			goto unlock;
51962306a36Sopenharmony_ci
52062306a36Sopenharmony_ci		mxc_isi_channel_get(m2m->pipe);
52162306a36Sopenharmony_ci	}
52262306a36Sopenharmony_ci
52362306a36Sopenharmony_ci	m2m->usage_count++;
52462306a36Sopenharmony_ci
52562306a36Sopenharmony_ci	/*
52662306a36Sopenharmony_ci	 * Allocate resources for the channel, counting how many users require
52762306a36Sopenharmony_ci	 * buffer chaining.
52862306a36Sopenharmony_ci	 */
52962306a36Sopenharmony_ci	if (!ctx->chained && out_pix->width > MXC_ISI_MAX_WIDTH_UNCHAINED) {
53062306a36Sopenharmony_ci		ret = mxc_isi_channel_chain(m2m->pipe, bypass);
53162306a36Sopenharmony_ci		if (ret)
53262306a36Sopenharmony_ci			goto deinit;
53362306a36Sopenharmony_ci
53462306a36Sopenharmony_ci		m2m->chained_count++;
53562306a36Sopenharmony_ci		ctx->chained = true;
53662306a36Sopenharmony_ci	}
53762306a36Sopenharmony_ci
53862306a36Sopenharmony_ci	/*
53962306a36Sopenharmony_ci	 * Drop the lock to start the stream, as the .device_run() operation
54062306a36Sopenharmony_ci	 * needs to acquire it.
54162306a36Sopenharmony_ci	 */
54262306a36Sopenharmony_ci	mutex_unlock(&m2m->lock);
54362306a36Sopenharmony_ci	ret = v4l2_m2m_ioctl_streamon(file, fh, type);
54462306a36Sopenharmony_ci	if (ret) {
54562306a36Sopenharmony_ci		/* Reacquire the lock for the cleanup path. */
54662306a36Sopenharmony_ci		mutex_lock(&m2m->lock);
54762306a36Sopenharmony_ci		goto unchain;
54862306a36Sopenharmony_ci	}
54962306a36Sopenharmony_ci
55062306a36Sopenharmony_ci	return 0;
55162306a36Sopenharmony_ci
55262306a36Sopenharmony_ciunchain:
55362306a36Sopenharmony_ci	if (ctx->chained && --m2m->chained_count == 0)
55462306a36Sopenharmony_ci		mxc_isi_channel_unchain(m2m->pipe);
55562306a36Sopenharmony_ci	ctx->chained = false;
55662306a36Sopenharmony_ci
55762306a36Sopenharmony_cideinit:
55862306a36Sopenharmony_ci	if (--m2m->usage_count == 0) {
55962306a36Sopenharmony_ci		mxc_isi_channel_put(m2m->pipe);
56062306a36Sopenharmony_ci		mxc_isi_channel_release(m2m->pipe);
56162306a36Sopenharmony_ci	}
56262306a36Sopenharmony_ci
56362306a36Sopenharmony_ciunlock:
56462306a36Sopenharmony_ci	mutex_unlock(&m2m->lock);
56562306a36Sopenharmony_ci	return ret;
56662306a36Sopenharmony_ci}
56762306a36Sopenharmony_ci
56862306a36Sopenharmony_cistatic int mxc_isi_m2m_streamoff(struct file *file, void *fh,
56962306a36Sopenharmony_ci				 enum v4l2_buf_type type)
57062306a36Sopenharmony_ci{
57162306a36Sopenharmony_ci	struct mxc_isi_m2m_ctx *ctx = to_isi_m2m_ctx(fh);
57262306a36Sopenharmony_ci	struct mxc_isi_m2m *m2m = ctx->m2m;
57362306a36Sopenharmony_ci
57462306a36Sopenharmony_ci	v4l2_m2m_ioctl_streamoff(file, fh, type);
57562306a36Sopenharmony_ci
57662306a36Sopenharmony_ci	mutex_lock(&m2m->lock);
57762306a36Sopenharmony_ci
57862306a36Sopenharmony_ci	/*
57962306a36Sopenharmony_ci	 * If the last context is this one, reset it to make sure the device
58062306a36Sopenharmony_ci	 * will be reconfigured when streaming is restarted.
58162306a36Sopenharmony_ci	 */
58262306a36Sopenharmony_ci	if (m2m->last_ctx == ctx)
58362306a36Sopenharmony_ci		m2m->last_ctx = NULL;
58462306a36Sopenharmony_ci
58562306a36Sopenharmony_ci	/* Free the channel resources if this is the last chained context. */
58662306a36Sopenharmony_ci	if (ctx->chained && --m2m->chained_count == 0)
58762306a36Sopenharmony_ci		mxc_isi_channel_unchain(m2m->pipe);
58862306a36Sopenharmony_ci	ctx->chained = false;
58962306a36Sopenharmony_ci
59062306a36Sopenharmony_ci	/* Turn off the light with the last user. */
59162306a36Sopenharmony_ci	if (--m2m->usage_count == 0) {
59262306a36Sopenharmony_ci		mxc_isi_channel_disable(m2m->pipe);
59362306a36Sopenharmony_ci		mxc_isi_channel_put(m2m->pipe);
59462306a36Sopenharmony_ci		mxc_isi_channel_release(m2m->pipe);
59562306a36Sopenharmony_ci	}
59662306a36Sopenharmony_ci
59762306a36Sopenharmony_ci	WARN_ON(m2m->usage_count < 0);
59862306a36Sopenharmony_ci
59962306a36Sopenharmony_ci	mutex_unlock(&m2m->lock);
60062306a36Sopenharmony_ci
60162306a36Sopenharmony_ci	return 0;
60262306a36Sopenharmony_ci}
60362306a36Sopenharmony_ci
60462306a36Sopenharmony_cistatic const struct v4l2_ioctl_ops mxc_isi_m2m_ioctl_ops = {
60562306a36Sopenharmony_ci	.vidioc_querycap		= mxc_isi_m2m_querycap,
60662306a36Sopenharmony_ci
60762306a36Sopenharmony_ci	.vidioc_enum_fmt_vid_cap	= mxc_isi_m2m_enum_fmt_vid,
60862306a36Sopenharmony_ci	.vidioc_enum_fmt_vid_out	= mxc_isi_m2m_enum_fmt_vid,
60962306a36Sopenharmony_ci	.vidioc_g_fmt_vid_cap_mplane	= mxc_isi_m2m_g_fmt_vid,
61062306a36Sopenharmony_ci	.vidioc_g_fmt_vid_out_mplane	= mxc_isi_m2m_g_fmt_vid,
61162306a36Sopenharmony_ci	.vidioc_s_fmt_vid_cap_mplane	= mxc_isi_m2m_s_fmt_vid,
61262306a36Sopenharmony_ci	.vidioc_s_fmt_vid_out_mplane	= mxc_isi_m2m_s_fmt_vid,
61362306a36Sopenharmony_ci	.vidioc_try_fmt_vid_cap_mplane	= mxc_isi_m2m_try_fmt_vid,
61462306a36Sopenharmony_ci	.vidioc_try_fmt_vid_out_mplane	= mxc_isi_m2m_try_fmt_vid,
61562306a36Sopenharmony_ci
61662306a36Sopenharmony_ci	.vidioc_reqbufs			= v4l2_m2m_ioctl_reqbufs,
61762306a36Sopenharmony_ci	.vidioc_querybuf		= v4l2_m2m_ioctl_querybuf,
61862306a36Sopenharmony_ci	.vidioc_qbuf			= v4l2_m2m_ioctl_qbuf,
61962306a36Sopenharmony_ci	.vidioc_dqbuf			= v4l2_m2m_ioctl_dqbuf,
62062306a36Sopenharmony_ci	.vidioc_expbuf			= v4l2_m2m_ioctl_expbuf,
62162306a36Sopenharmony_ci	.vidioc_prepare_buf		= v4l2_m2m_ioctl_prepare_buf,
62262306a36Sopenharmony_ci	.vidioc_create_bufs		= v4l2_m2m_ioctl_create_bufs,
62362306a36Sopenharmony_ci
62462306a36Sopenharmony_ci	.vidioc_streamon		= mxc_isi_m2m_streamon,
62562306a36Sopenharmony_ci	.vidioc_streamoff		= mxc_isi_m2m_streamoff,
62662306a36Sopenharmony_ci
62762306a36Sopenharmony_ci	.vidioc_subscribe_event		= v4l2_ctrl_subscribe_event,
62862306a36Sopenharmony_ci	.vidioc_unsubscribe_event	= v4l2_event_unsubscribe,
62962306a36Sopenharmony_ci};
63062306a36Sopenharmony_ci
63162306a36Sopenharmony_ci/* -----------------------------------------------------------------------------
63262306a36Sopenharmony_ci * Video device file operations
63362306a36Sopenharmony_ci */
63462306a36Sopenharmony_ci
63562306a36Sopenharmony_cistatic void mxc_isi_m2m_init_format(struct mxc_isi_m2m_ctx *ctx,
63662306a36Sopenharmony_ci				    struct mxc_isi_m2m_ctx_queue_data *qdata,
63762306a36Sopenharmony_ci				    enum mxc_isi_video_type type)
63862306a36Sopenharmony_ci{
63962306a36Sopenharmony_ci	qdata->format.width = MXC_ISI_DEF_WIDTH;
64062306a36Sopenharmony_ci	qdata->format.height = MXC_ISI_DEF_HEIGHT;
64162306a36Sopenharmony_ci	qdata->format.pixelformat = MXC_ISI_DEF_PIXEL_FORMAT;
64262306a36Sopenharmony_ci
64362306a36Sopenharmony_ci	qdata->info = mxc_isi_format_try(ctx->m2m->pipe, &qdata->format, type);
64462306a36Sopenharmony_ci}
64562306a36Sopenharmony_ci
64662306a36Sopenharmony_cistatic int mxc_isi_m2m_open(struct file *file)
64762306a36Sopenharmony_ci{
64862306a36Sopenharmony_ci	struct video_device *vdev = video_devdata(file);
64962306a36Sopenharmony_ci	struct mxc_isi_m2m *m2m = video_drvdata(file);
65062306a36Sopenharmony_ci	struct mxc_isi_m2m_ctx *ctx;
65162306a36Sopenharmony_ci	int ret;
65262306a36Sopenharmony_ci
65362306a36Sopenharmony_ci	ctx = kzalloc(sizeof(*ctx), GFP_KERNEL);
65462306a36Sopenharmony_ci	if (!ctx)
65562306a36Sopenharmony_ci		return -ENOMEM;
65662306a36Sopenharmony_ci
65762306a36Sopenharmony_ci	ctx->m2m = m2m;
65862306a36Sopenharmony_ci	mutex_init(&ctx->vb2_lock);
65962306a36Sopenharmony_ci
66062306a36Sopenharmony_ci	v4l2_fh_init(&ctx->fh, vdev);
66162306a36Sopenharmony_ci	file->private_data = &ctx->fh;
66262306a36Sopenharmony_ci
66362306a36Sopenharmony_ci	ctx->fh.m2m_ctx = v4l2_m2m_ctx_init(m2m->m2m_dev, ctx,
66462306a36Sopenharmony_ci					    &mxc_isi_m2m_queue_init);
66562306a36Sopenharmony_ci	if (IS_ERR(ctx->fh.m2m_ctx)) {
66662306a36Sopenharmony_ci		ret = PTR_ERR(ctx->fh.m2m_ctx);
66762306a36Sopenharmony_ci		ctx->fh.m2m_ctx = NULL;
66862306a36Sopenharmony_ci		goto err_fh;
66962306a36Sopenharmony_ci	}
67062306a36Sopenharmony_ci
67162306a36Sopenharmony_ci	mxc_isi_m2m_init_format(ctx, &ctx->queues.out, MXC_ISI_VIDEO_M2M_OUT);
67262306a36Sopenharmony_ci	mxc_isi_m2m_init_format(ctx, &ctx->queues.cap, MXC_ISI_VIDEO_M2M_CAP);
67362306a36Sopenharmony_ci
67462306a36Sopenharmony_ci	ret = mxc_isi_m2m_ctx_ctrls_create(ctx);
67562306a36Sopenharmony_ci	if (ret)
67662306a36Sopenharmony_ci		goto err_ctx;
67762306a36Sopenharmony_ci
67862306a36Sopenharmony_ci	ret = pm_runtime_resume_and_get(m2m->isi->dev);
67962306a36Sopenharmony_ci	if (ret)
68062306a36Sopenharmony_ci		goto err_ctrls;
68162306a36Sopenharmony_ci
68262306a36Sopenharmony_ci	v4l2_fh_add(&ctx->fh);
68362306a36Sopenharmony_ci
68462306a36Sopenharmony_ci	return 0;
68562306a36Sopenharmony_ci
68662306a36Sopenharmony_cierr_ctrls:
68762306a36Sopenharmony_ci	mxc_isi_m2m_ctx_ctrls_delete(ctx);
68862306a36Sopenharmony_cierr_ctx:
68962306a36Sopenharmony_ci	v4l2_m2m_ctx_release(ctx->fh.m2m_ctx);
69062306a36Sopenharmony_cierr_fh:
69162306a36Sopenharmony_ci	v4l2_fh_exit(&ctx->fh);
69262306a36Sopenharmony_ci	mutex_destroy(&ctx->vb2_lock);
69362306a36Sopenharmony_ci	kfree(ctx);
69462306a36Sopenharmony_ci	return ret;
69562306a36Sopenharmony_ci}
69662306a36Sopenharmony_ci
69762306a36Sopenharmony_cistatic int mxc_isi_m2m_release(struct file *file)
69862306a36Sopenharmony_ci{
69962306a36Sopenharmony_ci	struct mxc_isi_m2m *m2m = video_drvdata(file);
70062306a36Sopenharmony_ci	struct mxc_isi_m2m_ctx *ctx = to_isi_m2m_ctx(file->private_data);
70162306a36Sopenharmony_ci
70262306a36Sopenharmony_ci	v4l2_m2m_ctx_release(ctx->fh.m2m_ctx);
70362306a36Sopenharmony_ci	mxc_isi_m2m_ctx_ctrls_delete(ctx);
70462306a36Sopenharmony_ci
70562306a36Sopenharmony_ci	v4l2_fh_del(&ctx->fh);
70662306a36Sopenharmony_ci	v4l2_fh_exit(&ctx->fh);
70762306a36Sopenharmony_ci
70862306a36Sopenharmony_ci	mutex_destroy(&ctx->vb2_lock);
70962306a36Sopenharmony_ci	kfree(ctx);
71062306a36Sopenharmony_ci
71162306a36Sopenharmony_ci	pm_runtime_put(m2m->isi->dev);
71262306a36Sopenharmony_ci
71362306a36Sopenharmony_ci	return 0;
71462306a36Sopenharmony_ci}
71562306a36Sopenharmony_ci
71662306a36Sopenharmony_cistatic const struct v4l2_file_operations mxc_isi_m2m_fops = {
71762306a36Sopenharmony_ci	.owner		= THIS_MODULE,
71862306a36Sopenharmony_ci	.open		= mxc_isi_m2m_open,
71962306a36Sopenharmony_ci	.release	= mxc_isi_m2m_release,
72062306a36Sopenharmony_ci	.poll		= v4l2_m2m_fop_poll,
72162306a36Sopenharmony_ci	.unlocked_ioctl	= video_ioctl2,
72262306a36Sopenharmony_ci	.mmap		= v4l2_m2m_fop_mmap,
72362306a36Sopenharmony_ci};
72462306a36Sopenharmony_ci
72562306a36Sopenharmony_ci/* -----------------------------------------------------------------------------
72662306a36Sopenharmony_ci * Registration
72762306a36Sopenharmony_ci */
72862306a36Sopenharmony_ci
72962306a36Sopenharmony_ciint mxc_isi_m2m_register(struct mxc_isi_dev *isi, struct v4l2_device *v4l2_dev)
73062306a36Sopenharmony_ci{
73162306a36Sopenharmony_ci	struct mxc_isi_m2m *m2m = &isi->m2m;
73262306a36Sopenharmony_ci	struct video_device *vdev = &m2m->vdev;
73362306a36Sopenharmony_ci	struct media_link *link;
73462306a36Sopenharmony_ci	int ret;
73562306a36Sopenharmony_ci
73662306a36Sopenharmony_ci	m2m->isi = isi;
73762306a36Sopenharmony_ci	m2m->pipe = &isi->pipes[0];
73862306a36Sopenharmony_ci
73962306a36Sopenharmony_ci	mutex_init(&m2m->lock);
74062306a36Sopenharmony_ci
74162306a36Sopenharmony_ci	/* Initialize the video device and create controls. */
74262306a36Sopenharmony_ci	snprintf(vdev->name, sizeof(vdev->name), "mxc_isi.m2m");
74362306a36Sopenharmony_ci
74462306a36Sopenharmony_ci	vdev->fops	= &mxc_isi_m2m_fops;
74562306a36Sopenharmony_ci	vdev->ioctl_ops	= &mxc_isi_m2m_ioctl_ops;
74662306a36Sopenharmony_ci	vdev->v4l2_dev	= v4l2_dev;
74762306a36Sopenharmony_ci	vdev->minor	= -1;
74862306a36Sopenharmony_ci	vdev->release	= video_device_release_empty;
74962306a36Sopenharmony_ci	vdev->vfl_dir	= VFL_DIR_M2M;
75062306a36Sopenharmony_ci
75162306a36Sopenharmony_ci	vdev->device_caps = V4L2_CAP_STREAMING | V4L2_CAP_VIDEO_M2M_MPLANE;
75262306a36Sopenharmony_ci	video_set_drvdata(vdev, m2m);
75362306a36Sopenharmony_ci
75462306a36Sopenharmony_ci	/* Create the M2M device. */
75562306a36Sopenharmony_ci	m2m->m2m_dev = v4l2_m2m_init(&mxc_isi_m2m_ops);
75662306a36Sopenharmony_ci	if (IS_ERR(m2m->m2m_dev)) {
75762306a36Sopenharmony_ci		dev_err(isi->dev, "failed to initialize m2m device\n");
75862306a36Sopenharmony_ci		ret = PTR_ERR(m2m->m2m_dev);
75962306a36Sopenharmony_ci		goto err_mutex;
76062306a36Sopenharmony_ci	}
76162306a36Sopenharmony_ci
76262306a36Sopenharmony_ci	/* Register the video device. */
76362306a36Sopenharmony_ci	ret = video_register_device(vdev, VFL_TYPE_VIDEO, -1);
76462306a36Sopenharmony_ci	if (ret < 0) {
76562306a36Sopenharmony_ci		dev_err(isi->dev, "failed to register m2m device\n");
76662306a36Sopenharmony_ci		goto err_m2m;
76762306a36Sopenharmony_ci	}
76862306a36Sopenharmony_ci
76962306a36Sopenharmony_ci	/*
77062306a36Sopenharmony_ci	 * Populate the media graph. We can't use the mem2mem helper
77162306a36Sopenharmony_ci	 * v4l2_m2m_register_media_controller() as the M2M interface needs to
77262306a36Sopenharmony_ci	 * be connected to the existing entities in the graph, so we have to
77362306a36Sopenharmony_ci	 * wire things up manually:
77462306a36Sopenharmony_ci	 *
77562306a36Sopenharmony_ci	 * - The entity in the video_device, which isn't touched by the V4L2
77662306a36Sopenharmony_ci	 *   core for M2M devices, is used as the source I/O entity in the
77762306a36Sopenharmony_ci	 *   graph, connected to the crossbar switch.
77862306a36Sopenharmony_ci	 *
77962306a36Sopenharmony_ci	 * - The video device at the end of the pipeline provides the sink
78062306a36Sopenharmony_ci	 *   entity, and is already wired up in the graph.
78162306a36Sopenharmony_ci	 *
78262306a36Sopenharmony_ci	 * - A new interface is created, pointing at both entities. The sink
78362306a36Sopenharmony_ci	 *   entity will thus have two interfaces pointing to it.
78462306a36Sopenharmony_ci	 */
78562306a36Sopenharmony_ci	m2m->pad.flags = MEDIA_PAD_FL_SOURCE;
78662306a36Sopenharmony_ci	vdev->entity.name = "mxc_isi.output";
78762306a36Sopenharmony_ci	vdev->entity.function = MEDIA_ENT_F_IO_V4L;
78862306a36Sopenharmony_ci	ret = media_entity_pads_init(&vdev->entity, 1, &m2m->pad);
78962306a36Sopenharmony_ci	if (ret)
79062306a36Sopenharmony_ci		goto err_video;
79162306a36Sopenharmony_ci
79262306a36Sopenharmony_ci	ret = media_device_register_entity(v4l2_dev->mdev, &vdev->entity);
79362306a36Sopenharmony_ci	if (ret)
79462306a36Sopenharmony_ci		goto err_entity_cleanup;
79562306a36Sopenharmony_ci
79662306a36Sopenharmony_ci	ret = media_create_pad_link(&vdev->entity, 0,
79762306a36Sopenharmony_ci				    &m2m->isi->crossbar.sd.entity,
79862306a36Sopenharmony_ci				    m2m->isi->crossbar.num_sinks - 1,
79962306a36Sopenharmony_ci				    MEDIA_LNK_FL_IMMUTABLE |
80062306a36Sopenharmony_ci				    MEDIA_LNK_FL_ENABLED);
80162306a36Sopenharmony_ci	if (ret)
80262306a36Sopenharmony_ci		goto err_entity_unreg;
80362306a36Sopenharmony_ci
80462306a36Sopenharmony_ci	m2m->intf = media_devnode_create(v4l2_dev->mdev, MEDIA_INTF_T_V4L_VIDEO,
80562306a36Sopenharmony_ci					 0, VIDEO_MAJOR, vdev->minor);
80662306a36Sopenharmony_ci	if (!m2m->intf) {
80762306a36Sopenharmony_ci		ret = -ENOMEM;
80862306a36Sopenharmony_ci		goto err_entity_unreg;
80962306a36Sopenharmony_ci	}
81062306a36Sopenharmony_ci
81162306a36Sopenharmony_ci	link = media_create_intf_link(&vdev->entity, &m2m->intf->intf,
81262306a36Sopenharmony_ci				      MEDIA_LNK_FL_IMMUTABLE |
81362306a36Sopenharmony_ci				      MEDIA_LNK_FL_ENABLED);
81462306a36Sopenharmony_ci	if (!link) {
81562306a36Sopenharmony_ci		ret = -ENOMEM;
81662306a36Sopenharmony_ci		goto err_devnode;
81762306a36Sopenharmony_ci	}
81862306a36Sopenharmony_ci
81962306a36Sopenharmony_ci	link = media_create_intf_link(&m2m->pipe->video.vdev.entity,
82062306a36Sopenharmony_ci				      &m2m->intf->intf,
82162306a36Sopenharmony_ci				      MEDIA_LNK_FL_IMMUTABLE |
82262306a36Sopenharmony_ci				      MEDIA_LNK_FL_ENABLED);
82362306a36Sopenharmony_ci	if (!link) {
82462306a36Sopenharmony_ci		ret = -ENOMEM;
82562306a36Sopenharmony_ci		goto err_devnode;
82662306a36Sopenharmony_ci	}
82762306a36Sopenharmony_ci
82862306a36Sopenharmony_ci	return 0;
82962306a36Sopenharmony_ci
83062306a36Sopenharmony_cierr_devnode:
83162306a36Sopenharmony_ci	media_devnode_remove(m2m->intf);
83262306a36Sopenharmony_cierr_entity_unreg:
83362306a36Sopenharmony_ci	media_device_unregister_entity(&vdev->entity);
83462306a36Sopenharmony_cierr_entity_cleanup:
83562306a36Sopenharmony_ci	media_entity_cleanup(&vdev->entity);
83662306a36Sopenharmony_cierr_video:
83762306a36Sopenharmony_ci	video_unregister_device(vdev);
83862306a36Sopenharmony_cierr_m2m:
83962306a36Sopenharmony_ci	v4l2_m2m_release(m2m->m2m_dev);
84062306a36Sopenharmony_cierr_mutex:
84162306a36Sopenharmony_ci	mutex_destroy(&m2m->lock);
84262306a36Sopenharmony_ci	return ret;
84362306a36Sopenharmony_ci}
84462306a36Sopenharmony_ci
84562306a36Sopenharmony_ciint mxc_isi_m2m_unregister(struct mxc_isi_dev *isi)
84662306a36Sopenharmony_ci{
84762306a36Sopenharmony_ci	struct mxc_isi_m2m *m2m = &isi->m2m;
84862306a36Sopenharmony_ci	struct video_device *vdev = &m2m->vdev;
84962306a36Sopenharmony_ci
85062306a36Sopenharmony_ci	video_unregister_device(vdev);
85162306a36Sopenharmony_ci
85262306a36Sopenharmony_ci	v4l2_m2m_release(m2m->m2m_dev);
85362306a36Sopenharmony_ci	media_devnode_remove(m2m->intf);
85462306a36Sopenharmony_ci	media_entity_cleanup(&vdev->entity);
85562306a36Sopenharmony_ci	mutex_destroy(&m2m->lock);
85662306a36Sopenharmony_ci
85762306a36Sopenharmony_ci	return 0;
85862306a36Sopenharmony_ci}
859