// SPDX-License-Identifier: GPL-2.0-or-later
/*
* i.MX IPUv3 IC PP mem2mem CSC/Scaler driver
*
* Copyright (C) 2011 Pengutronix, Sascha Hauer
* Copyright (C) 2018 Pengutronix, Philipp Zabel
*/
#include <linux/module.h>
#include <linux/delay.h>
#include <linux/fs.h>
#include <linux/sched.h>
#include <linux/slab.h>
#include <video/imx-ipu-v3.h>
#include <video/imx-ipu-image-convert.h>
#include <media/media-device.h>
#include <media/v4l2-ctrls.h>
#include <media/v4l2-event.h>
#include <media/v4l2-mem2mem.h>
#include <media/v4l2-device.h>
#include <media/v4l2-ioctl.h>
#include <media/videobuf2-dma-contig.h>
#include "imx-media.h"
#define fh_to_ctx(__fh) container_of(__fh, struct ipu_csc_scaler_ctx, fh)
enum {
V4L2_M2M_SRC = 0,
V4L2_M2M_DST = 1,
};
struct ipu_csc_scaler_priv {
struct imx_media_video_dev vdev;
struct v4l2_m2m_dev *m2m_dev;
struct device *dev;
struct imx_media_dev *md;
struct mutex mutex; /* mem2mem device mutex */
};
#define vdev_to_priv(v) container_of(v, struct ipu_csc_scaler_priv, vdev)
/* Per-queue, driver-specific private data */
struct ipu_csc_scaler_q_data {
struct v4l2_pix_format cur_fmt;
struct v4l2_rect rect;
};
struct ipu_csc_scaler_ctx {
struct ipu_csc_scaler_priv *priv;
struct v4l2_fh fh;
struct ipu_csc_scaler_q_data q_data[2];
struct ipu_image_convert_ctx *icc;
struct v4l2_ctrl_handler ctrl_hdlr;
int rotate;
bool hflip;
bool vflip;
enum ipu_rotate_mode rot_mode;
unsigned int sequence;
};
static struct ipu_csc_scaler_q_data *get_q_data(struct ipu_csc_scaler_ctx *ctx,
enum v4l2_buf_type type)
{
if (V4L2_TYPE_IS_OUTPUT(type))
return &ctx->q_data[V4L2_M2M_SRC];
else
return &ctx->q_data[V4L2_M2M_DST];
}
/*
* mem2mem callbacks
*/
static void job_abort(void *_ctx)
{
struct ipu_csc_scaler_ctx *ctx = _ctx;
if (ctx->icc)
ipu_image_convert_abort(ctx->icc);
}
static void ipu_ic_pp_complete(struct ipu_image_convert_run *run, void *_ctx)
{
struct ipu_csc_scaler_ctx *ctx = _ctx;
struct ipu_csc_scaler_priv *priv = ctx->priv;
struct vb2_v4l2_buffer *src_buf, *dst_buf;
src_buf = v4l2_m2m_src_buf_remove(ctx->fh.m2m_ctx);
dst_buf = v4l2_m2m_dst_buf_remove(ctx->fh.m2m_ctx);
v4l2_m2m_buf_copy_metadata(src_buf, dst_buf, true);
src_buf->sequence = ctx->sequence++;
dst_buf->sequence = src_buf->sequence;
v4l2_m2m_buf_done(src_buf, run->status ? VB2_BUF_STATE_ERROR :
VB2_BUF_STATE_DONE);
v4l2_m2m_buf_done(dst_buf, run->status ? VB2_BUF_STATE_ERROR :
VB2_BUF_STATE_DONE);
v4l2_m2m_job_finish(priv->m2m_dev, ctx->fh.m2m_ctx);
kfree(run);
}
static void device_run(void *_ctx)
{
struct ipu_csc_scaler_ctx *ctx = _ctx;
struct ipu_csc_scaler_priv *priv = ctx->priv;
struct vb2_v4l2_buffer *src_buf, *dst_buf;
struct ipu_image_convert_run *run;
int ret;
src_buf = v4l2_m2m_next_src_buf(ctx->fh.m2m_ctx);
dst_buf = v4l2_m2m_next_dst_buf(ctx->fh.m2m_ctx);
run = kzalloc(sizeof(*run), GFP_KERNEL);
if (!run)
goto err;
run->ctx = ctx->icc;
run->in_phys = vb2_dma_contig_plane_dma_addr(&src_buf->vb2_buf, 0);
run->out_phys = vb2_dma_contig_plane_dma_addr(&dst_buf->vb2_buf, 0);
ret = ipu_image_convert_queue(run);
if (ret < 0) {
v4l2_err(ctx->priv->vdev.vfd->v4l2_dev,
"%s: failed to queue: %d\n", __func__, ret);
goto err;
}
return;
err:
v4l2_m2m_src_buf_remove(ctx->fh.m2m_ctx);
v4l2_m2m_dst_buf_remove(ctx->fh.m2m_ctx);
v4l2_m2m_buf_done(src_buf, VB2_BUF_STATE_ERROR);
v4l2_m2m_buf_done(dst_buf, VB2_BUF_STATE_ERROR);
v4l2_m2m_job_finish(priv->m2m_dev, ctx->fh.m2m_ctx);
}
/*
* Video ioctls
*/
static int ipu_csc_scaler_querycap(struct file *file, void *priv,
struct v4l2_capability *cap)
{
strscpy(cap->driver, "imx-media-csc-scaler", sizeof(cap->driver));
strscpy(cap->card, "imx-media-csc-scaler", sizeof(cap->card));
strscpy(cap->bus_info, "platform:imx-media-csc-scaler",
sizeof(cap->bus_info));
return 0;
}
static int ipu_csc_scaler_enum_fmt(struct file *file, void *fh,
struct v4l2_fmtdesc *f)
{
u32 fourcc;
int ret;
ret = imx_media_enum_format(&fourcc, f->index, CS_SEL_YUV_RGB);