Files
linux/drivers/media/platform/mediatek/mdp3/mtk-mdp3-m2m.c
Moudy Ho 61890ccaef media: platform: mtk-mdp3: add MediaTek MDP3 driver
This patch adds driver for MediaTek's Media Data Path ver.3 (MDP3).
It provides the following functions:
  color transform, format conversion, resize, crop, rotate, flip
  and additional image quality enhancement.

The MDP3 driver is mainly used for Google Chromebook products to
import the new architecture to set the HW settings as shown below:
  User -> V4L2 framework
    -> MDP3 driver -> SCP (setting calculations)
      -> MDP3 driver -> CMDQ (GCE driver) -> HW

Each modules' related operation control is sited in mtk-mdp3-comp.c
Each modules' register table is defined in file with "mdp_reg_" prefix
GCE related API, operation control  sited in mtk-mdp3-cmdq.c
V4L2 m2m device functions are implemented in mtk-mdp3-m2m.c
Probe, power, suspend/resume, system level functions are defined in
mtk-mdp3-core.c

[hverkuil: add 'depends on REMOTEPROC']

Signed-off-by: Ping-Hsun Wu <ping-hsun.wu@mediatek.com>
Signed-off-by: daoyuan huang <daoyuan.huang@mediatek.com>
Signed-off-by: Moudy Ho <moudy.ho@mediatek.com>
Tested-by: AngeloGioacchino Del Regno <angelogioacchino.delregno@collabora.com>
Signed-off-by: Hans Verkuil <hverkuil-cisco@xs4all.nl>
Signed-off-by: Mauro Carvalho Chehab <mchehab@kernel.org>
2022-08-30 16:25:51 +02:00

725 lines
19 KiB
C

// SPDX-License-Identifier: GPL-2.0-only
/*
* Copyright (c) 2022 MediaTek Inc.
* Author: Ping-Hsun Wu <ping-hsun.wu@mediatek.com>
*/
#include <linux/platform_device.h>
#include <media/v4l2-ioctl.h>
#include <media/v4l2-event.h>
#include <media/videobuf2-dma-contig.h>
#include "mtk-mdp3-m2m.h"
static inline struct mdp_m2m_ctx *fh_to_ctx(struct v4l2_fh *fh)
{
return container_of(fh, struct mdp_m2m_ctx, fh);
}
static inline struct mdp_m2m_ctx *ctrl_to_ctx(struct v4l2_ctrl *ctrl)
{
return container_of(ctrl->handler, struct mdp_m2m_ctx, ctrl_handler);
}
static inline struct mdp_frame *ctx_get_frame(struct mdp_m2m_ctx *ctx,
enum v4l2_buf_type type)
{
if (V4L2_TYPE_IS_OUTPUT(type))
return &ctx->curr_param.output;
else
return &ctx->curr_param.captures[0];
}
static inline void mdp_m2m_ctx_set_state(struct mdp_m2m_ctx *ctx, u32 state)
{
atomic_or(state, &ctx->curr_param.state);
}
static inline bool mdp_m2m_ctx_is_state_set(struct mdp_m2m_ctx *ctx, u32 mask)
{
return ((atomic_read(&ctx->curr_param.state) & mask) == mask);
}
static void mdp_m2m_process_done(void *priv, int vb_state)
{
struct mdp_m2m_ctx *ctx = priv;
struct vb2_v4l2_buffer *src_vbuf, *dst_vbuf;
src_vbuf = (struct vb2_v4l2_buffer *)
v4l2_m2m_src_buf_remove(ctx->m2m_ctx);
dst_vbuf = (struct vb2_v4l2_buffer *)
v4l2_m2m_dst_buf_remove(ctx->m2m_ctx);
ctx->curr_param.frame_no = ctx->frame_count[MDP_M2M_SRC];
src_vbuf->sequence = ctx->frame_count[MDP_M2M_SRC]++;
dst_vbuf->sequence = ctx->frame_count[MDP_M2M_DST]++;
v4l2_m2m_buf_copy_metadata(src_vbuf, dst_vbuf, true);
v4l2_m2m_buf_done(src_vbuf, vb_state);
v4l2_m2m_buf_done(dst_vbuf, vb_state);
v4l2_m2m_job_finish(ctx->mdp_dev->m2m_dev, ctx->m2m_ctx);
}
static void mdp_m2m_device_run(void *priv)
{
struct mdp_m2m_ctx *ctx = priv;
struct mdp_frame *frame;
struct vb2_v4l2_buffer *src_vb, *dst_vb;
struct img_ipi_frameparam param = {};
struct mdp_cmdq_param task = {};
enum vb2_buffer_state vb_state = VB2_BUF_STATE_ERROR;
int ret;
if (mdp_m2m_ctx_is_state_set(ctx, MDP_M2M_CTX_ERROR)) {
dev_err(&ctx->mdp_dev->pdev->dev,
"mdp_m2m_ctx is in error state\n");
goto worker_end;
}
param.frame_no = ctx->curr_param.frame_no;
param.type = ctx->curr_param.type;
param.num_inputs = 1;
param.num_outputs = 1;
frame = ctx_get_frame(ctx, V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE);
src_vb = v4l2_m2m_next_src_buf(ctx->m2m_ctx);
mdp_set_src_config(&param.inputs[0], frame, &src_vb->vb2_buf);
frame = ctx_get_frame(ctx, V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE);
dst_vb = v4l2_m2m_next_dst_buf(ctx->m2m_ctx);
mdp_set_dst_config(&param.outputs[0], frame, &dst_vb->vb2_buf);
ret = mdp_vpu_process(&ctx->vpu, &param);
if (ret) {
dev_err(&ctx->mdp_dev->pdev->dev,
"VPU MDP process failed: %d\n", ret);
goto worker_end;
}
task.config = ctx->vpu.config;
task.param = &param;
task.composes[0] = &frame->compose;
task.cmdq_cb = NULL;
task.cb_data = NULL;
task.mdp_ctx = ctx;
ret = mdp_cmdq_send(ctx->mdp_dev, &task);
if (ret) {
dev_err(&ctx->mdp_dev->pdev->dev,
"CMDQ sendtask failed: %d\n", ret);
goto worker_end;
}
return;
worker_end:
mdp_m2m_process_done(ctx, vb_state);
}
static int mdp_m2m_start_streaming(struct vb2_queue *q, unsigned int count)
{
struct mdp_m2m_ctx *ctx = vb2_get_drv_priv(q);
struct mdp_frame *capture;
struct vb2_queue *vq;
int ret;
bool out_streaming, cap_streaming;
if (V4L2_TYPE_IS_OUTPUT(q->type))
ctx->frame_count[MDP_M2M_SRC] = 0;
if (V4L2_TYPE_IS_CAPTURE(q->type))
ctx->frame_count[MDP_M2M_DST] = 0;
capture = ctx_get_frame(ctx, V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE);
vq = v4l2_m2m_get_src_vq(ctx->m2m_ctx);
out_streaming = vb2_is_streaming(vq);
vq = v4l2_m2m_get_dst_vq(ctx->m2m_ctx);
cap_streaming = vb2_is_streaming(vq);
/* Check to see if scaling ratio is within supported range */
if ((V4L2_TYPE_IS_OUTPUT(q->type) && cap_streaming) ||
(V4L2_TYPE_IS_CAPTURE(q->type) && out_streaming)) {
ret = mdp_check_scaling_ratio(&capture->crop.c,
&capture->compose,
capture->rotation,
ctx->curr_param.limit);
if (ret) {
dev_err(&ctx->mdp_dev->pdev->dev,
"Out of scaling range\n");
return ret;
}
}
if (!mdp_m2m_ctx_is_state_set(ctx, MDP_VPU_INIT)) {
ret = mdp_vpu_get_locked(ctx->mdp_dev);
if (ret)
return ret;
ret = mdp_vpu_ctx_init(&ctx->vpu, &ctx->mdp_dev->vpu,
MDP_DEV_M2M);
if (ret) {
dev_err(&ctx->mdp_dev->pdev->dev,
"VPU init failed %d\n", ret);
return -EINVAL;
}
mdp_m2m_ctx_set_state(ctx, MDP_VPU_INIT);
}
return 0;
}
static struct vb2_v4l2_buffer *mdp_m2m_buf_remove(struct mdp_m2m_ctx *ctx,
unsigned int type)
{
if (V4L2_TYPE_IS_OUTPUT(type))
return (struct vb2_v4l2_buffer *)
v4l2_m2m_src_buf_remove(ctx->m2m_ctx);
else
return (struct vb2_v4l2_buffer *)
v4l2_m2m_dst_buf_remove(ctx->m2m_ctx);
}
static void mdp_m2m_stop_streaming(struct vb2_queue *q)
{
struct mdp_m2m_ctx *ctx = vb2_get_drv_priv(q);
struct vb2_v4l2_buffer *vb;
vb = mdp_m2m_buf_remove(ctx, q->type);
while (vb) {
v4l2_m2m_buf_done(vb, VB2_BUF_STATE_ERROR);
vb = mdp_m2m_buf_remove(ctx, q->type);
}
}
static int mdp_m2m_queue_setup(struct vb2_queue *q,
unsigned int *num_buffers,
unsigned int *num_planes, unsigned int sizes[],
struct device *alloc_devs[])
{
struct mdp_m2m_ctx *ctx = vb2_get_drv_priv(q);
struct v4l2_pix_format_mplane *pix_mp;
u32 i;
pix_mp = &ctx_get_frame(ctx, q->type)->format.fmt.pix_mp;
/* from VIDIOC_CREATE_BUFS */
if (*num_planes) {
if (*num_planes != pix_mp->num_planes)
return -EINVAL;
for (i = 0; i < pix_mp->num_planes; ++i)
if (sizes[i] < pix_mp->plane_fmt[i].sizeimage)
return -EINVAL;
} else {/* from VIDIOC_REQBUFS */
*num_planes = pix_mp->num_planes;
for (i = 0; i < pix_mp->num_planes; ++i)
sizes[i] = pix_mp->plane_fmt[i].sizeimage;
}
return 0;
}
static int mdp_m2m_buf_prepare(struct vb2_buffer *vb)
{
struct mdp_m2m_ctx *ctx = vb2_get_drv_priv(vb->vb2_queue);
struct v4l2_pix_format_mplane *pix_mp;
struct vb2_v4l2_buffer *v4l2_buf = to_vb2_v4l2_buffer(vb);
u32 i;
v4l2_buf->field = V4L2_FIELD_NONE;
if (V4L2_TYPE_IS_CAPTURE(vb->type)) {
pix_mp = &ctx_get_frame(ctx, vb->type)->format.fmt.pix_mp;
for (i = 0; i < pix_mp->num_planes; ++i) {
vb2_set_plane_payload(vb, i,
pix_mp->plane_fmt[i].sizeimage);
}
}
return 0;
}
static int mdp_m2m_buf_out_validate(struct vb2_buffer *vb)
{
struct vb2_v4l2_buffer *v4l2_buf = to_vb2_v4l2_buffer(vb);
v4l2_buf->field = V4L2_FIELD_NONE;
return 0;
}
static void mdp_m2m_buf_queue(struct vb2_buffer *vb)
{
struct mdp_m2m_ctx *ctx = vb2_get_drv_priv(vb->vb2_queue);
struct vb2_v4l2_buffer *v4l2_buf = to_vb2_v4l2_buffer(vb);
v4l2_buf->field = V4L2_FIELD_NONE;
v4l2_m2m_buf_queue(ctx->m2m_ctx, to_vb2_v4l2_buffer(vb));
}
static const struct vb2_ops mdp_m2m_qops = {
.queue_setup = mdp_m2m_queue_setup,
.wait_prepare = vb2_ops_wait_prepare,
.wait_finish = vb2_ops_wait_finish,
.buf_prepare = mdp_m2m_buf_prepare,
.start_streaming = mdp_m2m_start_streaming,
.stop_streaming = mdp_m2m_stop_streaming,
.buf_queue = mdp_m2m_buf_queue,
.buf_out_validate = mdp_m2m_buf_out_validate,
};
static int mdp_m2m_querycap(struct file *file, void *fh,
struct v4l2_capability *cap)
{
strscpy(cap->driver, MDP_MODULE_NAME, sizeof(cap->driver));
strscpy(cap->card, MDP_DEVICE_NAME, sizeof(cap->card));
return 0;
}
static int mdp_m2m_enum_fmt_mplane(struct file *file, void *fh,
struct v4l2_fmtdesc *f)
{
return mdp_enum_fmt_mplane(f);
}
static int mdp_m2m_g_fmt_mplane(struct file *file, void *fh,
struct v4l2_format *f)
{
struct mdp_m2m_ctx *ctx = fh_to_ctx(fh);
struct mdp_frame *frame;
struct v4l2_pix_format_mplane *pix_mp;
frame = ctx_get_frame(ctx, f->type);
*f = frame->format;
pix_mp = &f->fmt.pix_mp;
pix_mp->colorspace = ctx->curr_param.colorspace;
pix_mp->xfer_func = ctx->curr_param.xfer_func;
pix_mp->ycbcr_enc = ctx->curr_param.ycbcr_enc;
pix_mp->quantization = ctx->curr_param.quant;
return 0;
}
static int mdp_m2m_s_fmt_mplane(struct file *file, void *fh,
struct v4l2_format *f)
{
struct mdp_m2m_ctx *ctx = fh_to_ctx(fh);
struct mdp_frame *frame = ctx_get_frame(ctx, f->type);
struct mdp_frame *capture;
const struct mdp_format *fmt;
struct vb2_queue *vq;
fmt = mdp_try_fmt_mplane(f, &ctx->curr_param, ctx->id);
if (!fmt)
return -EINVAL;
vq = v4l2_m2m_get_vq(ctx->m2m_ctx, f->type);
if (vb2_is_busy(vq))
return -EBUSY;
frame->format = *f;
frame->mdp_fmt = fmt;
frame->ycbcr_prof = mdp_map_ycbcr_prof_mplane(f, fmt->mdp_color);
frame->usage = V4L2_TYPE_IS_OUTPUT(f->type) ?
MDP_BUFFER_USAGE_HW_READ : MDP_BUFFER_USAGE_MDP;
capture = ctx_get_frame(ctx, V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE);
if (V4L2_TYPE_IS_OUTPUT(f->type)) {
capture->crop.c.left = 0;
capture->crop.c.top = 0;
capture->crop.c.width = f->fmt.pix_mp.width;
capture->crop.c.height = f->fmt.pix_mp.height;
ctx->curr_param.colorspace = f->fmt.pix_mp.colorspace;
ctx->curr_param.ycbcr_enc = f->fmt.pix_mp.ycbcr_enc;
ctx->curr_param.quant = f->fmt.pix_mp.quantization;
ctx->curr_param.xfer_func = f->fmt.pix_mp.xfer_func;
} else {
capture->compose.left = 0;
capture->compose.top = 0;
capture->compose.width = f->fmt.pix_mp.width;
capture->compose.height = f->fmt.pix_mp.height;
}
return 0;
}
static int mdp_m2m_try_fmt_mplane(struct file *file, void *fh,
struct v4l2_format *f)
{
struct mdp_m2m_ctx *ctx = fh_to_ctx(fh);
if (!mdp_try_fmt_mplane(f, &ctx->curr_param, ctx->id))
return -EINVAL;
return 0;
}
static int mdp_m2m_g_selection(struct file *file, void *fh,
struct v4l2_selection *s)
{
struct mdp_m2m_ctx *ctx = fh_to_ctx(fh);
struct mdp_frame *frame;
bool valid = false;
if (s->type == V4L2_BUF_TYPE_VIDEO_OUTPUT)
valid = mdp_target_is_crop(s->target);
else if (s->type == V4L2_BUF_TYPE_VIDEO_CAPTURE)
valid = mdp_target_is_compose(s->target);
if (!valid)
return -EINVAL;
switch (s->target) {
case V4L2_SEL_TGT_CROP:
if (s->type != V4L2_BUF_TYPE_VIDEO_OUTPUT)
return -EINVAL;
frame = ctx_get_frame(ctx, V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE);
s->r = frame->crop.c;
return 0;
case V4L2_SEL_TGT_COMPOSE:
if (s->type != V4L2_BUF_TYPE_VIDEO_CAPTURE)
return -EINVAL;
frame = ctx_get_frame(ctx, V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE);
s->r = frame->compose;
return 0;
case V4L2_SEL_TGT_CROP_DEFAULT:
case V4L2_SEL_TGT_CROP_BOUNDS:
if (s->type != V4L2_BUF_TYPE_VIDEO_OUTPUT)
return -EINVAL;
frame = ctx_get_frame(ctx, s->type);
s->r.left = 0;
s->r.top = 0;
s->r.width = frame->format.fmt.pix_mp.width;
s->r.height = frame->format.fmt.pix_mp.height;
return 0;
case V4L2_SEL_TGT_COMPOSE_DEFAULT:
case V4L2_SEL_TGT_COMPOSE_BOUNDS:
if (s->type != V4L2_BUF_TYPE_VIDEO_CAPTURE)
return -EINVAL;
frame = ctx_get_frame(ctx, s->type);
s->r.left = 0;
s->r.top = 0;
s->r.width = frame->format.fmt.pix_mp.width;
s->r.height = frame->format.fmt.pix_mp.height;
return 0;
}
return -EINVAL;
}
static int mdp_m2m_s_selection(struct file *file, void *fh,
struct v4l2_selection *s)
{
struct mdp_m2m_ctx *ctx = fh_to_ctx(fh);
struct mdp_frame *frame = ctx_get_frame(ctx, s->type);
struct mdp_frame *capture;
struct v4l2_rect r;
struct device *dev = &ctx->mdp_dev->pdev->dev;
bool valid = false;
int ret;
if (s->type == V4L2_BUF_TYPE_VIDEO_OUTPUT)
valid = (s->target == V4L2_SEL_TGT_CROP);
else if (s->type == V4L2_BUF_TYPE_VIDEO_CAPTURE)
valid = (s->target == V4L2_SEL_TGT_COMPOSE);
if (!valid) {
dev_dbg(dev, "[%s:%d] invalid type:%u target:%u", __func__,
ctx->id, s->type, s->target);
return -EINVAL;
}
ret = mdp_try_crop(ctx, &r, s, frame);
if (ret)
return ret;
capture = ctx_get_frame(ctx, V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE);
if (mdp_target_is_crop(s->target))
capture->crop.c = r;
else
capture->compose = r;
s->r = r;
return 0;
}
static const struct v4l2_ioctl_ops mdp_m2m_ioctl_ops = {
.vidioc_querycap = mdp_m2m_querycap,
.vidioc_enum_fmt_vid_cap = mdp_m2m_enum_fmt_mplane,
.vidioc_enum_fmt_vid_out = mdp_m2m_enum_fmt_mplane,
.vidioc_g_fmt_vid_cap_mplane = mdp_m2m_g_fmt_mplane,
.vidioc_g_fmt_vid_out_mplane = mdp_m2m_g_fmt_mplane,
.vidioc_s_fmt_vid_cap_mplane = mdp_m2m_s_fmt_mplane,
.vidioc_s_fmt_vid_out_mplane = mdp_m2m_s_fmt_mplane,
.vidioc_try_fmt_vid_cap_mplane = mdp_m2m_try_fmt_mplane,
.vidioc_try_fmt_vid_out_mplane = mdp_m2m_try_fmt_mplane,
.vidioc_reqbufs = v4l2_m2m_ioctl_reqbufs,
.vidioc_querybuf = v4l2_m2m_ioctl_querybuf,
.vidioc_qbuf = v4l2_m2m_ioctl_qbuf,
.vidioc_expbuf = v4l2_m2m_ioctl_expbuf,
.vidioc_dqbuf = v4l2_m2m_ioctl_dqbuf,
.vidioc_create_bufs = v4l2_m2m_ioctl_create_bufs,
.vidioc_streamon = v4l2_m2m_ioctl_streamon,
.vidioc_streamoff = v4l2_m2m_ioctl_streamoff,
.vidioc_g_selection = mdp_m2m_g_selection,
.vidioc_s_selection = mdp_m2m_s_selection,
.vidioc_subscribe_event = v4l2_ctrl_subscribe_event,
.vidioc_unsubscribe_event = v4l2_event_unsubscribe,
};
static int mdp_m2m_queue_init(void *priv,
struct vb2_queue *src_vq,
struct vb2_queue *dst_vq)
{
struct mdp_m2m_ctx *ctx = priv;
int ret;
src_vq->type = V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE;
src_vq->io_modes = VB2_MMAP | VB2_DMABUF;
src_vq->ops = &mdp_m2m_qops;
src_vq->mem_ops = &vb2_dma_contig_memops;
src_vq->drv_priv = ctx;
src_vq->buf_struct_size = sizeof(struct v4l2_m2m_buffer);
src_vq->timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_COPY;
src_vq->dev = &ctx->mdp_dev->pdev->dev;
src_vq->lock = &ctx->ctx_lock;
ret = vb2_queue_init(src_vq);
if (ret)
return ret;
dst_vq->type = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE;
dst_vq->io_modes = VB2_MMAP | VB2_DMABUF;
dst_vq->ops = &mdp_m2m_qops;
dst_vq->mem_ops = &vb2_dma_contig_memops;
dst_vq->drv_priv = ctx;
dst_vq->buf_struct_size = sizeof(struct v4l2_m2m_buffer);
dst_vq->timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_COPY;
dst_vq->dev = &ctx->mdp_dev->pdev->dev;
dst_vq->lock = &ctx->ctx_lock;
return vb2_queue_init(dst_vq);
}
static int mdp_m2m_s_ctrl(struct v4l2_ctrl *ctrl)
{
struct mdp_m2m_ctx *ctx = ctrl_to_ctx(ctrl);
struct mdp_frame *capture;
capture = ctx_get_frame(ctx, V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE);
switch (ctrl->id) {
case V4L2_CID_HFLIP:
capture->hflip = ctrl->val;
break;
case V4L2_CID_VFLIP:
capture->vflip = ctrl->val;
break;
case V4L2_CID_ROTATE:
capture->rotation = ctrl->val;
break;
}
return 0;
}
static const struct v4l2_ctrl_ops mdp_m2m_ctrl_ops = {
.s_ctrl = mdp_m2m_s_ctrl,
};
static int mdp_m2m_ctrls_create(struct mdp_m2m_ctx *ctx)
{
v4l2_ctrl_handler_init(&ctx->ctrl_handler, MDP_MAX_CTRLS);
ctx->ctrls.hflip = v4l2_ctrl_new_std(&ctx->ctrl_handler,
&mdp_m2m_ctrl_ops, V4L2_CID_HFLIP,
0, 1, 1, 0);
ctx->ctrls.vflip = v4l2_ctrl_new_std(&ctx->ctrl_handler,
&mdp_m2m_ctrl_ops, V4L2_CID_VFLIP,
0, 1, 1, 0);
ctx->ctrls.rotate = v4l2_ctrl_new_std(&ctx->ctrl_handler,
&mdp_m2m_ctrl_ops,
V4L2_CID_ROTATE, 0, 270, 90, 0);
if (ctx->ctrl_handler.error) {
int err = ctx->ctrl_handler.error;
v4l2_ctrl_handler_free(&ctx->ctrl_handler);
dev_err(&ctx->mdp_dev->pdev->dev,
"Failed to register controls\n");
return err;
}
return 0;
}
static int mdp_m2m_open(struct file *file)
{
struct video_device *vdev = video_devdata(file);
struct mdp_dev *mdp = video_get_drvdata(vdev);
struct mdp_m2m_ctx *ctx;
struct device *dev = &mdp->pdev->dev;
int ret;
struct v4l2_format default_format = {};
ctx = kzalloc(sizeof(*ctx), GFP_KERNEL);
if (!ctx)
return -ENOMEM;
if (mutex_lock_interruptible(&mdp->m2m_lock)) {
ret = -ERESTARTSYS;
goto err_free_ctx;
}
ctx->id = ida_alloc(&mdp->mdp_ida, GFP_KERNEL);
ctx->mdp_dev = mdp;
v4l2_fh_init(&ctx->fh, vdev);
file->private_data = &ctx->fh;
ret = mdp_m2m_ctrls_create(ctx);
if (ret)
goto err_exit_fh;
/* Use separate control handler per file handle */
ctx->fh.ctrl_handler = &ctx->ctrl_handler;
v4l2_fh_add(&ctx->fh);
mutex_init(&ctx->ctx_lock);
ctx->m2m_ctx = v4l2_m2m_ctx_init(mdp->m2m_dev, ctx, mdp_m2m_queue_init);
if (IS_ERR(ctx->m2m_ctx)) {
dev_err(dev, "Failed to initialize m2m context\n");
ret = PTR_ERR(ctx->m2m_ctx);
goto err_release_handler;
}
ctx->fh.m2m_ctx = ctx->m2m_ctx;
ctx->curr_param.ctx = ctx;
ret = mdp_frameparam_init(&ctx->curr_param);
if (ret) {
dev_err(dev, "Failed to initialize mdp parameter\n");
goto err_release_m2m_ctx;
}
mutex_unlock(&mdp->m2m_lock);
/* Default format */
default_format.type = V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE;
default_format.fmt.pix_mp.width = 32;
default_format.fmt.pix_mp.height = 32;
default_format.fmt.pix_mp.pixelformat = V4L2_PIX_FMT_YUV420M;
mdp_m2m_s_fmt_mplane(file, &ctx->fh, &default_format);
default_format.type = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE;
mdp_m2m_s_fmt_mplane(file, &ctx->fh, &default_format);
dev_dbg(dev, "%s:[%d]", __func__, ctx->id);
return 0;
err_release_m2m_ctx:
v4l2_m2m_ctx_release(ctx->m2m_ctx);
err_release_handler:
v4l2_ctrl_handler_free(&ctx->ctrl_handler);
v4l2_fh_del(&ctx->fh);
err_exit_fh:
v4l2_fh_exit(&ctx->fh);
mutex_unlock(&mdp->m2m_lock);
err_free_ctx:
kfree(ctx);
return ret;
}
static int mdp_m2m_release(struct file *file)
{
struct mdp_m2m_ctx *ctx = fh_to_ctx(file->private_data);
struct mdp_dev *mdp = video_drvdata(file);
struct device *dev = &mdp->pdev->dev;
mutex_lock(&mdp->m2m_lock);
v4l2_m2m_ctx_release(ctx->m2m_ctx);
if (mdp_m2m_ctx_is_state_set(ctx, MDP_VPU_INIT)) {
mdp_vpu_ctx_deinit(&ctx->vpu);
mdp_vpu_put_locked(mdp);
}
v4l2_ctrl_handler_free(&ctx->ctrl_handler);
v4l2_fh_del(&ctx->fh);
v4l2_fh_exit(&ctx->fh);
ida_free(&mdp->mdp_ida, ctx->id);
mutex_unlock(&mdp->m2m_lock);
dev_dbg(dev, "%s:[%d]", __func__, ctx->id);
kfree(ctx);
return 0;
}
static const struct v4l2_file_operations mdp_m2m_fops = {
.owner = THIS_MODULE,
.poll = v4l2_m2m_fop_poll,
.unlocked_ioctl = video_ioctl2,
.mmap = v4l2_m2m_fop_mmap,
.open = mdp_m2m_open,
.release = mdp_m2m_release,
};
static const struct v4l2_m2m_ops mdp_m2m_ops = {
.device_run = mdp_m2m_device_run,
};
int mdp_m2m_device_register(struct mdp_dev *mdp)
{
struct device *dev = &mdp->pdev->dev;
int ret = 0;
mdp->m2m_vdev = video_device_alloc();
if (!mdp->m2m_vdev) {
dev_err(dev, "Failed to allocate video device\n");
ret = -ENOMEM;
goto err_video_alloc;
}
mdp->m2m_vdev->device_caps = V4L2_CAP_VIDEO_M2M_MPLANE |
V4L2_CAP_STREAMING;
mdp->m2m_vdev->fops = &mdp_m2m_fops;
mdp->m2m_vdev->ioctl_ops = &mdp_m2m_ioctl_ops;
mdp->m2m_vdev->release = mdp_video_device_release;
mdp->m2m_vdev->lock = &mdp->m2m_lock;
mdp->m2m_vdev->vfl_dir = VFL_DIR_M2M;
mdp->m2m_vdev->v4l2_dev = &mdp->v4l2_dev;
snprintf(mdp->m2m_vdev->name, sizeof(mdp->m2m_vdev->name), "%s:m2m",
MDP_MODULE_NAME);
video_set_drvdata(mdp->m2m_vdev, mdp);
mdp->m2m_dev = v4l2_m2m_init(&mdp_m2m_ops);
if (IS_ERR(mdp->m2m_dev)) {
dev_err(dev, "Failed to initialize v4l2-m2m device\n");
ret = PTR_ERR(mdp->m2m_dev);
goto err_m2m_init;
}
ret = video_register_device(mdp->m2m_vdev, VFL_TYPE_VIDEO, -1);
if (ret) {
dev_err(dev, "Failed to register video device\n");
goto err_video_register;
}
v4l2_info(&mdp->v4l2_dev, "Driver registered as /dev/video%d",
mdp->m2m_vdev->num);
return 0;
err_video_register:
v4l2_m2m_release(mdp->m2m_dev);
err_m2m_init:
video_device_release(mdp->m2m_vdev);
err_video_alloc:
return ret;
}
void mdp_m2m_device_unregister(struct mdp_dev *mdp)
{
video_unregister_device(mdp->m2m_vdev);
}
void mdp_m2m_job_finish(struct mdp_m2m_ctx *ctx)
{
enum vb2_buffer_state vb_state = VB2_BUF_STATE_DONE;
mdp_m2m_process_done(ctx, vb_state);
}