162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-or-later
262306a36Sopenharmony_ci/*
362306a36Sopenharmony_ci * Samsung S5P/EXYNOS4 SoC series FIMC (video postprocessor) driver
462306a36Sopenharmony_ci *
562306a36Sopenharmony_ci * Copyright (C) 2012 - 2013 Samsung Electronics Co., Ltd.
662306a36Sopenharmony_ci * Sylwester Nawrocki <s.nawrocki@samsung.com>
762306a36Sopenharmony_ci */
862306a36Sopenharmony_ci
962306a36Sopenharmony_ci#include <linux/module.h>
1062306a36Sopenharmony_ci#include <linux/kernel.h>
1162306a36Sopenharmony_ci#include <linux/types.h>
1262306a36Sopenharmony_ci#include <linux/errno.h>
1362306a36Sopenharmony_ci#include <linux/bug.h>
1462306a36Sopenharmony_ci#include <linux/interrupt.h>
1562306a36Sopenharmony_ci#include <linux/device.h>
1662306a36Sopenharmony_ci#include <linux/platform_device.h>
1762306a36Sopenharmony_ci#include <linux/pm_runtime.h>
1862306a36Sopenharmony_ci#include <linux/list.h>
1962306a36Sopenharmony_ci#include <linux/io.h>
2062306a36Sopenharmony_ci#include <linux/slab.h>
2162306a36Sopenharmony_ci#include <linux/clk.h>
2262306a36Sopenharmony_ci#include <media/v4l2-ioctl.h>
2362306a36Sopenharmony_ci#include <media/videobuf2-v4l2.h>
2462306a36Sopenharmony_ci#include <media/videobuf2-dma-contig.h>
2562306a36Sopenharmony_ci
2662306a36Sopenharmony_ci#include "common.h"
2762306a36Sopenharmony_ci#include "fimc-core.h"
2862306a36Sopenharmony_ci#include "fimc-reg.h"
2962306a36Sopenharmony_ci#include "media-dev.h"
3062306a36Sopenharmony_ci
3162306a36Sopenharmony_cistatic unsigned int get_m2m_fmt_flags(unsigned int stream_type)
3262306a36Sopenharmony_ci{
3362306a36Sopenharmony_ci	if (stream_type == V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE)
3462306a36Sopenharmony_ci		return FMT_FLAGS_M2M_IN;
3562306a36Sopenharmony_ci	else
3662306a36Sopenharmony_ci		return FMT_FLAGS_M2M_OUT;
3762306a36Sopenharmony_ci}
3862306a36Sopenharmony_ci
3962306a36Sopenharmony_civoid fimc_m2m_job_finish(struct fimc_ctx *ctx, int vb_state)
4062306a36Sopenharmony_ci{
4162306a36Sopenharmony_ci	struct vb2_v4l2_buffer *src_vb, *dst_vb;
4262306a36Sopenharmony_ci
4362306a36Sopenharmony_ci	if (!ctx || !ctx->fh.m2m_ctx)
4462306a36Sopenharmony_ci		return;
4562306a36Sopenharmony_ci
4662306a36Sopenharmony_ci	src_vb = v4l2_m2m_src_buf_remove(ctx->fh.m2m_ctx);
4762306a36Sopenharmony_ci	dst_vb = v4l2_m2m_dst_buf_remove(ctx->fh.m2m_ctx);
4862306a36Sopenharmony_ci
4962306a36Sopenharmony_ci	if (src_vb)
5062306a36Sopenharmony_ci		v4l2_m2m_buf_done(src_vb, vb_state);
5162306a36Sopenharmony_ci	if (dst_vb)
5262306a36Sopenharmony_ci		v4l2_m2m_buf_done(dst_vb, vb_state);
5362306a36Sopenharmony_ci	if (src_vb && dst_vb)
5462306a36Sopenharmony_ci		v4l2_m2m_job_finish(ctx->fimc_dev->m2m.m2m_dev,
5562306a36Sopenharmony_ci				    ctx->fh.m2m_ctx);
5662306a36Sopenharmony_ci}
5762306a36Sopenharmony_ci
5862306a36Sopenharmony_ci/* Complete the transaction which has been scheduled for execution. */
5962306a36Sopenharmony_cistatic void fimc_m2m_shutdown(struct fimc_ctx *ctx)
6062306a36Sopenharmony_ci{
6162306a36Sopenharmony_ci	struct fimc_dev *fimc = ctx->fimc_dev;
6262306a36Sopenharmony_ci
6362306a36Sopenharmony_ci	if (!fimc_m2m_pending(fimc))
6462306a36Sopenharmony_ci		return;
6562306a36Sopenharmony_ci
6662306a36Sopenharmony_ci	fimc_ctx_state_set(FIMC_CTX_SHUT, ctx);
6762306a36Sopenharmony_ci
6862306a36Sopenharmony_ci	wait_event_timeout(fimc->irq_queue,
6962306a36Sopenharmony_ci			!fimc_ctx_state_is_set(FIMC_CTX_SHUT, ctx),
7062306a36Sopenharmony_ci			FIMC_SHUTDOWN_TIMEOUT);
7162306a36Sopenharmony_ci}
7262306a36Sopenharmony_ci
7362306a36Sopenharmony_cistatic int start_streaming(struct vb2_queue *q, unsigned int count)
7462306a36Sopenharmony_ci{
7562306a36Sopenharmony_ci	struct fimc_ctx *ctx = q->drv_priv;
7662306a36Sopenharmony_ci
7762306a36Sopenharmony_ci	return pm_runtime_resume_and_get(&ctx->fimc_dev->pdev->dev);
7862306a36Sopenharmony_ci}
7962306a36Sopenharmony_ci
8062306a36Sopenharmony_cistatic void stop_streaming(struct vb2_queue *q)
8162306a36Sopenharmony_ci{
8262306a36Sopenharmony_ci	struct fimc_ctx *ctx = q->drv_priv;
8362306a36Sopenharmony_ci
8462306a36Sopenharmony_ci	fimc_m2m_shutdown(ctx);
8562306a36Sopenharmony_ci	fimc_m2m_job_finish(ctx, VB2_BUF_STATE_ERROR);
8662306a36Sopenharmony_ci	pm_runtime_put(&ctx->fimc_dev->pdev->dev);
8762306a36Sopenharmony_ci}
8862306a36Sopenharmony_ci
8962306a36Sopenharmony_cistatic void fimc_device_run(void *priv)
9062306a36Sopenharmony_ci{
9162306a36Sopenharmony_ci	struct vb2_v4l2_buffer *src_vb, *dst_vb;
9262306a36Sopenharmony_ci	struct fimc_ctx *ctx = priv;
9362306a36Sopenharmony_ci	struct fimc_frame *sf, *df;
9462306a36Sopenharmony_ci	struct fimc_dev *fimc;
9562306a36Sopenharmony_ci	unsigned long flags;
9662306a36Sopenharmony_ci	int ret;
9762306a36Sopenharmony_ci
9862306a36Sopenharmony_ci	if (WARN(!ctx, "Null context\n"))
9962306a36Sopenharmony_ci		return;
10062306a36Sopenharmony_ci
10162306a36Sopenharmony_ci	fimc = ctx->fimc_dev;
10262306a36Sopenharmony_ci	spin_lock_irqsave(&fimc->slock, flags);
10362306a36Sopenharmony_ci
10462306a36Sopenharmony_ci	set_bit(ST_M2M_PEND, &fimc->state);
10562306a36Sopenharmony_ci	sf = &ctx->s_frame;
10662306a36Sopenharmony_ci	df = &ctx->d_frame;
10762306a36Sopenharmony_ci
10862306a36Sopenharmony_ci	if (ctx->state & FIMC_PARAMS) {
10962306a36Sopenharmony_ci		/* Prepare the DMA offsets for scaler */
11062306a36Sopenharmony_ci		fimc_prepare_dma_offset(ctx, sf);
11162306a36Sopenharmony_ci		fimc_prepare_dma_offset(ctx, df);
11262306a36Sopenharmony_ci	}
11362306a36Sopenharmony_ci
11462306a36Sopenharmony_ci	src_vb = v4l2_m2m_next_src_buf(ctx->fh.m2m_ctx);
11562306a36Sopenharmony_ci	ret = fimc_prepare_addr(ctx, &src_vb->vb2_buf, sf, &sf->addr);
11662306a36Sopenharmony_ci	if (ret)
11762306a36Sopenharmony_ci		goto dma_unlock;
11862306a36Sopenharmony_ci
11962306a36Sopenharmony_ci	dst_vb = v4l2_m2m_next_dst_buf(ctx->fh.m2m_ctx);
12062306a36Sopenharmony_ci	ret = fimc_prepare_addr(ctx, &dst_vb->vb2_buf, df, &df->addr);
12162306a36Sopenharmony_ci	if (ret)
12262306a36Sopenharmony_ci		goto dma_unlock;
12362306a36Sopenharmony_ci
12462306a36Sopenharmony_ci	dst_vb->vb2_buf.timestamp = src_vb->vb2_buf.timestamp;
12562306a36Sopenharmony_ci	dst_vb->flags &= ~V4L2_BUF_FLAG_TSTAMP_SRC_MASK;
12662306a36Sopenharmony_ci	dst_vb->flags |=
12762306a36Sopenharmony_ci		src_vb->flags & V4L2_BUF_FLAG_TSTAMP_SRC_MASK;
12862306a36Sopenharmony_ci
12962306a36Sopenharmony_ci	/* Reconfigure hardware if the context has changed. */
13062306a36Sopenharmony_ci	if (fimc->m2m.ctx != ctx) {
13162306a36Sopenharmony_ci		ctx->state |= FIMC_PARAMS;
13262306a36Sopenharmony_ci		fimc->m2m.ctx = ctx;
13362306a36Sopenharmony_ci	}
13462306a36Sopenharmony_ci
13562306a36Sopenharmony_ci	if (ctx->state & FIMC_PARAMS) {
13662306a36Sopenharmony_ci		fimc_set_yuv_order(ctx);
13762306a36Sopenharmony_ci		fimc_hw_set_input_path(ctx);
13862306a36Sopenharmony_ci		fimc_hw_set_in_dma(ctx);
13962306a36Sopenharmony_ci		ret = fimc_set_scaler_info(ctx);
14062306a36Sopenharmony_ci		if (ret)
14162306a36Sopenharmony_ci			goto dma_unlock;
14262306a36Sopenharmony_ci		fimc_hw_set_prescaler(ctx);
14362306a36Sopenharmony_ci		fimc_hw_set_mainscaler(ctx);
14462306a36Sopenharmony_ci		fimc_hw_set_target_format(ctx);
14562306a36Sopenharmony_ci		fimc_hw_set_rotation(ctx);
14662306a36Sopenharmony_ci		fimc_hw_set_effect(ctx);
14762306a36Sopenharmony_ci		fimc_hw_set_out_dma(ctx);
14862306a36Sopenharmony_ci		if (fimc->drv_data->alpha_color)
14962306a36Sopenharmony_ci			fimc_hw_set_rgb_alpha(ctx);
15062306a36Sopenharmony_ci		fimc_hw_set_output_path(ctx);
15162306a36Sopenharmony_ci	}
15262306a36Sopenharmony_ci	fimc_hw_set_input_addr(fimc, &sf->addr);
15362306a36Sopenharmony_ci	fimc_hw_set_output_addr(fimc, &df->addr, -1);
15462306a36Sopenharmony_ci
15562306a36Sopenharmony_ci	fimc_activate_capture(ctx);
15662306a36Sopenharmony_ci	ctx->state &= (FIMC_CTX_M2M | FIMC_CTX_CAP);
15762306a36Sopenharmony_ci	fimc_hw_activate_input_dma(fimc, true);
15862306a36Sopenharmony_ci
15962306a36Sopenharmony_cidma_unlock:
16062306a36Sopenharmony_ci	spin_unlock_irqrestore(&fimc->slock, flags);
16162306a36Sopenharmony_ci}
16262306a36Sopenharmony_ci
16362306a36Sopenharmony_cistatic void fimc_job_abort(void *priv)
16462306a36Sopenharmony_ci{
16562306a36Sopenharmony_ci	fimc_m2m_shutdown(priv);
16662306a36Sopenharmony_ci}
16762306a36Sopenharmony_ci
16862306a36Sopenharmony_cistatic int fimc_queue_setup(struct vb2_queue *vq,
16962306a36Sopenharmony_ci			    unsigned int *num_buffers, unsigned int *num_planes,
17062306a36Sopenharmony_ci			    unsigned int sizes[], struct device *alloc_devs[])
17162306a36Sopenharmony_ci{
17262306a36Sopenharmony_ci	struct fimc_ctx *ctx = vb2_get_drv_priv(vq);
17362306a36Sopenharmony_ci	struct fimc_frame *f;
17462306a36Sopenharmony_ci	int i;
17562306a36Sopenharmony_ci
17662306a36Sopenharmony_ci	f = ctx_get_frame(ctx, vq->type);
17762306a36Sopenharmony_ci	if (IS_ERR(f))
17862306a36Sopenharmony_ci		return PTR_ERR(f);
17962306a36Sopenharmony_ci	/*
18062306a36Sopenharmony_ci	 * Return number of non-contiguous planes (plane buffers)
18162306a36Sopenharmony_ci	 * depending on the configured color format.
18262306a36Sopenharmony_ci	 */
18362306a36Sopenharmony_ci	if (!f->fmt)
18462306a36Sopenharmony_ci		return -EINVAL;
18562306a36Sopenharmony_ci
18662306a36Sopenharmony_ci	*num_planes = f->fmt->memplanes;
18762306a36Sopenharmony_ci	for (i = 0; i < f->fmt->memplanes; i++)
18862306a36Sopenharmony_ci		sizes[i] = f->payload[i];
18962306a36Sopenharmony_ci	return 0;
19062306a36Sopenharmony_ci}
19162306a36Sopenharmony_ci
19262306a36Sopenharmony_cistatic int fimc_buf_prepare(struct vb2_buffer *vb)
19362306a36Sopenharmony_ci{
19462306a36Sopenharmony_ci	struct fimc_ctx *ctx = vb2_get_drv_priv(vb->vb2_queue);
19562306a36Sopenharmony_ci	struct fimc_frame *frame;
19662306a36Sopenharmony_ci	int i;
19762306a36Sopenharmony_ci
19862306a36Sopenharmony_ci	frame = ctx_get_frame(ctx, vb->vb2_queue->type);
19962306a36Sopenharmony_ci	if (IS_ERR(frame))
20062306a36Sopenharmony_ci		return PTR_ERR(frame);
20162306a36Sopenharmony_ci
20262306a36Sopenharmony_ci	for (i = 0; i < frame->fmt->memplanes; i++)
20362306a36Sopenharmony_ci		vb2_set_plane_payload(vb, i, frame->payload[i]);
20462306a36Sopenharmony_ci
20562306a36Sopenharmony_ci	return 0;
20662306a36Sopenharmony_ci}
20762306a36Sopenharmony_ci
20862306a36Sopenharmony_cistatic void fimc_buf_queue(struct vb2_buffer *vb)
20962306a36Sopenharmony_ci{
21062306a36Sopenharmony_ci	struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb);
21162306a36Sopenharmony_ci	struct fimc_ctx *ctx = vb2_get_drv_priv(vb->vb2_queue);
21262306a36Sopenharmony_ci	v4l2_m2m_buf_queue(ctx->fh.m2m_ctx, vbuf);
21362306a36Sopenharmony_ci}
21462306a36Sopenharmony_ci
21562306a36Sopenharmony_cistatic const struct vb2_ops fimc_qops = {
21662306a36Sopenharmony_ci	.queue_setup	 = fimc_queue_setup,
21762306a36Sopenharmony_ci	.buf_prepare	 = fimc_buf_prepare,
21862306a36Sopenharmony_ci	.buf_queue	 = fimc_buf_queue,
21962306a36Sopenharmony_ci	.wait_prepare	 = vb2_ops_wait_prepare,
22062306a36Sopenharmony_ci	.wait_finish	 = vb2_ops_wait_finish,
22162306a36Sopenharmony_ci	.stop_streaming	 = stop_streaming,
22262306a36Sopenharmony_ci	.start_streaming = start_streaming,
22362306a36Sopenharmony_ci};
22462306a36Sopenharmony_ci
22562306a36Sopenharmony_ci/*
22662306a36Sopenharmony_ci * V4L2 ioctl handlers
22762306a36Sopenharmony_ci */
22862306a36Sopenharmony_cistatic int fimc_m2m_querycap(struct file *file, void *fh,
22962306a36Sopenharmony_ci				     struct v4l2_capability *cap)
23062306a36Sopenharmony_ci{
23162306a36Sopenharmony_ci	struct fimc_dev *fimc = video_drvdata(file);
23262306a36Sopenharmony_ci
23362306a36Sopenharmony_ci	__fimc_vidioc_querycap(&fimc->pdev->dev, cap);
23462306a36Sopenharmony_ci	return 0;
23562306a36Sopenharmony_ci}
23662306a36Sopenharmony_ci
23762306a36Sopenharmony_cistatic int fimc_m2m_enum_fmt(struct file *file, void *priv,
23862306a36Sopenharmony_ci			     struct v4l2_fmtdesc *f)
23962306a36Sopenharmony_ci{
24062306a36Sopenharmony_ci	struct fimc_fmt *fmt;
24162306a36Sopenharmony_ci
24262306a36Sopenharmony_ci	fmt = fimc_find_format(NULL, NULL, get_m2m_fmt_flags(f->type),
24362306a36Sopenharmony_ci			       f->index);
24462306a36Sopenharmony_ci	if (!fmt)
24562306a36Sopenharmony_ci		return -EINVAL;
24662306a36Sopenharmony_ci
24762306a36Sopenharmony_ci	f->pixelformat = fmt->fourcc;
24862306a36Sopenharmony_ci	return 0;
24962306a36Sopenharmony_ci}
25062306a36Sopenharmony_ci
25162306a36Sopenharmony_cistatic int fimc_m2m_g_fmt_mplane(struct file *file, void *fh,
25262306a36Sopenharmony_ci				 struct v4l2_format *f)
25362306a36Sopenharmony_ci{
25462306a36Sopenharmony_ci	struct fimc_ctx *ctx = fh_to_ctx(fh);
25562306a36Sopenharmony_ci	struct fimc_frame *frame = ctx_get_frame(ctx, f->type);
25662306a36Sopenharmony_ci
25762306a36Sopenharmony_ci	if (IS_ERR(frame))
25862306a36Sopenharmony_ci		return PTR_ERR(frame);
25962306a36Sopenharmony_ci
26062306a36Sopenharmony_ci	__fimc_get_format(frame, f);
26162306a36Sopenharmony_ci	return 0;
26262306a36Sopenharmony_ci}
26362306a36Sopenharmony_ci
26462306a36Sopenharmony_cistatic int fimc_try_fmt_mplane(struct fimc_ctx *ctx, struct v4l2_format *f)
26562306a36Sopenharmony_ci{
26662306a36Sopenharmony_ci	struct fimc_dev *fimc = ctx->fimc_dev;
26762306a36Sopenharmony_ci	const struct fimc_variant *variant = fimc->variant;
26862306a36Sopenharmony_ci	struct v4l2_pix_format_mplane *pix = &f->fmt.pix_mp;
26962306a36Sopenharmony_ci	struct fimc_fmt *fmt;
27062306a36Sopenharmony_ci	u32 max_w, mod_x, mod_y;
27162306a36Sopenharmony_ci
27262306a36Sopenharmony_ci	if (!IS_M2M(f->type))
27362306a36Sopenharmony_ci		return -EINVAL;
27462306a36Sopenharmony_ci
27562306a36Sopenharmony_ci	fmt = fimc_find_format(&pix->pixelformat, NULL,
27662306a36Sopenharmony_ci			       get_m2m_fmt_flags(f->type), 0);
27762306a36Sopenharmony_ci	if (WARN(fmt == NULL, "Pixel format lookup failed"))
27862306a36Sopenharmony_ci		return -EINVAL;
27962306a36Sopenharmony_ci
28062306a36Sopenharmony_ci	if (pix->field == V4L2_FIELD_ANY)
28162306a36Sopenharmony_ci		pix->field = V4L2_FIELD_NONE;
28262306a36Sopenharmony_ci	else if (pix->field != V4L2_FIELD_NONE)
28362306a36Sopenharmony_ci		return -EINVAL;
28462306a36Sopenharmony_ci
28562306a36Sopenharmony_ci	if (f->type == V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE) {
28662306a36Sopenharmony_ci		max_w = variant->pix_limit->scaler_dis_w;
28762306a36Sopenharmony_ci		mod_x = ffs(variant->min_inp_pixsize) - 1;
28862306a36Sopenharmony_ci	} else {
28962306a36Sopenharmony_ci		max_w = variant->pix_limit->out_rot_dis_w;
29062306a36Sopenharmony_ci		mod_x = ffs(variant->min_out_pixsize) - 1;
29162306a36Sopenharmony_ci	}
29262306a36Sopenharmony_ci
29362306a36Sopenharmony_ci	if (tiled_fmt(fmt)) {
29462306a36Sopenharmony_ci		mod_x = 6; /* 64 x 32 pixels tile */
29562306a36Sopenharmony_ci		mod_y = 5;
29662306a36Sopenharmony_ci	} else {
29762306a36Sopenharmony_ci		if (variant->min_vsize_align == 1)
29862306a36Sopenharmony_ci			mod_y = fimc_fmt_is_rgb(fmt->color) ? 0 : 1;
29962306a36Sopenharmony_ci		else
30062306a36Sopenharmony_ci			mod_y = ffs(variant->min_vsize_align) - 1;
30162306a36Sopenharmony_ci	}
30262306a36Sopenharmony_ci
30362306a36Sopenharmony_ci	v4l_bound_align_image(&pix->width, 16, max_w, mod_x,
30462306a36Sopenharmony_ci		&pix->height, 8, variant->pix_limit->scaler_dis_w, mod_y, 0);
30562306a36Sopenharmony_ci
30662306a36Sopenharmony_ci	fimc_adjust_mplane_format(fmt, pix->width, pix->height, &f->fmt.pix_mp);
30762306a36Sopenharmony_ci	return 0;
30862306a36Sopenharmony_ci}
30962306a36Sopenharmony_ci
31062306a36Sopenharmony_cistatic int fimc_m2m_try_fmt_mplane(struct file *file, void *fh,
31162306a36Sopenharmony_ci				   struct v4l2_format *f)
31262306a36Sopenharmony_ci{
31362306a36Sopenharmony_ci	struct fimc_ctx *ctx = fh_to_ctx(fh);
31462306a36Sopenharmony_ci	return fimc_try_fmt_mplane(ctx, f);
31562306a36Sopenharmony_ci}
31662306a36Sopenharmony_ci
31762306a36Sopenharmony_cistatic void __set_frame_format(struct fimc_frame *frame, struct fimc_fmt *fmt,
31862306a36Sopenharmony_ci			       struct v4l2_pix_format_mplane *pixm)
31962306a36Sopenharmony_ci{
32062306a36Sopenharmony_ci	int i;
32162306a36Sopenharmony_ci
32262306a36Sopenharmony_ci	for (i = 0; i < fmt->memplanes; i++) {
32362306a36Sopenharmony_ci		frame->bytesperline[i] = pixm->plane_fmt[i].bytesperline;
32462306a36Sopenharmony_ci		frame->payload[i] = pixm->plane_fmt[i].sizeimage;
32562306a36Sopenharmony_ci	}
32662306a36Sopenharmony_ci
32762306a36Sopenharmony_ci	frame->f_width = pixm->width;
32862306a36Sopenharmony_ci	frame->f_height	= pixm->height;
32962306a36Sopenharmony_ci	frame->o_width = pixm->width;
33062306a36Sopenharmony_ci	frame->o_height = pixm->height;
33162306a36Sopenharmony_ci	frame->width = pixm->width;
33262306a36Sopenharmony_ci	frame->height = pixm->height;
33362306a36Sopenharmony_ci	frame->offs_h = 0;
33462306a36Sopenharmony_ci	frame->offs_v = 0;
33562306a36Sopenharmony_ci	frame->fmt = fmt;
33662306a36Sopenharmony_ci}
33762306a36Sopenharmony_ci
33862306a36Sopenharmony_cistatic int fimc_m2m_s_fmt_mplane(struct file *file, void *fh,
33962306a36Sopenharmony_ci				 struct v4l2_format *f)
34062306a36Sopenharmony_ci{
34162306a36Sopenharmony_ci	struct fimc_ctx *ctx = fh_to_ctx(fh);
34262306a36Sopenharmony_ci	struct fimc_dev *fimc = ctx->fimc_dev;
34362306a36Sopenharmony_ci	struct fimc_fmt *fmt;
34462306a36Sopenharmony_ci	struct vb2_queue *vq;
34562306a36Sopenharmony_ci	struct fimc_frame *frame;
34662306a36Sopenharmony_ci	int ret;
34762306a36Sopenharmony_ci
34862306a36Sopenharmony_ci	ret = fimc_try_fmt_mplane(ctx, f);
34962306a36Sopenharmony_ci	if (ret)
35062306a36Sopenharmony_ci		return ret;
35162306a36Sopenharmony_ci
35262306a36Sopenharmony_ci	vq = v4l2_m2m_get_vq(ctx->fh.m2m_ctx, f->type);
35362306a36Sopenharmony_ci
35462306a36Sopenharmony_ci	if (vb2_is_busy(vq)) {
35562306a36Sopenharmony_ci		v4l2_err(&fimc->m2m.vfd, "queue (%d) busy\n", f->type);
35662306a36Sopenharmony_ci		return -EBUSY;
35762306a36Sopenharmony_ci	}
35862306a36Sopenharmony_ci
35962306a36Sopenharmony_ci	if (f->type == V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE)
36062306a36Sopenharmony_ci		frame = &ctx->s_frame;
36162306a36Sopenharmony_ci	else
36262306a36Sopenharmony_ci		frame = &ctx->d_frame;
36362306a36Sopenharmony_ci
36462306a36Sopenharmony_ci	fmt = fimc_find_format(&f->fmt.pix_mp.pixelformat, NULL,
36562306a36Sopenharmony_ci			       get_m2m_fmt_flags(f->type), 0);
36662306a36Sopenharmony_ci	if (!fmt)
36762306a36Sopenharmony_ci		return -EINVAL;
36862306a36Sopenharmony_ci
36962306a36Sopenharmony_ci	__set_frame_format(frame, fmt, &f->fmt.pix_mp);
37062306a36Sopenharmony_ci
37162306a36Sopenharmony_ci	/* Update RGB Alpha control state and value range */
37262306a36Sopenharmony_ci	fimc_alpha_ctrl_update(ctx);
37362306a36Sopenharmony_ci
37462306a36Sopenharmony_ci	return 0;
37562306a36Sopenharmony_ci}
37662306a36Sopenharmony_ci
37762306a36Sopenharmony_cistatic int fimc_m2m_g_selection(struct file *file, void *fh,
37862306a36Sopenharmony_ci				struct v4l2_selection *s)
37962306a36Sopenharmony_ci{
38062306a36Sopenharmony_ci	struct fimc_ctx *ctx = fh_to_ctx(fh);
38162306a36Sopenharmony_ci	struct fimc_frame *frame;
38262306a36Sopenharmony_ci
38362306a36Sopenharmony_ci	frame = ctx_get_frame(ctx, s->type);
38462306a36Sopenharmony_ci	if (IS_ERR(frame))
38562306a36Sopenharmony_ci		return PTR_ERR(frame);
38662306a36Sopenharmony_ci
38762306a36Sopenharmony_ci	switch (s->target) {
38862306a36Sopenharmony_ci	case V4L2_SEL_TGT_CROP:
38962306a36Sopenharmony_ci	case V4L2_SEL_TGT_CROP_DEFAULT:
39062306a36Sopenharmony_ci	case V4L2_SEL_TGT_CROP_BOUNDS:
39162306a36Sopenharmony_ci		if (s->type != V4L2_BUF_TYPE_VIDEO_OUTPUT)
39262306a36Sopenharmony_ci			return -EINVAL;
39362306a36Sopenharmony_ci		break;
39462306a36Sopenharmony_ci	case V4L2_SEL_TGT_COMPOSE:
39562306a36Sopenharmony_ci	case V4L2_SEL_TGT_COMPOSE_DEFAULT:
39662306a36Sopenharmony_ci	case V4L2_SEL_TGT_COMPOSE_BOUNDS:
39762306a36Sopenharmony_ci		if (s->type != V4L2_BUF_TYPE_VIDEO_CAPTURE)
39862306a36Sopenharmony_ci			return -EINVAL;
39962306a36Sopenharmony_ci		break;
40062306a36Sopenharmony_ci	default:
40162306a36Sopenharmony_ci		return -EINVAL;
40262306a36Sopenharmony_ci	}
40362306a36Sopenharmony_ci
40462306a36Sopenharmony_ci	switch (s->target) {
40562306a36Sopenharmony_ci	case V4L2_SEL_TGT_CROP:
40662306a36Sopenharmony_ci	case V4L2_SEL_TGT_COMPOSE:
40762306a36Sopenharmony_ci		s->r.left = frame->offs_h;
40862306a36Sopenharmony_ci		s->r.top = frame->offs_v;
40962306a36Sopenharmony_ci		s->r.width = frame->width;
41062306a36Sopenharmony_ci		s->r.height = frame->height;
41162306a36Sopenharmony_ci		break;
41262306a36Sopenharmony_ci	case V4L2_SEL_TGT_CROP_DEFAULT:
41362306a36Sopenharmony_ci	case V4L2_SEL_TGT_CROP_BOUNDS:
41462306a36Sopenharmony_ci	case V4L2_SEL_TGT_COMPOSE_DEFAULT:
41562306a36Sopenharmony_ci	case V4L2_SEL_TGT_COMPOSE_BOUNDS:
41662306a36Sopenharmony_ci		s->r.left = 0;
41762306a36Sopenharmony_ci		s->r.top = 0;
41862306a36Sopenharmony_ci		s->r.width = frame->o_width;
41962306a36Sopenharmony_ci		s->r.height = frame->o_height;
42062306a36Sopenharmony_ci		break;
42162306a36Sopenharmony_ci	default:
42262306a36Sopenharmony_ci		return -EINVAL;
42362306a36Sopenharmony_ci	}
42462306a36Sopenharmony_ci	return 0;
42562306a36Sopenharmony_ci}
42662306a36Sopenharmony_ci
42762306a36Sopenharmony_cistatic int fimc_m2m_try_selection(struct fimc_ctx *ctx,
42862306a36Sopenharmony_ci				  struct v4l2_selection *s)
42962306a36Sopenharmony_ci{
43062306a36Sopenharmony_ci	struct fimc_dev *fimc = ctx->fimc_dev;
43162306a36Sopenharmony_ci	struct fimc_frame *f;
43262306a36Sopenharmony_ci	u32 min_size, halign, depth = 0;
43362306a36Sopenharmony_ci	int i;
43462306a36Sopenharmony_ci
43562306a36Sopenharmony_ci	if (s->r.top < 0 || s->r.left < 0) {
43662306a36Sopenharmony_ci		v4l2_err(&fimc->m2m.vfd,
43762306a36Sopenharmony_ci			"doesn't support negative values for top & left\n");
43862306a36Sopenharmony_ci		return -EINVAL;
43962306a36Sopenharmony_ci	}
44062306a36Sopenharmony_ci	if (s->type == V4L2_BUF_TYPE_VIDEO_CAPTURE) {
44162306a36Sopenharmony_ci		f = &ctx->d_frame;
44262306a36Sopenharmony_ci		if (s->target != V4L2_SEL_TGT_COMPOSE)
44362306a36Sopenharmony_ci			return -EINVAL;
44462306a36Sopenharmony_ci	} else if (s->type == V4L2_BUF_TYPE_VIDEO_OUTPUT) {
44562306a36Sopenharmony_ci		f = &ctx->s_frame;
44662306a36Sopenharmony_ci		if (s->target != V4L2_SEL_TGT_CROP)
44762306a36Sopenharmony_ci			return -EINVAL;
44862306a36Sopenharmony_ci	} else {
44962306a36Sopenharmony_ci		return -EINVAL;
45062306a36Sopenharmony_ci	}
45162306a36Sopenharmony_ci
45262306a36Sopenharmony_ci	min_size = (f == &ctx->s_frame) ?
45362306a36Sopenharmony_ci		fimc->variant->min_inp_pixsize : fimc->variant->min_out_pixsize;
45462306a36Sopenharmony_ci
45562306a36Sopenharmony_ci	/* Get pixel alignment constraints. */
45662306a36Sopenharmony_ci	if (fimc->variant->min_vsize_align == 1)
45762306a36Sopenharmony_ci		halign = fimc_fmt_is_rgb(f->fmt->color) ? 0 : 1;
45862306a36Sopenharmony_ci	else
45962306a36Sopenharmony_ci		halign = ffs(fimc->variant->min_vsize_align) - 1;
46062306a36Sopenharmony_ci
46162306a36Sopenharmony_ci	for (i = 0; i < f->fmt->memplanes; i++)
46262306a36Sopenharmony_ci		depth += f->fmt->depth[i];
46362306a36Sopenharmony_ci
46462306a36Sopenharmony_ci	v4l_bound_align_image(&s->r.width, min_size, f->o_width,
46562306a36Sopenharmony_ci			      ffs(min_size) - 1,
46662306a36Sopenharmony_ci			      &s->r.height, min_size, f->o_height,
46762306a36Sopenharmony_ci			      halign, 64/(ALIGN(depth, 8)));
46862306a36Sopenharmony_ci
46962306a36Sopenharmony_ci	/* adjust left/top if cropping rectangle is out of bounds */
47062306a36Sopenharmony_ci	if (s->r.left + s->r.width > f->o_width)
47162306a36Sopenharmony_ci		s->r.left = f->o_width - s->r.width;
47262306a36Sopenharmony_ci	if (s->r.top + s->r.height > f->o_height)
47362306a36Sopenharmony_ci		s->r.top = f->o_height - s->r.height;
47462306a36Sopenharmony_ci
47562306a36Sopenharmony_ci	s->r.left = round_down(s->r.left, min_size);
47662306a36Sopenharmony_ci	s->r.top  = round_down(s->r.top, fimc->variant->hor_offs_align);
47762306a36Sopenharmony_ci
47862306a36Sopenharmony_ci	dbg("l:%d, t:%d, w:%d, h:%d, f_w: %d, f_h: %d",
47962306a36Sopenharmony_ci	    s->r.left, s->r.top, s->r.width, s->r.height,
48062306a36Sopenharmony_ci	    f->f_width, f->f_height);
48162306a36Sopenharmony_ci
48262306a36Sopenharmony_ci	return 0;
48362306a36Sopenharmony_ci}
48462306a36Sopenharmony_ci
48562306a36Sopenharmony_cistatic int fimc_m2m_s_selection(struct file *file, void *fh,
48662306a36Sopenharmony_ci				struct v4l2_selection *s)
48762306a36Sopenharmony_ci{
48862306a36Sopenharmony_ci	struct fimc_ctx *ctx = fh_to_ctx(fh);
48962306a36Sopenharmony_ci	struct fimc_dev *fimc = ctx->fimc_dev;
49062306a36Sopenharmony_ci	struct fimc_frame *f;
49162306a36Sopenharmony_ci	int ret;
49262306a36Sopenharmony_ci
49362306a36Sopenharmony_ci	ret = fimc_m2m_try_selection(ctx, s);
49462306a36Sopenharmony_ci	if (ret)
49562306a36Sopenharmony_ci		return ret;
49662306a36Sopenharmony_ci
49762306a36Sopenharmony_ci	f = (s->type == V4L2_BUF_TYPE_VIDEO_OUTPUT) ?
49862306a36Sopenharmony_ci		&ctx->s_frame : &ctx->d_frame;
49962306a36Sopenharmony_ci
50062306a36Sopenharmony_ci	/* Check to see if scaling ratio is within supported range */
50162306a36Sopenharmony_ci	if (s->type == V4L2_BUF_TYPE_VIDEO_OUTPUT) {
50262306a36Sopenharmony_ci		ret = fimc_check_scaler_ratio(ctx, s->r.width,
50362306a36Sopenharmony_ci				s->r.height, ctx->d_frame.width,
50462306a36Sopenharmony_ci				ctx->d_frame.height, ctx->rotation);
50562306a36Sopenharmony_ci	} else {
50662306a36Sopenharmony_ci		ret = fimc_check_scaler_ratio(ctx, ctx->s_frame.width,
50762306a36Sopenharmony_ci				ctx->s_frame.height, s->r.width,
50862306a36Sopenharmony_ci				s->r.height, ctx->rotation);
50962306a36Sopenharmony_ci	}
51062306a36Sopenharmony_ci	if (ret) {
51162306a36Sopenharmony_ci		v4l2_err(&fimc->m2m.vfd, "Out of scaler range\n");
51262306a36Sopenharmony_ci		return -EINVAL;
51362306a36Sopenharmony_ci	}
51462306a36Sopenharmony_ci
51562306a36Sopenharmony_ci	f->offs_h = s->r.left;
51662306a36Sopenharmony_ci	f->offs_v = s->r.top;
51762306a36Sopenharmony_ci	f->width  = s->r.width;
51862306a36Sopenharmony_ci	f->height = s->r.height;
51962306a36Sopenharmony_ci
52062306a36Sopenharmony_ci	fimc_ctx_state_set(FIMC_PARAMS, ctx);
52162306a36Sopenharmony_ci
52262306a36Sopenharmony_ci	return 0;
52362306a36Sopenharmony_ci}
52462306a36Sopenharmony_ci
52562306a36Sopenharmony_cistatic const struct v4l2_ioctl_ops fimc_m2m_ioctl_ops = {
52662306a36Sopenharmony_ci	.vidioc_querycap		= fimc_m2m_querycap,
52762306a36Sopenharmony_ci	.vidioc_enum_fmt_vid_cap	= fimc_m2m_enum_fmt,
52862306a36Sopenharmony_ci	.vidioc_enum_fmt_vid_out	= fimc_m2m_enum_fmt,
52962306a36Sopenharmony_ci	.vidioc_g_fmt_vid_cap_mplane	= fimc_m2m_g_fmt_mplane,
53062306a36Sopenharmony_ci	.vidioc_g_fmt_vid_out_mplane	= fimc_m2m_g_fmt_mplane,
53162306a36Sopenharmony_ci	.vidioc_try_fmt_vid_cap_mplane	= fimc_m2m_try_fmt_mplane,
53262306a36Sopenharmony_ci	.vidioc_try_fmt_vid_out_mplane	= fimc_m2m_try_fmt_mplane,
53362306a36Sopenharmony_ci	.vidioc_s_fmt_vid_cap_mplane	= fimc_m2m_s_fmt_mplane,
53462306a36Sopenharmony_ci	.vidioc_s_fmt_vid_out_mplane	= fimc_m2m_s_fmt_mplane,
53562306a36Sopenharmony_ci	.vidioc_reqbufs			= v4l2_m2m_ioctl_reqbufs,
53662306a36Sopenharmony_ci	.vidioc_querybuf		= v4l2_m2m_ioctl_querybuf,
53762306a36Sopenharmony_ci	.vidioc_qbuf			= v4l2_m2m_ioctl_qbuf,
53862306a36Sopenharmony_ci	.vidioc_dqbuf			= v4l2_m2m_ioctl_dqbuf,
53962306a36Sopenharmony_ci	.vidioc_expbuf			= v4l2_m2m_ioctl_expbuf,
54062306a36Sopenharmony_ci	.vidioc_streamon		= v4l2_m2m_ioctl_streamon,
54162306a36Sopenharmony_ci	.vidioc_streamoff		= v4l2_m2m_ioctl_streamoff,
54262306a36Sopenharmony_ci	.vidioc_g_selection		= fimc_m2m_g_selection,
54362306a36Sopenharmony_ci	.vidioc_s_selection		= fimc_m2m_s_selection,
54462306a36Sopenharmony_ci
54562306a36Sopenharmony_ci};
54662306a36Sopenharmony_ci
54762306a36Sopenharmony_cistatic int queue_init(void *priv, struct vb2_queue *src_vq,
54862306a36Sopenharmony_ci		      struct vb2_queue *dst_vq)
54962306a36Sopenharmony_ci{
55062306a36Sopenharmony_ci	struct fimc_ctx *ctx = priv;
55162306a36Sopenharmony_ci	int ret;
55262306a36Sopenharmony_ci
55362306a36Sopenharmony_ci	src_vq->type = V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE;
55462306a36Sopenharmony_ci	src_vq->io_modes = VB2_MMAP | VB2_USERPTR | VB2_DMABUF;
55562306a36Sopenharmony_ci	src_vq->drv_priv = ctx;
55662306a36Sopenharmony_ci	src_vq->ops = &fimc_qops;
55762306a36Sopenharmony_ci	src_vq->mem_ops = &vb2_dma_contig_memops;
55862306a36Sopenharmony_ci	src_vq->buf_struct_size = sizeof(struct v4l2_m2m_buffer);
55962306a36Sopenharmony_ci	src_vq->timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_COPY;
56062306a36Sopenharmony_ci	src_vq->lock = &ctx->fimc_dev->lock;
56162306a36Sopenharmony_ci	src_vq->dev = &ctx->fimc_dev->pdev->dev;
56262306a36Sopenharmony_ci
56362306a36Sopenharmony_ci	ret = vb2_queue_init(src_vq);
56462306a36Sopenharmony_ci	if (ret)
56562306a36Sopenharmony_ci		return ret;
56662306a36Sopenharmony_ci
56762306a36Sopenharmony_ci	dst_vq->type = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE;
56862306a36Sopenharmony_ci	dst_vq->io_modes = VB2_MMAP | VB2_USERPTR | VB2_DMABUF;
56962306a36Sopenharmony_ci	dst_vq->drv_priv = ctx;
57062306a36Sopenharmony_ci	dst_vq->ops = &fimc_qops;
57162306a36Sopenharmony_ci	dst_vq->mem_ops = &vb2_dma_contig_memops;
57262306a36Sopenharmony_ci	dst_vq->buf_struct_size = sizeof(struct v4l2_m2m_buffer);
57362306a36Sopenharmony_ci	dst_vq->timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_COPY;
57462306a36Sopenharmony_ci	dst_vq->lock = &ctx->fimc_dev->lock;
57562306a36Sopenharmony_ci	dst_vq->dev = &ctx->fimc_dev->pdev->dev;
57662306a36Sopenharmony_ci
57762306a36Sopenharmony_ci	return vb2_queue_init(dst_vq);
57862306a36Sopenharmony_ci}
57962306a36Sopenharmony_ci
58062306a36Sopenharmony_cistatic int fimc_m2m_set_default_format(struct fimc_ctx *ctx)
58162306a36Sopenharmony_ci{
58262306a36Sopenharmony_ci	struct v4l2_pix_format_mplane pixm = {
58362306a36Sopenharmony_ci		.pixelformat	= V4L2_PIX_FMT_RGB32,
58462306a36Sopenharmony_ci		.width		= 800,
58562306a36Sopenharmony_ci		.height		= 600,
58662306a36Sopenharmony_ci		.plane_fmt[0]	= {
58762306a36Sopenharmony_ci			.bytesperline = 800 * 4,
58862306a36Sopenharmony_ci			.sizeimage = 800 * 4 * 600,
58962306a36Sopenharmony_ci		},
59062306a36Sopenharmony_ci	};
59162306a36Sopenharmony_ci	struct fimc_fmt *fmt;
59262306a36Sopenharmony_ci
59362306a36Sopenharmony_ci	fmt = fimc_find_format(&pixm.pixelformat, NULL, FMT_FLAGS_M2M, 0);
59462306a36Sopenharmony_ci	if (!fmt)
59562306a36Sopenharmony_ci		return -EINVAL;
59662306a36Sopenharmony_ci
59762306a36Sopenharmony_ci	__set_frame_format(&ctx->s_frame, fmt, &pixm);
59862306a36Sopenharmony_ci	__set_frame_format(&ctx->d_frame, fmt, &pixm);
59962306a36Sopenharmony_ci
60062306a36Sopenharmony_ci	return 0;
60162306a36Sopenharmony_ci}
60262306a36Sopenharmony_ci
60362306a36Sopenharmony_cistatic int fimc_m2m_open(struct file *file)
60462306a36Sopenharmony_ci{
60562306a36Sopenharmony_ci	struct fimc_dev *fimc = video_drvdata(file);
60662306a36Sopenharmony_ci	struct fimc_ctx *ctx;
60762306a36Sopenharmony_ci	int ret = -EBUSY;
60862306a36Sopenharmony_ci
60962306a36Sopenharmony_ci	pr_debug("pid: %d, state: %#lx\n", task_pid_nr(current), fimc->state);
61062306a36Sopenharmony_ci
61162306a36Sopenharmony_ci	if (mutex_lock_interruptible(&fimc->lock))
61262306a36Sopenharmony_ci		return -ERESTARTSYS;
61362306a36Sopenharmony_ci	/*
61462306a36Sopenharmony_ci	 * Don't allow simultaneous open() of the mem-to-mem and the
61562306a36Sopenharmony_ci	 * capture video node that belong to same FIMC IP instance.
61662306a36Sopenharmony_ci	 */
61762306a36Sopenharmony_ci	if (test_bit(ST_CAPT_BUSY, &fimc->state))
61862306a36Sopenharmony_ci		goto unlock;
61962306a36Sopenharmony_ci
62062306a36Sopenharmony_ci	ctx = kzalloc(sizeof(*ctx), GFP_KERNEL);
62162306a36Sopenharmony_ci	if (!ctx) {
62262306a36Sopenharmony_ci		ret = -ENOMEM;
62362306a36Sopenharmony_ci		goto unlock;
62462306a36Sopenharmony_ci	}
62562306a36Sopenharmony_ci	v4l2_fh_init(&ctx->fh, &fimc->m2m.vfd);
62662306a36Sopenharmony_ci	ctx->fimc_dev = fimc;
62762306a36Sopenharmony_ci
62862306a36Sopenharmony_ci	/* Default color format */
62962306a36Sopenharmony_ci	ctx->s_frame.fmt = fimc_get_format(0);
63062306a36Sopenharmony_ci	ctx->d_frame.fmt = fimc_get_format(0);
63162306a36Sopenharmony_ci
63262306a36Sopenharmony_ci	ret = fimc_ctrls_create(ctx);
63362306a36Sopenharmony_ci	if (ret)
63462306a36Sopenharmony_ci		goto error_fh;
63562306a36Sopenharmony_ci
63662306a36Sopenharmony_ci	/* Use separate control handler per file handle */
63762306a36Sopenharmony_ci	ctx->fh.ctrl_handler = &ctx->ctrls.handler;
63862306a36Sopenharmony_ci	file->private_data = &ctx->fh;
63962306a36Sopenharmony_ci	v4l2_fh_add(&ctx->fh);
64062306a36Sopenharmony_ci
64162306a36Sopenharmony_ci	/* Setup the device context for memory-to-memory mode */
64262306a36Sopenharmony_ci	ctx->state = FIMC_CTX_M2M;
64362306a36Sopenharmony_ci	ctx->flags = 0;
64462306a36Sopenharmony_ci	ctx->in_path = FIMC_IO_DMA;
64562306a36Sopenharmony_ci	ctx->out_path = FIMC_IO_DMA;
64662306a36Sopenharmony_ci	ctx->scaler.enabled = 1;
64762306a36Sopenharmony_ci
64862306a36Sopenharmony_ci	ctx->fh.m2m_ctx = v4l2_m2m_ctx_init(fimc->m2m.m2m_dev, ctx, queue_init);
64962306a36Sopenharmony_ci	if (IS_ERR(ctx->fh.m2m_ctx)) {
65062306a36Sopenharmony_ci		ret = PTR_ERR(ctx->fh.m2m_ctx);
65162306a36Sopenharmony_ci		goto error_c;
65262306a36Sopenharmony_ci	}
65362306a36Sopenharmony_ci
65462306a36Sopenharmony_ci	if (fimc->m2m.refcnt++ == 0)
65562306a36Sopenharmony_ci		set_bit(ST_M2M_RUN, &fimc->state);
65662306a36Sopenharmony_ci
65762306a36Sopenharmony_ci	ret = fimc_m2m_set_default_format(ctx);
65862306a36Sopenharmony_ci	if (ret < 0)
65962306a36Sopenharmony_ci		goto error_m2m_ctx;
66062306a36Sopenharmony_ci
66162306a36Sopenharmony_ci	mutex_unlock(&fimc->lock);
66262306a36Sopenharmony_ci	return 0;
66362306a36Sopenharmony_ci
66462306a36Sopenharmony_cierror_m2m_ctx:
66562306a36Sopenharmony_ci	v4l2_m2m_ctx_release(ctx->fh.m2m_ctx);
66662306a36Sopenharmony_cierror_c:
66762306a36Sopenharmony_ci	fimc_ctrls_delete(ctx);
66862306a36Sopenharmony_ci	v4l2_fh_del(&ctx->fh);
66962306a36Sopenharmony_cierror_fh:
67062306a36Sopenharmony_ci	v4l2_fh_exit(&ctx->fh);
67162306a36Sopenharmony_ci	kfree(ctx);
67262306a36Sopenharmony_ciunlock:
67362306a36Sopenharmony_ci	mutex_unlock(&fimc->lock);
67462306a36Sopenharmony_ci	return ret;
67562306a36Sopenharmony_ci}
67662306a36Sopenharmony_ci
67762306a36Sopenharmony_cistatic int fimc_m2m_release(struct file *file)
67862306a36Sopenharmony_ci{
67962306a36Sopenharmony_ci	struct fimc_ctx *ctx = fh_to_ctx(file->private_data);
68062306a36Sopenharmony_ci	struct fimc_dev *fimc = ctx->fimc_dev;
68162306a36Sopenharmony_ci
68262306a36Sopenharmony_ci	dbg("pid: %d, state: 0x%lx, refcnt= %d",
68362306a36Sopenharmony_ci		task_pid_nr(current), fimc->state, fimc->m2m.refcnt);
68462306a36Sopenharmony_ci
68562306a36Sopenharmony_ci	mutex_lock(&fimc->lock);
68662306a36Sopenharmony_ci
68762306a36Sopenharmony_ci	v4l2_m2m_ctx_release(ctx->fh.m2m_ctx);
68862306a36Sopenharmony_ci	fimc_ctrls_delete(ctx);
68962306a36Sopenharmony_ci	v4l2_fh_del(&ctx->fh);
69062306a36Sopenharmony_ci	v4l2_fh_exit(&ctx->fh);
69162306a36Sopenharmony_ci
69262306a36Sopenharmony_ci	if (--fimc->m2m.refcnt <= 0)
69362306a36Sopenharmony_ci		clear_bit(ST_M2M_RUN, &fimc->state);
69462306a36Sopenharmony_ci	kfree(ctx);
69562306a36Sopenharmony_ci
69662306a36Sopenharmony_ci	mutex_unlock(&fimc->lock);
69762306a36Sopenharmony_ci	return 0;
69862306a36Sopenharmony_ci}
69962306a36Sopenharmony_ci
70062306a36Sopenharmony_cistatic const struct v4l2_file_operations fimc_m2m_fops = {
70162306a36Sopenharmony_ci	.owner		= THIS_MODULE,
70262306a36Sopenharmony_ci	.open		= fimc_m2m_open,
70362306a36Sopenharmony_ci	.release	= fimc_m2m_release,
70462306a36Sopenharmony_ci	.poll		= v4l2_m2m_fop_poll,
70562306a36Sopenharmony_ci	.unlocked_ioctl	= video_ioctl2,
70662306a36Sopenharmony_ci	.mmap		= v4l2_m2m_fop_mmap,
70762306a36Sopenharmony_ci};
70862306a36Sopenharmony_ci
70962306a36Sopenharmony_cistatic const struct v4l2_m2m_ops m2m_ops = {
71062306a36Sopenharmony_ci	.device_run	= fimc_device_run,
71162306a36Sopenharmony_ci	.job_abort	= fimc_job_abort,
71262306a36Sopenharmony_ci};
71362306a36Sopenharmony_ci
71462306a36Sopenharmony_ciint fimc_register_m2m_device(struct fimc_dev *fimc,
71562306a36Sopenharmony_ci			     struct v4l2_device *v4l2_dev)
71662306a36Sopenharmony_ci{
71762306a36Sopenharmony_ci	struct video_device *vfd = &fimc->m2m.vfd;
71862306a36Sopenharmony_ci	int ret;
71962306a36Sopenharmony_ci
72062306a36Sopenharmony_ci	fimc->v4l2_dev = v4l2_dev;
72162306a36Sopenharmony_ci
72262306a36Sopenharmony_ci	memset(vfd, 0, sizeof(*vfd));
72362306a36Sopenharmony_ci	vfd->fops = &fimc_m2m_fops;
72462306a36Sopenharmony_ci	vfd->ioctl_ops = &fimc_m2m_ioctl_ops;
72562306a36Sopenharmony_ci	vfd->v4l2_dev = v4l2_dev;
72662306a36Sopenharmony_ci	vfd->minor = -1;
72762306a36Sopenharmony_ci	vfd->release = video_device_release_empty;
72862306a36Sopenharmony_ci	vfd->lock = &fimc->lock;
72962306a36Sopenharmony_ci	vfd->vfl_dir = VFL_DIR_M2M;
73062306a36Sopenharmony_ci	vfd->device_caps = V4L2_CAP_STREAMING | V4L2_CAP_VIDEO_M2M_MPLANE;
73162306a36Sopenharmony_ci	set_bit(V4L2_FL_QUIRK_INVERTED_CROP, &vfd->flags);
73262306a36Sopenharmony_ci
73362306a36Sopenharmony_ci	snprintf(vfd->name, sizeof(vfd->name), "fimc.%d.m2m", fimc->id);
73462306a36Sopenharmony_ci	video_set_drvdata(vfd, fimc);
73562306a36Sopenharmony_ci
73662306a36Sopenharmony_ci	fimc->m2m.m2m_dev = v4l2_m2m_init(&m2m_ops);
73762306a36Sopenharmony_ci	if (IS_ERR(fimc->m2m.m2m_dev)) {
73862306a36Sopenharmony_ci		v4l2_err(v4l2_dev, "failed to initialize v4l2-m2m device\n");
73962306a36Sopenharmony_ci		return PTR_ERR(fimc->m2m.m2m_dev);
74062306a36Sopenharmony_ci	}
74162306a36Sopenharmony_ci
74262306a36Sopenharmony_ci	ret = media_entity_pads_init(&vfd->entity, 0, NULL);
74362306a36Sopenharmony_ci	if (ret)
74462306a36Sopenharmony_ci		goto err_me;
74562306a36Sopenharmony_ci
74662306a36Sopenharmony_ci	ret = video_register_device(vfd, VFL_TYPE_VIDEO, -1);
74762306a36Sopenharmony_ci	if (ret)
74862306a36Sopenharmony_ci		goto err_vd;
74962306a36Sopenharmony_ci
75062306a36Sopenharmony_ci	v4l2_info(v4l2_dev, "Registered %s as /dev/%s\n",
75162306a36Sopenharmony_ci		  vfd->name, video_device_node_name(vfd));
75262306a36Sopenharmony_ci	return 0;
75362306a36Sopenharmony_ci
75462306a36Sopenharmony_cierr_vd:
75562306a36Sopenharmony_ci	media_entity_cleanup(&vfd->entity);
75662306a36Sopenharmony_cierr_me:
75762306a36Sopenharmony_ci	v4l2_m2m_release(fimc->m2m.m2m_dev);
75862306a36Sopenharmony_ci	return ret;
75962306a36Sopenharmony_ci}
76062306a36Sopenharmony_ci
76162306a36Sopenharmony_civoid fimc_unregister_m2m_device(struct fimc_dev *fimc)
76262306a36Sopenharmony_ci{
76362306a36Sopenharmony_ci	if (!fimc)
76462306a36Sopenharmony_ci		return;
76562306a36Sopenharmony_ci
76662306a36Sopenharmony_ci	if (fimc->m2m.m2m_dev)
76762306a36Sopenharmony_ci		v4l2_m2m_release(fimc->m2m.m2m_dev);
76862306a36Sopenharmony_ci
76962306a36Sopenharmony_ci	if (video_is_registered(&fimc->m2m.vfd)) {
77062306a36Sopenharmony_ci		video_unregister_device(&fimc->m2m.vfd);
77162306a36Sopenharmony_ci		media_entity_cleanup(&fimc->m2m.vfd.entity);
77262306a36Sopenharmony_ci	}
77362306a36Sopenharmony_ci}
774