Re: [PATCH 3/3] media: add Rockchip VPU driver

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



On 05/07/18 19:28, Ezequiel Garcia wrote:
> Add a mem2mem driver for the VPU available on Rockchip SoCs.
> Currently only JPEG encoding is supported, for RK3399 and RK3288
> platforms.
> 
> Signed-off-by: Ezequiel Garcia <ezequiel@xxxxxxxxxxxxx>
> ---
>  drivers/media/platform/Kconfig                |  12 +
>  drivers/media/platform/Makefile               |   1 +
>  drivers/media/platform/rockchip/vpu/Makefile  |   8 +
>  .../platform/rockchip/vpu/rk3288_vpu_hw.c     | 127 +++
>  .../rockchip/vpu/rk3288_vpu_hw_jpege.c        | 156 ++++
>  .../platform/rockchip/vpu/rk3288_vpu_regs.h   | 442 ++++++++++
>  .../platform/rockchip/vpu/rk3399_vpu_hw.c     | 127 +++
>  .../rockchip/vpu/rk3399_vpu_hw_jpege.c        | 165 ++++
>  .../platform/rockchip/vpu/rk3399_vpu_regs.h   | 601 ++++++++++++++
>  .../platform/rockchip/vpu/rockchip_vpu.h      | 270 +++++++
>  .../platform/rockchip/vpu/rockchip_vpu_drv.c  | 416 ++++++++++
>  .../platform/rockchip/vpu/rockchip_vpu_enc.c  | 763 ++++++++++++++++++
>  .../platform/rockchip/vpu/rockchip_vpu_enc.h  |  25 +
>  .../platform/rockchip/vpu/rockchip_vpu_hw.h   |  67 ++
>  14 files changed, 3180 insertions(+)
>  create mode 100644 drivers/media/platform/rockchip/vpu/Makefile
>  create mode 100644 drivers/media/platform/rockchip/vpu/rk3288_vpu_hw.c
>  create mode 100644 drivers/media/platform/rockchip/vpu/rk3288_vpu_hw_jpege.c
>  create mode 100644 drivers/media/platform/rockchip/vpu/rk3288_vpu_regs.h
>  create mode 100644 drivers/media/platform/rockchip/vpu/rk3399_vpu_hw.c
>  create mode 100644 drivers/media/platform/rockchip/vpu/rk3399_vpu_hw_jpege.c
>  create mode 100644 drivers/media/platform/rockchip/vpu/rk3399_vpu_regs.h
>  create mode 100644 drivers/media/platform/rockchip/vpu/rockchip_vpu.h
>  create mode 100644 drivers/media/platform/rockchip/vpu/rockchip_vpu_drv.c
>  create mode 100644 drivers/media/platform/rockchip/vpu/rockchip_vpu_enc.c
>  create mode 100644 drivers/media/platform/rockchip/vpu/rockchip_vpu_enc.h
>  create mode 100644 drivers/media/platform/rockchip/vpu/rockchip_vpu_hw.h
> 
> diff --git a/drivers/media/platform/Kconfig b/drivers/media/platform/Kconfig
> index 2728376b04b5..7244a2360732 100644
> --- a/drivers/media/platform/Kconfig
> +++ b/drivers/media/platform/Kconfig
> @@ -448,6 +448,18 @@ config VIDEO_ROCKCHIP_RGA
>  
>  	  To compile this driver as a module choose m here.
>  
> +config VIDEO_ROCKCHIP_VPU
> +	tristate "Rockchip VPU driver"
> +	depends on VIDEO_DEV && VIDEO_V4L2
> +	depends on ARCH_ROCKCHIP || COMPILE_TEST
> +	select VIDEOBUF2_DMA_CONTIG
> +	select V4L2_MEM2MEM_DEV
> +	default n
> +	---help---
> +	  Support for the VPU video codec found on Rockchip SoC.
> +	  To compile this driver as a module, choose M here: the module
> +	  will be called rockchip-vpu.
> +
>  config VIDEO_TI_VPE
>  	tristate "TI VPE (Video Processing Engine) driver"
>  	depends on VIDEO_DEV && VIDEO_V4L2
> diff --git a/drivers/media/platform/Makefile b/drivers/media/platform/Makefile
> index 04bc1502a30e..83378180d8ac 100644
> --- a/drivers/media/platform/Makefile
> +++ b/drivers/media/platform/Makefile
> @@ -66,6 +66,7 @@ obj-$(CONFIG_VIDEO_RENESAS_JPU)		+= rcar_jpu.o
>  obj-$(CONFIG_VIDEO_RENESAS_VSP1)	+= vsp1/
>  
>  obj-$(CONFIG_VIDEO_ROCKCHIP_RGA)	+= rockchip/rga/
> +obj-$(CONFIG_VIDEO_ROCKCHIP_VPU)        += rockchip/vpu/
>  
>  obj-y	+= omap/
>  
> diff --git a/drivers/media/platform/rockchip/vpu/Makefile b/drivers/media/platform/rockchip/vpu/Makefile
> new file mode 100644
> index 000000000000..cab0123c49d4
> --- /dev/null
> +++ b/drivers/media/platform/rockchip/vpu/Makefile
> @@ -0,0 +1,8 @@
> +obj-$(CONFIG_VIDEO_ROCKCHIP_VPU) += rockchip-vpu.o
> +
> +rockchip-vpu-y += rockchip_vpu_drv.o \
> +		rockchip_vpu_enc.o \
> +		rk3288_vpu_hw.o \
> +		rk3288_vpu_hw_jpege.o \
> +		rk3399_vpu_hw.o \
> +		rk3399_vpu_hw_jpege.o
> diff --git a/drivers/media/platform/rockchip/vpu/rk3288_vpu_hw.c b/drivers/media/platform/rockchip/vpu/rk3288_vpu_hw.c
> new file mode 100644
> index 000000000000..0caff8ecf343
> --- /dev/null
> +++ b/drivers/media/platform/rockchip/vpu/rk3288_vpu_hw.c
> @@ -0,0 +1,127 @@
> +// SPDX-License-Identifier: GPL-2.0
> +/*
> + * Rockchip VPU codec driver
> + *
> + * Copyright (C) 2018 Rockchip Electronics Co., Ltd.
> + *	Jeffy Chen <jeffy.chen@xxxxxxxxxxxxxx>
> + */
> +
> +#include <linux/clk.h>
> +
> +#include "rockchip_vpu.h"
> +#include "rk3288_vpu_regs.h"
> +
> +#define RK3288_ACLK_MAX_FREQ (400 * 1000 * 1000)
> +
> +/*
> + * Supported formats.
> + */
> +
> +static const struct rockchip_vpu_fmt rk3288_vpu_enc_fmts[] = {
> +	/* Source formats. */
> +	{
> +		.name = "4:2:0 3 planes Y/Cb/Cr",

Drop the name field, it's not needed.

> +		.fourcc = V4L2_PIX_FMT_YUV420M,
> +		.codec_mode = RK_VPU_CODEC_NONE,
> +		.num_planes = 3,
> +		.depth = { 8, 4, 4 },
> +		.enc_fmt = RK3288_VPU_ENC_FMT_YUV420P,
> +	},
> +	{
> +		.name = "4:2:0 2 plane Y/CbCr",
> +		.fourcc = V4L2_PIX_FMT_NV12M,
> +		.codec_mode = RK_VPU_CODEC_NONE,
> +		.num_planes = 2,
> +		.depth = { 8, 8 },
> +		.enc_fmt = RK3288_VPU_ENC_FMT_YUV420SP,
> +	},
> +	{
> +		.name = "4:2:2 1 plane YUYV",
> +		.fourcc = V4L2_PIX_FMT_YUYV,
> +		.codec_mode = RK_VPU_CODEC_NONE,
> +		.num_planes = 1,
> +		.depth = { 16 },
> +		.enc_fmt = RK3288_VPU_ENC_FMT_YUYV422,
> +	},
> +	{
> +		.name = "4:2:2 1 plane UYVY",
> +		.fourcc = V4L2_PIX_FMT_UYVY,
> +		.codec_mode = RK_VPU_CODEC_NONE,
> +		.num_planes = 1,
> +		.depth = { 16 },
> +		.enc_fmt = RK3288_VPU_ENC_FMT_UYVY422,
> +	},
> +	/* Destination formats. */
> +	{
> +		.name = "JPEG Encoded Stream",
> +		.fourcc = V4L2_PIX_FMT_JPEG_RAW,
> +		.codec_mode = RK_VPU_CODEC_JPEGE,
> +		.num_planes = 1,
> +		.frmsize = {
> +			.min_width = 96,
> +			.max_width = 8192,
> +			.step_width = MB_DIM,
> +			.min_height = 32,
> +			.max_height = 8192,
> +			.step_height = MB_DIM,
> +		},
> +	},
> +};

<snip>

> diff --git a/drivers/media/platform/rockchip/vpu/rockchip_vpu_drv.c b/drivers/media/platform/rockchip/vpu/rockchip_vpu_drv.c
> new file mode 100644
> index 000000000000..aea16850682c
> --- /dev/null
> +++ b/drivers/media/platform/rockchip/vpu/rockchip_vpu_drv.c
> @@ -0,0 +1,416 @@
> +// SPDX-License-Identifier: GPL-2.0
> +/*
> + * Rockchip VPU codec driver
> + *
> + * Copyright (C) 2018 Collabora, Ltd.
> + * Copyright (C) 2014 Google, Inc.
> + *	Tomasz Figa <tfiga@xxxxxxxxxxxx>
> + *
> + * Based on s5p-mfc driver by Samsung Electronics Co., Ltd.
> + * Copyright (C) 2011 Samsung Electronics Co., Ltd.
> + */
> +
> +#include <linux/clk.h>
> +#include <linux/module.h>
> +#include <linux/of.h>
> +#include <linux/platform_device.h>
> +#include <linux/pm.h>
> +#include <linux/pm_runtime.h>
> +#include <linux/slab.h>
> +#include <linux/videodev2.h>
> +#include <linux/workqueue.h>
> +#include <media/v4l2-event.h>
> +#include <media/v4l2-mem2mem.h>
> +#include <media/videobuf2-core.h>
> +#include <media/videobuf2-core.h>
> +#include <media/videobuf2-dma-contig.h>
> +
> +#include "rockchip_vpu.h"
> +#include "rockchip_vpu_enc.h"
> +#include "rockchip_vpu_hw.h"
> +
> +#define DRIVER_NAME "rockchip-vpu"
> +
> +int rockchip_vpu_debug;
> +module_param_named(debug, rockchip_vpu_debug, int, 0644);
> +MODULE_PARM_DESC(debug,
> +		 "Debug level - higher value produces more verbose messages");
> +
> +static inline struct rockchip_vpu_ctx *
> +rockchip_vpu_set_ctx(struct rockchip_vpu_dev *vpu,
> +		     struct rockchip_vpu_ctx *new_ctx)
> +{
> +	struct rockchip_vpu_ctx *ctx;
> +	unsigned long flags;
> +
> +	spin_lock_irqsave(&vpu->irqlock, flags);
> +	ctx = vpu->running_ctx;
> +	vpu->running_ctx = new_ctx;
> +	spin_unlock_irqrestore(&vpu->irqlock, flags);
> +
> +	return ctx;
> +}
> +
> +void rockchip_vpu_irq_done(struct rockchip_vpu_dev *vpu)
> +{
> +	struct rockchip_vpu_ctx *ctx = rockchip_vpu_set_ctx(vpu, NULL);
> +
> +	/* Atomic watchdog cancel. The worker may still be
> +	 * running after calling this.
> +	 */
> +	cancel_delayed_work(&vpu->watchdog_work);
> +	if (ctx)
> +		ctx->codec_ops->done(ctx, VB2_BUF_STATE_DONE);
> +}
> +
> +void rockchip_vpu_watchdog(struct work_struct *work)
> +{
> +	struct rockchip_vpu_dev *vpu = container_of(to_delayed_work(work),
> +		struct rockchip_vpu_dev, watchdog_work);
> +	struct rockchip_vpu_ctx *ctx = rockchip_vpu_set_ctx(vpu, NULL);
> +
> +	if (ctx) {
> +		vpu_err("frame processing timed out!\n");
> +		ctx->codec_ops->reset(ctx);
> +		ctx->codec_ops->done(ctx, VB2_BUF_STATE_ERROR);
> +	}
> +}
> +
> +static void device_run(void *priv)
> +{
> +	struct rockchip_vpu_ctx *ctx = priv;
> +	struct rockchip_vpu_dev *vpu = ctx->dev;
> +
> +	rockchip_vpu_set_ctx(vpu, ctx);
> +	ctx->codec_ops->run(ctx);
> +}
> +
> +static struct v4l2_m2m_ops vpu_m2m_ops = {
> +	.device_run = device_run,
> +};
> +
> +static int
> +queue_init(void *priv, struct vb2_queue *src_vq, struct vb2_queue *dst_vq)
> +{
> +	struct rockchip_vpu_ctx *ctx = priv;
> +	int ret;
> +
> +	src_vq->type = V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE;
> +	src_vq->io_modes = VB2_MMAP | VB2_USERPTR | VB2_DMABUF;

Any reason for setting USERPTR?

> +	src_vq->drv_priv = ctx;
> +	src_vq->ops = &rockchip_vpu_enc_queue_ops;
> +	src_vq->mem_ops = &vb2_dma_contig_memops;

It isn't really useful in combination with dma_contig.

> +	src_vq->dma_attrs = DMA_ATTR_ALLOC_SINGLE_PAGES |
> +			    DMA_ATTR_NO_KERNEL_MAPPING;
> +	src_vq->buf_struct_size = sizeof(struct v4l2_m2m_buffer);
> +	src_vq->timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_COPY;
> +	src_vq->lock = &ctx->dev->vpu_mutex;
> +	src_vq->dev = ctx->dev->v4l2_dev.dev;
> +
> +	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_USERPTR | VB2_DMABUF;

Ditto.

> +	dst_vq->drv_priv = ctx;
> +	dst_vq->ops = &rockchip_vpu_enc_queue_ops;
> +	dst_vq->mem_ops = &vb2_dma_contig_memops;
> +	dst_vq->dma_attrs = DMA_ATTR_ALLOC_SINGLE_PAGES;
> +	dst_vq->buf_struct_size = sizeof(struct v4l2_m2m_buffer);
> +	dst_vq->timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_COPY;
> +	dst_vq->lock = &ctx->dev->vpu_mutex;
> +	dst_vq->dev = ctx->dev->v4l2_dev.dev;
> +
> +	return vb2_queue_init(dst_vq);
> +}
> +
> +/*
> + * V4L2 file operations.
> + */
> +
> +static int rockchip_vpu_open(struct file *filp)
> +{
> +	struct rockchip_vpu_dev *vpu = video_drvdata(filp);
> +	struct rockchip_vpu_ctx *ctx;
> +	int ret;
> +
> +	/*
> +	 * We do not need any extra locking here, because we operate only
> +	 * on local data here, except reading few fields from dev, which
> +	 * do not change through device's lifetime (which is guaranteed by
> +	 * reference on module from open()) and V4L2 internal objects (such
> +	 * as vdev and ctx->fh), which have proper locking done in respective
> +	 * helper functions used here.
> +	 */
> +
> +	ctx = kzalloc(sizeof(*ctx), GFP_KERNEL);
> +	if (!ctx)
> +		return -ENOMEM;
> +
> +	ctx->dev = vpu;
> +	ctx->fh.m2m_ctx = v4l2_m2m_ctx_init(vpu->m2m_dev, ctx, &queue_init);
> +	if (IS_ERR(ctx->fh.m2m_ctx)) {
> +		ret = PTR_ERR(ctx->fh.m2m_ctx);
> +		kfree(ctx);
> +		return ret;
> +	}
> +	v4l2_fh_init(&ctx->fh, video_devdata(filp));
> +	filp->private_data = &ctx->fh;
> +	v4l2_fh_add(&ctx->fh);
> +
> +	ctx->colorspace = V4L2_COLORSPACE_JPEG,
> +	ctx->ycbcr_enc = V4L2_YCBCR_ENC_DEFAULT;
> +	ctx->quantization = V4L2_QUANTIZATION_DEFAULT;
> +	ctx->xfer_func = V4L2_XFER_FUNC_DEFAULT;
> +
> +	ret = rockchip_vpu_enc_init(ctx);
> +	if (ret) {
> +		vpu_err("Failed to initialize encoder context\n");
> +		goto err_fh_free;
> +	}
> +	ctx->fh.ctrl_handler = &ctx->ctrl_handler;
> +	return 0;
> +
> +err_fh_free:
> +	v4l2_fh_del(&ctx->fh);
> +	v4l2_fh_exit(&ctx->fh);
> +	kfree(ctx);
> +	return ret;
> +}
> +
> +static int rockchip_vpu_release(struct file *filp)
> +{
> +	struct rockchip_vpu_ctx *ctx = fh_to_ctx(filp->private_data);
> +
> +	/*
> +	 * No need for extra locking because this was the last reference
> +	 * to this file.
> +	 */
> +	v4l2_m2m_ctx_release(ctx->fh.m2m_ctx);
> +	v4l2_fh_del(&ctx->fh);
> +	v4l2_fh_exit(&ctx->fh);
> +	rockchip_vpu_enc_exit(ctx);
> +	kfree(ctx);
> +
> +	return 0;
> +}
> +
> +static const struct v4l2_file_operations rockchip_vpu_fops = {
> +	.owner = THIS_MODULE,
> +	.open = rockchip_vpu_open,
> +	.release = rockchip_vpu_release,
> +	.poll = v4l2_m2m_fop_poll,
> +	.unlocked_ioctl = video_ioctl2,
> +	.mmap = v4l2_m2m_fop_mmap,
> +};
> +
> +static const struct of_device_id of_rockchip_vpu_match[] = {
> +	{ .compatible = "rockchip,rk3399-vpu", .data = &rk3399_vpu_variant, },
> +	{ .compatible = "rockchip,rk3288-vpu", .data = &rk3288_vpu_variant, },
> +	{ /* sentinel */ }
> +};
> +MODULE_DEVICE_TABLE(of, of_rockchip_vpu_match);
> +
> +static int rockchip_vpu_video_device_register(struct rockchip_vpu_dev *vpu)
> +{
> +	struct video_device *vfd;
> +	int ret;
> +
> +	vfd = video_device_alloc();
> +	if (!vfd) {
> +		v4l2_err(&vpu->v4l2_dev, "Failed to allocate video device\n");
> +		return -ENOMEM;
> +	}
> +
> +	vpu->vfd = vfd;
> +	vfd->fops = &rockchip_vpu_fops;
> +	vfd->release = video_device_release;
> +	vfd->lock = &vpu->vpu_mutex;
> +	vfd->v4l2_dev = &vpu->v4l2_dev;
> +	vfd->vfl_dir = VFL_DIR_M2M;
> +	vfd->ioctl_ops = &rockchip_vpu_enc_ioctl_ops;
> +	snprintf(vfd->name, sizeof(vfd->name), "%s-enc", DRIVER_NAME);
> +
> +	video_set_drvdata(vfd, vpu);
> +
> +	vpu->m2m_dev = v4l2_m2m_init(&vpu_m2m_ops);
> +	if (IS_ERR(vpu->m2m_dev)) {
> +		v4l2_err(&vpu->v4l2_dev, "Failed to init mem2mem device\n");
> +		ret = PTR_ERR(vpu->m2m_dev);
> +		goto err_dev_reg;
> +	}
> +
> +	ret = video_register_device(vfd, VFL_TYPE_GRABBER, 0);
> +	if (ret) {
> +		v4l2_err(&vpu->v4l2_dev, "Failed to register video device\n");
> +		goto err_m2m_rel;
> +	}
> +	return 0;
> +
> +err_m2m_rel:
> +	v4l2_m2m_release(vpu->m2m_dev);
> +err_dev_reg:
> +	video_device_release(vfd);
> +	return ret;
> +}
> +
> +static int rockchip_vpu_probe(struct platform_device *pdev)
> +{
> +	const struct of_device_id *match;
> +	struct rockchip_vpu_dev *vpu;
> +	struct resource *res;
> +	int irq, i, ret;
> +
> +	vpu = devm_kzalloc(&pdev->dev, sizeof(*vpu), GFP_KERNEL);
> +	if (!vpu)
> +		return -ENOMEM;
> +
> +	vpu->dev = &pdev->dev;
> +	vpu->pdev = pdev;
> +	mutex_init(&vpu->vpu_mutex);
> +	spin_lock_init(&vpu->irqlock);
> +
> +	match = of_match_node(of_rockchip_vpu_match, pdev->dev.of_node);
> +	vpu->variant = match->data;
> +
> +	INIT_DELAYED_WORK(&vpu->watchdog_work, rockchip_vpu_watchdog);
> +
> +	for (i = 0; i < vpu->variant->num_clocks; i++) {
> +		vpu->clocks[i] = devm_clk_get(&pdev->dev,
> +					      vpu->variant->clk_names[i]);
> +		if (IS_ERR(vpu->clocks[i])) {
> +			dev_err(&pdev->dev, "failed to get clock: %s\n",
> +				vpu->variant->clk_names[i]);
> +			return PTR_ERR(vpu->clocks[i]);
> +		}
> +	}
> +
> +	res = platform_get_resource(vpu->pdev, IORESOURCE_MEM, 0);
> +	vpu->base = devm_ioremap_resource(vpu->dev, res);
> +	if (IS_ERR(vpu->base))
> +		return PTR_ERR(vpu->base);
> +	vpu->enc_base = vpu->base + vpu->variant->enc_offset;
> +
> +	ret = dma_set_coherent_mask(vpu->dev, DMA_BIT_MASK(32));
> +	if (ret) {
> +		dev_err(vpu->dev, "Could not set DMA coherent mask.\n");
> +		return ret;
> +	}
> +
> +	irq = platform_get_irq_byname(vpu->pdev, "vepu");
> +	if (irq <= 0) {
> +		dev_err(vpu->dev, "Could not get vepu IRQ.\n");
> +		return -ENXIO;
> +	}
> +
> +	ret = devm_request_irq(vpu->dev, irq, vpu->variant->vepu_irq,
> +			       0, dev_name(vpu->dev), vpu);
> +	if (ret) {
> +		dev_err(vpu->dev, "Could not request vepu IRQ.\n");
> +		return ret;
> +	}
> +
> +	ret = vpu->variant->init(vpu);
> +	if (ret) {
> +		dev_err(&pdev->dev, "Failed to init VPU hardware\n");
> +		return ret;
> +	}
> +
> +	ret = v4l2_device_register(&pdev->dev, &vpu->v4l2_dev);
> +	if (ret) {
> +		dev_err(&pdev->dev, "Failed to register v4l2 device\n");
> +		return ret;
> +	}
> +
> +	platform_set_drvdata(pdev, vpu);
> +
> +	pm_runtime_set_autosuspend_delay(vpu->dev, 100);
> +	pm_runtime_use_autosuspend(vpu->dev);
> +	pm_runtime_enable(vpu->dev);
> +	pm_runtime_get_sync(vpu->dev);
> +
> +	ret = rockchip_vpu_video_device_register(vpu);
> +	if (ret) {
> +		dev_err(&pdev->dev, "Failed to register encoder\n");
> +		goto err_v4l2_dev_unreg;
> +	}
> +
> +	return 0;
> +
> +err_v4l2_dev_unreg:
> +	v4l2_device_unregister(&vpu->v4l2_dev);
> +	pm_runtime_mark_last_busy(vpu->dev);
> +	pm_runtime_put_autosuspend(vpu->dev);
> +	pm_runtime_disable(vpu->dev);
> +	return ret;
> +}
> +
> +static int rockchip_vpu_remove(struct platform_device *pdev)
> +{
> +	struct rockchip_vpu_dev *vpu = platform_get_drvdata(pdev);
> +
> +	v4l2_info(&vpu->v4l2_dev, "Removing %s\n", pdev->name);
> +
> +	video_unregister_device(vpu->vfd);
> +	video_device_release(vpu->vfd);
> +	v4l2_device_unregister(&vpu->v4l2_dev);
> +	pm_runtime_mark_last_busy(vpu->dev);
> +	pm_runtime_put_autosuspend(vpu->dev);
> +	pm_runtime_disable(vpu->dev);
> +
> +	return 0;
> +}
> +
> +static int __maybe_unused rockchip_vpu_runtime_suspend(struct device *dev)
> +{
> +	struct rockchip_vpu_dev *vpu = dev_get_drvdata(dev);
> +	int i;
> +
> +	for (i = vpu->variant->num_clocks - 1; i >= 0; i--)
> +		clk_disable_unprepare(vpu->clocks[i]);
> +	return 0;
> +}
> +
> +static int __maybe_unused rockchip_vpu_runtime_resume(struct device *dev)
> +{
> +	struct rockchip_vpu_dev *vpu = dev_get_drvdata(dev);
> +	int i;
> +
> +	for (i = 0; i < vpu->variant->num_clocks; i++) {
> +		int ret;
> +
> +		ret = clk_prepare_enable(vpu->clocks[i]);
> +		if (ret) {
> +			while (--i >= 0)
> +				clk_disable_unprepare(vpu->clocks[i]);
> +			return ret;
> +		}
> +	}
> +
> +	return 0;
> +}
> +
> +static const struct dev_pm_ops rockchip_vpu_pm_ops = {
> +	SET_SYSTEM_SLEEP_PM_OPS(pm_runtime_force_suspend,
> +				pm_runtime_force_resume)
> +	SET_RUNTIME_PM_OPS(rockchip_vpu_runtime_suspend,
> +			   rockchip_vpu_runtime_resume, NULL)
> +};
> +
> +static struct platform_driver rockchip_vpu_driver = {
> +	.probe = rockchip_vpu_probe,
> +	.remove = rockchip_vpu_remove,
> +	.driver = {
> +		   .name = DRIVER_NAME,
> +		   .of_match_table = of_match_ptr(of_rockchip_vpu_match),
> +		   .pm = &rockchip_vpu_pm_ops,
> +	},
> +};
> +module_platform_driver(rockchip_vpu_driver);
> +
> +MODULE_LICENSE("GPL v2");
> +MODULE_AUTHOR("Alpha Lin <Alpha.Lin@xxxxxxxxxxxxxx>");
> +MODULE_AUTHOR("Tomasz Figa <tfiga@xxxxxxxxxxxx>");
> +MODULE_AUTHOR("Ezequiel Garcia <ezequiel@xxxxxxxxxxxxx>");
> +MODULE_DESCRIPTION("Rockchip VPU codec driver");
> diff --git a/drivers/media/platform/rockchip/vpu/rockchip_vpu_enc.c b/drivers/media/platform/rockchip/vpu/rockchip_vpu_enc.c
> new file mode 100644
> index 000000000000..6a5e45f7d69f
> --- /dev/null
> +++ b/drivers/media/platform/rockchip/vpu/rockchip_vpu_enc.c
> @@ -0,0 +1,763 @@
> +// SPDX-License-Identifier: GPL-2.0
> +/*
> + * Rockchip VPU codec driver
> + *
> + * Copyright (C) 2018 Collabora, Ltd.
> + * Copyright (C) 2018 Rockchip Electronics Co., Ltd.
> + *	Alpha Lin <Alpha.Lin@xxxxxxxxxxxxxx>
> + *	Jeffy Chen <jeffy.chen@xxxxxxxxxxxxxx>
> + *
> + * Copyright (C) 2018 Google, Inc.
> + *	Tomasz Figa <tfiga@xxxxxxxxxxxx>
> + *
> + * Based on s5p-mfc driver by Samsung Electronics Co., Ltd.
> + * Copyright (C) 2010-2011 Samsung Electronics Co., Ltd.
> + */
> +
> +#include <linux/interrupt.h>
> +#include <linux/io.h>
> +#include <linux/module.h>
> +#include <linux/pm_runtime.h>
> +#include <linux/videodev2.h>
> +#include <linux/workqueue.h>
> +#include <media/v4l2-ctrls.h>
> +#include <media/v4l2-event.h>
> +#include <media/v4l2-mem2mem.h>
> +#include <media/videobuf2-core.h>
> +#include <media/videobuf2-dma-sg.h>
> +
> +#include "rockchip_vpu.h"
> +#include "rockchip_vpu_enc.h"
> +#include "rockchip_vpu_hw.h"
> +
> +#define JPEG_MAX_BYTES_PER_PIXEL 2
> +
> +static const struct rockchip_vpu_fmt *
> +rockchip_vpu_find_format(struct rockchip_vpu_dev *dev, u32 fourcc)
> +{
> +	const struct rockchip_vpu_fmt *formats = dev->variant->enc_fmts;
> +	unsigned int i;
> +
> +	for (i = 0; i < dev->variant->num_enc_fmts; i++) {
> +		if (formats[i].fourcc == fourcc)
> +			return &formats[i];
> +	}
> +
> +	return NULL;
> +}
> +
> +static const struct rockchip_vpu_fmt *
> +rockchip_vpu_get_default_fmt(struct rockchip_vpu_dev *dev, bool bitstream)
> +{
> +	const struct rockchip_vpu_fmt *formats = dev->variant->enc_fmts;
> +	unsigned int i;
> +
> +	for (i = 0; i < dev->variant->num_enc_fmts; i++) {
> +		if (bitstream == (formats[i].codec_mode != RK_VPU_CODEC_NONE))
> +			return &formats[i];
> +	}
> +
> +	/* There must be at least one raw and one coded format in the array. */
> +	BUG_ON(i >= dev->variant->num_enc_fmts);
> +	return NULL;
> +}
> +
> +static const struct v4l2_ctrl_config controls[] = {
> +	[ROCKCHIP_VPU_ENC_CTRL_Y_QUANT_TBL] = {
> +		.id = V4L2_CID_JPEG_LUMA_QUANTIZATION,
> +		.type = V4L2_CTRL_TYPE_U8,
> +		.step = 1,
> +		.def = 0x00,
> +		.min = 0x00,
> +		.max = 0xff,
> +		.dims = { 8, 8 }
> +	},
> +	[ROCKCHIP_VPU_ENC_CTRL_C_QUANT_TBL] = {
> +		.id = V4L2_CID_JPEG_CHROMA_QUANTIZATION,
> +		.type = V4L2_CTRL_TYPE_U8,
> +		.step = 1,
> +		.def = 0x00,
> +		.min = 0x00,
> +		.max = 0xff,
> +		.dims = { 8, 8 }
> +	},
> +};
> +
> +static int vidioc_querycap(struct file *file, void *priv,
> +			   struct v4l2_capability *cap)
> +{
> +	struct rockchip_vpu_dev *vpu = video_drvdata(file);
> +
> +	strlcpy(cap->driver, vpu->dev->driver->name, sizeof(cap->driver));
> +	strlcpy(cap->card, vpu->vfd->name, sizeof(cap->card));
> +	snprintf(cap->bus_info, sizeof(cap->bus_info), "platform: %s",
> +		 vpu->dev->driver->name);
> +
> +	/*
> +	 * This is only a mem-to-mem video device.
> +	 */
> +	cap->device_caps = V4L2_CAP_VIDEO_M2M_MPLANE | V4L2_CAP_STREAMING;
> +	cap->capabilities = cap->device_caps | V4L2_CAP_DEVICE_CAPS;
> +
> +	return 0;
> +}
> +
> +static int vidioc_enum_framesizes(struct file *file, void *prov,
> +				  struct v4l2_frmsizeenum *fsize)
> +{
> +	struct rockchip_vpu_dev *dev = video_drvdata(file);
> +	const struct rockchip_vpu_fmt *fmt;
> +
> +	if (fsize->index != 0) {
> +		vpu_debug(0, "invalid frame size index (expected 0, got %d)\n",
> +				fsize->index);
> +		return -EINVAL;
> +	}
> +
> +	fmt = rockchip_vpu_find_format(dev, fsize->pixel_format);
> +	if (!fmt) {
> +		vpu_debug(0, "unsupported bitstream format (%08x)\n",
> +				fsize->pixel_format);
> +		return -EINVAL;
> +	}
> +
> +	/* This only makes sense for codec formats */
> +	if (fmt->codec_mode == RK_VPU_CODEC_NONE)
> +		return -ENOTTY;
> +
> +	fsize->type = V4L2_FRMSIZE_TYPE_STEPWISE;
> +	fsize->stepwise = fmt->frmsize;
> +
> +	return 0;
> +}
> +
> +static int vidioc_enum_fmt_vid_cap_mplane(struct file *file, void *priv,
> +					  struct v4l2_fmtdesc *f)
> +{
> +	struct rockchip_vpu_dev *dev = video_drvdata(file);
> +	const struct rockchip_vpu_fmt *fmt;
> +	const struct rockchip_vpu_fmt *formats = dev->variant->enc_fmts;
> +	int i, j = 0;
> +
> +	for (i = 0; i < dev->variant->num_enc_fmts; i++) {
> +		/* Skip uncompressed formats */
> +		if (formats[i].codec_mode == RK_VPU_CODEC_NONE)
> +			continue;
> +		if (j == f->index) {
> +			fmt = &formats[i];
> +			strlcpy(f->description,
> +				fmt->name, sizeof(f->description));

Don't fill in the description. The v4l2 core will take care of that.

> +			f->pixelformat = fmt->fourcc;
> +			f->flags = 0;
> +			f->flags |= V4L2_FMT_FLAG_COMPRESSED;

Same for this.

> +			return 0;
> +		}
> +		++j;
> +	}
> +	return -EINVAL;
> +}
> +
> +static int vidioc_enum_fmt_vid_out_mplane(struct file *file, void *priv,
> +					  struct v4l2_fmtdesc *f)
> +{
> +	struct rockchip_vpu_dev *dev = video_drvdata(file);
> +	const struct rockchip_vpu_fmt *fmt;
> +	const struct rockchip_vpu_fmt *formats = dev->variant->enc_fmts;
> +	int i, j = 0;
> +
> +	for (i = 0; i < dev->variant->num_enc_fmts; i++) {
> +		if (formats[i].codec_mode != RK_VPU_CODEC_NONE)
> +			continue;
> +		if (j == f->index) {
> +			fmt = &formats[i];
> +			strlcpy(f->description,
> +				fmt->name, sizeof(f->description));

Ditto.

> +			f->pixelformat = fmt->fourcc;
> +			f->flags = 0;

Ditto.

> +			return 0;
> +		}
> +		++j;
> +	}
> +	return -EINVAL;
> +}
> +
> +static int vidioc_g_fmt_out(struct file *file, void *priv,
> +				struct v4l2_format *f)
> +{
> +	struct v4l2_pix_format_mplane *pix_mp = &f->fmt.pix_mp;
> +	struct rockchip_vpu_ctx *ctx = fh_to_ctx(priv);
> +
> +	vpu_debug(4, "f->type = %d\n", f->type);
> +
> +	*pix_mp = ctx->src_fmt;
> +	pix_mp->colorspace = ctx->colorspace;
> +	pix_mp->ycbcr_enc = ctx->ycbcr_enc;
> +	pix_mp->xfer_func = ctx->xfer_func;
> +	pix_mp->quantization = ctx->quantization;
> +
> +	return 0;
> +}
> +
> +static int vidioc_g_fmt_cap(struct file *file, void *priv,
> +				struct v4l2_format *f)
> +{
> +	struct v4l2_pix_format_mplane *pix_mp = &f->fmt.pix_mp;
> +	struct rockchip_vpu_ctx *ctx = fh_to_ctx(priv);
> +
> +	vpu_debug(4, "f->type = %d\n", f->type);
> +
> +	*pix_mp = ctx->dst_fmt;
> +	pix_mp->colorspace = ctx->colorspace;
> +	pix_mp->ycbcr_enc = ctx->ycbcr_enc;
> +	pix_mp->xfer_func = ctx->xfer_func;
> +	pix_mp->quantization = ctx->quantization;
> +
> +	return 0;
> +}
> +
> +static void calculate_plane_sizes(const struct rockchip_vpu_fmt *fmt,
> +				  struct v4l2_pix_format_mplane *pix_mp)
> +{
> +	unsigned int w = pix_mp->width;
> +	unsigned int h = pix_mp->height;
> +	int i;
> +
> +	for (i = 0; i < fmt->num_planes; ++i) {
> +		memset(pix_mp->plane_fmt[i].reserved, 0,
> +		       sizeof(pix_mp->plane_fmt[i].reserved));
> +		pix_mp->plane_fmt[i].bytesperline = w * fmt->depth[i] / 8;
> +		pix_mp->plane_fmt[i].sizeimage = h *
> +					pix_mp->plane_fmt[i].bytesperline;
> +		/*
> +		 * All of multiplanar formats we support have chroma
> +		 * planes subsampled by 2 vertically.
> +		 */
> +		if (i != 0)
> +			pix_mp->plane_fmt[i].sizeimage /= 2;
> +	}
> +}
> +
> +static int vidioc_try_fmt_cap(struct file *file, void *priv, struct v4l2_format *f)
> +{
> +	struct rockchip_vpu_dev *dev = video_drvdata(file);
> +	struct rockchip_vpu_ctx *ctx = fh_to_ctx(priv);
> +	struct v4l2_pix_format_mplane *pix_mp = &f->fmt.pix_mp;
> +	const struct rockchip_vpu_fmt *fmt;
> +	char str[5];
> +
> +	vpu_debug(4, "%s\n", fmt2str(pix_mp->pixelformat, str));
> +
> +	fmt = rockchip_vpu_find_format(dev, pix_mp->pixelformat);
> +	if (!fmt) {
> +		fmt = rockchip_vpu_get_default_fmt(dev, true);
> +		f->fmt.pix.pixelformat = fmt->fourcc;
> +	}
> +
> +	/* Limit to hardware min/max. */
> +	pix_mp->width = clamp(pix_mp->width,
> +			ctx->vpu_dst_fmt->frmsize.min_width,
> +			ctx->vpu_dst_fmt->frmsize.max_width);
> +	pix_mp->height = clamp(pix_mp->height,
> +			ctx->vpu_dst_fmt->frmsize.min_height,
> +			ctx->vpu_dst_fmt->frmsize.max_height);
> +	pix_mp->num_planes = fmt->num_planes;
> +
> +	pix_mp->plane_fmt[0].sizeimage =
> +		pix_mp->width * pix_mp->height * JPEG_MAX_BYTES_PER_PIXEL;
> +	memset(pix_mp->plane_fmt[0].reserved, 0,
> +	       sizeof(pix_mp->plane_fmt[0].reserved));
> +	pix_mp->field = V4L2_FIELD_NONE;
> +
> +	return 0;
> +}
> +
> +static int vidioc_try_fmt_out(struct file *file, void *priv, struct v4l2_format *f)
> +{
> +	struct rockchip_vpu_dev *dev = video_drvdata(file);
> +	struct rockchip_vpu_ctx *ctx = fh_to_ctx(priv);
> +	struct v4l2_pix_format_mplane *pix_mp = &f->fmt.pix_mp;
> +	const struct rockchip_vpu_fmt *fmt;
> +	char str[5];
> +	unsigned long dma_align;
> +	bool need_alignment;
> +	int i;
> +
> +	vpu_debug(4, "%s\n", fmt2str(pix_mp->pixelformat, str));
> +
> +	fmt = rockchip_vpu_find_format(dev, pix_mp->pixelformat);
> +	if (!fmt) {
> +		fmt = rockchip_vpu_get_default_fmt(dev, false);
> +		f->fmt.pix.pixelformat = fmt->fourcc;
> +	}
> +
> +	/* Limit to hardware min/max. */
> +	pix_mp->width = clamp(pix_mp->width,
> +			ctx->vpu_dst_fmt->frmsize.min_width,
> +			ctx->vpu_dst_fmt->frmsize.max_width);
> +	pix_mp->height = clamp(pix_mp->height,
> +			ctx->vpu_dst_fmt->frmsize.min_height,
> +			ctx->vpu_dst_fmt->frmsize.max_height);
> +	/* Round up to macroblocks. */
> +	pix_mp->width = round_up(pix_mp->width, MB_DIM);
> +	pix_mp->height = round_up(pix_mp->height, MB_DIM);
> +	pix_mp->num_planes = fmt->num_planes;
> +	pix_mp->field = V4L2_FIELD_NONE;
> +
> +	vpu_debug(0, "OUTPUT codec mode: %d\n", fmt->codec_mode);
> +	vpu_debug(0, "fmt - w: %d, h: %d, mb - w: %d, h: %d\n",
> +		  pix_mp->width, pix_mp->height,
> +		  MB_WIDTH(pix_mp->width),
> +		  MB_HEIGHT(pix_mp->height));
> +
> +	/* Fill remaining fields */
> +	calculate_plane_sizes(fmt, pix_mp);
> +
> +	dma_align = dma_get_cache_alignment();
> +	need_alignment = false;
> +	for (i = 0; i < fmt->num_planes; i++) {
> +		if (!IS_ALIGNED(pix_mp->plane_fmt[i].sizeimage,
> +				dma_align)) {
> +			need_alignment = true;
> +			break;
> +		}
> +	}
> +	if (!need_alignment)
> +		return 0;
> +
> +	pix_mp->height = round_up(pix_mp->height, dma_align * 4 / MB_DIM);
> +	if (pix_mp->height > ctx->vpu_dst_fmt->frmsize.max_height) {
> +		vpu_err("Aligned height higher than maximum.\n");
> +		return -EINVAL;
> +	}
> +	/* Fill in remaining fields, again */
> +	calculate_plane_sizes(fmt, pix_mp);
> +	return 0;
> +}
> +
> +static void rockchip_vpu_reset_dst_fmt(struct rockchip_vpu_dev *vpu,
> +					struct rockchip_vpu_ctx *ctx)
> +{
> +	struct v4l2_pix_format_mplane *fmt = &ctx->dst_fmt;
> +
> +	ctx->vpu_dst_fmt = rockchip_vpu_get_default_fmt(vpu, true);
> +
> +	memset(fmt, 0, sizeof(*fmt));
> +
> +	fmt->width = ctx->vpu_dst_fmt->frmsize.min_width;
> +	fmt->height = ctx->vpu_dst_fmt->frmsize.min_height;
> +	fmt->pixelformat = ctx->vpu_dst_fmt->fourcc;
> +	fmt->num_planes = ctx->vpu_dst_fmt->num_planes;
> +	fmt->plane_fmt[0].sizeimage =
> +		fmt->width * fmt->height * JPEG_MAX_BYTES_PER_PIXEL;
> +
> +	fmt->field = V4L2_FIELD_NONE;
> +
> +	fmt->colorspace = ctx->colorspace;
> +	fmt->ycbcr_enc = ctx->ycbcr_enc;
> +	fmt->xfer_func = ctx->xfer_func;
> +	fmt->quantization = ctx->quantization;
> +}
> +
> +static void rockchip_vpu_reset_src_fmt(struct rockchip_vpu_dev *vpu,
> +					struct rockchip_vpu_ctx *ctx)
> +{
> +	struct v4l2_pix_format_mplane *fmt = &ctx->src_fmt;
> +
> +	ctx->vpu_src_fmt = rockchip_vpu_get_default_fmt(vpu, false);
> +
> +	memset(fmt, 0, sizeof(*fmt));
> +
> +	fmt->width = ctx->vpu_dst_fmt->frmsize.min_width;
> +	fmt->height = ctx->vpu_dst_fmt->frmsize.min_height;
> +	fmt->pixelformat = ctx->vpu_src_fmt->fourcc;
> +	fmt->num_planes = ctx->vpu_src_fmt->num_planes;
> +
> +	fmt->field = V4L2_FIELD_NONE;
> +
> +	fmt->colorspace = ctx->colorspace;
> +	fmt->ycbcr_enc = ctx->ycbcr_enc;
> +	fmt->xfer_func = ctx->xfer_func;
> +	fmt->quantization = ctx->quantization;
> +
> +	calculate_plane_sizes(ctx->vpu_src_fmt, fmt);
> +}
> +
> +static int vidioc_s_fmt_out(struct file *file, void *priv, struct v4l2_format *f)
> +{
> +	struct v4l2_pix_format_mplane *pix_mp = &f->fmt.pix_mp;
> +	struct rockchip_vpu_ctx *ctx = fh_to_ctx(priv);
> +	struct rockchip_vpu_dev *vpu = ctx->dev;
> +	struct vb2_queue *vq, *peer_vq;
> +	int ret;
> +
> +	/* Change not allowed if queue is streaming. */
> +	vq = v4l2_m2m_get_vq(ctx->fh.m2m_ctx, f->type);
> +	if (vb2_is_streaming(vq))
> +		return -EBUSY;
> +
> +	ctx->colorspace = pix_mp->colorspace;
> +	ctx->ycbcr_enc = pix_mp->ycbcr_enc;
> +	ctx->xfer_func = pix_mp->xfer_func;
> +	ctx->quantization = pix_mp->quantization;
> +
> +	/*
> +	 * Pixel format change is not allowed when the other queue has
> +	 * buffers allocated.
> +	 */
> +	peer_vq = v4l2_m2m_get_vq(ctx->fh.m2m_ctx,
> +		V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE);
> +	if (vb2_is_busy(peer_vq) &&
> +	    pix_mp->pixelformat != ctx->src_fmt.pixelformat)
> +		return -EBUSY;
> +
> +	ret = vidioc_try_fmt_out(file, priv, f);
> +	if (ret)
> +		return ret;
> +
> +	ctx->vpu_src_fmt = rockchip_vpu_find_format(vpu,
> +		pix_mp->pixelformat);
> +
> +	/* Reset crop rectangle. */
> +	ctx->src_crop.width = pix_mp->width;
> +	ctx->src_crop.height = pix_mp->height;
> +	ctx->src_fmt = *pix_mp;
> +
> +	return 0;
> +}
> +
> +static int vidioc_s_fmt_cap(struct file *file, void *priv, struct v4l2_format *f)
> +{
> +	struct v4l2_pix_format_mplane *pix_mp = &f->fmt.pix_mp;
> +	struct rockchip_vpu_ctx *ctx = fh_to_ctx(priv);
> +	struct rockchip_vpu_dev *vpu = ctx->dev;
> +	struct vb2_queue *vq, *peer_vq;
> +	int ret;
> +
> +	/* Change not allowed if queue is streaming. */
> +	vq = v4l2_m2m_get_vq(ctx->fh.m2m_ctx, f->type);
> +	if (vb2_is_streaming(vq))
> +		return -EBUSY;
> +
> +	ctx->colorspace = pix_mp->colorspace;
> +	ctx->ycbcr_enc = pix_mp->ycbcr_enc;
> +	ctx->xfer_func = pix_mp->xfer_func;
> +	ctx->quantization = pix_mp->quantization;
> +
> +	/*
> +	 * Pixel format change is not allowed when the other queue has
> +	 * buffers allocated.
> +	 */
> +	peer_vq = v4l2_m2m_get_vq(ctx->fh.m2m_ctx,
> +			V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE);
> +	if (vb2_is_busy(peer_vq) &&
> +	    pix_mp->pixelformat != ctx->dst_fmt.pixelformat)
> +		return -EBUSY;
> +
> +	ret = vidioc_try_fmt_cap(file, priv, f);
> +	if (ret)
> +		return ret;
> +
> +	ctx->vpu_dst_fmt = rockchip_vpu_find_format(vpu, pix_mp->pixelformat);
> +	ctx->dst_fmt = *pix_mp;
> +
> +	/*
> +	 * Current raw format might have become invalid with newly
> +	 * selected codec, so reset it to default just to be safe and
> +	 * keep internal driver state sane. User is mandated to set
> +	 * the raw format again after we return, so we don't need
> +	 * anything smarter.
> +	 */
> +	rockchip_vpu_reset_src_fmt(vpu, ctx);
> +
> +	return 0;
> +}
> +
> +static int vidioc_cropcap(struct file *file, void *priv,
> +			  struct v4l2_cropcap *cap)
> +{
> +	struct rockchip_vpu_ctx *ctx = fh_to_ctx(priv);
> +	struct v4l2_pix_format_mplane *fmt = &ctx->src_fmt;
> +
> +	/* Crop only supported on source. */
> +	if (cap->type != V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE)
> +		return -EINVAL;
> +
> +	cap->bounds.left = 0;
> +	cap->bounds.top = 0;
> +	cap->bounds.width = fmt->width;
> +	cap->bounds.height = fmt->height;
> +	cap->defrect = cap->bounds;
> +	cap->pixelaspect.numerator = 1;
> +	cap->pixelaspect.denominator = 1;
> +
> +	return 0;
> +}
> +
> +static int vidioc_g_crop(struct file *file, void *priv, struct v4l2_crop *crop)
> +{
> +	struct rockchip_vpu_ctx *ctx = fh_to_ctx(priv);
> +	int ret = 0;
> +
> +	/* Crop only supported on source. */
> +	if (crop->type != V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE)
> +		return -EINVAL;
> +
> +	crop->c = ctx->src_crop;
> +
> +	return ret;
> +}
> +
> +static int vidioc_s_crop(struct file *file, void *priv,
> +			 const struct v4l2_crop *crop)
> +{
> +	struct rockchip_vpu_ctx *ctx = fh_to_ctx(priv);
> +	struct v4l2_pix_format_mplane *fmt = &ctx->src_fmt;
> +	const struct v4l2_rect *rect = &crop->c;
> +	struct vb2_queue *vq;
> +
> +	/* Crop only supported on source. */
> +	if (crop->type != V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE)
> +		return -EINVAL;
> +
> +	/* Change not allowed if the queue is streaming. */
> +	vq = v4l2_m2m_get_vq(ctx->fh.m2m_ctx, crop->type);
> +	if (vb2_is_streaming(vq))
> +		return -EBUSY;
> +
> +	/* We do not support offsets. */
> +	if (rect->left != 0 || rect->top != 0)
> +		goto fallback;
> +
> +	/* We can crop only inside right- or bottom-most macroblocks. */
> +	if (round_up(rect->width, MB_DIM) != fmt->width
> +	    || round_up(rect->height, MB_DIM) != fmt->height)
> +		goto fallback;
> +
> +	/* We support widths aligned to 4 pixels and arbitrary heights. */
> +	ctx->src_crop.width = round_up(rect->width, 4);
> +	ctx->src_crop.height = rect->height;
> +
> +	return 0;
> +
> +fallback:
> +	/* Default to full frame for incorrect settings. */
> +	ctx->src_crop.width = fmt->width;
> +	ctx->src_crop.height = fmt->height;
> +	return 0;
> +}

Replace crop by the selection API. The old crop API is no longer allowed
in new drivers.

> +
> +const struct v4l2_ioctl_ops rockchip_vpu_enc_ioctl_ops = {
> +	.vidioc_querycap = vidioc_querycap,
> +	.vidioc_enum_framesizes = vidioc_enum_framesizes,
> +
> +	.vidioc_try_fmt_vid_cap_mplane = vidioc_try_fmt_cap,
> +	.vidioc_try_fmt_vid_out_mplane = vidioc_try_fmt_out,
> +	.vidioc_s_fmt_vid_out_mplane = vidioc_s_fmt_out,
> +	.vidioc_s_fmt_vid_cap_mplane = vidioc_s_fmt_cap,
> +	.vidioc_g_fmt_vid_out_mplane = vidioc_g_fmt_out,
> +	.vidioc_g_fmt_vid_cap_mplane = vidioc_g_fmt_cap,
> +	.vidioc_enum_fmt_vid_out_mplane = vidioc_enum_fmt_vid_out_mplane,
> +	.vidioc_enum_fmt_vid_cap_mplane = vidioc_enum_fmt_vid_cap_mplane,
> +
> +	.vidioc_reqbufs = v4l2_m2m_ioctl_reqbufs,
> +	.vidioc_querybuf = v4l2_m2m_ioctl_querybuf,
> +	.vidioc_qbuf = v4l2_m2m_ioctl_qbuf,
> +	.vidioc_dqbuf = v4l2_m2m_ioctl_dqbuf,
> +	.vidioc_prepare_buf = v4l2_m2m_ioctl_prepare_buf,
> +	.vidioc_create_bufs = v4l2_m2m_ioctl_create_bufs,
> +	.vidioc_expbuf = v4l2_m2m_ioctl_expbuf,
> +
> +	.vidioc_subscribe_event = v4l2_ctrl_subscribe_event,
> +	.vidioc_unsubscribe_event = v4l2_event_unsubscribe,
> +
> +	.vidioc_streamon = v4l2_m2m_ioctl_streamon,
> +	.vidioc_streamoff = v4l2_m2m_ioctl_streamoff,
> +
> +	.vidioc_cropcap = vidioc_cropcap,
> +	.vidioc_g_crop = vidioc_g_crop,
> +	.vidioc_s_crop = vidioc_s_crop,
> +};
> +
> +static int rockchip_vpu_queue_setup(struct vb2_queue *vq,
> +				  unsigned int *num_buffers,
> +				  unsigned int *num_planes,
> +				  unsigned int sizes[],
> +				  struct device *alloc_devs[])
> +{
> +	struct rockchip_vpu_ctx *ctx = vb2_get_drv_priv(vq);
> +	int ret = 0;
> +	int i;
> +
> +	*num_buffers = clamp_t(unsigned int,
> +			*num_buffers, 1, VIDEO_MAX_FRAME);
> +
> +	switch (vq->type) {
> +	case V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE:
> +		*num_planes = ctx->vpu_dst_fmt->num_planes;
> +
> +		sizes[0] = ctx->dst_fmt.plane_fmt[0].sizeimage;
> +		vpu_debug(0, "capture sizes[%d]: %d\n", 0, sizes[0]);
> +		break;
> +
> +	case V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE:
> +		*num_planes = ctx->vpu_src_fmt->num_planes;
> +
> +		for (i = 0; i < ctx->vpu_src_fmt->num_planes; ++i) {
> +			sizes[i] = ctx->src_fmt.plane_fmt[i].sizeimage;
> +			vpu_debug(0, "output sizes[%d]: %d\n", i, sizes[i]);
> +		}
> +		break;
> +
> +	default:
> +		vpu_err("invalid queue type: %d\n", vq->type);
> +		ret = -EINVAL;
> +	}
> +
> +	return ret;
> +}
> +
> +static int rockchip_vpu_buf_prepare(struct vb2_buffer *vb)
> +{
> +	struct vb2_queue *vq = vb->vb2_queue;
> +	struct rockchip_vpu_ctx *ctx = vb2_get_drv_priv(vq);
> +	unsigned int sz;
> +	int ret = 0;
> +	int i;
> +
> +	switch (vq->type) {
> +	case V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE:
> +		sz = ctx->dst_fmt.plane_fmt[0].sizeimage;
> +
> +		vpu_debug(4, "plane size: %ld, dst size: %d\n",
> +			vb2_plane_size(vb, 0), sz);
> +		if (vb2_plane_size(vb, 0) < sz) {
> +			vpu_err("plane size is too small for capture\n");
> +			ret = -EINVAL;
> +		}
> +		break;
> +
> +	case V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE:
> +		for (i = 0; i < ctx->vpu_src_fmt->num_planes; ++i) {
> +			sz = ctx->src_fmt.plane_fmt[i].sizeimage;
> +
> +			vpu_debug(4, "plane %d size: %ld, sizeimage: %u\n", i,
> +				vb2_plane_size(vb, i), sz);
> +			if (vb2_plane_size(vb, i) < sz) {
> +				vpu_err("size of plane %d is too small for output\n",
> +					i);
> +				break;
> +			}
> +		}
> +
> +		if (i != ctx->vpu_src_fmt->num_planes)
> +			ret = -EINVAL;
> +		break;
> +
> +	default:
> +		vpu_err("invalid queue type: %d\n", vq->type);
> +		ret = -EINVAL;
> +	}
> +
> +	return ret;
> +}
> +
> +static void rockchip_vpu_buf_queue(struct vb2_buffer *vb)
> +{
> +	struct rockchip_vpu_ctx *ctx = vb2_get_drv_priv(vb->vb2_queue);
> +	struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb);
> +
> +	v4l2_m2m_buf_queue(ctx->fh.m2m_ctx, vbuf);
> +}
> +
> +static int rockchip_vpu_start_streaming(struct vb2_queue *q, unsigned int count)
> +{
> +	struct rockchip_vpu_ctx *ctx = vb2_get_drv_priv(q);
> +	enum rockchip_vpu_codec_mode codec_mode;
> +
> +	/* Set codec_ops for the chosen destination format */
> +	codec_mode = ctx->vpu_dst_fmt->codec_mode;
> +	ctx->codec_ops = &ctx->dev->variant->codec_ops[codec_mode];
> +
> +	return 0;
> +}
> +
> +static void rockchip_vpu_stop_streaming(struct vb2_queue *q)
> +{
> +	struct rockchip_vpu_ctx *ctx = vb2_get_drv_priv(q);
> +
> +	/* The mem2mem framework calls v4l2_m2m_cancel_job before
> +	 * .stop_streaming, so there isn't any job running and
> +	 * it is safe to return all the buffers.
> +	 */
> +	for (;;) {
> +		struct vb2_v4l2_buffer *vbuf;
> +
> +		if (V4L2_TYPE_IS_OUTPUT(q->type))
> +			vbuf = v4l2_m2m_src_buf_remove(ctx->fh.m2m_ctx);
> +		else
> +			vbuf = v4l2_m2m_dst_buf_remove(ctx->fh.m2m_ctx);
> +		if (!vbuf)
> +			break;
> +		v4l2_m2m_buf_done(vbuf, VB2_BUF_STATE_ERROR);
> +	}
> +}
> +
> +const struct vb2_ops rockchip_vpu_enc_queue_ops = {
> +	.queue_setup = rockchip_vpu_queue_setup,
> +	.buf_prepare = rockchip_vpu_buf_prepare,
> +	.buf_queue = rockchip_vpu_buf_queue,
> +	.start_streaming = rockchip_vpu_start_streaming,
> +	.stop_streaming = rockchip_vpu_stop_streaming,
> +};
> +
> +int rockchip_vpu_enc_ctrls_setup(struct rockchip_vpu_ctx *ctx)
> +{
> +	int i, num_ctrls = ARRAY_SIZE(controls);
> +
> +	if (num_ctrls > ARRAY_SIZE(ctx->ctrls)) {
> +		vpu_err("context control array not large enough\n");
> +		return -EINVAL;
> +	}
> +
> +	v4l2_ctrl_handler_init(&ctx->ctrl_handler, num_ctrls);
> +	if (ctx->ctrl_handler.error) {
> +		vpu_err("v4l2_ctrl_handler_init failed\n");
> +		return ctx->ctrl_handler.error;
> +	}
> +
> +	for (i = 0; i < num_ctrls; i++) {
> +		ctx->ctrls[i] = v4l2_ctrl_new_custom(&ctx->ctrl_handler,
> +						     &controls[i], NULL);
> +		if (ctx->ctrl_handler.error) {
> +			vpu_err("Adding control (%d) failed %d\n", i,
> +				ctx->ctrl_handler.error);
> +			v4l2_ctrl_handler_free(&ctx->ctrl_handler);
> +			return ctx->ctrl_handler.error;
> +		}
> +	}
> +
> +	v4l2_ctrl_handler_setup(&ctx->ctrl_handler);
> +	ctx->num_ctrls = num_ctrls;
> +	return 0;
> +}
> +
> +int rockchip_vpu_enc_init(struct rockchip_vpu_ctx *ctx)
> +{
> +	struct rockchip_vpu_dev *vpu = ctx->dev;
> +	int ret;
> +
> +	rockchip_vpu_reset_dst_fmt(vpu, ctx);
> +	rockchip_vpu_reset_src_fmt(vpu, ctx);
> +
> +	ret = rockchip_vpu_enc_ctrls_setup(ctx);
> +	if (ret) {
> +		vpu_err("Failed to set up controls.\n");
> +		return ret;
> +	}
> +	return 0;
> +}
> +
> +void rockchip_vpu_enc_exit(struct rockchip_vpu_ctx *ctx)
> +{
> +	v4l2_ctrl_handler_free(&ctx->ctrl_handler);
> +}
> diff --git a/drivers/media/platform/rockchip/vpu/rockchip_vpu_enc.h b/drivers/media/platform/rockchip/vpu/rockchip_vpu_enc.h
> new file mode 100644
> index 000000000000..4742cbd9295c
> --- /dev/null
> +++ b/drivers/media/platform/rockchip/vpu/rockchip_vpu_enc.h
> @@ -0,0 +1,25 @@
> +// SPDX-License-Identifier: GPL-2.0
> +/*
> + * Rockchip VPU codec driver
> + *
> + * Copyright (C) 2018 Rockchip Electronics Co., Ltd.
> + *	Alpha Lin <Alpha.Lin@xxxxxxxxxxxxxx>
> + *	Jeffy Chen <jeffy.chen@xxxxxxxxxxxxxx>
> + *
> + * Copyright (C) 2018 Google, Inc.
> + *	Tomasz Figa <tfiga@xxxxxxxxxxxx>
> + *
> + * Based on s5p-mfc driver by Samsung Electronics Co., Ltd.
> + * Copyright (C) 2011 Samsung Electronics Co., Ltd.
> + */
> +
> +#ifndef ROCKCHIP_VPU_ENC_H_
> +#define ROCKCHIP_VPU_ENC_H_
> +
> +extern const struct v4l2_ioctl_ops rockchip_vpu_enc_ioctl_ops;
> +extern const struct vb2_ops rockchip_vpu_enc_queue_ops;
> +
> +int rockchip_vpu_enc_init(struct rockchip_vpu_ctx *ctx);
> +void rockchip_vpu_enc_exit(struct rockchip_vpu_ctx *ctx);
> +
> +#endif /* ROCKCHIP_VPU_ENC_H_  */
> diff --git a/drivers/media/platform/rockchip/vpu/rockchip_vpu_hw.h b/drivers/media/platform/rockchip/vpu/rockchip_vpu_hw.h
> new file mode 100644
> index 000000000000..3298e21aa68c
> --- /dev/null
> +++ b/drivers/media/platform/rockchip/vpu/rockchip_vpu_hw.h
> @@ -0,0 +1,67 @@
> +// SPDX-License-Identifier: GPL-2.0
> +/*
> + * Rockchip VPU codec driver
> + *
> + * Copyright (C) 2018 Google, Inc.
> + *	Tomasz Figa <tfiga@xxxxxxxxxxxx>
> + */
> +
> +#ifndef ROCKCHIP_VPU_HW_H_
> +#define ROCKCHIP_VPU_HW_H_
> +
> +#include <linux/interrupt.h>
> +#include <linux/v4l2-controls.h>
> +#include <media/videobuf2-core.h>
> +
> +#define ROCKCHIP_HEADER_SIZE		1280
> +#define ROCKCHIP_HW_PARAMS_SIZE		5487
> +#define ROCKCHIP_RET_PARAMS_SIZE	488
> +#define ROCKCHIP_JPEG_QUANT_ELE_SIZE	64
> +
> +#define ROCKCHIP_VPU_CABAC_TABLE_SIZE	(52 * 2 * 464)
> +
> +struct rockchip_vpu_dev;
> +struct rockchip_vpu_ctx;
> +struct rockchip_vpu_buf;
> +struct rockchip_vpu_variant;
> +
> +/**
> + * struct rockchip_vpu_codec_ops - codec mode specific operations
> + *
> + * @run:	Start single {en,de)coding job. Called from atomic context
> + *		to indicate that a pair of buffers is ready and the hardware
> + *		should be programmed and started.
> + * @done:	Read back processing results and additional data from hardware.
> + * @reset:	Reset the hardware in case of a timeout.
> + */
> +struct rockchip_vpu_codec_ops {
> +	void (*run)(struct rockchip_vpu_ctx *ctx);
> +	void (*done)(struct rockchip_vpu_ctx *ctx, enum vb2_buffer_state);
> +	void (*reset)(struct rockchip_vpu_ctx *ctx);
> +};
> +
> +/**
> + * enum rockchip_vpu_enc_fmt - source format ID for hardware registers.
> + */
> +enum rockchip_vpu_enc_fmt {
> +	RK3288_VPU_ENC_FMT_YUV420P = 0,
> +	RK3288_VPU_ENC_FMT_YUV420SP = 1,
> +	RK3288_VPU_ENC_FMT_YUYV422 = 2,
> +	RK3288_VPU_ENC_FMT_UYVY422 = 3,
> +};
> +
> +extern const struct rockchip_vpu_variant rk3399_vpu_variant;
> +extern const struct rockchip_vpu_variant rk3288_vpu_variant;
> +
> +void rockchip_vpu_watchdog(struct work_struct *work);
> +void rockchip_vpu_run(struct rockchip_vpu_ctx *ctx);
> +void rockchip_vpu_irq_done(struct rockchip_vpu_dev *vpu);
> +
> +void rk3288_vpu_jpege_run(struct rockchip_vpu_ctx *ctx);
> +void rk3288_vpu_jpege_done(struct rockchip_vpu_ctx *ctx,
> +			   enum vb2_buffer_state result);
> +void rk3399_vpu_jpege_run(struct rockchip_vpu_ctx *ctx);
> +void rk3399_vpu_jpege_done(struct rockchip_vpu_ctx *ctx,
> +			   enum vb2_buffer_state result);
> +
> +#endif /* ROCKCHIP_VPU_HW_H_ */
> 

I skipped the review of the colorspace handling. I'll see if I can come back
to that later today. I'm not sure if it is correct, but to be honest I doubt
that there is any JPEG encoder that does this right anyway.

BTW, please show the 'v4l2-compliance -s' output in the cover letter for the
next version.

Regards,

	Hans



[Index of Archives]     [Linux Input]     [Video for Linux]     [Gstreamer Embedded]     [Mplayer Users]     [Linux USB Devel]     [Linux Audio Users]     [Linux Kernel]     [Linux SCSI]     [Yosemite Backpacking]

  Powered by Linux