Commit e5640dbb authored by Detlev Casanova's avatar Detlev Casanova Committed by Hans Verkuil
Browse files

media: rkvdec: Add RCB and SRAM support



The RCB (Rows and Cols Buffers) are a set of buffers used by other
variations of the decoder to store temporary data.

Those variation come with a dedicated SRAM area used to store those
buffers for better performances.

The buffer sizes are either the width or height of the frame being
decoded multiplied by a documented factor and can be stored either
in SRAM or RAM.
A fallback to RAM is provided if the SRAM is full (e.g.: multiple
streams are being decoded at the same time).

To manage the different kind of allocation, an enum is added to the
rkvdec_aux_buf struct to specify how the buffer was allocated, and
so, how to free it.

This commit is in preparation of other variants support.

Tested-by: Diederik de Haas <didi.debian@cknow.org>  # Rock 5B
Reviewed-by: default avatarNicolas Dufresne <nicolas.dufresne@collabora.com>
Signed-off-by: default avatarDetlev Casanova <detlev.casanova@collabora.com>
Signed-off-by: default avatarNicolas Dufresne <nicolas.dufresne@collabora.com>
Signed-off-by: default avatarHans Verkuil <hverkuil+cisco@kernel.org>
parent ae2070ca
Loading
Loading
Loading
Loading
+1 −0
Original line number Diff line number Diff line
@@ -7,4 +7,5 @@ rockchip-vdec-y += \
		   rkvdec-h264-common.o \
		   rkvdec-hevc.o \
		   rkvdec-hevc-common.o \
		   rkvdec-rcb.o \
		   rkvdec-vp9.o
+179 −0
Original line number Diff line number Diff line
// SPDX-License-Identifier: GPL-2.0
/*
 * Rockchip video decoder Rows and Cols Buffers manager
 *
 * Copyright (C) 2025 Collabora, Ltd.
 *  Detlev Casanova <detlev.casanova@collabora.com>
 */

#include "rkvdec.h"
#include "rkvdec-rcb.h"

#include <linux/iommu.h>
#include <linux/genalloc.h>
#include <linux/sizes.h>
#include <linux/types.h>

struct rkvdec_rcb_config {
	struct rkvdec_aux_buf *rcb_bufs;
	size_t rcb_count;
};

static size_t rkvdec_rcb_size(const struct rcb_size_info *size_info,
			      unsigned int width, unsigned int height)
{
	return size_info->multiplier * (size_info->axis == PIC_HEIGHT ? height : width);
}

dma_addr_t rkvdec_rcb_buf_dma_addr(struct rkvdec_ctx *ctx, int id)
{
	return ctx->rcb_config->rcb_bufs[id].dma;
}

size_t rkvdec_rcb_buf_size(struct rkvdec_ctx *ctx, int id)
{
	return ctx->rcb_config->rcb_bufs[id].size;
}

int rkvdec_rcb_buf_count(struct rkvdec_ctx *ctx)
{
	return ctx->rcb_config->rcb_count;
}

void rkvdec_free_rcb(struct rkvdec_ctx *ctx)
{
	struct rkvdec_dev *dev = ctx->dev;
	struct rkvdec_rcb_config *cfg = ctx->rcb_config;
	unsigned long virt_addr;
	int i;

	if (!cfg)
		return;

	for (i = 0; i < cfg->rcb_count; i++) {
		size_t rcb_size = cfg->rcb_bufs[i].size;

		if (!cfg->rcb_bufs[i].cpu)
			continue;

		switch (cfg->rcb_bufs[i].type) {
		case RKVDEC_ALLOC_SRAM:
			virt_addr = (unsigned long)cfg->rcb_bufs[i].cpu;

			if (dev->iommu_domain)
				iommu_unmap(dev->iommu_domain, virt_addr, rcb_size);
			gen_pool_free(dev->sram_pool, virt_addr, rcb_size);
			break;
		case RKVDEC_ALLOC_DMA:
			dma_free_coherent(dev->dev,
					  rcb_size,
					  cfg->rcb_bufs[i].cpu,
					  cfg->rcb_bufs[i].dma);
			break;
		}
	}

	if (cfg->rcb_bufs)
		devm_kfree(dev->dev, cfg->rcb_bufs);

	devm_kfree(dev->dev, cfg);
}

int rkvdec_allocate_rcb(struct rkvdec_ctx *ctx,
			const struct rcb_size_info *size_info,
			size_t rcb_count)
{
	int ret, i;
	u32 width, height;
	struct rkvdec_dev *rkvdec = ctx->dev;
	struct rkvdec_rcb_config *cfg;

	if (!size_info || !rcb_count) {
		ctx->rcb_config = NULL;
		return 0;
	}

	ctx->rcb_config = devm_kzalloc(rkvdec->dev, sizeof(*ctx->rcb_config), GFP_KERNEL);
	if (!ctx->rcb_config)
		return -ENOMEM;

	cfg = ctx->rcb_config;

	cfg->rcb_bufs = devm_kzalloc(rkvdec->dev, sizeof(*cfg->rcb_bufs) * rcb_count, GFP_KERNEL);
	if (!cfg->rcb_bufs) {
		ret = -ENOMEM;
		goto err_alloc;
	}

	width = ctx->decoded_fmt.fmt.pix_mp.width;
	height = ctx->decoded_fmt.fmt.pix_mp.height;

	for (i = 0; i < rcb_count; i++) {
		void *cpu = NULL;
		dma_addr_t dma;
		size_t rcb_size = rkvdec_rcb_size(&size_info[i], width, height);
		enum rkvdec_alloc_type alloc_type = RKVDEC_ALLOC_SRAM;

		/* Try allocating an SRAM buffer */
		if (ctx->dev->sram_pool) {
			if (rkvdec->iommu_domain)
				rcb_size = ALIGN(rcb_size, SZ_4K);

			cpu = gen_pool_dma_zalloc_align(ctx->dev->sram_pool,
							rcb_size,
							&dma,
							SZ_4K);
		}

		/* If an IOMMU is used, map the SRAM address through it */
		if (cpu && rkvdec->iommu_domain) {
			unsigned long virt_addr = (unsigned long)cpu;
			phys_addr_t phys_addr = dma;

			ret = iommu_map(rkvdec->iommu_domain, virt_addr, phys_addr,
					rcb_size, IOMMU_READ | IOMMU_WRITE, 0);
			if (ret) {
				gen_pool_free(ctx->dev->sram_pool,
					      (unsigned long)cpu,
					      rcb_size);
				cpu = NULL;
				goto ram_fallback;
			}

			/*
			 * The registers will be configured with the virtual
			 * address so that it goes through the IOMMU
			 */
			dma = virt_addr;
		}

ram_fallback:
		/* Fallback to RAM */
		if (!cpu) {
			cpu = dma_alloc_coherent(ctx->dev->dev,
						 rcb_size,
						 &dma,
						 GFP_KERNEL);
			alloc_type = RKVDEC_ALLOC_DMA;
		}

		if (!cpu) {
			ret = -ENOMEM;
			goto err_alloc;
		}

		cfg->rcb_bufs[i].cpu = cpu;
		cfg->rcb_bufs[i].dma = dma;
		cfg->rcb_bufs[i].size = rcb_size;
		cfg->rcb_bufs[i].type = alloc_type;

		cfg->rcb_count += 1;
	}

	return 0;

err_alloc:
	rkvdec_free_rcb(ctx);

	return ret;
}
+29 −0
Original line number Diff line number Diff line
/* SPDX-License-Identifier: GPL-2.0 */
/*
 * Rockchip video decoder Rows and Cols Buffers manager
 *
 * Copyright (C) 2025 Collabora, Ltd.
 *  Detlev Casanova <detlev.casanova@collabora.com>
 */

#include <linux/types.h>

struct rkvdec_ctx;

enum rcb_axis {
	PIC_WIDTH = 0,
	PIC_HEIGHT = 1
};

struct rcb_size_info {
	u8 multiplier;
	enum rcb_axis axis;
};

int rkvdec_allocate_rcb(struct rkvdec_ctx *ctx,
			const struct rcb_size_info *size_info,
			size_t rcb_count);
dma_addr_t rkvdec_rcb_buf_dma_addr(struct rkvdec_ctx *ctx, int id);
size_t rkvdec_rcb_buf_size(struct rkvdec_ctx *ctx, int id);
int rkvdec_rcb_buf_count(struct rkvdec_ctx *ctx);
void rkvdec_free_rcb(struct rkvdec_ctx *ctx);
+25 −2
Original line number Diff line number Diff line
@@ -10,6 +10,7 @@
 */

#include <linux/clk.h>
#include <linux/genalloc.h>
#include <linux/interrupt.h>
#include <linux/iommu.h>
#include <linux/module.h>
@@ -28,6 +29,7 @@

#include "rkvdec.h"
#include "rkvdec-regs.h"
#include "rkvdec-rcb.h"

static bool rkvdec_image_fmt_match(enum rkvdec_image_fmt fmt1,
				   enum rkvdec_image_fmt fmt2)
@@ -778,6 +780,7 @@ static int rkvdec_start_streaming(struct vb2_queue *q, unsigned int count)
{
	struct rkvdec_ctx *ctx = vb2_get_drv_priv(q);
	const struct rkvdec_coded_fmt_desc *desc;
	const struct rkvdec_variant *variant = ctx->dev->variant;
	int ret;

	if (V4L2_TYPE_IS_CAPTURE(q->type))
@@ -787,13 +790,22 @@ static int rkvdec_start_streaming(struct vb2_queue *q, unsigned int count)
	if (WARN_ON(!desc))
		return -EINVAL;

	ret = rkvdec_allocate_rcb(ctx, variant->rcb_sizes, variant->num_rcb_sizes);
	if (ret)
		return ret;

	if (desc->ops->start) {
		ret = desc->ops->start(ctx);
		if (ret)
			return ret;
			goto err_ops_start;
	}

	return 0;

err_ops_start:
	rkvdec_free_rcb(ctx);

	return ret;
}

static void rkvdec_queue_cleanup(struct vb2_queue *vq, u32 state)
@@ -829,6 +841,8 @@ static void rkvdec_stop_streaming(struct vb2_queue *q)

		if (desc->ops->stop)
			desc->ops->stop(ctx);

		rkvdec_free_rcb(ctx);
	}

	rkvdec_queue_cleanup(q, VB2_BUF_STATE_ERROR);
@@ -1345,6 +1359,10 @@ static int rkvdec_probe(struct platform_device *pdev)
		return ret;
	}

	rkvdec->sram_pool = of_gen_pool_get(pdev->dev.of_node, "sram", 0);
	if (!rkvdec->sram_pool && rkvdec->variant->num_rcb_sizes > 0)
		dev_info(&pdev->dev, "No sram node, RCB will be stored in RAM\n");

	pm_runtime_set_autosuspend_delay(&pdev->dev, 100);
	pm_runtime_use_autosuspend(&pdev->dev);
	pm_runtime_enable(&pdev->dev);
@@ -1353,7 +1371,8 @@ static int rkvdec_probe(struct platform_device *pdev)
	if (ret)
		goto err_disable_runtime_pm;

	if (iommu_get_domain_for_dev(&pdev->dev)) {
	rkvdec->iommu_domain = iommu_get_domain_for_dev(&pdev->dev);
	if (rkvdec->iommu_domain) {
		rkvdec->empty_domain = iommu_paging_domain_alloc(rkvdec->dev);

		if (IS_ERR(rkvdec->empty_domain)) {
@@ -1367,6 +1386,10 @@ static int rkvdec_probe(struct platform_device *pdev)
err_disable_runtime_pm:
	pm_runtime_dont_use_autosuspend(&pdev->dev);
	pm_runtime_disable(&pdev->dev);

	if (rkvdec->sram_pool)
		gen_pool_destroy(rkvdec->sram_pool);

	return ret;
}

+13 −0
Original line number Diff line number Diff line
@@ -19,12 +19,14 @@
#include <media/v4l2-ctrls.h>
#include <media/v4l2-device.h>
#include <media/v4l2-ioctl.h>
#include <media/v4l2-mem2mem.h>
#include <media/videobuf2-core.h>
#include <media/videobuf2-dma-contig.h>

#define RKVDEC_QUIRK_DISABLE_QOS	BIT(0)

struct rkvdec_ctx;
struct rkvdec_rcb_config;

struct rkvdec_ctrl_desc {
	struct v4l2_ctrl_config cfg;
@@ -69,6 +71,8 @@ struct rkvdec_variant {
	unsigned int num_regs;
	const struct rkvdec_coded_fmt_desc *coded_fmts;
	size_t num_coded_fmts;
	const struct rcb_size_info *rcb_sizes;
	size_t num_rcb_sizes;
	unsigned int quirks;
};

@@ -119,6 +123,8 @@ struct rkvdec_dev {
	void __iomem *regs;
	struct mutex vdev_lock; /* serializes ioctls */
	struct delayed_work watchdog_work;
	struct gen_pool *sram_pool;
	struct iommu_domain *iommu_domain;
	struct iommu_domain *empty_domain;
	const struct rkvdec_variant *variant;
};
@@ -131,6 +137,7 @@ struct rkvdec_ctx {
	struct v4l2_ctrl_handler ctrl_hdl;
	struct rkvdec_dev *dev;
	enum rkvdec_image_fmt image_fmt;
	struct rkvdec_rcb_config *rcb_config;
	void *priv;
};

@@ -139,10 +146,16 @@ static inline struct rkvdec_ctx *file_to_rkvdec_ctx(struct file *filp)
	return container_of(file_to_v4l2_fh(filp), struct rkvdec_ctx, fh);
}

enum rkvdec_alloc_type {
	RKVDEC_ALLOC_DMA  = 0,
	RKVDEC_ALLOC_SRAM = 1,
};

struct rkvdec_aux_buf {
	void *cpu;
	dma_addr_t dma;
	size_t size;
	enum rkvdec_alloc_type type;
};

void rkvdec_run_preamble(struct rkvdec_ctx *ctx, struct rkvdec_run *run);