From: Daniel Golle <daniel@makrotopia.org>
To: Daniel Golle <daniel@makrotopia.org>,
Mauro Carvalho Chehab <mchehab@kernel.org>,
Rob Herring <robh@kernel.org>,
Krzysztof Kozlowski <krzk+dt@kernel.org>,
Conor Dooley <conor+dt@kernel.org>,
Heiko Stuebner <heiko@sntech.de>, Stephen Boyd <sboyd@kernel.org>,
Brian Masney <bmasney+clk@redhat.com>,
Jerome Brunet <jbrunet+clk@baylibre.com>,
Philipp Zabel <p.zabel@pengutronix.de>,
Sakari Ailus <sakari.ailus@linux.intel.com>,
Michael Riesch <michael.riesch@collabora.com>,
Mehdi Djait <mehdi.djait@linux.intel.com>,
Hans Verkuil <hverkuil+cisco@kernel.org>,
Gerald Loacker <gerald.loacker@wolfvision.net>,
linux-media@vger.kernel.org, linux-rockchip@lists.infradead.org,
devicetree@vger.kernel.org, linux-arm-kernel@lists.infradead.org,
linux-kernel@vger.kernel.org, linux-clk@vger.kernel.org
Cc: nicolas.dufresne@collabora.com, detlev.casanova@collabora.com,
paulk@sys-base.io, ezequiel@vanguardiasur.com.ar
Subject: [RFC PATCH 3/6] media: rockchip: Add RKVENC v1 video encoder driver
Date: Sat, 14 Nov 2026 14:54:56 +0000 [thread overview]
Message-ID: <96b018d341e884c12e1d5c5c6d42880f134a644c.1794666330.git.daniel@makrotopia.org> (raw)
In-Reply-To: <cover.1794666330.git.daniel@makrotopia.org>
Add a stateful V4L2 memory-to-memory encoder driver for the RKVENC v1
H.264 and HEVC encoder of the RV1109/RV1126 ("vepu541") and the
RK3566/RK3568 ("vepu540").
The driver takes NV12 or NV12M and produces low-delay streams of IDR
and P pictures with one short-term reference, POC type 0 and no B
frames. It writes the SPS, PPS and VPS itself, the hardware writes
the slice headers and data. The SEPARATE and JOINED_WITH_1ST_FRAME
header modes are supported, the latter also with
PREPEND_SPSPPS_TO_IDR. Pictures too small for P frames are coded as
IDR pictures only.
The register image builder takes a struct rkvenc_frame with no V4L2
types, so that a stateless front end can drive the same hardware back
end; rkvenc-v4l2.c is the stateful front end on the mem2mem framework.
Rate control is frame level CBR: the hardware row rate control stays
off and every picture is coded at one QP. An I picture takes the mean
P QP of the previous GOP lowered by 4 for HEVC and 2 for H.264, the P
QP moves by at most one per picture, and the deviation from the plan
is taken at GOP boundaries and spread over the next GOP. A bitstream
overflow is fed to the rate control and the picture is encoded once
more. A failed job is stopped with a force clear, and with a safe
clear and a block reset only when that does not stop it.
Source frames are imported without a kernel mapping and a capture
buffer is sized to half the pixel count, so that 4K streams fit the
vmalloc space of 32-bit systems.
Signed-off-by: Daniel Golle <daniel@makrotopia.org>
---
MAINTAINERS | 1 +
drivers/media/platform/rockchip/Kconfig | 1 +
drivers/media/platform/rockchip/Makefile | 1 +
.../media/platform/rockchip/rkvenc/Kconfig | 17 +
.../media/platform/rockchip/rkvenc/Makefile | 15 +
.../platform/rockchip/rkvenc/rkvenc-gop.c | 72 ++
.../platform/rockchip/rkvenc/rkvenc-h264.c | 82 ++
.../platform/rockchip/rkvenc/rkvenc-hevc.c | 63 +
.../platform/rockchip/rkvenc/rkvenc-nal.c | 351 ++++++
.../platform/rockchip/rkvenc/rkvenc-nal.h | 12 +
.../platform/rockchip/rkvenc/rkvenc-rc.c | 214 ++++
.../platform/rockchip/rkvenc/rkvenc-ref.c | 114 ++
.../platform/rockchip/rkvenc/rkvenc-v4l2.c | 1069 +++++++++++++++++
.../rockchip/rkvenc/rkvenc-vepu541-h264.c | 206 ++++
.../rockchip/rkvenc/rkvenc-vepu541-hevc.c | 182 +++
.../rockchip/rkvenc/rkvenc-vepu541-regs.h | 283 +++++
.../rockchip/rkvenc/rkvenc-vepu541-tables.c | 256 ++++
.../platform/rockchip/rkvenc/rkvenc-vepu541.c | 215 ++++
.../platform/rockchip/rkvenc/rkvenc-vepu541.h | 201 ++++
.../media/platform/rockchip/rkvenc/rkvenc.c | 489 ++++++++
.../media/platform/rockchip/rkvenc/rkvenc.h | 216 ++++
21 files changed, 4060 insertions(+)
create mode 100644 drivers/media/platform/rockchip/rkvenc/Kconfig
create mode 100644 drivers/media/platform/rockchip/rkvenc/Makefile
create mode 100644 drivers/media/platform/rockchip/rkvenc/rkvenc-gop.c
create mode 100644 drivers/media/platform/rockchip/rkvenc/rkvenc-h264.c
create mode 100644 drivers/media/platform/rockchip/rkvenc/rkvenc-hevc.c
create mode 100644 drivers/media/platform/rockchip/rkvenc/rkvenc-nal.c
create mode 100644 drivers/media/platform/rockchip/rkvenc/rkvenc-nal.h
create mode 100644 drivers/media/platform/rockchip/rkvenc/rkvenc-rc.c
create mode 100644 drivers/media/platform/rockchip/rkvenc/rkvenc-ref.c
create mode 100644 drivers/media/platform/rockchip/rkvenc/rkvenc-v4l2.c
create mode 100644 drivers/media/platform/rockchip/rkvenc/rkvenc-vepu541-h264.c
create mode 100644 drivers/media/platform/rockchip/rkvenc/rkvenc-vepu541-hevc.c
create mode 100644 drivers/media/platform/rockchip/rkvenc/rkvenc-vepu541-regs.h
create mode 100644 drivers/media/platform/rockchip/rkvenc/rkvenc-vepu541-tables.c
create mode 100644 drivers/media/platform/rockchip/rkvenc/rkvenc-vepu541.c
create mode 100644 drivers/media/platform/rockchip/rkvenc/rkvenc-vepu541.h
create mode 100644 drivers/media/platform/rockchip/rkvenc/rkvenc.c
create mode 100644 drivers/media/platform/rockchip/rkvenc/rkvenc.h
diff --git a/MAINTAINERS b/MAINTAINERS
index 26991dd3a3569..a70ee0cf2655b 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -24550,6 +24550,7 @@ L: linux-media@vger.kernel.org
L: linux-rockchip@lists.infradead.org
S: Maintained
F: Documentation/devicetree/bindings/media/rockchip,rv1126-rkvenc.yaml
+F: drivers/media/platform/rockchip/rkvenc/
ROCKCHIP RK3308 INTERNAL AUDIO CODEC
M: Luca Ceresoli <luca.ceresoli@bootlin.com>
diff --git a/drivers/media/platform/rockchip/Kconfig b/drivers/media/platform/rockchip/Kconfig
index ba401d32f01ba..e6afa60923f1b 100644
--- a/drivers/media/platform/rockchip/Kconfig
+++ b/drivers/media/platform/rockchip/Kconfig
@@ -6,3 +6,4 @@ source "drivers/media/platform/rockchip/rga/Kconfig"
source "drivers/media/platform/rockchip/rkcif/Kconfig"
source "drivers/media/platform/rockchip/rkisp1/Kconfig"
source "drivers/media/platform/rockchip/rkvdec/Kconfig"
+source "drivers/media/platform/rockchip/rkvenc/Kconfig"
diff --git a/drivers/media/platform/rockchip/Makefile b/drivers/media/platform/rockchip/Makefile
index 0e0b2cbbd4bd2..6a1d847a10a43 100644
--- a/drivers/media/platform/rockchip/Makefile
+++ b/drivers/media/platform/rockchip/Makefile
@@ -3,3 +3,4 @@ obj-y += rga/
obj-y += rkcif/
obj-y += rkisp1/
obj-y += rkvdec/
+obj-y += rkvenc/
diff --git a/drivers/media/platform/rockchip/rkvenc/Kconfig b/drivers/media/platform/rockchip/rkvenc/Kconfig
new file mode 100644
index 0000000000000..1d76314a2ea6d
--- /dev/null
+++ b/drivers/media/platform/rockchip/rkvenc/Kconfig
@@ -0,0 +1,17 @@
+# SPDX-License-Identifier: GPL-2.0-only
+config VIDEO_ROCKCHIP_RKVENC
+ tristate "Rockchip RKVENC (vepu540/vepu541) H.264/HEVC encoder"
+ depends on ARCH_ROCKCHIP || COMPILE_TEST
+ depends on VIDEO_DEV
+ select MEDIA_CONTROLLER
+ select VIDEOBUF2_DMA_CONTIG
+ select V4L2_MEM2MEM_DEV
+ help
+ Support for the Rockchip RKVENC v1 hardware video encoder found
+ in the RV1109/RV1126 (vepu541) and RK3566/RK3568 (vepu540) SoCs.
+
+ It encodes NV12 raw frames into H.264 (AVC) or HEVC (H.265)
+ bitstreams using a stateful memory-to-memory V4L2 interface.
+
+ To compile this driver as a module, choose M here: the module
+ will be called rockchip-rkvenc.
diff --git a/drivers/media/platform/rockchip/rkvenc/Makefile b/drivers/media/platform/rockchip/rkvenc/Makefile
new file mode 100644
index 0000000000000..026c81aec492c
--- /dev/null
+++ b/drivers/media/platform/rockchip/rkvenc/Makefile
@@ -0,0 +1,15 @@
+# SPDX-License-Identifier: GPL-2.0-only
+rockchip-rkvenc-y += rkvenc.o \
+ rkvenc-v4l2.o \
+ rkvenc-gop.o \
+ rkvenc-ref.o \
+ rkvenc-rc.o \
+ rkvenc-h264.o \
+ rkvenc-hevc.o \
+ rkvenc-nal.o \
+ rkvenc-vepu541.o \
+ rkvenc-vepu541-h264.o \
+ rkvenc-vepu541-hevc.o \
+ rkvenc-vepu541-tables.o
+
+obj-$(CONFIG_VIDEO_ROCKCHIP_RKVENC) += rockchip-rkvenc.o
diff --git a/drivers/media/platform/rockchip/rkvenc/rkvenc-gop.c b/drivers/media/platform/rockchip/rkvenc/rkvenc-gop.c
new file mode 100644
index 0000000000000..208bb122a3f87
--- /dev/null
+++ b/drivers/media/platform/rockchip/rkvenc/rkvenc-gop.c
@@ -0,0 +1,72 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Low-delay IDR + P structure of the RKVENC v1 encoder: one short-term
+ * reference, no B frames. A failed frame never commits, so rollback is
+ * free.
+ */
+
+#include "rkvenc.h"
+
+void rkvenc_gop_init(struct rkvenc_ctx *ctx)
+{
+ ctx->gop.frames_since_idr = 0;
+ ctx->gop.idr_pic_id = 0;
+ ctx->cur = 0;
+ ctx->last = 0;
+ ctx->last_good_valid = false;
+ ctx->consecutive_errors = 0;
+}
+
+/* The hardware codes P frames of these sizes wrongly */
+static bool rkvenc_gop_intra_only(struct rkvenc_ctx *ctx)
+{
+ u32 w16 = ALIGN(ctx->width, 16) / 16;
+ u32 h16 = ALIGN(ctx->height, 16) / 16;
+
+ if (ctx->codec == RKVENC_CODEC_HEVC)
+ return ctx->height <= 64;
+
+ return !((w16 >= 3 && h16 >= 2) || (w16 == 1 && h16 == 1));
+}
+
+bool rkvenc_gop_is_idr(struct rkvenc_ctx *ctx)
+{
+ struct rkvenc_gop *g = &ctx->gop;
+
+ if (g->frames_since_idr == 0 || rkvenc_gop_intra_only(ctx))
+ return true;
+ if (ctx->c.gop_size && g->frames_since_idr >= ctx->c.gop_size)
+ return true;
+ if (ctx->c.force_keyframe)
+ return true;
+ if (!ctx->last_good_valid)
+ return true;
+ return false;
+}
+
+void rkvenc_gop_commit(struct rkvenc_ctx *ctx)
+{
+ struct rkvenc_gop *g = &ctx->gop;
+
+ if (ctx->cur_intra) {
+ g->idr_pic_id++;
+ ctx->last_good = ctx->cur;
+ ctx->last_good_valid = true;
+ g->frames_since_idr = 1;
+ } else {
+ g->frames_since_idr++;
+ }
+ ctx->last = ctx->cur;
+ ctx->cur ^= 1;
+ ctx->c.force_keyframe = false;
+}
+
+void rkvenc_gop_rollback(struct rkvenc_ctx *ctx)
+{
+ ctx->c.force_keyframe = false;
+}
+
+void rkvenc_gop_force_keyframe(struct rkvenc_ctx *ctx)
+{
+ ctx->c.force_keyframe = true;
+}
diff --git a/drivers/media/platform/rockchip/rkvenc/rkvenc-h264.c b/drivers/media/platform/rockchip/rkvenc/rkvenc-h264.c
new file mode 100644
index 0000000000000..391755a42dd3b
--- /dev/null
+++ b/drivers/media/platform/rockchip/rkvenc/rkvenc-h264.c
@@ -0,0 +1,82 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * H.264 parameter sets of the RKVENC v1 encoder. One struct
+ * rkvenc_h264_seq feeds both the slice header registers and the SPS/PPS
+ * writer, so the two cannot disagree.
+ */
+
+#include <linux/string.h>
+
+#include "rkvenc.h"
+#include "rkvenc-nal.h"
+
+static const u8 rkvenc_h264_level_idc[] = {
+ 10, 11, 11, 12, 13, 20, 21, 22, 30, 31, 32, 40,
+ 41, 42, 50, 51, 52, 60, 61, 62,
+};
+
+static u8 rkvenc_h264_level(u32 ctrl)
+{
+ if (ctrl >= ARRAY_SIZE(rkvenc_h264_level_idc))
+ return 62;
+ return rkvenc_h264_level_idc[ctrl];
+}
+
+static u8 rkvenc_h264_profile_idc(u32 ctrl)
+{
+ switch (ctrl) {
+ case V4L2_MPEG_VIDEO_H264_PROFILE_BASELINE:
+ case V4L2_MPEG_VIDEO_H264_PROFILE_CONSTRAINED_BASELINE:
+ return 66;
+ case V4L2_MPEG_VIDEO_H264_PROFILE_MAIN:
+ return 77;
+ case V4L2_MPEG_VIDEO_H264_PROFILE_HIGH:
+ default:
+ return 100;
+ }
+}
+
+int rkvenc_h264_setup(struct rkvenc_ctx *ctx)
+{
+ struct rkvenc_h264_seq *seq = &ctx->h264_seq;
+
+ memset(seq, 0, sizeof(*seq));
+ seq->width = ctx->width;
+ seq->height = ctx->height;
+ seq->profile_idc = rkvenc_h264_profile_idc(ctx->c.profile);
+ seq->level_idc = rkvenc_h264_level(ctx->c.level);
+ seq->log2_max_frame_num_m4 = 4;
+ seq->log2_max_poc_lsb_m4 = 5;
+ seq->pic_init_qp = ctx->c.p_frame_qp;
+ seq->chroma_qp_offset = ctx->c.chroma_qp_offset;
+ seq->cabac = ctx->c.cabac && seq->profile_idc != 66;
+ if (ctx->c.profile == V4L2_MPEG_VIDEO_H264_PROFILE_HIGH)
+ seq->transform_8x8 = ctx->c.transform_8x8;
+ seq->dblk_idc = ctx->c.lf_mode;
+ seq->alpha_offset = ctx->c.lf_alpha;
+ seq->beta_offset = ctx->c.lf_beta;
+ seq->timing = true;
+ seq->fps = ctx->fps ?: 30;
+
+ return rkvenc_h264_headers_write(seq, ctx->hdr, RKVENC_HDR_MAX);
+}
+
+int rkvenc_h264_write_headers(struct rkvenc_ctx *ctx, u8 *buf, u32 size)
+{
+ if (ctx->hdr_len > size)
+ return -ENOSPC;
+ memcpy(buf, ctx->hdr, ctx->hdr_len);
+ return ctx->hdr_len;
+}
+
+void rkvenc_h264_build_pic(struct rkvenc_ctx *ctx,
+ struct rkvenc_h264_pic *pic, bool idr)
+{
+ struct rkvenc_gop *g = &ctx->gop;
+
+ pic->seq = &ctx->h264_seq;
+ pic->nal_ref_idc = idr ? 3 : 2;
+ pic->frame_num = g->frames_since_idr & 0xff;
+ pic->poc_lsb = (2 * g->frames_since_idr) & 0x1ff;
+ pic->idr_pic_id = g->idr_pic_id & 0xffff;
+}
diff --git a/drivers/media/platform/rockchip/rkvenc/rkvenc-hevc.c b/drivers/media/platform/rockchip/rkvenc/rkvenc-hevc.c
new file mode 100644
index 0000000000000..49bf3cccdf8a0
--- /dev/null
+++ b/drivers/media/platform/rockchip/rkvenc/rkvenc-hevc.c
@@ -0,0 +1,63 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * HEVC parameter sets of the RKVENC v1 encoder. One struct
+ * rkvenc_hevc_seq feeds both the slice header registers and the
+ * VPS/SPS/PPS writer.
+ */
+
+#include <linux/string.h>
+
+#include "rkvenc.h"
+#include "rkvenc-nal.h"
+
+#define HEVC_NAL_TRAIL_R 1
+#define HEVC_NAL_IDR_W_RADL 19
+
+static const u16 rkvenc_hevc_level_idc[] = {
+ 30, 60, 63, 90, 93, 120, 123, 150, 153, 156, 180, 183, 186,
+};
+
+static u16 rkvenc_hevc_level(u32 ctrl)
+{
+ if (ctrl >= ARRAY_SIZE(rkvenc_hevc_level_idc))
+ return 186;
+ return rkvenc_hevc_level_idc[ctrl];
+}
+
+int rkvenc_hevc_setup(struct rkvenc_ctx *ctx)
+{
+ struct rkvenc_hevc_seq *seq = &ctx->hevc_seq;
+
+ memset(seq, 0, sizeof(*seq));
+ seq->width = ctx->width;
+ seq->height = ctx->height;
+ seq->level_idc = rkvenc_hevc_level(ctx->c.level);
+ seq->log2_max_poc_lsb_m4 = 4;
+ seq->pic_init_qp = ctx->c.p_frame_qp;
+ seq->max_merge_cand = ctx->c.hevc_max_merge ?: 2;
+ seq->sao = ctx->c.hevc_sao;
+ seq->deblock_disabled = ctx->c.lf_mode ==
+ V4L2_MPEG_VIDEO_HEVC_LOOP_FILTER_MODE_DISABLED;
+ seq->beta_offset = ctx->c.hevc_beta_offset;
+ seq->tc_offset = ctx->c.hevc_tc_offset;
+ seq->strong_intra_smoothing = ctx->c.hevc_strong_intra;
+ seq->cabac_init_present = ctx->c.hevc_cabac_init;
+
+ return rkvenc_hevc_headers_write(seq, ctx->hdr, RKVENC_HDR_MAX);
+}
+
+int rkvenc_hevc_write_headers(struct rkvenc_ctx *ctx, u8 *buf, u32 size)
+{
+ if (ctx->hdr_len > size)
+ return -ENOSPC;
+ memcpy(buf, ctx->hdr, ctx->hdr_len);
+ return ctx->hdr_len;
+}
+
+void rkvenc_hevc_build_pic(struct rkvenc_ctx *ctx,
+ struct rkvenc_hevc_pic *pic, bool idr)
+{
+ pic->seq = &ctx->hevc_seq;
+ pic->nal_unit_type = idr ? HEVC_NAL_IDR_W_RADL : HEVC_NAL_TRAIL_R;
+ pic->poc_lsb = ctx->gop.frames_since_idr & 0xff;
+}
diff --git a/drivers/media/platform/rockchip/rkvenc/rkvenc-nal.c b/drivers/media/platform/rockchip/rkvenc/rkvenc-nal.c
new file mode 100644
index 0000000000000..b5a3f01852c0b
--- /dev/null
+++ b/drivers/media/platform/rockchip/rkvenc/rkvenc-nal.c
@@ -0,0 +1,351 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * H.264 SPS/PPS (ITU-T H.264 7.3.2) and HEVC VPS/SPS/PPS (ITU-T H.265 7.3.2)
+ * writer for the RKVENC v1 encoder.
+ */
+
+#include "rkvenc-nal.h"
+
+#define NAL_RBSP_MAX 256
+
+#define H264_NAL_SPS 7
+#define H264_NAL_PPS 8
+#define H264_PROFILE_HIGH 100
+#define HEVC_NAL_VPS 32
+#define HEVC_NAL_SPS 33
+#define HEVC_NAL_PPS 34
+
+struct nal_bw {
+ u8 buf[NAL_RBSP_MAX];
+ u32 bits;
+ bool overflow;
+};
+
+static void bw_put(struct nal_bw *bw, u32 val, u32 bits)
+{
+ u32 i, bit;
+
+ for (i = bits; i > 0; i--) {
+ if (bw->bits >= 8 * NAL_RBSP_MAX) {
+ bw->overflow = true;
+ return;
+ }
+ bit = (val >> (i - 1)) & 1;
+ if (bit)
+ bw->buf[bw->bits / 8] |= 0x80 >> (bw->bits % 8);
+ bw->bits++;
+ }
+}
+
+static void bw_ue(struct nal_bw *bw, u32 val)
+{
+ u32 len = 32 - __builtin_clz(val + 1);
+
+ bw_put(bw, 0, len - 1);
+ bw_put(bw, val + 1, len);
+}
+
+static void bw_se(struct nal_bw *bw, s32 val)
+{
+ bw_ue(bw, val > 0 ? 2 * val - 1 : -2 * val);
+}
+
+static void bw_trailing(struct nal_bw *bw)
+{
+ bw_put(bw, 1, 1);
+ while (bw->bits % 8)
+ bw_put(bw, 0, 1);
+}
+
+static int nal_emit(const struct nal_bw *bw, const u8 *hdr, u32 hdr_len,
+ u8 *out, u32 size)
+{
+ u32 len = bw->bits / 8, pos = 0, zeros = 0, i;
+
+ if (bw->overflow || size < 4 + hdr_len)
+ return -ENOSPC;
+
+ out[pos++] = 0;
+ out[pos++] = 0;
+ out[pos++] = 0;
+ out[pos++] = 1;
+ for (i = 0; i < hdr_len; i++)
+ out[pos++] = hdr[i];
+
+ /* Emulation prevention, H.264 7.4.1 */
+ for (i = 0; i < len; i++) {
+ if (pos + 2 > size)
+ return -ENOSPC;
+ if (zeros == 2 && bw->buf[i] <= 3) {
+ out[pos++] = 3;
+ zeros = 0;
+ }
+ out[pos++] = bw->buf[i];
+ zeros = bw->buf[i] ? 0 : zeros + 1;
+ }
+
+ return pos;
+}
+
+static void h264_vui_write(struct nal_bw *bw, const struct rkvenc_h264_seq *seq)
+{
+ bw_put(bw, 0, 4);
+ bw_put(bw, seq->timing, 1);
+ if (seq->timing) {
+ bw_put(bw, 1, 32);
+ bw_put(bw, 2 * seq->fps, 32);
+ bw_put(bw, 1, 1);
+ }
+ bw_put(bw, 0, 3);
+ bw_put(bw, 1, 1); /* bitstream_restriction_flag */
+ bw_put(bw, 1, 1);
+ bw_ue(bw, 0);
+ bw_ue(bw, 0);
+ bw_ue(bw, 16);
+ bw_ue(bw, 16);
+ bw_ue(bw, 0); /* max_num_reorder_frames */
+ bw_ue(bw, 1); /* max_dec_frame_buffering */
+}
+
+static void h264_sps_write(struct nal_bw *bw, const struct rkvenc_h264_seq *seq)
+{
+ u32 w16 = ALIGN(seq->width, 16), h16 = ALIGN(seq->height, 16);
+ bool crop = w16 != seq->width || h16 != seq->height;
+ u32 constraints;
+
+ if (seq->profile_idc == H264_PROFILE_HIGH)
+ constraints = 0;
+ else if (seq->profile_idc == 66)
+ constraints = 0xc0;
+ else
+ constraints = 0x40;
+
+ bw_put(bw, seq->profile_idc, 8);
+ bw_put(bw, constraints, 8);
+ bw_put(bw, seq->level_idc, 8);
+ bw_ue(bw, 0);
+ if (seq->profile_idc == H264_PROFILE_HIGH) {
+ bw_ue(bw, 1);
+ bw_ue(bw, 0);
+ bw_ue(bw, 0);
+ bw_put(bw, 0, 2);
+ }
+ bw_ue(bw, seq->log2_max_frame_num_m4);
+ bw_ue(bw, 0);
+ bw_ue(bw, seq->log2_max_poc_lsb_m4);
+ bw_ue(bw, 1);
+ bw_put(bw, 0, 1);
+ bw_ue(bw, w16 / 16 - 1);
+ bw_ue(bw, h16 / 16 - 1);
+ bw_put(bw, 1, 1);
+ bw_put(bw, 1, 1);
+ bw_put(bw, crop, 1);
+ if (crop) {
+ bw_ue(bw, 0);
+ bw_ue(bw, (w16 - seq->width) / 2);
+ bw_ue(bw, 0);
+ bw_ue(bw, (h16 - seq->height) / 2);
+ }
+ bw_put(bw, 1, 1);
+ h264_vui_write(bw, seq);
+ bw_trailing(bw);
+}
+
+static void h264_pps_write(struct nal_bw *bw, const struct rkvenc_h264_seq *seq)
+{
+ bw_ue(bw, 0);
+ bw_ue(bw, 0);
+ bw_put(bw, seq->cabac, 1);
+ bw_put(bw, 0, 1);
+ bw_ue(bw, 0);
+ bw_ue(bw, 0);
+ bw_ue(bw, 0);
+ bw_put(bw, 0, 3);
+ bw_se(bw, seq->pic_init_qp - 26);
+ bw_se(bw, 0);
+ bw_se(bw, seq->chroma_qp_offset);
+ bw_put(bw, 1, 1);
+ bw_put(bw, 0, 2);
+ if (seq->transform_8x8) {
+ bw_put(bw, 1, 1);
+ bw_put(bw, 0, 1);
+ bw_se(bw, seq->chroma_qp_offset);
+ }
+ bw_trailing(bw);
+}
+
+int rkvenc_h264_headers_write(const struct rkvenc_h264_seq *seq, u8 *buf,
+ u32 size)
+{
+ u8 hdr;
+ struct nal_bw bw;
+ int len, pos;
+
+ memset(&bw, 0, sizeof(bw));
+ h264_sps_write(&bw, seq);
+ hdr = 3 << 5 | H264_NAL_SPS;
+ len = nal_emit(&bw, &hdr, 1, buf, size);
+ if (len < 0)
+ return len;
+ pos = len;
+
+ memset(&bw, 0, sizeof(bw));
+ h264_pps_write(&bw, seq);
+ hdr = 3 << 5 | H264_NAL_PPS;
+ len = nal_emit(&bw, &hdr, 1, buf + pos, size - pos);
+ if (len < 0)
+ return len;
+
+ return pos + len;
+}
+
+static void hevc_ptl_write(struct nal_bw *bw, const struct rkvenc_hevc_seq *seq)
+{
+ bw_put(bw, 0, 2);
+ bw_put(bw, 0, 1);
+ bw_put(bw, 1, 5); /* general_profile_idc: Main */
+ bw_put(bw, 0x60000000, 32); /* compatible with profiles 1, 2 */
+ bw_put(bw, 1, 1);
+ bw_put(bw, 0, 1);
+ bw_put(bw, 0, 1);
+ bw_put(bw, 1, 1);
+ bw_put(bw, 0, 32);
+ bw_put(bw, 0, 12);
+ bw_put(bw, seq->level_idc, 8);
+}
+
+static void hevc_vps_write(struct nal_bw *bw, const struct rkvenc_hevc_seq *seq)
+{
+ bw_put(bw, 0, 4);
+ bw_put(bw, 1, 1);
+ bw_put(bw, 1, 1);
+ bw_put(bw, 0, 6);
+ bw_put(bw, 0, 3);
+ bw_put(bw, 1, 1);
+ bw_put(bw, 0xffff, 16);
+ hevc_ptl_write(bw, seq);
+ bw_put(bw, 1, 1);
+ bw_ue(bw, 1);
+ bw_ue(bw, 0);
+ bw_ue(bw, 0);
+ bw_put(bw, 0, 6);
+ bw_ue(bw, 0);
+ bw_put(bw, 0, 1);
+ bw_put(bw, 0, 1);
+ bw_trailing(bw);
+}
+
+static void hevc_sps_write(struct nal_bw *bw, const struct rkvenc_hevc_seq *seq)
+{
+ u32 w8 = ALIGN(seq->width, 8), h8 = ALIGN(seq->height, 8);
+ bool crop = w8 != seq->width || h8 != seq->height;
+
+ bw_put(bw, 0, 4);
+ bw_put(bw, 0, 3);
+ bw_put(bw, 1, 1);
+ hevc_ptl_write(bw, seq);
+ bw_ue(bw, 0);
+ bw_ue(bw, 1); /* chroma_format_idc 4:2:0 */
+ bw_ue(bw, w8);
+ bw_ue(bw, h8);
+ bw_put(bw, crop, 1);
+ if (crop) {
+ bw_ue(bw, 0);
+ bw_ue(bw, (w8 - seq->width) / 2);
+ bw_ue(bw, 0);
+ bw_ue(bw, (h8 - seq->height) / 2);
+ }
+ bw_ue(bw, 0);
+ bw_ue(bw, 0);
+ bw_ue(bw, seq->log2_max_poc_lsb_m4);
+ bw_put(bw, 1, 1);
+ bw_ue(bw, 1); /* sps_max_dec_pic_buffering_minus1 */
+ bw_ue(bw, 0);
+ bw_ue(bw, 0);
+ bw_ue(bw, 0); /* min CU 8x8 */
+ bw_ue(bw, 3); /* CTU 64x64 */
+ bw_ue(bw, 0); /* min TU 4x4 */
+ bw_ue(bw, 3); /* max TU 32x32 */
+ bw_ue(bw, 0);
+ bw_ue(bw, 0);
+ bw_put(bw, 0, 1);
+ bw_put(bw, 0, 1);
+ bw_put(bw, seq->sao, 1);
+ bw_put(bw, 0, 1);
+ bw_ue(bw, 0);
+ bw_put(bw, 0, 1);
+ bw_put(bw, 0, 1);
+ bw_put(bw, seq->strong_intra_smoothing, 1);
+ bw_put(bw, 0, 1);
+ bw_put(bw, 0, 1);
+ bw_trailing(bw);
+}
+
+static void hevc_pps_write(struct nal_bw *bw, const struct rkvenc_hevc_seq *seq)
+{
+ bw_ue(bw, 0);
+ bw_ue(bw, 0);
+ bw_put(bw, 0, 1);
+ bw_put(bw, 0, 1);
+ bw_put(bw, 0, 3);
+ bw_put(bw, 0, 1);
+ bw_put(bw, seq->cabac_init_present, 1);
+ bw_ue(bw, 0);
+ bw_ue(bw, 0);
+ bw_se(bw, seq->pic_init_qp - 26);
+ bw_put(bw, 0, 1);
+ bw_put(bw, 0, 1);
+ bw_put(bw, 1, 1); /* cu_qp_delta_enabled_flag */
+ bw_ue(bw, 0);
+ bw_se(bw, 0);
+ bw_se(bw, 0);
+ bw_put(bw, 0, 1);
+ bw_put(bw, 0, 1);
+ bw_put(bw, 0, 1);
+ bw_put(bw, 0, 1);
+ bw_put(bw, 0, 1);
+ bw_put(bw, 0, 1);
+ bw_put(bw, 0, 1);
+ bw_put(bw, 1, 1);
+ bw_put(bw, 0, 1);
+ bw_put(bw, seq->deblock_disabled, 1);
+ if (!seq->deblock_disabled) {
+ bw_se(bw, seq->beta_offset);
+ bw_se(bw, seq->tc_offset);
+ }
+ bw_put(bw, 0, 1);
+ bw_put(bw, 0, 1);
+ bw_ue(bw, 0);
+ bw_put(bw, 0, 1);
+ bw_put(bw, 0, 1);
+ bw_trailing(bw);
+}
+
+int rkvenc_hevc_headers_write(const struct rkvenc_hevc_seq *seq, u8 *buf,
+ u32 size)
+{
+ static const u8 types[] = { HEVC_NAL_VPS, HEVC_NAL_SPS, HEVC_NAL_PPS };
+ struct nal_bw bw;
+ int len, pos;
+ u8 hdr[2];
+ u32 i;
+
+ pos = 0;
+ for (i = 0; i < ARRAY_SIZE(types); i++) {
+ memset(&bw, 0, sizeof(bw));
+ if (types[i] == HEVC_NAL_VPS)
+ hevc_vps_write(&bw, seq);
+ else if (types[i] == HEVC_NAL_SPS)
+ hevc_sps_write(&bw, seq);
+ else
+ hevc_pps_write(&bw, seq);
+ hdr[0] = types[i] << 1;
+ hdr[1] = 1;
+ len = nal_emit(&bw, hdr, 2, buf + pos, size - pos);
+ if (len < 0)
+ return len;
+ pos += len;
+ }
+
+ return pos;
+}
diff --git a/drivers/media/platform/rockchip/rkvenc/rkvenc-nal.h b/drivers/media/platform/rockchip/rkvenc/rkvenc-nal.h
new file mode 100644
index 0000000000000..58b30d2ac6124
--- /dev/null
+++ b/drivers/media/platform/rockchip/rkvenc/rkvenc-nal.h
@@ -0,0 +1,12 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+#ifndef RKVENC_NAL_H
+#define RKVENC_NAL_H
+
+#include "rkvenc-vepu541.h"
+
+int rkvenc_h264_headers_write(const struct rkvenc_h264_seq *seq, u8 *buf,
+ u32 size);
+int rkvenc_hevc_headers_write(const struct rkvenc_hevc_seq *seq, u8 *buf,
+ u32 size);
+
+#endif
diff --git a/drivers/media/platform/rockchip/rkvenc/rkvenc-rc.c b/drivers/media/platform/rockchip/rkvenc/rkvenc-rc.c
new file mode 100644
index 0000000000000..b7ab57e490cf0
--- /dev/null
+++ b/drivers/media/platform/rockchip/rkvenc/rkvenc-rc.c
@@ -0,0 +1,214 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Frame-level rate control of the RKVENC v1 encoder. The hardware row-level
+ * rate control is off, every picture is coded at one QP picked here.
+ */
+
+#include <linux/math.h>
+#include <linux/math64.h>
+
+#include "rkvenc.h"
+
+/* qs[q] = round(256 * 2^((q-4)/6)) in Q8, q = 0..51. */
+#define RKVENC_QS_BASE { 256, 287, 323, 362, 406, 456 }
+
+#define RKVENC_RC_DEFAULT_FPS 30u
+
+/* I/P complexity ratio cap, Q8; static scenes reach 37 */
+#define RKVENC_RC_W_CLAMP (64u * 256)
+/* A larger step makes the P QP alternate between two values */
+#define RKVENC_RC_QP_STEP 1
+/* Q8 weight of the newest P frame in the P complexity */
+#define RKVENC_RC_X_WEIGHT 32
+
+/* I frame QP offset below the mean P frame QP */
+#define RKVENC_RC_QP_DELTA_IP_HEVC 4
+#define RKVENC_RC_QP_DELTA_IP_H264 2
+
+static u32 rkvenc_qs[RKVENC_QP_MAX + 1];
+
+static void rkvenc_qs_build(void)
+{
+ static const u32 base[6] = RKVENC_QS_BASE;
+ unsigned int q;
+ int k, sh, m;
+ u64 v;
+
+ for (q = 0; q <= RKVENC_QP_MAX; q++) {
+ k = (int)q - 4;
+ /* floor(k / 6), correct for negative k. */
+ sh = (k - (k < 0 ? 5 : 0)) / 6;
+ m = k - 6 * sh;
+ if (sh >= 0)
+ v = (u64)base[m] << sh;
+ else
+ v = (u64)base[m] >> (-sh);
+ rkvenc_qs[q] = (u32)v;
+ }
+}
+
+static u32 rkvenc_rc_capacity(struct rkvenc_ctx *ctx)
+{
+ if (ctx->c.vbv_size)
+ return ctx->c.vbv_size;
+ return ctx->rc_tavg * max_t(u32, ctx->c.gop_size, 1);
+}
+
+/* The plan budgets the I frame, so the bucket is on plan at GOP starts */
+static u64 rkvenc_rc_correct(struct rkvenc_ctx *ctx, u64 t_t, u32 n)
+{
+ s64 c = div_s64(ctx->rc_buf_gop, n);
+
+ if (c < 0)
+ return t_t + (u64)-c;
+ if ((u64)c >= t_t)
+ return 1;
+ return t_t - (u64)c;
+}
+
+static u32 rkvenc_rc_complexity(struct rkvenc_ctx *ctx, unsigned int t, u64 s,
+ u32 qp_avg)
+{
+ u32 x = (u32)div_u64(s * rkvenc_qs[min(qp_avg, RKVENC_QP_MAX)], 256);
+
+ /* a P frame below the skip threshold alone would swing the model */
+ if (!t || ctx->rc_first[1])
+ return x;
+ return (u32)div_u64((u64)ctx->rc_x[1] * (256 - RKVENC_RC_X_WEIGHT) +
+ (u64)x * RKVENC_RC_X_WEIGHT,
+ 256);
+}
+
+static int rkvenc_rc_p_qp_mean(struct rkvenc_ctx *ctx)
+{
+ int q = ctx->rc_qp[1];
+
+ if (ctx->rc_p_qp_cnt)
+ q = DIV_ROUND_CLOSEST(ctx->rc_p_qp_sum, ctx->rc_p_qp_cnt);
+ ctx->rc_p_qp_sum = 0;
+ ctx->rc_p_qp_cnt = 0;
+ return q;
+}
+
+void rkvenc_rc_init(struct rkvenc_ctx *ctx)
+{
+ u32 bitrate = ctx->c.bitrate ?: 4000000;
+ u32 fps = ctx->fps ?: RKVENC_RC_DEFAULT_FPS;
+
+ rkvenc_qs_build();
+
+ ctx->rc_tavg = div_u64((u64)bitrate, fps);
+ if (!ctx->rc_tavg)
+ ctx->rc_tavg = 1;
+ ctx->rc_buf = 0;
+ ctx->rc_buf_gop = 0;
+ ctx->rc_p_qp_sum = 0;
+ ctx->rc_p_qp_cnt = 0;
+ ctx->rc_x[0] = ctx->rc_x[1] = 0;
+ ctx->rc_qp[0] = ctx->c.i_frame_qp;
+ ctx->rc_qp[1] = ctx->c.p_frame_qp;
+ ctx->rc_first[0] = true;
+ ctx->rc_first[1] = true;
+}
+
+void rkvenc_rc_pick(struct rkvenc_ctx *ctx, u8 *qp, u8 frame_type)
+{
+ unsigned int t = frame_type ? 1 : 0;
+ u8 lo = ctx->c.min_qp;
+ u8 hi = ctx->c.max_qp;
+ s32 prev = ctx->rc_qp[t];
+ u32 target;
+ int q;
+
+ if (!ctx->rc_active) {
+ *qp = frame_type ? ctx->c.p_frame_qp : ctx->c.i_frame_qp;
+ return;
+ }
+
+ if (ctx->rc_first[t]) {
+ *qp = frame_type ? ctx->c.p_frame_qp : ctx->c.i_frame_qp;
+ return;
+ }
+
+ if (!t)
+ ctx->rc_buf_gop = ctx->rc_buf;
+
+ /* An I frame coarser than the P frames of the GOP shows as a pulse */
+ if (!t && !ctx->rc_first[1]) {
+ q = rkvenc_rc_p_qp_mean(ctx);
+ if (ctx->codec == RKVENC_CODEC_HEVC)
+ q -= RKVENC_RC_QP_DELTA_IP_HEVC;
+ else
+ q -= RKVENC_RC_QP_DELTA_IP_H264;
+ q = clamp_t(int, q, lo, hi);
+ ctx->rc_qp[0] = (s8)q;
+ *qp = q;
+ return;
+ }
+
+ if (ctx->rc_x[0] && ctx->rc_x[1]) {
+ u64 w = div_u64((u64)ctx->rc_x[0] * 256,
+ ctx->rc_x[1] ?: 1);
+ u32 n = max_t(u32, ctx->c.gop_size, 1);
+ u64 t_p, t_t;
+
+ w = min_t(u64, w, RKVENC_RC_W_CLAMP);
+ t_p = div64_u64((u64)ctx->rc_tavg * n * 256,
+ w + 256 * (n - 1));
+ t_t = t ? t_p : t_p * w / 256;
+ t_t = rkvenc_rc_correct(ctx, t_t, n);
+
+ target = (u32)div_u64((u64)ctx->rc_x[t] * 256, t_t);
+
+ for (q = 0; q <= RKVENC_QP_MAX; q++)
+ if (rkvenc_qs[q] >= target)
+ break;
+ if (q > RKVENC_QP_MAX)
+ q = RKVENC_QP_MAX;
+ } else {
+ q = prev;
+ }
+
+ q = clamp_t(int, q, lo, hi);
+ if (q > prev + RKVENC_RC_QP_STEP)
+ q = prev + RKVENC_RC_QP_STEP;
+ if (q < prev - RKVENC_RC_QP_STEP)
+ q = prev - RKVENC_RC_QP_STEP;
+ q = clamp_t(int, q, lo, hi);
+
+ ctx->rc_qp[t] = (s8)q;
+ if (t) {
+ ctx->rc_p_qp_sum += q;
+ ctx->rc_p_qp_cnt++;
+ }
+ *qp = q;
+}
+
+void rkvenc_rc_update(struct rkvenc_ctx *ctx, u32 bytes, u32 qp_avg,
+ u8 frame_type)
+{
+ unsigned int t = frame_type ? 1 : 0;
+ u64 s = (u64)bytes * 8;
+ s64 cap = max_t(u64, rkvenc_rc_capacity(ctx), 1);
+
+ if (!ctx->rc_active)
+ return;
+
+ ctx->rc_x[t] = rkvenc_rc_complexity(ctx, t, s, qp_avg);
+
+ ctx->rc_buf += (s32)s - (s32)ctx->rc_tavg;
+ ctx->rc_buf = clamp_t(s64, ctx->rc_buf, -cap, cap);
+
+ ctx->rc_first[t] = false;
+}
+
+/* The step limit alone would need frames that the overflow keeps dropping */
+void rkvenc_rc_overflow(struct rkvenc_ctx *ctx, u32 bytes, u8 frame_type)
+{
+ s8 start = frame_type ? ctx->c.p_frame_qp : ctx->c.i_frame_qp;
+ unsigned int t = frame_type ? 1 : 0;
+
+ rkvenc_rc_update(ctx, bytes, ctx->cur_qp, frame_type);
+ if (ctx->rc_qp[t] < start)
+ ctx->rc_qp[t] = start;
+}
diff --git a/drivers/media/platform/rockchip/rkvenc/rkvenc-ref.c b/drivers/media/platform/rockchip/rkvenc/rkvenc-ref.c
new file mode 100644
index 0000000000000..b0f15d31f1e11
--- /dev/null
+++ b/drivers/media/platform/rockchip/rkvenc/rkvenc-ref.c
@@ -0,0 +1,114 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Reconstruction and reference buffers of the RKVENC v1 encoder: two slots
+ * alternate as the single short-term reference.
+ */
+
+#include <linux/dma-mapping.h>
+#include <linux/slab.h>
+
+#include "rkvenc.h"
+
+enum {
+ RKVENC_BUF_HDR = 0,
+ RKVENC_BUF_BODY,
+ RKVENC_BUF_DSP,
+ RKVENC_BUF_CMV,
+};
+
+static void rkvenc_ref_free_slot(struct rkvenc_ctx *ctx, unsigned int i)
+{
+ struct device *dev = ctx->dev->dev;
+ size_t sizes[RKVENC_SLOT_BUFS];
+ u32 handles[RKVENC_SLOT_BUFS];
+ unsigned int b;
+
+ sizes[RKVENC_BUF_HDR] = ctx->slot_sz.rfp_hdr;
+ sizes[RKVENC_BUF_BODY] = ctx->slot_sz.rfp_body;
+ sizes[RKVENC_BUF_DSP] = ctx->slot_sz.dsp;
+ sizes[RKVENC_BUF_CMV] = ctx->slot_sz.cmv;
+ handles[RKVENC_BUF_HDR] = ctx->slot[i].rfp_hdr;
+ handles[RKVENC_BUF_BODY] = ctx->slot[i].rfp_body;
+ handles[RKVENC_BUF_DSP] = ctx->slot[i].dsp;
+ handles[RKVENC_BUF_CMV] = ctx->slot[i].cmv;
+
+ for (b = 0; b < RKVENC_SLOT_BUFS; b++) {
+ if (ctx->slot_cpu[i][b])
+ dma_free_attrs(dev, sizes[b], ctx->slot_cpu[i][b],
+ handles[b], DMA_ATTR_NO_KERNEL_MAPPING);
+ ctx->slot_cpu[i][b] = NULL;
+ }
+ memset(&ctx->slot[i], 0, sizeof(ctx->slot[i]));
+}
+
+int rkvenc_ref_alloc(struct rkvenc_ctx *ctx)
+{
+ struct device *dev = ctx->dev->dev;
+ struct rkvenc_slot_sizes *sz = &ctx->slot_sz;
+ dma_addr_t dma;
+ void *cpu;
+ unsigned int i;
+ int ret = 0;
+
+ if (ctx->slots_alloc)
+ return 0;
+
+ rkvenc_vepu541_slot_sizes(ctx->codec, ctx->width, ctx->height, sz);
+
+ for (i = 0; i < RKVENC_SLOTS; i++) {
+ cpu = dma_alloc_attrs(dev, sz->rfp_hdr, &dma, GFP_KERNEL,
+ DMA_ATTR_NO_KERNEL_MAPPING);
+ if (!cpu)
+ goto err;
+ ctx->slot_cpu[i][RKVENC_BUF_HDR] = cpu;
+ ctx->slot[i].rfp_hdr = lower_32_bits(dma);
+
+ cpu = dma_alloc_attrs(dev, sz->rfp_body, &dma, GFP_KERNEL,
+ DMA_ATTR_NO_KERNEL_MAPPING);
+ if (!cpu)
+ goto err;
+ ctx->slot_cpu[i][RKVENC_BUF_BODY] = cpu;
+ ctx->slot[i].rfp_body = lower_32_bits(dma);
+
+ cpu = dma_alloc_attrs(dev, sz->dsp, &dma, GFP_KERNEL,
+ DMA_ATTR_NO_KERNEL_MAPPING);
+ if (!cpu)
+ goto err;
+ ctx->slot_cpu[i][RKVENC_BUF_DSP] = cpu;
+ ctx->slot[i].dsp = lower_32_bits(dma);
+
+ if (ctx->codec == RKVENC_CODEC_HEVC && sz->cmv) {
+ cpu = dma_alloc_attrs(dev, sz->cmv, &dma, GFP_KERNEL,
+ DMA_ATTR_NO_KERNEL_MAPPING);
+ if (!cpu)
+ goto err;
+ ctx->slot_cpu[i][RKVENC_BUF_CMV] = cpu;
+ ctx->slot[i].cmv = lower_32_bits(dma);
+ } else {
+ ctx->slot[i].cmv = 0;
+ }
+ }
+
+ ctx->slots_alloc = true;
+ return 0;
+
+err:
+ for (i = 0; i < RKVENC_SLOTS; i++)
+ rkvenc_ref_free_slot(ctx, i);
+ ret = -ENOMEM;
+ return ret;
+}
+
+void rkvenc_ref_free(struct rkvenc_ctx *ctx)
+{
+ unsigned int i;
+
+ if (!ctx->slots_alloc)
+ return;
+
+ for (i = 0; i < RKVENC_SLOTS; i++)
+ rkvenc_ref_free_slot(ctx, i);
+ ctx->slots_alloc = false;
+ ctx->cur = 0;
+ ctx->last = 0;
+}
diff --git a/drivers/media/platform/rockchip/rkvenc/rkvenc-v4l2.c b/drivers/media/platform/rockchip/rkvenc/rkvenc-v4l2.c
new file mode 100644
index 0000000000000..3be496f5cf0c1
--- /dev/null
+++ b/drivers/media/platform/rockchip/rkvenc/rkvenc-v4l2.c
@@ -0,0 +1,1069 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * V4L2 stateful encoder front end of the Rockchip RKVENC v1 encoder.
+ */
+
+#include <linux/dma-mapping.h>
+#include <linux/pm_runtime.h>
+#include <linux/slab.h>
+#include <media/v4l2-ctrls.h>
+#include <media/v4l2-event.h>
+#include <media/v4l2-ioctl.h>
+#include <media/v4l2-mem2mem.h>
+#include <media/videobuf2-dma-contig.h>
+
+#include "rkvenc.h"
+
+static inline struct rkvenc_ctx *fh_to_rkvenc_ctx(void *priv)
+{
+ return container_of(priv, struct rkvenc_ctx, fh);
+}
+
+struct rkvenc_fmt {
+ u32 fourcc;
+ u8 num_planes;
+};
+
+static const struct rkvenc_fmt rkvenc_output_fmts[] = {
+ { V4L2_PIX_FMT_NV12, 1 },
+ { V4L2_PIX_FMT_NV12M, 2 },
+};
+
+static const struct rkvenc_fmt rkvenc_capture_fmts[] = {
+ { V4L2_PIX_FMT_H264, 1 },
+ { V4L2_PIX_FMT_HEVC, 1 },
+};
+
+static const struct rkvenc_fmt *
+rkvenc_find_fmt(const struct rkvenc_fmt *fmts, unsigned int n, u32 fourcc)
+{
+ unsigned int i;
+
+ for (i = 0; i < n; i++)
+ if (fmts[i].fourcc == fourcc)
+ return &fmts[i];
+ return NULL;
+}
+
+static bool rkvenc_is_h264(u32 fourcc)
+{
+ return fourcc == V4L2_PIX_FMT_H264;
+}
+
+static u32 rkvenc_ctrl_val(struct rkvenc_ctx *ctx, u32 id)
+{
+ struct v4l2_ctrl *ctrl = v4l2_ctrl_find(&ctx->ctrl_hdl, id);
+
+ if (!ctrl)
+ return 0;
+ return ctrl->cur.val;
+}
+
+/* Runs for every frame, so that control changes apply to the next one */
+static void rkvenc_apply_ctrls(struct rkvenc_ctx *ctx)
+{
+ struct rkvenc_ctrls *c = &ctx->c;
+ bool h264 = ctx->codec == RKVENC_CODEC_H264;
+
+ c->bitrate_mode = rkvenc_ctrl_val(ctx, V4L2_CID_MPEG_VIDEO_BITRATE_MODE);
+ c->bitrate = rkvenc_ctrl_val(ctx, V4L2_CID_MPEG_VIDEO_BITRATE);
+ c->vbv_size = rkvenc_ctrl_val(ctx, V4L2_CID_MPEG_VIDEO_VBV_SIZE);
+ c->frame_rc_enable =
+ rkvenc_ctrl_val(ctx, V4L2_CID_MPEG_VIDEO_FRAME_RC_ENABLE);
+ c->gop_size = rkvenc_ctrl_val(ctx, V4L2_CID_MPEG_VIDEO_GOP_SIZE);
+ c->i_period = rkvenc_ctrl_val(ctx, V4L2_CID_MPEG_VIDEO_H264_I_PERIOD);
+ c->header_mode = rkvenc_ctrl_val(ctx, V4L2_CID_MPEG_VIDEO_HEADER_MODE);
+ c->prepend_spspps =
+ rkvenc_ctrl_val(ctx, V4L2_CID_MPEG_VIDEO_PREPEND_SPSPPS_TO_IDR);
+
+ if (h264) {
+ c->profile = rkvenc_ctrl_val(ctx, V4L2_CID_MPEG_VIDEO_H264_PROFILE);
+ c->level = rkvenc_ctrl_val(ctx, V4L2_CID_MPEG_VIDEO_H264_LEVEL);
+ c->cabac = rkvenc_ctrl_val(ctx,
+ V4L2_CID_MPEG_VIDEO_H264_ENTROPY_MODE) ==
+ V4L2_MPEG_VIDEO_H264_ENTROPY_MODE_CABAC;
+ c->transform_8x8 =
+ rkvenc_ctrl_val(ctx, V4L2_CID_MPEG_VIDEO_H264_8X8_TRANSFORM);
+ c->lf_mode =
+ rkvenc_ctrl_val(ctx,
+ V4L2_CID_MPEG_VIDEO_H264_LOOP_FILTER_MODE);
+ c->lf_alpha =
+ rkvenc_ctrl_val(ctx,
+ V4L2_CID_MPEG_VIDEO_H264_LOOP_FILTER_ALPHA);
+ c->lf_beta =
+ rkvenc_ctrl_val(ctx,
+ V4L2_CID_MPEG_VIDEO_H264_LOOP_FILTER_BETA);
+ c->chroma_qp_offset =
+ rkvenc_ctrl_val(ctx,
+ V4L2_CID_MPEG_VIDEO_H264_CHROMA_QP_INDEX_OFFSET);
+ c->min_qp = rkvenc_ctrl_val(ctx, V4L2_CID_MPEG_VIDEO_H264_MIN_QP);
+ c->max_qp = rkvenc_ctrl_val(ctx, V4L2_CID_MPEG_VIDEO_H264_MAX_QP);
+ c->i_frame_qp =
+ rkvenc_ctrl_val(ctx, V4L2_CID_MPEG_VIDEO_H264_I_FRAME_QP);
+ c->p_frame_qp =
+ rkvenc_ctrl_val(ctx, V4L2_CID_MPEG_VIDEO_H264_P_FRAME_QP);
+ if (rkvenc_ctrl_val(ctx, V4L2_CID_MPEG_VIDEO_H264_I_PERIOD) >
+ c->gop_size)
+ c->gop_size =
+ rkvenc_ctrl_val(ctx,
+ V4L2_CID_MPEG_VIDEO_H264_I_PERIOD);
+ } else {
+ c->level = rkvenc_ctrl_val(ctx, V4L2_CID_MPEG_VIDEO_HEVC_LEVEL);
+ c->tier = rkvenc_ctrl_val(ctx, V4L2_CID_MPEG_VIDEO_HEVC_TIER);
+ c->lf_mode =
+ rkvenc_ctrl_val(ctx,
+ V4L2_CID_MPEG_VIDEO_HEVC_LOOP_FILTER_MODE);
+ c->hevc_beta_offset = rkvenc_ctrl_val(
+ ctx, V4L2_CID_MPEG_VIDEO_HEVC_LF_BETA_OFFSET_DIV2);
+ c->hevc_tc_offset = rkvenc_ctrl_val(
+ ctx, V4L2_CID_MPEG_VIDEO_HEVC_LF_TC_OFFSET_DIV2);
+ c->min_qp = rkvenc_ctrl_val(ctx, V4L2_CID_MPEG_VIDEO_HEVC_MIN_QP);
+ c->max_qp = rkvenc_ctrl_val(ctx, V4L2_CID_MPEG_VIDEO_HEVC_MAX_QP);
+ c->i_frame_qp =
+ rkvenc_ctrl_val(ctx, V4L2_CID_MPEG_VIDEO_HEVC_I_FRAME_QP);
+ c->p_frame_qp =
+ rkvenc_ctrl_val(ctx, V4L2_CID_MPEG_VIDEO_HEVC_P_FRAME_QP);
+ c->hevc_max_merge =
+ rkvenc_ctrl_val(ctx,
+ V4L2_CID_MPEG_VIDEO_HEVC_MAX_NUM_MERGE_MV_MINUS1);
+ /* There is no SAO control, SAO follows the loop filter mode */
+ c->hevc_sao =
+ c->lf_mode != V4L2_MPEG_VIDEO_HEVC_LOOP_FILTER_MODE_DISABLED;
+ c->hevc_strong_intra =
+ rkvenc_ctrl_val(ctx,
+ V4L2_CID_MPEG_VIDEO_HEVC_STRONG_SMOOTHING);
+ c->hevc_tmv =
+ rkvenc_ctrl_val(ctx,
+ V4L2_CID_MPEG_VIDEO_HEVC_TMV_PREDICTION);
+ c->hevc_cabac_init = 0;
+ if (rkvenc_ctrl_val(ctx, V4L2_CID_MPEG_VIDEO_HEVC_REFRESH_PERIOD) >
+ c->gop_size)
+ c->gop_size =
+ rkvenc_ctrl_val(ctx,
+ V4L2_CID_MPEG_VIDEO_HEVC_REFRESH_PERIOD);
+ }
+
+ c->framerate = ctx->fps ?: 30;
+ ctx->rc_active = c->frame_rc_enable;
+}
+
+static void rkvenc_refresh_header(struct rkvenc_ctx *ctx)
+{
+ rkvenc_apply_ctrls(ctx);
+ ctx->hdr_len = ctx->codec == RKVENC_CODEC_H264 ?
+ rkvenc_h264_setup(ctx) : rkvenc_hevc_setup(ctx);
+}
+
+static void rkvenc_ensure_header(struct rkvenc_ctx *ctx)
+{
+ if (ctx->hdr_len == 0)
+ rkvenc_refresh_header(ctx);
+}
+
+static int rkvenc_copy_headers(struct rkvenc_ctx *ctx, u8 *buf, u32 size)
+{
+ return ctx->codec == RKVENC_CODEC_H264 ?
+ rkvenc_h264_write_headers(ctx, buf, size) :
+ rkvenc_hevc_write_headers(ctx, buf, size);
+}
+
+static void rkvenc_update_blocks(struct rkvenc_ctx *ctx)
+{
+ if (ctx->codec == RKVENC_CODEC_H264) {
+ ctx->blocks = (ALIGN(ctx->width, 16) / 16) *
+ (ALIGN(ctx->height, 16) / 16);
+ ctx->qp_blocks = ctx->blocks;
+ } else {
+ ctx->blocks = (ALIGN(ctx->width, 64) / 64) *
+ (ALIGN(ctx->height, 64) / 64);
+ ctx->qp_blocks = (ALIGN(ctx->width, 8) / 8) *
+ (ALIGN(ctx->height, 8) / 8);
+ }
+}
+
+static u32 rkvenc_output_plane_size(struct rkvenc_ctx *ctx, unsigned int plane)
+{
+ u32 bpl = ctx->bytesperline;
+ u32 h = ctx->height;
+
+ if (ctx->src_num_planes == 1)
+ return bpl * h * 3 / 2;
+ return plane == 0 ? bpl * h : bpl * h / 2;
+}
+
+static u32 rkvenc_capture_size(struct rkvenc_ctx *ctx)
+{
+ if (ctx->codec == RKVENC_CODEC_H264) {
+ u32 mbw = ALIGN(ctx->width, 16) / 16;
+ u32 mbh = ALIGN(ctx->height, 16) / 16;
+
+ return min(mbw * mbh * 400, ctx->width * ctx->height / 2) +
+ RKVENC_HDR_MAX;
+ }
+ return ctx->width * ctx->height / 2 + RKVENC_HDR_MAX;
+}
+
+static int rkvenc_queue_setup(struct vb2_queue *vq, unsigned int *num_buffers,
+ unsigned int *num_planes, unsigned int sizes[],
+ struct device *alloc_devs[])
+{
+ struct rkvenc_ctx *ctx = vb2_get_drv_priv(vq);
+ const struct rkvenc_fmt *fmt;
+ unsigned int i;
+ u32 size;
+
+ if (V4L2_TYPE_IS_OUTPUT(vq->type)) {
+ fmt = rkvenc_find_fmt(rkvenc_output_fmts,
+ ARRAY_SIZE(rkvenc_output_fmts),
+ ctx->src_fourcc);
+ if (!fmt)
+ return -EINVAL;
+ *num_planes = fmt->num_planes;
+ for (i = 0; i < *num_planes; i++)
+ sizes[i] = rkvenc_output_plane_size(ctx, i);
+ return 0;
+ }
+
+ fmt = rkvenc_find_fmt(rkvenc_capture_fmts,
+ ARRAY_SIZE(rkvenc_capture_fmts),
+ ctx->coded_fourcc);
+ if (!fmt)
+ return -EINVAL;
+ size = rkvenc_capture_size(ctx);
+ *num_planes = 1;
+ if (*num_buffers)
+ sizes[0] = max(sizes[0], size);
+ else
+ sizes[0] = size;
+ return 0;
+}
+
+static int rkvenc_buf_prepare(struct vb2_buffer *vb)
+{
+ struct vb2_queue *vq = vb->vb2_queue;
+ struct rkvenc_ctx *ctx = vb2_get_drv_priv(vq);
+
+ if (V4L2_TYPE_IS_OUTPUT(vq->type)) {
+ if (vb2_plane_size(vb, 0) < rkvenc_output_plane_size(ctx, 0))
+ return -EINVAL;
+ } else {
+ if (vb2_plane_size(vb, 0) < rkvenc_capture_size(ctx))
+ return -EINVAL;
+ }
+ return 0;
+}
+
+static void rkvenc_buf_queue(struct vb2_buffer *vb)
+{
+ struct rkvenc_ctx *ctx = vb2_get_drv_priv(vb->vb2_queue);
+ struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb);
+
+ if (V4L2_TYPE_IS_CAPTURE(vb->vb2_queue->type)) {
+ if (vb2_is_streaming(vb->vb2_queue) &&
+ v4l2_m2m_dst_buf_is_last(ctx->m2m_ctx)) {
+ vb2_set_plane_payload(vb, 0, 0);
+ vbuf->field = V4L2_FIELD_NONE;
+ v4l2_m2m_last_buffer_done(ctx->m2m_ctx, vbuf);
+ return;
+ }
+
+ /* The parameter sets take a CAPTURE buffer of their own */
+ if (ctx->header_pending &&
+ ctx->c.header_mode ==
+ V4L2_MPEG_VIDEO_HEADER_MODE_SEPARATE) {
+ u8 *vaddr = vb2_plane_vaddr(vb, 0);
+
+ if (vaddr) {
+ rkvenc_ensure_header(ctx);
+ if (rkvenc_copy_headers(ctx, vaddr,
+ vb2_plane_size(vb, 0)) < 0)
+ vb2_set_plane_payload(vb, 0, 0);
+ else
+ vb2_set_plane_payload(vb, 0,
+ ctx->hdr_len);
+ } else {
+ vb2_set_plane_payload(vb, 0, 0);
+ }
+ vbuf->field = V4L2_FIELD_NONE;
+ v4l2_m2m_buf_done(vbuf, VB2_BUF_STATE_DONE);
+ ctx->header_pending = false;
+ return;
+ }
+ }
+
+ v4l2_m2m_buf_queue(ctx->m2m_ctx, vbuf);
+}
+
+static int rkvenc_buf_out_validate(struct vb2_buffer *vb)
+{
+ struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb);
+
+ vbuf->field = V4L2_FIELD_NONE;
+ return 0;
+}
+
+static void rkvenc_return_dst_bufs(struct rkvenc_ctx *ctx)
+{
+ struct vb2_v4l2_buffer *vbuf;
+
+ while ((vbuf = v4l2_m2m_dst_buf_remove(ctx->m2m_ctx)))
+ v4l2_m2m_buf_done(vbuf, VB2_BUF_STATE_QUEUED);
+}
+
+static int rkvenc_start_streaming(struct vb2_queue *q, unsigned int count)
+{
+ struct rkvenc_ctx *ctx = vb2_get_drv_priv(q);
+ int ret;
+
+ if (V4L2_TYPE_IS_OUTPUT(q->type))
+ return 0;
+
+ if (!ctx->coded_fourcc || !ctx->src_fourcc) {
+ rkvenc_return_dst_bufs(ctx);
+ return -EINVAL;
+ }
+
+ /* Hardware bug: H.264 at most 2 MBs wide and over 1 MB high hangs */
+ if (ctx->codec == RKVENC_CODEC_H264 && ALIGN(ctx->width, 16) <= 32 &&
+ ALIGN(ctx->height, 16) > 16) {
+ rkvenc_return_dst_bufs(ctx);
+ return -EINVAL;
+ }
+
+ rkvenc_update_blocks(ctx);
+ rkvenc_refresh_header(ctx);
+
+ ret = rkvenc_ref_alloc(ctx);
+ if (ret) {
+ rkvenc_return_dst_bufs(ctx);
+ return ret;
+ }
+
+ rkvenc_rc_init(ctx);
+ rkvenc_gop_init(ctx);
+ ctx->joined_header_done = false;
+ return 0;
+}
+
+static void rkvenc_stop_streaming(struct vb2_queue *q)
+{
+ struct rkvenc_ctx *ctx = vb2_get_drv_priv(q);
+ struct vb2_v4l2_buffer *vbuf;
+
+ v4l2_m2m_update_stop_streaming_state(ctx->m2m_ctx, q);
+
+ if (V4L2_TYPE_IS_OUTPUT(q->type)) {
+ while ((vbuf = v4l2_m2m_src_buf_remove(ctx->m2m_ctx)))
+ v4l2_m2m_buf_done(vbuf, VB2_BUF_STATE_ERROR);
+ } else {
+ while ((vbuf = v4l2_m2m_dst_buf_remove(ctx->m2m_ctx)))
+ v4l2_m2m_buf_done(vbuf, VB2_BUF_STATE_ERROR);
+
+ rkvenc_ref_free(ctx);
+ }
+}
+
+static const struct vb2_ops rkvenc_queue_ops = {
+ .queue_setup = rkvenc_queue_setup,
+ .buf_prepare = rkvenc_buf_prepare,
+ .buf_queue = rkvenc_buf_queue,
+ .buf_out_validate = rkvenc_buf_out_validate,
+ .start_streaming = rkvenc_start_streaming,
+ .stop_streaming = rkvenc_stop_streaming,
+};
+
+static void rkvenc_device_run(void *priv)
+{
+ struct rkvenc_ctx *ctx = priv;
+ struct rkvenc_dev *dev = ctx->dev;
+ struct vb2_v4l2_buffer *src, *dst;
+ struct rkvenc_frame f = {};
+ u8 qp;
+ bool idr;
+ u32 hdr_len = 0;
+ u8 *vaddr;
+ int ret;
+
+ dev->job_ctx = ctx;
+ dev->job_running = true;
+
+ src = v4l2_m2m_next_src_buf(ctx->m2m_ctx);
+ dst = v4l2_m2m_next_dst_buf(ctx->m2m_ctx);
+ if (!src || !dst)
+ goto finish_error;
+
+ idr = rkvenc_gop_is_idr(ctx);
+
+ if (idr)
+ ctx->gop.frames_since_idr = 0;
+
+ rkvenc_apply_ctrls(ctx);
+ rkvenc_rc_pick(ctx, &qp, idr ? 0 : 1);
+ ctx->cur_qp = qp;
+ ctx->cur_intra = idr;
+
+ if (idr)
+ rkvenc_refresh_header(ctx);
+
+ if (ctx->c.header_mode ==
+ V4L2_MPEG_VIDEO_HEADER_MODE_JOINED_WITH_1ST_FRAME &&
+ idr && (!ctx->joined_header_done || ctx->c.prepend_spspps)) {
+ vaddr = vb2_plane_vaddr(&dst->vb2_buf, 0);
+ if (vaddr) {
+ if (rkvenc_copy_headers(ctx, vaddr,
+ vb2_plane_size(&dst->vb2_buf, 0)) >= 0) {
+ hdr_len = ctx->hdr_len;
+ ctx->joined_header_done = true;
+ }
+ } else {
+ dev_warn_once(dev->dev,
+ "JOINED header needs an MMAP capture buffer\n");
+ }
+ }
+ ctx->cur_prepend = hdr_len;
+
+ f.codec = ctx->codec;
+ f.intra = idr;
+ f.referenced = true;
+ f.src.width = ctx->width;
+ f.src.height = ctx->height;
+ f.src.stride[0] = ctx->bytesperline;
+ f.src.stride[1] = ctx->bytesperline;
+ f.src.plane[0] = lower_32_bits(
+ vb2_dma_contig_plane_dma_addr(&src->vb2_buf, 0));
+ if (ctx->src_num_planes == 1)
+ f.src.plane[1] = f.src.plane[0] + ctx->bytesperline * ctx->height;
+ else
+ f.src.plane[1] = lower_32_bits(
+ vb2_dma_contig_plane_dma_addr(&src->vb2_buf, 1) +
+ src->vb2_buf.planes[1].data_offset);
+
+ f.bs.base = lower_32_bits(
+ vb2_dma_contig_plane_dma_addr(&dst->vb2_buf, 0));
+ f.bs.size = vb2_plane_size(&dst->vb2_buf, 0);
+ f.bs.offset = hdr_len;
+
+ f.recon = ctx->slot[ctx->cur];
+ f.ref = idr ? ctx->slot[ctx->cur] : ctx->slot[ctx->last];
+
+ f.qp = qp;
+ f.qp_min = ctx->c.min_qp;
+ f.qp_max = ctx->c.max_qp;
+ f.rc = NULL;
+ f.wdg_vs_load = 0;
+
+ if (f.codec == RKVENC_CODEC_H264)
+ rkvenc_h264_build_pic(ctx, &f.h264, idr);
+ else
+ rkvenc_hevc_build_pic(ctx, &f.hevc, idr);
+
+ ret = pm_runtime_resume_and_get(dev->dev);
+ if (ret)
+ goto finish_error;
+
+ ret = rkvenc_hw_run(dev, &f);
+ if (ret) {
+ pm_runtime_put(dev->dev);
+ goto finish_error;
+ }
+ return;
+
+finish_error:
+ dev->job_running = false;
+ dev->job_ctx = NULL;
+ if (src && dst)
+ v4l2_m2m_buf_done_and_job_finish(dev->m2m_dev, ctx->m2m_ctx,
+ VB2_BUF_STATE_ERROR);
+ else
+ v4l2_m2m_job_finish(dev->m2m_dev, ctx->m2m_ctx);
+}
+
+const struct v4l2_m2m_ops rkvenc_m2m_ops = {
+ .device_run = rkvenc_device_run,
+};
+
+static int rkvenc_s_ctrl(struct v4l2_ctrl *ctrl)
+{
+ struct rkvenc_ctx *ctx =
+ container_of(ctrl->handler, struct rkvenc_ctx, ctrl_hdl);
+
+ switch (ctrl->id) {
+ case V4L2_CID_MPEG_VIDEO_FORCE_KEY_FRAME:
+ rkvenc_gop_force_keyframe(ctx);
+ break;
+ case V4L2_CID_MPEG_VIDEO_HEADER_MODE:
+ if (ctrl->val == V4L2_MPEG_VIDEO_HEADER_MODE_SEPARATE)
+ ctx->header_pending = true;
+ break;
+ default:
+ break;
+ }
+ return 0;
+}
+
+static const struct v4l2_ctrl_ops rkvenc_ctrl_ops = {
+ .s_ctrl = rkvenc_s_ctrl,
+};
+
+static int rkvenc_init_ctrls(struct rkvenc_ctx *ctx)
+{
+ struct v4l2_ctrl_handler *hdl = &ctx->ctrl_hdl;
+
+ v4l2_ctrl_handler_init(hdl, 48);
+
+ v4l2_ctrl_new_std_menu(hdl, &rkvenc_ctrl_ops,
+ V4L2_CID_MPEG_VIDEO_BITRATE_MODE,
+ V4L2_MPEG_VIDEO_BITRATE_MODE_CBR,
+ ~(1 << V4L2_MPEG_VIDEO_BITRATE_MODE_CBR),
+ V4L2_MPEG_VIDEO_BITRATE_MODE_CBR);
+ v4l2_ctrl_new_std(hdl, &rkvenc_ctrl_ops, V4L2_CID_MPEG_VIDEO_BITRATE,
+ 25600, 40 * 1000 * 1000, 1, 4 * 1000 * 1000);
+ v4l2_ctrl_new_std(hdl, &rkvenc_ctrl_ops,
+ V4L2_CID_MPEG_VIDEO_FRAME_RC_ENABLE, 0, 1, 1, 1);
+ v4l2_ctrl_new_std(hdl, &rkvenc_ctrl_ops, V4L2_CID_MPEG_VIDEO_VBV_SIZE,
+ 0, 1 << 24, 1, 0);
+
+ v4l2_ctrl_new_std(hdl, &rkvenc_ctrl_ops, V4L2_CID_MPEG_VIDEO_GOP_SIZE,
+ 0, 300, 1, 30);
+ v4l2_ctrl_new_std(hdl, &rkvenc_ctrl_ops,
+ V4L2_CID_MPEG_VIDEO_H264_I_PERIOD, 0, 300, 1, 30);
+ v4l2_ctrl_new_std(hdl, &rkvenc_ctrl_ops,
+ V4L2_CID_MPEG_VIDEO_HEVC_REFRESH_PERIOD, 0, 300, 1, 30);
+ v4l2_ctrl_new_std_menu(hdl, &rkvenc_ctrl_ops,
+ V4L2_CID_MPEG_VIDEO_HEVC_REFRESH_TYPE,
+ V4L2_MPEG_VIDEO_HEVC_REFRESH_IDR,
+ ~(1 << V4L2_MPEG_VIDEO_HEVC_REFRESH_IDR),
+ V4L2_MPEG_VIDEO_HEVC_REFRESH_IDR);
+ v4l2_ctrl_new_std(hdl, &rkvenc_ctrl_ops,
+ V4L2_CID_MPEG_VIDEO_FORCE_KEY_FRAME, 0, 0, 0, 0);
+
+ v4l2_ctrl_new_std_menu(hdl, &rkvenc_ctrl_ops,
+ V4L2_CID_MPEG_VIDEO_HEADER_MODE,
+ V4L2_MPEG_VIDEO_HEADER_MODE_JOINED_WITH_1ST_FRAME,
+ 0,
+ V4L2_MPEG_VIDEO_HEADER_MODE_JOINED_WITH_1ST_FRAME);
+ v4l2_ctrl_new_std(hdl, &rkvenc_ctrl_ops,
+ V4L2_CID_MPEG_VIDEO_PREPEND_SPSPPS_TO_IDR,
+ 0, 1, 1, 0);
+
+ v4l2_ctrl_new_std_menu(hdl, &rkvenc_ctrl_ops,
+ V4L2_CID_MPEG_VIDEO_H264_PROFILE,
+ V4L2_MPEG_VIDEO_H264_PROFILE_HIGH,
+ ~((1 << V4L2_MPEG_VIDEO_H264_PROFILE_BASELINE) |
+ (1 << V4L2_MPEG_VIDEO_H264_PROFILE_MAIN) |
+ (1 << V4L2_MPEG_VIDEO_H264_PROFILE_HIGH)),
+ V4L2_MPEG_VIDEO_H264_PROFILE_HIGH);
+ v4l2_ctrl_new_std_menu(hdl, &rkvenc_ctrl_ops,
+ V4L2_CID_MPEG_VIDEO_H264_LEVEL,
+ V4L2_MPEG_VIDEO_H264_LEVEL_5_2,
+ 0, V4L2_MPEG_VIDEO_H264_LEVEL_4_0);
+ v4l2_ctrl_new_std_menu(hdl, &rkvenc_ctrl_ops,
+ V4L2_CID_MPEG_VIDEO_H264_ENTROPY_MODE,
+ V4L2_MPEG_VIDEO_H264_ENTROPY_MODE_CABAC, 0,
+ V4L2_MPEG_VIDEO_H264_ENTROPY_MODE_CABAC);
+ v4l2_ctrl_new_std(hdl, &rkvenc_ctrl_ops,
+ V4L2_CID_MPEG_VIDEO_H264_8X8_TRANSFORM,
+ 0, 1, 1, 1);
+ v4l2_ctrl_new_std_menu(
+ hdl, &rkvenc_ctrl_ops,
+ V4L2_CID_MPEG_VIDEO_H264_LOOP_FILTER_MODE,
+ V4L2_MPEG_VIDEO_H264_LOOP_FILTER_MODE_DISABLED_AT_SLICE_BOUNDARY,
+ 0, V4L2_MPEG_VIDEO_H264_LOOP_FILTER_MODE_ENABLED);
+ v4l2_ctrl_new_std(hdl, &rkvenc_ctrl_ops,
+ V4L2_CID_MPEG_VIDEO_H264_LOOP_FILTER_ALPHA,
+ -6, 6, 1, 0);
+ v4l2_ctrl_new_std(hdl, &rkvenc_ctrl_ops,
+ V4L2_CID_MPEG_VIDEO_H264_LOOP_FILTER_BETA,
+ -6, 6, 1, 0);
+ v4l2_ctrl_new_std(hdl, &rkvenc_ctrl_ops,
+ V4L2_CID_MPEG_VIDEO_H264_CHROMA_QP_INDEX_OFFSET,
+ -12, 12, 1, 0);
+
+ v4l2_ctrl_new_std_menu(hdl, &rkvenc_ctrl_ops,
+ V4L2_CID_MPEG_VIDEO_HEVC_PROFILE,
+ V4L2_MPEG_VIDEO_HEVC_PROFILE_MAIN, 0,
+ V4L2_MPEG_VIDEO_HEVC_PROFILE_MAIN);
+ v4l2_ctrl_new_std_menu(hdl, &rkvenc_ctrl_ops,
+ V4L2_CID_MPEG_VIDEO_HEVC_LEVEL,
+ V4L2_MPEG_VIDEO_HEVC_LEVEL_5_2,
+ 0, V4L2_MPEG_VIDEO_HEVC_LEVEL_4);
+ v4l2_ctrl_new_std_menu(hdl, &rkvenc_ctrl_ops,
+ V4L2_CID_MPEG_VIDEO_HEVC_TIER,
+ V4L2_MPEG_VIDEO_HEVC_TIER_HIGH,
+ ~(1 << V4L2_MPEG_VIDEO_HEVC_TIER_MAIN),
+ V4L2_MPEG_VIDEO_HEVC_TIER_MAIN);
+ v4l2_ctrl_new_std_menu(hdl, &rkvenc_ctrl_ops,
+ V4L2_CID_MPEG_VIDEO_HEVC_LOOP_FILTER_MODE,
+ V4L2_MPEG_VIDEO_HEVC_LOOP_FILTER_MODE_ENABLED,
+ 0,
+ V4L2_MPEG_VIDEO_HEVC_LOOP_FILTER_MODE_ENABLED);
+ v4l2_ctrl_new_std(hdl, &rkvenc_ctrl_ops,
+ V4L2_CID_MPEG_VIDEO_HEVC_LF_BETA_OFFSET_DIV2,
+ -6, 6, 1, 0);
+ v4l2_ctrl_new_std(hdl, &rkvenc_ctrl_ops,
+ V4L2_CID_MPEG_VIDEO_HEVC_LF_TC_OFFSET_DIV2,
+ -6, 6, 1, 0);
+ v4l2_ctrl_new_std(hdl, &rkvenc_ctrl_ops,
+ V4L2_CID_MPEG_VIDEO_HEVC_STRONG_SMOOTHING,
+ 0, 1, 1, 1);
+ v4l2_ctrl_new_std(hdl, &rkvenc_ctrl_ops,
+ V4L2_CID_MPEG_VIDEO_HEVC_TMV_PREDICTION,
+ 0, 1, 1, 0);
+ v4l2_ctrl_new_std(hdl, &rkvenc_ctrl_ops,
+ V4L2_CID_MPEG_VIDEO_HEVC_MAX_NUM_MERGE_MV_MINUS1,
+ 0, 4, 1, 2);
+
+ v4l2_ctrl_new_std(hdl, &rkvenc_ctrl_ops, V4L2_CID_MPEG_VIDEO_H264_MIN_QP,
+ 1, 51, 1, 1);
+ v4l2_ctrl_new_std(hdl, &rkvenc_ctrl_ops, V4L2_CID_MPEG_VIDEO_H264_MAX_QP,
+ 1, 51, 1, 51);
+ v4l2_ctrl_new_std(hdl, &rkvenc_ctrl_ops,
+ V4L2_CID_MPEG_VIDEO_H264_I_FRAME_QP, 1, 51, 1, 26);
+ v4l2_ctrl_new_std(hdl, &rkvenc_ctrl_ops,
+ V4L2_CID_MPEG_VIDEO_H264_P_FRAME_QP, 1, 51, 1, 28);
+ v4l2_ctrl_new_std(hdl, &rkvenc_ctrl_ops, V4L2_CID_MPEG_VIDEO_HEVC_MIN_QP,
+ 1, 51, 1, 1);
+ v4l2_ctrl_new_std(hdl, &rkvenc_ctrl_ops, V4L2_CID_MPEG_VIDEO_HEVC_MAX_QP,
+ 1, 51, 1, 51);
+ v4l2_ctrl_new_std(hdl, &rkvenc_ctrl_ops,
+ V4L2_CID_MPEG_VIDEO_HEVC_I_FRAME_QP, 1, 51, 1, 26);
+ v4l2_ctrl_new_std(hdl, &rkvenc_ctrl_ops,
+ V4L2_CID_MPEG_VIDEO_HEVC_P_FRAME_QP, 1, 51, 1, 28);
+
+ v4l2_ctrl_new_std(hdl, &rkvenc_ctrl_ops,
+ V4L2_CID_MPEG_VIDEO_AVERAGE_QP, 0, 51, 1, 0);
+
+ if (hdl->error)
+ return hdl->error;
+
+ v4l2_ctrl_handler_setup(hdl);
+
+ ctx->header_pending =
+ rkvenc_ctrl_val(ctx, V4L2_CID_MPEG_VIDEO_HEADER_MODE) ==
+ V4L2_MPEG_VIDEO_HEADER_MODE_SEPARATE;
+
+ ctx->fh.ctrl_handler = hdl;
+ return 0;
+}
+
+static int rkvenc_querycap(struct file *file, void *priv,
+ struct v4l2_capability *cap)
+{
+ struct rkvenc_ctx *ctx = fh_to_rkvenc_ctx(file->private_data);
+ struct rkvenc_dev *dev = ctx->dev;
+
+ strscpy(cap->driver, "rkvenc", sizeof(cap->driver));
+ strscpy(cap->card, "rockchip-rkvenc", sizeof(cap->card));
+ snprintf(cap->bus_info, sizeof(cap->bus_info), "platform:%s",
+ dev_name(dev->dev));
+
+ return 0;
+}
+
+static int rkvenc_enum_fmt(struct file *file, void *priv,
+ struct v4l2_fmtdesc *f, bool capture)
+{
+ const struct rkvenc_fmt *fmts = capture ? rkvenc_capture_fmts
+ : rkvenc_output_fmts;
+ unsigned int n = capture ? ARRAY_SIZE(rkvenc_capture_fmts)
+ : ARRAY_SIZE(rkvenc_output_fmts);
+
+ if (f->index >= n)
+ return -EINVAL;
+ f->pixelformat = fmts[f->index].fourcc;
+ f->flags = 0;
+ return 0;
+}
+
+static int rkvenc_enum_fmt_vid_out(struct file *file, void *priv,
+ struct v4l2_fmtdesc *f)
+{
+ return rkvenc_enum_fmt(file, priv, f, false);
+}
+
+static int rkvenc_enum_fmt_vid_cap(struct file *file, void *priv,
+ struct v4l2_fmtdesc *f)
+{
+ return rkvenc_enum_fmt(file, priv, f, true);
+}
+
+static void rkvenc_try_fmt_vid_out(struct rkvenc_ctx *ctx, struct v4l2_format *f)
+{
+ struct v4l2_pix_format_mplane *pix = &f->fmt.pix_mp;
+ const struct rkvenc_fmt *fmt;
+ u32 bpl;
+
+ fmt = rkvenc_find_fmt(rkvenc_output_fmts,
+ ARRAY_SIZE(rkvenc_output_fmts),
+ pix->pixelformat);
+ if (!fmt) {
+ fmt = &rkvenc_output_fmts[0];
+ pix->pixelformat = fmt->fourcc;
+ }
+
+ pix->field = V4L2_FIELD_NONE;
+ pix->width = clamp(round_up(pix->width, 2), 16, ctx->dev->max_width);
+ pix->height = clamp(round_up(pix->height, 2), 16, ctx->dev->max_height);
+ pix->num_planes = fmt->num_planes;
+
+ bpl = ALIGN(pix->width, 16);
+ pix->plane_fmt[0].bytesperline = bpl;
+ if (fmt->num_planes == 2) {
+ pix->plane_fmt[0].sizeimage = bpl * pix->height;
+ pix->plane_fmt[1].bytesperline = bpl;
+ pix->plane_fmt[1].sizeimage = bpl * pix->height / 2;
+ } else {
+ pix->plane_fmt[0].sizeimage = bpl * pix->height * 3 / 2;
+ }
+}
+
+static void rkvenc_try_fmt_vid_cap(struct rkvenc_ctx *ctx, struct v4l2_format *f)
+{
+ struct v4l2_pix_format_mplane *pix = &f->fmt.pix_mp;
+ const struct rkvenc_fmt *fmt;
+
+ fmt = rkvenc_find_fmt(rkvenc_capture_fmts,
+ ARRAY_SIZE(rkvenc_capture_fmts),
+ pix->pixelformat);
+ if (!fmt) {
+ fmt = &rkvenc_capture_fmts[0];
+ pix->pixelformat = fmt->fourcc;
+ }
+
+ pix->field = V4L2_FIELD_NONE;
+ pix->width = clamp(round_up(pix->width, 2), 16, ctx->dev->max_width);
+ pix->height = clamp(round_up(pix->height, 2), 16, ctx->dev->max_height);
+ pix->num_planes = 1;
+ pix->plane_fmt[0].bytesperline = 0;
+
+ if (fmt->fourcc == V4L2_PIX_FMT_H264) {
+ u32 mbw = ALIGN(pix->width, 16) / 16;
+ u32 mbh = ALIGN(pix->height, 16) / 16;
+
+ pix->plane_fmt[0].sizeimage =
+ min(mbw * mbh * 400, pix->width * pix->height / 2) +
+ RKVENC_HDR_MAX;
+ } else {
+ pix->plane_fmt[0].sizeimage =
+ pix->width * pix->height / 2 + RKVENC_HDR_MAX;
+ }
+}
+
+static int rkvenc_try_fmt_vid_out_ioctl(struct file *file, void *priv,
+ struct v4l2_format *f)
+{
+ struct rkvenc_ctx *ctx = fh_to_rkvenc_ctx(file->private_data);
+
+ rkvenc_try_fmt_vid_out(ctx, f);
+ return 0;
+}
+
+static int rkvenc_try_fmt_vid_cap_ioctl(struct file *file, void *priv,
+ struct v4l2_format *f)
+{
+ struct rkvenc_ctx *ctx = fh_to_rkvenc_ctx(file->private_data);
+
+ rkvenc_try_fmt_vid_cap(ctx, f);
+ return 0;
+}
+
+static int rkvenc_s_fmt_vid_out(struct file *file, void *priv,
+ struct v4l2_format *f)
+{
+ struct rkvenc_ctx *ctx = fh_to_rkvenc_ctx(file->private_data);
+ struct vb2_queue *vq = v4l2_m2m_get_vq(ctx->m2m_ctx, f->type);
+
+ if (vb2_is_busy(vq))
+ return -EBUSY;
+
+ rkvenc_try_fmt_vid_out(ctx, f);
+ ctx->src_fourcc = f->fmt.pix_mp.pixelformat;
+ ctx->src_num_planes = f->fmt.pix_mp.num_planes;
+ ctx->width = f->fmt.pix_mp.width;
+ ctx->height = f->fmt.pix_mp.height;
+ ctx->bytesperline = f->fmt.pix_mp.plane_fmt[0].bytesperline;
+ return 0;
+}
+
+static int rkvenc_s_fmt_vid_cap(struct file *file, void *priv,
+ struct v4l2_format *f)
+{
+ struct rkvenc_ctx *ctx = fh_to_rkvenc_ctx(file->private_data);
+ struct vb2_queue *vq = v4l2_m2m_get_vq(ctx->m2m_ctx, f->type);
+
+ if (vb2_is_busy(vq))
+ return -EBUSY;
+
+ rkvenc_try_fmt_vid_cap(ctx, f);
+ ctx->coded_fourcc = f->fmt.pix_mp.pixelformat;
+ ctx->codec = rkvenc_is_h264(f->fmt.pix_mp.pixelformat) ?
+ RKVENC_CODEC_H264 : RKVENC_CODEC_HEVC;
+ return 0;
+}
+
+static int rkvenc_g_fmt(struct file *file, void *priv, struct v4l2_format *f)
+{
+ struct rkvenc_ctx *ctx = fh_to_rkvenc_ctx(file->private_data);
+ struct v4l2_pix_format_mplane *pix = &f->fmt.pix_mp;
+
+ if (V4L2_TYPE_IS_OUTPUT(f->type)) {
+ pix->pixelformat = ctx->src_fourcc;
+ pix->num_planes = ctx->src_num_planes;
+ } else {
+ pix->pixelformat = ctx->coded_fourcc;
+ pix->num_planes = 1;
+ }
+ pix->width = ctx->width;
+ pix->height = ctx->height;
+ pix->field = V4L2_FIELD_NONE;
+
+ if (V4L2_TYPE_IS_OUTPUT(f->type)) {
+ pix->plane_fmt[0].bytesperline = ctx->bytesperline;
+ pix->plane_fmt[0].sizeimage =
+ rkvenc_output_plane_size(ctx, 0);
+ if (ctx->src_num_planes == 2) {
+ pix->plane_fmt[1].bytesperline = ctx->bytesperline;
+ pix->plane_fmt[1].sizeimage =
+ rkvenc_output_plane_size(ctx, 1);
+ }
+ } else {
+ pix->plane_fmt[0].sizeimage = rkvenc_capture_size(ctx);
+ }
+ return 0;
+}
+
+static int rkvenc_s_parm(struct file *file, void *priv,
+ struct v4l2_streamparm *a)
+{
+ struct rkvenc_ctx *ctx = fh_to_rkvenc_ctx(file->private_data);
+ struct v4l2_fract *tpf = &a->parm.output.timeperframe;
+ unsigned int fps;
+
+ if (!V4L2_TYPE_IS_MULTIPLANAR(a->type))
+ return -EINVAL;
+
+ if (tpf->numerator == 0 || tpf->denominator == 0) {
+ tpf->numerator = 1;
+ tpf->denominator = 30;
+ }
+ fps = tpf->denominator / tpf->numerator;
+ if (fps < 1)
+ fps = 1;
+ ctx->fps = fps;
+ a->parm.output.capability = V4L2_CAP_TIMEPERFRAME;
+ return 0;
+}
+
+static int rkvenc_g_parm(struct file *file, void *priv,
+ struct v4l2_streamparm *a)
+{
+ struct rkvenc_ctx *ctx = fh_to_rkvenc_ctx(file->private_data);
+
+ if (!V4L2_TYPE_IS_MULTIPLANAR(a->type))
+ return -EINVAL;
+
+ a->parm.output.capability = V4L2_CAP_TIMEPERFRAME;
+ a->parm.output.timeperframe.numerator = 1;
+ a->parm.output.timeperframe.denominator = ctx->fps ?: 30;
+ return 0;
+}
+
+static int rkvenc_vidioc_encoder_cmd(struct file *file, void *priv,
+ struct v4l2_encoder_cmd *cmd)
+{
+ if (cmd->cmd != V4L2_ENC_CMD_STOP && cmd->cmd != V4L2_ENC_CMD_START)
+ return -EINVAL;
+ return v4l2_m2m_ioctl_encoder_cmd(file, priv, cmd);
+}
+
+static int rkvenc_queue_init(void *priv, struct vb2_queue *src_vq,
+ struct vb2_queue *dst_vq)
+{
+ struct rkvenc_ctx *ctx = priv;
+ struct rkvenc_dev *dev = ctx->dev;
+ int ret;
+
+ src_vq->type = V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE;
+ src_vq->io_modes = VB2_MMAP | VB2_DMABUF;
+ src_vq->drv_priv = ctx;
+ src_vq->ops = &rkvenc_queue_ops;
+ src_vq->mem_ops = &vb2_dma_contig_memops;
+ src_vq->dma_attrs = 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 = &dev->vdev_lock;
+ src_vq->dev = 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_DMABUF;
+ dst_vq->drv_priv = ctx;
+ dst_vq->ops = &rkvenc_queue_ops;
+ dst_vq->mem_ops = &vb2_dma_contig_memops;
+ dst_vq->buf_struct_size = sizeof(struct v4l2_m2m_buffer);
+ dst_vq->timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_COPY;
+ dst_vq->lock = &dev->vdev_lock;
+ dst_vq->dev = dev->v4l2_dev.dev;
+ return vb2_queue_init(dst_vq);
+}
+
+static int rkvenc_open(struct file *filp)
+{
+ struct rkvenc_dev *dev = video_drvdata(filp);
+ struct rkvenc_ctx *ctx;
+ int ret;
+
+ ctx = kzalloc_obj(*ctx);
+ if (!ctx)
+ return -ENOMEM;
+
+ ctx->dev = dev;
+ ctx->codec = RKVENC_CODEC_H264;
+ ctx->coded_fourcc = V4L2_PIX_FMT_H264;
+ ctx->src_fourcc = V4L2_PIX_FMT_NV12;
+ ctx->src_num_planes = 1;
+ ctx->width = 1920;
+ ctx->height = 1080;
+ ctx->bytesperline = ALIGN(1920, 16);
+ ctx->fps = 30;
+ mutex_init(&ctx->vlock);
+
+ v4l2_fh_init(&ctx->fh, video_devdata(filp));
+ filp->private_data = &ctx->fh;
+
+ ctx->m2m_ctx = v4l2_m2m_ctx_init(dev->m2m_dev, ctx, rkvenc_queue_init);
+ if (IS_ERR(ctx->m2m_ctx)) {
+ ret = PTR_ERR(ctx->m2m_ctx);
+ goto err_fh;
+ }
+ ctx->fh.m2m_ctx = ctx->m2m_ctx;
+
+ ret = rkvenc_init_ctrls(ctx);
+ if (ret)
+ goto err_m2m;
+
+ v4l2_fh_add(&ctx->fh, filp);
+ return 0;
+
+err_m2m:
+ v4l2_m2m_ctx_release(ctx->m2m_ctx);
+err_fh:
+ v4l2_fh_exit(&ctx->fh);
+ kfree(ctx);
+ return ret;
+}
+
+static int rkvenc_release(struct file *filp)
+{
+ struct rkvenc_ctx *ctx = fh_to_rkvenc_ctx(filp->private_data);
+
+ v4l2_m2m_ctx_release(ctx->m2m_ctx);
+ v4l2_ctrl_handler_free(&ctx->ctrl_hdl);
+ v4l2_fh_del(&ctx->fh, filp);
+ v4l2_fh_exit(&ctx->fh);
+ kfree(ctx);
+ return 0;
+}
+
+static const struct v4l2_ioctl_ops rkvenc_v4l2_ioctl_ops = {
+ .vidioc_querycap = rkvenc_querycap,
+
+ .vidioc_enum_fmt_vid_cap = rkvenc_enum_fmt_vid_cap,
+ .vidioc_enum_fmt_vid_out = rkvenc_enum_fmt_vid_out,
+ .vidioc_try_fmt_vid_cap_mplane = rkvenc_try_fmt_vid_cap_ioctl,
+ .vidioc_try_fmt_vid_out_mplane = rkvenc_try_fmt_vid_out_ioctl,
+ .vidioc_s_fmt_vid_cap_mplane = rkvenc_s_fmt_vid_cap,
+ .vidioc_s_fmt_vid_out_mplane = rkvenc_s_fmt_vid_out,
+ .vidioc_g_fmt_vid_cap_mplane = rkvenc_g_fmt,
+ .vidioc_g_fmt_vid_out_mplane = rkvenc_g_fmt,
+
+ .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_expbuf = v4l2_m2m_ioctl_expbuf,
+ .vidioc_create_bufs = v4l2_m2m_ioctl_create_bufs,
+ .vidioc_prepare_buf = v4l2_m2m_ioctl_prepare_buf,
+ .vidioc_streamon = v4l2_m2m_ioctl_streamon,
+ .vidioc_streamoff = v4l2_m2m_ioctl_streamoff,
+
+ .vidioc_s_parm = rkvenc_s_parm,
+ .vidioc_g_parm = rkvenc_g_parm,
+
+ .vidioc_encoder_cmd = rkvenc_vidioc_encoder_cmd,
+ .vidioc_try_encoder_cmd = v4l2_m2m_ioctl_try_encoder_cmd,
+
+ .vidioc_subscribe_event = v4l2_ctrl_subscribe_event,
+ .vidioc_unsubscribe_event = v4l2_event_unsubscribe,
+};
+
+static const struct v4l2_file_operations rkvenc_v4l2_fops = {
+ .owner = THIS_MODULE,
+ .open = rkvenc_open,
+ .release = rkvenc_release,
+ .poll = v4l2_m2m_fop_poll,
+ .unlocked_ioctl = video_ioctl2,
+ .mmap = v4l2_m2m_fop_mmap,
+};
+
+int rkvenc_v4l2_init(struct rkvenc_dev *dev)
+{
+ struct video_device *vdev = &dev->vdev;
+ int ret;
+
+ dev->mdev.dev = dev->dev;
+ strscpy(dev->mdev.model, "rkvenc", sizeof(dev->mdev.model));
+ strscpy(dev->mdev.bus_info, "platform:rkvenc",
+ sizeof(dev->mdev.bus_info));
+ media_device_init(&dev->mdev);
+ dev->v4l2_dev.mdev = &dev->mdev;
+
+ vdev->lock = &dev->vdev_lock;
+ vdev->v4l2_dev = &dev->v4l2_dev;
+ vdev->fops = &rkvenc_v4l2_fops;
+ vdev->release = video_device_release_empty;
+ vdev->vfl_dir = VFL_DIR_M2M;
+ vdev->device_caps = V4L2_CAP_STREAMING | V4L2_CAP_VIDEO_M2M_MPLANE;
+ vdev->ioctl_ops = &rkvenc_v4l2_ioctl_ops;
+ video_set_drvdata(vdev, dev);
+ strscpy(vdev->name, "rkvenc", sizeof(vdev->name));
+
+ ret = video_register_device(vdev, VFL_TYPE_VIDEO, -1);
+ if (ret) {
+ v4l2_err(&dev->v4l2_dev, "Failed to register video device\n");
+ goto err_cleanup_mc;
+ }
+
+ ret = v4l2_m2m_register_media_controller(dev->m2m_dev, vdev,
+ MEDIA_ENT_F_PROC_VIDEO_ENCODER);
+ if (ret) {
+ v4l2_err(&dev->v4l2_dev, "Failed to init M2M media controller\n");
+ goto err_unregister_vdev;
+ }
+
+ ret = media_device_register(&dev->mdev);
+ if (ret) {
+ v4l2_err(&dev->v4l2_dev, "Failed to register media device\n");
+ goto err_unregister_mc;
+ }
+
+ return 0;
+
+err_unregister_mc:
+ v4l2_m2m_unregister_media_controller(dev->m2m_dev);
+err_unregister_vdev:
+ video_unregister_device(vdev);
+err_cleanup_mc:
+ media_device_cleanup(&dev->mdev);
+ return ret;
+}
+
+void rkvenc_v4l2_cleanup(struct rkvenc_dev *dev)
+{
+ media_device_unregister(&dev->mdev);
+ v4l2_m2m_unregister_media_controller(dev->m2m_dev);
+ video_unregister_device(&dev->vdev);
+ media_device_cleanup(&dev->mdev);
+}
diff --git a/drivers/media/platform/rockchip/rkvenc/rkvenc-vepu541-h264.c b/drivers/media/platform/rockchip/rkvenc/rkvenc-vepu541-h264.c
new file mode 100644
index 0000000000000..202391672f2c7
--- /dev/null
+++ b/drivers/media/platform/rockchip/rkvenc/rkvenc-vepu541-h264.c
@@ -0,0 +1,206 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * RKVENC v1 register image builder, H.264 part.
+ */
+
+#include "rkvenc-vepu541.h"
+
+#define H264_PROFILE_MAIN 77
+#define H264_LEVEL_1 10
+#define H264_LEVEL_3 30
+#define H264_NAL_SLICE 1
+#define H264_NAL_IDR 5
+#define H264_SLICE_P 0
+#define H264_SLICE_I 2
+
+/* Coarse-ME RAM height by MB-aligned width */
+static const u16 h264_rama_h_width[] = {
+ 3584, 3136, 2816, 2560, 2368, 2176, 2048, 1856, 1792,
+};
+
+static void h264_me_build(const struct rkvenc_frame *f, struct rkvenc_img *img)
+{
+ const struct rkvenc_h264_seq *seq = f->h264.seq;
+ u32 w16 = ALIGN(f->src.width, 16), h16 = ALIGN(f->src.height, 16);
+ u32 mbw = w16 / 16, w64 = DIV_ROUND_UP(w16, 64);
+ u32 srch_h, srch_v, rama_h, v, hc, i;
+ u32 ah64 = ALIGN(h16, 64);
+
+ srch_h = mbw < 22 ? mbw / 2 : 11;
+ srch_v = seq->level_idc <= H264_LEVEL_1 ? 3 : 7;
+ if (ah64 <= 64)
+ srch_v = 0;
+ else if ((ah64 == 128 || ah64 == 192) && seq->level_idc > H264_LEVEL_1)
+ srch_v = 4;
+
+ rkvenc_img_l1(img, RKVENC_ME_RNGE,
+ FIELD_PREP(RKVENC_ME_RNGE_CME_SRCH_H, srch_h) |
+ FIELD_PREP(RKVENC_ME_RNGE_CME_SRCH_V, srch_v) |
+ FIELD_PREP(RKVENC_ME_RNGE_RME_SRCH_H, 7) |
+ FIELD_PREP(RKVENC_ME_RNGE_RME_SRCH_V, 5));
+
+ rkvenc_img_l1(img, RKVENC_ME_CNST,
+ FIELD_PREP(RKVENC_ME_CNST_PMV_MDST_H, f->intra ? 0 : 5) |
+ FIELD_PREP(RKVENC_ME_CNST_PMV_MDST_V, f->intra ? 0 : 5) |
+ FIELD_PREP(RKVENC_ME_CNST_MV_LIMIT, 2) |
+ FIELD_PREP(RKVENC_ME_CNST_PMV_NUM, 2));
+
+ rama_h = 17;
+ for (i = 0; i < ARRAY_SIZE(h264_rama_h_width); i++) {
+ if (w16 > h264_rama_h_width[i]) {
+ rama_h = 8 + i;
+ break;
+ }
+ }
+ v = 2 * srch_v + 1;
+ hc = (srch_h * 4 + 15) / 16 * 2 + 1;
+ rkvenc_img_l1(img, RKVENC_ME_RAM,
+ FIELD_PREP(RKVENC_ME_RAM_CME_RAMA_MAX,
+ (min(v, rama_h) - 1) * w64 + hc) |
+ FIELD_PREP(RKVENC_ME_RAM_CME_RAMA_H, rama_h));
+}
+
+static void h264_syntax_build(const struct rkvenc_frame *f,
+ struct rkvenc_img *img)
+{
+ const struct rkvenc_h264_seq *seq = f->h264.seq;
+ const struct rkvenc_h264_pic *pic = &f->h264;
+ u32 rdo, sli2;
+
+ rdo = RKVENC_H264_RDO_INTER_4X4 | RKVENC_H264_RDO_CHRM_SPCL |
+ RKVENC_H264_RDO_CCWA_E | RKVENC_H264_RDO_ATR_E;
+ if (seq->profile_idc < H264_PROFILE_MAIN && seq->level_idc <= H264_LEVEL_3)
+ rdo |= RKVENC_H264_RDO_RECT_SIZE;
+ if (seq->profile_idc < H264_PROFILE_MAIN && !seq->cabac)
+ rdo |= RKVENC_H264_RDO_VLC_LMT;
+ rkvenc_img_l1(img, RKVENC_RDO_CFG, rdo);
+ rkvenc_img_l1(img, RKVENC_IPRD_CSTS,
+ FIELD_PREP(RKVENC_IPRD_CSTS_VTHD_Y, 2501) |
+ FIELD_PREP(RKVENC_IPRD_CSTS_VTHD_C, 2501));
+
+ rkvenc_img_l1(img, RKVENC_SYNT_NAL,
+ FIELD_PREP(RKVENC_H264_NAL_REF_IDC, pic->nal_ref_idc) |
+ FIELD_PREP(RKVENC_H264_NAL_UNIT_TYPE,
+ f->intra ? H264_NAL_IDR : H264_NAL_SLICE));
+ rkvenc_img_l1(img, RKVENC_SYNT_SPS,
+ FIELD_PREP(RKVENC_H264_SPS_MAX_FNUM,
+ seq->log2_max_frame_num_m4) |
+ RKVENC_H264_SPS_DRCT_8X8 |
+ FIELD_PREP(RKVENC_H264_SPS_MPOC_LM4,
+ seq->log2_max_poc_lsb_m4));
+ rkvenc_img_l1(
+ img, RKVENC_SYNT_PPS,
+ (seq->cabac ? RKVENC_H264_PPS_ETPY_MODE : 0) |
+ (seq->transform_8x8 ? RKVENC_H264_PPS_TRNS_8X8 : 0) |
+ FIELD_PREP(RKVENC_PPS_PIC_INIT_QP, seq->pic_init_qp) |
+ FIELD_PREP(RKVENC_H264_PPS_CB_OFST,
+ rkvenc_s2u(seq->chroma_qp_offset, 5)) |
+ FIELD_PREP(RKVENC_H264_PPS_CR_OFST,
+ rkvenc_s2u(seq->chroma_qp_offset, 5)) |
+ RKVENC_H264_PPS_DBF_CP_FLG);
+ rkvenc_img_l1(img, RKVENC_SYNT_SLI0,
+ FIELD_PREP(RKVENC_H264_SLI0_SLI_TYPE,
+ f->intra ? H264_SLICE_I : H264_SLICE_P) |
+ FIELD_PREP(RKVENC_H264_SLI0_FRM_NUM, pic->frame_num));
+ rkvenc_img_l1(img, RKVENC_SYNT_SLI1,
+ FIELD_PREP(RKVENC_H264_SLI1_IDR_PIC_ID,
+ f->intra ? pic->idr_pic_id : 0) |
+ FIELD_PREP(RKVENC_H264_SLI1_POC_LSB, pic->poc_lsb));
+
+ sli2 = FIELD_PREP(RKVENC_H264_SLI2_DIS_DBLK_IDC, seq->dblk_idc);
+ sli2 |= FIELD_PREP(RKVENC_H264_SLI2_ALPH_OFST,
+ rkvenc_s2u(seq->alpha_offset, 4));
+ sli2 |= FIELD_PREP(RKVENC_H264_SLI2_BETA_OFST,
+ rkvenc_s2u(seq->beta_offset, 4));
+ rkvenc_img_l1(img, RKVENC_SYNT_SLI2, sli2);
+}
+
+static void h264_l2_build(const struct rkvenc_frame *f, struct rkvenc_img *img)
+{
+ u32 i;
+
+ for (i = 0; i < 2; i++) {
+ rkvenc_img_l2(img, RKVENC_L2_IPRD_TTHDY4(i),
+ i ? rkvenc_pair(9, 36) : rkvenc_pair(1, 4));
+ rkvenc_img_l2(img, RKVENC_L2_IPRD_TTHDC8(i),
+ i ? rkvenc_pair(9, 36) : rkvenc_pair(1, 4));
+ rkvenc_img_l2(img, RKVENC_L2_IPRD_TTHDY8(i),
+ i ? rkvenc_pair(9, 36) : rkvenc_pair(1, 4));
+ }
+ rkvenc_img_l2(img, RKVENC_L2_IPRD_TTHD_UL, 2501);
+ rkvenc_img_l2(img, RKVENC_L2_IPRD_WGTY8, rkvenc_bytes(48, 60, 40, 48));
+ rkvenc_img_l2(img, RKVENC_L2_IPRD_WGTY4, rkvenc_bytes(48, 60, 40, 48));
+ rkvenc_img_l2(img, RKVENC_L2_IPRD_WGTY16, rkvenc_bytes(48, 60, 40, 48));
+ rkvenc_img_l2(img, RKVENC_L2_IPRD_WGTC8, rkvenc_bytes(36, 42, 28, 32));
+
+ /* Rounding offsets in 1/2048 units */
+ rkvenc_img_l2(img, RKVENC_L2_QNT_BIAS,
+ FIELD_PREP(RKVENC_L2_QNT_BIAS_I, f->intra ? 683 : 583) |
+ FIELD_PREP(RKVENC_L2_QNT_BIAS_P, 171));
+
+ rkvenc_img_l2(img, RKVENC_L2_ATR_THD0, rkvenc_pair(1, 4));
+ rkvenc_img_l2(img, RKVENC_L2_ATR_THD1,
+ rkvenc_pair(f->intra ? 36 : 81, 45));
+ rkvenc_img_l2(img, RKVENC_L2_ATR_WGT16, f->intra ?
+ rkvenc_bytes(16, 16, 16, 0) : rkvenc_bytes(18, 17, 16, 0));
+ rkvenc_img_l2(img, RKVENC_L2_ATR_WGT8, rkvenc_bytes(20, 20, 20, 0));
+ rkvenc_img_l2(img, RKVENC_L2_ATR_WGT4, f->intra ?
+ rkvenc_bytes(20, 18, 16, 0) : rkvenc_bytes(18, 17, 16, 0));
+
+ rkvenc_img_l2(img, RKVENC_L2_ATF_TTHD(0), rkvenc_pair(0, 64));
+ rkvenc_img_l2(img, RKVENC_L2_ATF_TTHD(1), rkvenc_pair(144, 2500));
+ rkvenc_img_l2(img, RKVENC_L2_ATF_STHD(0), rkvenc_pair(80, 280));
+ rkvenc_img_l2(img, RKVENC_L2_ATF_STHD(1), rkvenc_pair(144, 192));
+ rkvenc_img_l2(img, RKVENC_L2_ATF_WGT(0), rkvenc_pair(26, 24));
+ rkvenc_img_l2(img, RKVENC_L2_ATF_WGT(1), rkvenc_pair(19, 22));
+ rkvenc_img_l2(img, RKVENC_L2_ATF_WGT(2), rkvenc_pair(19, 19));
+ rkvenc_img_l2(img, RKVENC_L2_ATF_OFST(0), rkvenc_pair(3500, 3500));
+ rkvenc_img_l2(img, RKVENC_L2_ATF_OFST(1), rkvenc_pair(0, 3500));
+ rkvenc_img_l2(img, RKVENC_L2_ATF_OFST(2), rkvenc_pair(1000, 0));
+
+ for (i = 0; i <= RKVENC_QP_MAX; i++) {
+ rkvenc_img_l2(img, RKVENC_L2_RDO_WGTA_QP(i),
+ rkvenc_h264_lambda[i + 1]);
+ rkvenc_img_l2(img, RKVENC_L2_RDO_WGTB_QP(i),
+ rkvenc_h264_lambda[i]);
+ }
+
+ rkvenc_img_l2_u8(img, RKVENC_L2_AQ_TTHD(0), rkvenc_h264_aq_thd, 16);
+ rkvenc_img_l2_s6(img, RKVENC_L2_AQ_STP(0), f->intra ?
+ rkvenc_h264_aq_step_i : rkvenc_h264_aq_step_p, 16);
+
+ rkvenc_img_l2(img, RKVENC_L2_RME_MVD_PNSH, 16000 << 6);
+ rkvenc_img_l2(img, RKVENC_L2_ATR1_THD0, rkvenc_pair(1, 4));
+ rkvenc_img_l2(img, RKVENC_L2_ATR1_THD1, 49);
+ img->l2_last = RKVENC_L2_H264_LAST;
+}
+
+void rkvenc_vepu541_h264_build(const struct rkvenc_frame *f,
+ struct rkvenc_img *img)
+{
+ u32 w16 = ALIGN(f->src.width, 16), h16 = ALIGN(f->src.height, 16);
+ u32 pic, i;
+
+ rkvenc_img_l1(img, RKVENC_ENC_RSL,
+ FIELD_PREP(RKVENC_ENC_RSL_WD8_M1, w16 / 8 - 1) |
+ FIELD_PREP(RKVENC_ENC_RSL_WFILL, w16 - f->src.width) |
+ FIELD_PREP(RKVENC_ENC_RSL_HD8_M1, h16 / 8 - 1) |
+ FIELD_PREP(RKVENC_ENC_RSL_HFILL, h16 - f->src.height));
+
+ pic = RKVENC_ENC_PIC_BS_SCP | FIELD_PREP(RKVENC_ENC_PIC_QP, f->qp);
+ if (f->referenced)
+ pic |= RKVENC_ENC_PIC_CUR_FRM_REF;
+ if (!f->intra)
+ pic |= RKVENC_ENC_PIC_LAMB_MOD_SEL;
+ rkvenc_img_l1(img, RKVENC_ENC_PIC, pic);
+
+ rkvenc_img_l1(img, RKVENC_DTRNS_CFG, RKVENC_DTRNS_CFG_DSPR_OTSD);
+
+ rkvenc_img_l1(img, RKVENC_KLUT_OFST, f->intra ? 0 : 3);
+ for (i = 0; i < RKVENC_KLUT_WORDS; i++)
+ rkvenc_img_l1(img, RKVENC_KLUT_WGT(i), rkvenc_h264_klut[i]);
+
+ h264_me_build(f, img);
+ h264_syntax_build(f, img);
+ h264_l2_build(f, img);
+}
diff --git a/drivers/media/platform/rockchip/rkvenc/rkvenc-vepu541-hevc.c b/drivers/media/platform/rockchip/rkvenc/rkvenc-vepu541-hevc.c
new file mode 100644
index 0000000000000..0a6dea9062a54
--- /dev/null
+++ b/drivers/media/platform/rockchip/rkvenc/rkvenc-vepu541-hevc.c
@@ -0,0 +1,182 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * RKVENC v1 register image builder, HEVC part.
+ */
+
+#include "rkvenc-vepu541.h"
+
+#define HEVC_SLICE_P 1
+#define HEVC_SLICE_I 2
+
+static void hevc_me_build(const struct rkvenc_frame *f, struct rkvenc_img *img)
+{
+ u32 w = ALIGN(f->src.width, 8), h = ALIGN(f->src.height, 8);
+ u32 wc = DIV_ROUND_UP(w, 64);
+ u32 sx, sy, srch_h, srch_v, rama_h, s, m, l2map;
+
+ sx = w >= 384 + 60 ? 384 : 2 * (min(384U, w) / 4);
+ sy = h >= 256 + 60 ? 256 : 2 * (min(256U, h) / 4);
+ srch_h = (sx / 2 - 1) / 16;
+ srch_v = (sy / 2 - 1) / 16;
+
+ rkvenc_img_l1(img, RKVENC_ME_RNGE,
+ FIELD_PREP(RKVENC_ME_RNGE_CME_SRCH_H, srch_h) |
+ FIELD_PREP(RKVENC_ME_RNGE_CME_SRCH_V, srch_v) |
+ FIELD_PREP(RKVENC_ME_RNGE_RME_SRCH_H, 7) |
+ FIELD_PREP(RKVENC_ME_RNGE_RME_SRCH_V, 5) |
+ FIELD_PREP(RKVENC_ME_RNGE_DLT_FRM_NUM, 1));
+ rkvenc_img_l1(img, RKVENC_ME_CNST,
+ FIELD_PREP(RKVENC_ME_CNST_PMV_MDST_H, 5) |
+ FIELD_PREP(RKVENC_ME_CNST_PMV_MDST_V, 5) |
+ FIELD_PREP(RKVENC_ME_CNST_PMV_NUM, 2));
+
+ rama_h = w > 2688 ? 12 : w > 2048 ? 16 : 20;
+ s = 2 * ((srch_h + 4) / 4) + 1;
+ m = min(2 * ((srch_v + 3) / 4) + 1, rama_h / 4);
+ if (wc * 64 <= 512)
+ l2map = 0;
+ else if (wc * 64 <= 1024)
+ l2map = 1;
+ else if (wc * 64 <= 2048)
+ l2map = 2;
+ else
+ l2map = 3;
+ rkvenc_img_l1(img, RKVENC_ME_RAM,
+ FIELD_PREP(RKVENC_ME_RAM_CME_RAMA_MAX,
+ wc * (m - 1) + (wc >= s ? s : 2 * wc)) |
+ FIELD_PREP(RKVENC_ME_RAM_CME_RAMA_H, rama_h) |
+ FIELD_PREP(RKVENC_ME_RAM_CACH_L2_MAP, l2map));
+}
+
+static void hevc_syntax_build(const struct rkvenc_frame *f,
+ struct rkvenc_img *img)
+{
+ const struct rkvenc_hevc_seq *seq = f->hevc.seq;
+ const struct rkvenc_hevc_pic *pic = &f->hevc;
+ u32 sli0, sli1;
+
+ rkvenc_img_l1(img, RKVENC_RDO_CFG,
+ RKVENC_HEVC_RDO_CHRM_SPECIAL |
+ FIELD_PREP(RKVENC_HEVC_RDO_CU_INTER_EN, 0xf) |
+ FIELD_PREP(RKVENC_HEVC_RDO_CU_INTRA_EN, 0xf) |
+ RKVENC_HEVC_RDO_CHRM_KLUT_EN);
+
+ rkvenc_img_l1(img, RKVENC_SYNT_NAL,
+ FIELD_PREP(RKVENC_HEVC_NAL_UNIT_TYPE, pic->nal_unit_type));
+ rkvenc_img_l1(img, RKVENC_SYNT_SPS,
+ (seq->sao ? RKVENC_HEVC_SPS_SAO_EN : 0) |
+ FIELD_PREP(RKVENC_HEVC_SPS_LOG2_MAX_POC_LSB,
+ seq->log2_max_poc_lsb_m4) |
+ (seq->strong_intra_smoothing ?
+ RKVENC_HEVC_SPS_STRG_INTRA_SMTH : 0));
+ rkvenc_img_l1(img, RKVENC_SYNT_PPS,
+ (seq->cabac_init_present ?
+ RKVENC_HEVC_PPS_CBC_INIT_PRSNT : 0) |
+ FIELD_PREP(RKVENC_PPS_PIC_INIT_QP, seq->pic_init_qp) |
+ RKVENC_HEVC_PPS_CU_QP_DLT_EN);
+
+ sli0 = RKVENC_HEVC_SLI0_MERGE_UP | RKVENC_HEVC_SLI0_MERGE_LEFT |
+ RKVENC_HEVC_SLI0_PIC_OUT_FLG |
+ FIELD_PREP(RKVENC_HEVC_SLI0_SLI_TYPE,
+ f->intra ? HEVC_SLICE_I : HEVC_SLICE_P);
+ if (seq->cabac_init_present)
+ sli0 |= RKVENC_HEVC_SLI0_CBC_INIT_FLG;
+ if (seq->sao)
+ sli0 |= RKVENC_HEVC_SLI0_SAO_LUMA | RKVENC_HEVC_SLI0_SAO_CHRM;
+ rkvenc_img_l1(img, RKVENC_SYNT_SLI0, sli0);
+
+ /* The header QP must match the QP the picture is quantised with */
+ sli1 = FIELD_PREP(RKVENC_HEVC_SLI1_SLI_QP, f->qp) |
+ FIELD_PREP(RKVENC_HEVC_SLI1_MAX_MRG_CND, seq->max_merge_cand) |
+ RKVENC_HEVC_SLI1_COL_FRM_L0;
+ sli1 |= FIELD_PREP(RKVENC_HEVC_SLI1_TC_OFST,
+ rkvenc_s2u(seq->tc_offset, 4));
+ sli1 |= FIELD_PREP(RKVENC_HEVC_SLI1_BETA_OFST,
+ rkvenc_s2u(seq->beta_offset, 4));
+ if (seq->deblock_disabled)
+ sli1 |= RKVENC_HEVC_SLI1_DBLK_DIS;
+ rkvenc_img_l1(img, RKVENC_SYNT_SLI1, sli1);
+ rkvenc_img_l1(img, RKVENC_SYNT_SLI2,
+ FIELD_PREP(RKVENC_HEVC_SLI2_POC_LSB, pic->poc_lsb));
+
+ if (!f->intra)
+ rkvenc_img_l1(img, RKVENC_SYNT_REFM1,
+ FIELD_PREP(RKVENC_HEVC_REFM1_USED_BY_S0, 1) |
+ FIELD_PREP(RKVENC_HEVC_REFM1_NUM_NEG_PIC, 1));
+}
+
+static void hevc_l2_build(struct rkvenc_img *img)
+{
+ const u16 *thd = rkvenc_hevc_intra_thd;
+ u32 i;
+
+ for (i = 0; i < 2; i++) {
+ rkvenc_img_l2(img, RKVENC_L2_HEVC_INTRA_THD32(i),
+ thd[2 * i] | thd[2 * i + 1] << 16);
+ rkvenc_img_l2(img, RKVENC_L2_HEVC_INTRA_THD16(i),
+ thd[2 * i] | thd[2 * i + 1] << 16);
+ }
+ rkvenc_img_l2_u8(img, RKVENC_L2_HEVC_INTRA_WGT32(0),
+ rkvenc_hevc_intra_wgt32, 7);
+ rkvenc_img_l2_u8(img, RKVENC_L2_HEVC_INTRA_WGT16(0),
+ rkvenc_hevc_intra_wgt16, 7);
+
+ /* Rounding offsets in 1/512 units */
+ rkvenc_img_l2(img, RKVENC_L2_QNT_BIAS,
+ FIELD_PREP(RKVENC_L2_QNT_BIAS_I, 171) |
+ FIELD_PREP(RKVENC_L2_QNT_BIAS_P, 85));
+
+ for (i = 0; i < 5; i++)
+ rkvenc_img_l2(img, RKVENC_L2_HEVC_ATF_THD(i), rkvenc_pair(0, 63));
+ rkvenc_img_l2(img, RKVENC_L2_HEVC_ATF_WGT(0), rkvenc_pair(16, 16));
+ rkvenc_img_l2(img, RKVENC_L2_HEVC_ATF_WGT(1), rkvenc_pair(16, 16));
+ rkvenc_img_l2(img, RKVENC_L2_HEVC_ATF_WGT(2), 16);
+
+ for (i = 0; i <= RKVENC_QP_MAX; i++) {
+ rkvenc_img_l2(img, RKVENC_L2_IPRD_WGT_QP(i),
+ rkvenc_hevc_lambda_satd[i]);
+ rkvenc_img_l2(img, RKVENC_L2_RDO_WGTA_QP(i),
+ rkvenc_hevc_lambda_a[i]);
+ rkvenc_img_l2(img, RKVENC_L2_RDO_WGTB_QP(i),
+ rkvenc_hevc_lambda_b[i]);
+ }
+
+ rkvenc_img_l2_u8(img, RKVENC_L2_AQ_TTHD(0), rkvenc_hevc_aq_thd, 16);
+ rkvenc_img_l2_s6(img, RKVENC_L2_AQ_STP(0), rkvenc_hevc_aq_step, 16);
+ img->l2_last = RKVENC_L2_HEVC_LAST;
+}
+
+void rkvenc_vepu541_hevc_build(const struct rkvenc_frame *f,
+ struct rkvenc_img *img)
+{
+ u32 w8 = ALIGN(f->src.width, 8), h8 = ALIGN(f->src.height, 8);
+ u32 ctus = DIV_ROUND_UP(w8, 64) * DIV_ROUND_UP(h8, 64);
+ u32 pic, i;
+
+ rkvenc_img_l1(img, RKVENC_ENC_RSL,
+ FIELD_PREP(RKVENC_ENC_RSL_WD8_M1, w8 / 8 - 1) |
+ FIELD_PREP(RKVENC_ENC_RSL_WFILL, w8 - f->src.width) |
+ FIELD_PREP(RKVENC_ENC_RSL_HD8_M1, h8 / 8 - 1) |
+ FIELD_PREP(RKVENC_ENC_RSL_HFILL, h8 - f->src.height));
+
+ pic = RKVENC_ENC_PIC_STND_HEVC | RKVENC_ENC_PIC_BS_SCP |
+ FIELD_PREP(RKVENC_ENC_PIC_QP, f->qp) |
+ FIELD_PREP(RKVENC_ENC_PIC_TOT_POC_NUM, f->intra ? 0 : 1) |
+ FIELD_PREP(RKVENC_ENC_PIC_LOG2_CTU_NUM, order_base_2(ctus));
+ if (f->referenced)
+ pic |= RKVENC_ENC_PIC_CUR_FRM_REF;
+ if (!f->intra)
+ pic |= RKVENC_ENC_PIC_LAMB_MOD_SEL;
+ rkvenc_img_l1(img, RKVENC_ENC_PIC, pic);
+
+ rkvenc_img_l1(img, RKVENC_DTRNS_CFG,
+ f->intra ? 0 : RKVENC_DTRNS_CFG_DSPR_OTSD);
+
+ rkvenc_img_l1(img, RKVENC_KLUT_OFST, f->intra ? 0 : 3);
+ for (i = 0; i < RKVENC_KLUT_WORDS; i++)
+ rkvenc_img_l1(img, RKVENC_KLUT_WGT(i), rkvenc_hevc_klut[i]);
+
+ hevc_me_build(f, img);
+ hevc_syntax_build(f, img);
+ hevc_l2_build(img);
+}
diff --git a/drivers/media/platform/rockchip/rkvenc/rkvenc-vepu541-regs.h b/drivers/media/platform/rockchip/rkvenc/rkvenc-vepu541-regs.h
new file mode 100644
index 0000000000000..9abe6628a5831
--- /dev/null
+++ b/drivers/media/platform/rockchip/rkvenc/rkvenc-vepu541-regs.h
@@ -0,0 +1,283 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+#ifndef RKVENC_VEPU541_REGS_H
+#define RKVENC_VEPU541_REGS_H
+
+#include <linux/bits.h>
+#include <linux/bitfield.h>
+
+#define RKVENC_VERSION 0x000
+
+#define RKVENC_ENC_STRT 0x004
+#define RKVENC_ENC_STRT_CMD GENMASK(9, 8)
+#define RKVENC_ENC_STRT_CMD_FRAME 1
+#define RKVENC_ENC_STRT_CLK_GATE_EN BIT(16)
+#define RKVENC_ENC_STRT_DONE_TMVP_EN BIT(18)
+
+#define RKVENC_ENC_CLR 0x008
+#define RKVENC_ENC_CLR_SAFE BIT(0)
+#define RKVENC_ENC_CLR_FORCE BIT(1)
+
+#define RKVENC_LKT_ADDR 0x00c
+#define RKVENC_INT_EN 0x010
+#define RKVENC_INT_MSK 0x014
+#define RKVENC_INT_CLR 0x018
+#define RKVENC_INT_STA 0x01c
+#define RKVENC_INT_ENC_DONE BIT(0)
+#define RKVENC_INT_LKT_DONE BIT(1)
+#define RKVENC_INT_SCLR_DONE BIT(2)
+#define RKVENC_INT_SLICE_DONE BIT(3)
+#define RKVENC_INT_OFLW BIT(4)
+#define RKVENC_INT_BRSP BIT(5)
+#define RKVENC_INT_BERR BIT(6)
+#define RKVENC_INT_RERR BIT(7)
+#define RKVENC_INT_WDG BIT(8)
+#define RKVENC_INT_ALL GENMASK(8, 0)
+#define RKVENC_INT_ERRORS GENMASK(8, 4)
+
+#define RKVENC_ENC_RSL 0x030
+#define RKVENC_ENC_RSL_WD8_M1 GENMASK(8, 0)
+#define RKVENC_ENC_RSL_WFILL GENMASK(15, 10)
+#define RKVENC_ENC_RSL_HD8_M1 GENMASK(24, 16)
+#define RKVENC_ENC_RSL_HFILL GENMASK(31, 26)
+
+#define RKVENC_ENC_PIC 0x034
+#define RKVENC_ENC_PIC_STND_HEVC BIT(0)
+#define RKVENC_ENC_PIC_ROI_ENC BIT(1)
+#define RKVENC_ENC_PIC_CUR_FRM_REF BIT(2)
+#define RKVENC_ENC_PIC_BS_SCP BIT(4)
+#define RKVENC_ENC_PIC_LAMB_MOD_SEL BIT(5)
+#define RKVENC_ENC_PIC_QP GENMASK(13, 8)
+#define RKVENC_ENC_PIC_TOT_POC_NUM GENMASK(18, 14)
+#define RKVENC_ENC_PIC_LOG2_CTU_NUM GENMASK(22, 19)
+
+#define RKVENC_ENC_WDG 0x038
+#define RKVENC_ENC_WDG_VS_LOAD_THD GENMASK(23, 0)
+
+#define RKVENC_DTRNS_MAP 0x03c
+#define RKVENC_DTRNS_MAP_BSW_BUS_EDIN GENMASK(14, 12)
+
+#define RKVENC_DTRNS_CFG 0x040
+#define RKVENC_DTRNS_CFG_DSPR_OTSD BIT(7)
+
+#define RKVENC_SRC_FMT 0x044
+#define RKVENC_SRC_FMT_CFMT GENMASK(5, 2)
+#define RKVENC_SRC_FMT_CFMT_NV12 6
+#define RKVENC_SRC_FMT_RANGE BIT(6)
+
+#define RKVENC_SRC_PROC 0x058
+#define RKVENC_SRC_PROC_TXA_EN BIT(29)
+
+#define RKVENC_KLUT_OFST 0x064
+#define RKVENC_KLUT_WGT(n) (0x068 + 4 * (n))
+#define RKVENC_KLUT_WORDS 24
+#define RKVENC_KLUT_ENTRIES 35
+
+#define RKVENC_RC_CFG 0x0c8
+#define RKVENC_RC_CFG_RC_EN BIT(0)
+#define RKVENC_RC_CFG_AQ_EN BIT(1)
+#define RKVENC_RC_CFG_AQ_MODE BIT(2)
+#define RKVENC_RC_CFG_CTU_NUM GENMASK(31, 16)
+
+#define RKVENC_RC_QP 0x0cc
+#define RKVENC_RC_QP_RANGE GENMASK(19, 16)
+#define RKVENC_RC_QP_MAX GENMASK(25, 20)
+#define RKVENC_RC_QP_MIN GENMASK(31, 26)
+
+#define RKVENC_RC_TGT 0x0d0
+#define RKVENC_RC_TGT_CTU_EBIT GENMASK(19, 0)
+
+#define RKVENC_RC_ADJ0 0x0d4
+#define RKVENC_RC_ADJ1 0x0d8
+#define RKVENC_RC_DTHD(n) (0x0dc + 4 * (n))
+#define RKVENC_RC_STEPS 9
+
+#define RKVENC_ROI_QTHD(n) (0x100 + 4 * (n))
+#define RKVENC_PIC_OFST 0x110
+
+#define RKVENC_SRC_STRID 0x114
+#define RKVENC_SRC_STRID_STRD0 GENMASK(15, 0)
+#define RKVENC_SRC_STRID_STRD1 GENMASK(31, 16)
+
+#define RKVENC_ADR_SRC0 0x118
+#define RKVENC_ADR_SRC1 0x11c
+#define RKVENC_ADR_SRC2 0x120
+#define RKVENC_ADR_ROI 0x124
+#define RKVENC_ADR_RFPW_H 0x128
+#define RKVENC_ADR_RFPW_B 0x12c
+#define RKVENC_ADR_RFPR_H 0x130
+#define RKVENC_ADR_RFPR_B 0x134
+#define RKVENC_ADR_CMVW 0x138
+#define RKVENC_ADR_CMVR 0x13c
+#define RKVENC_ADR_DSPW 0x140
+#define RKVENC_ADR_DSPR 0x144
+#define RKVENC_ADR_MEIW 0x148
+#define RKVENC_ADR_BSBT 0x14c
+#define RKVENC_ADR_BSBB 0x150
+#define RKVENC_ADR_BSBR 0x154
+#define RKVENC_ADR_BSBS 0x158
+
+#define RKVENC_SLI_SPLT 0x15c
+#define RKVENC_SLI_BYTE 0x160
+
+#define RKVENC_ME_RNGE 0x164
+#define RKVENC_ME_RNGE_CME_SRCH_H GENMASK(3, 0)
+#define RKVENC_ME_RNGE_CME_SRCH_V GENMASK(7, 4)
+#define RKVENC_ME_RNGE_RME_SRCH_H GENMASK(10, 8)
+#define RKVENC_ME_RNGE_RME_SRCH_V GENMASK(13, 11)
+#define RKVENC_ME_RNGE_DLT_FRM_NUM GENMASK(31, 16)
+
+#define RKVENC_ME_CNST 0x168
+#define RKVENC_ME_CNST_PMV_MDST_H GENMASK(7, 0)
+#define RKVENC_ME_CNST_PMV_MDST_V GENMASK(15, 8)
+#define RKVENC_ME_CNST_MV_LIMIT GENMASK(17, 16)
+#define RKVENC_ME_CNST_PMV_NUM GENMASK(19, 18)
+
+#define RKVENC_ME_RAM 0x16c
+#define RKVENC_ME_RAM_CME_RAMA_MAX GENMASK(10, 0)
+#define RKVENC_ME_RAM_CME_RAMA_H GENMASK(15, 11)
+#define RKVENC_ME_RAM_CACH_L2_MAP GENMASK(17, 16)
+
+#define RKVENC_IPRD_CSTS 0x194
+#define RKVENC_IPRD_CSTS_VTHD_Y GENMASK(11, 0)
+#define RKVENC_IPRD_CSTS_VTHD_C GENMASK(27, 16)
+
+#define RKVENC_RDO_CFG 0x198
+#define RKVENC_H264_RDO_RECT_SIZE BIT(0)
+#define RKVENC_H264_RDO_INTER_4X4 BIT(1)
+#define RKVENC_H264_RDO_VLC_LMT BIT(3)
+#define RKVENC_H264_RDO_CHRM_SPCL BIT(4)
+#define RKVENC_H264_RDO_CCWA_E BIT(13)
+#define RKVENC_H264_RDO_SCL_LST_SEL BIT(14)
+#define RKVENC_H264_RDO_ATR_E BIT(15)
+#define RKVENC_HEVC_RDO_CHRM_SPECIAL BIT(2)
+#define RKVENC_HEVC_RDO_CU_INTER_EN GENMASK(9, 6)
+#define RKVENC_HEVC_RDO_CU_INTRA_EN GENMASK(22, 19)
+#define RKVENC_HEVC_RDO_CHRM_KLUT_EN BIT(23)
+
+#define RKVENC_SYNT_NAL 0x19c
+#define RKVENC_H264_NAL_REF_IDC GENMASK(1, 0)
+#define RKVENC_H264_NAL_UNIT_TYPE GENMASK(6, 2)
+#define RKVENC_HEVC_NAL_UNIT_TYPE GENMASK(5, 0)
+
+#define RKVENC_SYNT_SPS 0x1a0
+#define RKVENC_H264_SPS_MAX_FNUM GENMASK(3, 0)
+#define RKVENC_H264_SPS_DRCT_8X8 BIT(4)
+#define RKVENC_H264_SPS_MPOC_LM4 GENMASK(8, 5)
+#define RKVENC_HEVC_SPS_SAO_EN BIT(0)
+#define RKVENC_HEVC_SPS_TMVP_EN BIT(15)
+#define RKVENC_HEVC_SPS_LOG2_MAX_POC_LSB GENMASK(19, 16)
+#define RKVENC_HEVC_SPS_STRG_INTRA_SMTH BIT(20)
+
+#define RKVENC_SYNT_PPS 0x1a4
+#define RKVENC_PPS_PIC_INIT_QP GENMASK(12, 7)
+#define RKVENC_H264_PPS_ETPY_MODE BIT(0)
+#define RKVENC_H264_PPS_TRNS_8X8 BIT(1)
+#define RKVENC_H264_PPS_CB_OFST GENMASK(17, 13)
+#define RKVENC_H264_PPS_CR_OFST GENMASK(22, 18)
+#define RKVENC_H264_PPS_DBF_CP_FLG BIT(24)
+#define RKVENC_HEVC_PPS_CBC_INIT_PRSNT BIT(6)
+#define RKVENC_HEVC_PPS_CU_QP_DLT_EN BIT(13)
+
+#define RKVENC_SYNT_SLI0 0x1a8
+#define RKVENC_H264_SLI0_SLI_TYPE GENMASK(1, 0)
+#define RKVENC_H264_SLI0_PPS_ID GENMASK(9, 2)
+#define RKVENC_H264_SLI0_CBC_INIT_IDC GENMASK(13, 12)
+#define RKVENC_H264_SLI0_FRM_NUM GENMASK(31, 16)
+#define RKVENC_HEVC_SLI0_CBC_INIT_FLG BIT(0)
+#define RKVENC_HEVC_SLI0_MERGE_UP BIT(2)
+#define RKVENC_HEVC_SLI0_MERGE_LEFT BIT(3)
+#define RKVENC_HEVC_SLI0_SAO_CHRM BIT(11)
+#define RKVENC_HEVC_SLI0_SAO_LUMA BIT(12)
+#define RKVENC_HEVC_SLI0_PIC_OUT_FLG BIT(14)
+#define RKVENC_HEVC_SLI0_SLI_TYPE GENMASK(16, 15)
+
+#define RKVENC_SYNT_SLI1 0x1ac
+#define RKVENC_H264_SLI1_IDR_PIC_ID GENMASK(15, 0)
+#define RKVENC_H264_SLI1_POC_LSB GENMASK(31, 16)
+#define RKVENC_HEVC_SLI1_TC_OFST GENMASK(3, 0)
+#define RKVENC_HEVC_SLI1_BETA_OFST GENMASK(7, 4)
+#define RKVENC_HEVC_SLI1_DBLK_DIS BIT(9)
+#define RKVENC_HEVC_SLI1_SLI_QP GENMASK(21, 16)
+#define RKVENC_HEVC_SLI1_MAX_MRG_CND GENMASK(24, 22)
+#define RKVENC_HEVC_SLI1_COL_FRM_L0 BIT(26)
+
+#define RKVENC_SYNT_SLI2 0x1b0
+#define RKVENC_H264_SLI2_BETA_OFST GENMASK(6, 3)
+#define RKVENC_H264_SLI2_ALPH_OFST GENMASK(10, 7)
+#define RKVENC_H264_SLI2_DIS_DBLK_IDC GENMASK(12, 11)
+#define RKVENC_HEVC_SLI2_POC_LSB GENMASK(15, 0)
+
+#define RKVENC_SYNT_REFM0 0x1b4
+#define RKVENC_SYNT_REFM1 0x1b8
+#define RKVENC_HEVC_REFM1_USED_BY_S0 GENMASK(3, 0)
+#define RKVENC_HEVC_REFM1_NUM_NEG_PIC GENMASK(9, 5)
+#define RKVENC_OSD_CFG 0x1c0
+#define RKVENC_SYNT_REFM2 0x1c8
+#define RKVENC_SYNT_REFM3 0x1cc
+
+#define RKVENC_L1_CFG_END 0x210
+
+#define RKVENC_ST_BSL 0x210
+#define RKVENC_ST_BSL_LEN GENMASK(26, 0)
+#define RKVENC_ST_SSE_L32 0x214
+#define RKVENC_ST_SSE_QP 0x218
+#define RKVENC_ST_ENC 0x228
+#define RKVENC_ST_BSB 0x234
+#define RKVENC_ST_BUS 0x238
+#define RKVENC_ST_PNUM(n) (0x244 + 4 * (n))
+#define RKVENC_ST_B8_QP(n) (0x264 + 4 * (n))
+#define RKVENC_ST_CPLX_TMP 0x334
+#define RKVENC_ST_BNUM_CME 0x338
+#define RKVENC_ST_CPLX_SPT 0x33c
+#define RKVENC_ST_BNUM_B16 0x340
+
+#define RKVENC_L2CFG_ADDR 0x3f0
+#define RKVENC_L2CFG_ADDR_AUTO_INC BIT(0)
+#define RKVENC_L2CFG_WDATA 0x3f4
+#define RKVENC_L2CFG_RDATA 0x3f8
+
+#define RKVENC_L2_FIRST 0x004
+#define RKVENC_L2_H264_LAST 0x30c
+#define RKVENC_L2_HEVC_LAST 0x300
+#define RKVENC_L2_WORDS (RKVENC_L2_H264_LAST / 4 + 1)
+
+#define RKVENC_L2_IPRD_TTHDY4(n) (0x004 + 4 * (n))
+#define RKVENC_L2_IPRD_TTHDC8(n) (0x00c + 4 * (n))
+#define RKVENC_L2_IPRD_TTHDY8(n) (0x014 + 4 * (n))
+#define RKVENC_L2_IPRD_TTHD_UL 0x01c
+#define RKVENC_L2_IPRD_WGTY8 0x020
+#define RKVENC_L2_IPRD_WGTY4 0x024
+#define RKVENC_L2_IPRD_WGTY16 0x028
+#define RKVENC_L2_IPRD_WGTC8 0x02c
+#define RKVENC_L2_QNT_BIAS 0x030
+#define RKVENC_L2_QNT_BIAS_I GENMASK(9, 0)
+#define RKVENC_L2_QNT_BIAS_P GENMASK(19, 10)
+#define RKVENC_L2_ATR_THD0 0x034
+#define RKVENC_L2_ATR_THD1 0x038
+#define RKVENC_L2_ATR_WGT16 0x03c
+#define RKVENC_L2_ATR_WGT8 0x040
+#define RKVENC_L2_ATR_WGT4 0x044
+#define RKVENC_L2_ATF_TTHD(n) (0x048 + 4 * (n))
+#define RKVENC_L2_ATF_STHD(n) (0x050 + 4 * (n))
+#define RKVENC_L2_ATF_WGT(n) (0x058 + 4 * (n))
+#define RKVENC_L2_ATF_OFST(n) (0x064 + 4 * (n))
+#define RKVENC_L2_IPRD_WGT_QP(q) (0x070 + 4 * (q))
+#define RKVENC_L2_RDO_WGTA_QP(q) (0x140 + 4 * (q))
+#define RKVENC_L2_RDO_WGTB_QP(q) (0x210 + 4 * (q))
+#define RKVENC_L2_MADI_CFG 0x2e0
+#define RKVENC_L2_AQ_TTHD(n) (0x2e4 + 4 * (n))
+#define RKVENC_L2_AQ_STP(n) (0x2f4 + 4 * (n))
+#define RKVENC_L2_RME_MVD_PNSH 0x304
+#define RKVENC_L2_ATR1_THD0 0x308
+#define RKVENC_L2_ATR1_THD1 0x30c
+
+#define RKVENC_L2_HEVC_INTRA_THD32(n) (0x004 + 4 * (n))
+#define RKVENC_L2_HEVC_INTRA_THD16(n) (0x00c + 4 * (n))
+#define RKVENC_L2_HEVC_INTRA_WGT32(n) (0x020 + 4 * (n))
+#define RKVENC_L2_HEVC_INTRA_WGT16(n) (0x028 + 4 * (n))
+#define RKVENC_L2_HEVC_ATF_THD(n) (0x048 + 4 * (n))
+#define RKVENC_L2_HEVC_ATF_WGT(n) (0x05c + 4 * (n))
+
+#define RKVENC_QP_MAX 51
+
+#endif
diff --git a/drivers/media/platform/rockchip/rkvenc/rkvenc-vepu541-tables.c b/drivers/media/platform/rockchip/rkvenc/rkvenc-vepu541-tables.c
new file mode 100644
index 0000000000000..da4794b82bb12
--- /dev/null
+++ b/drivers/media/platform/rockchip/rkvenc/rkvenc-vepu541-tables.c
@@ -0,0 +1,256 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Tuning tables of the RKVENC v1 encoder. Tables with a closed form are
+ * computed once at module load in integer fixed-point arithmetic.
+ */
+
+#include <linux/build_bug.h>
+#include <linux/cache.h>
+#include <linux/math64.h>
+
+#include "rkvenc-vepu541.h"
+
+/* Fraction bits of the fixed-point numbers; the largest value is below 2^8 */
+#define RKVENC_FX_BITS 56
+#define RKVENC_FX_ONE (1ULL << RKVENC_FX_BITS)
+#define RKVENC_FX_TWO (2ULL << RKVENC_FX_BITS)
+
+/* Weights and lambdas grow by 2^(1/3) per step, sixths give square roots */
+#define RKVENC_STEPS_PER_OCTAVE 3
+#define RKVENC_SIXTHS_PER_OCTAVE 6
+#define RKVENC_SIXTHS_PER_STEP 2
+
+/* H.264 weight k = floor(16 * 2^(k / 3)); 16 is the neutral weight */
+#define RKVENC_H264_KLUT_NEUTRAL 16
+
+/* H.264 lambda j = floor(3.6 * 2^((j + 5) / 3)), the factor as 18 / 5 */
+#define RKVENC_H264_LAMBDA_FACTOR_NUM 18
+#define RKVENC_H264_LAMBDA_FACTOR_DEN 5
+#define RKVENC_H264_LAMBDA_J_OFFSET 5
+
+/* HEVC lambda 0.57 * 2^((q - 12) / 3), 11 fraction bits */
+#define RKVENC_HEVC_LAMBDA_PCT 57
+#define RKVENC_HEVC_LAMBDA_QP_REF 12
+#define RKVENC_HEVC_LAMBDA_FRAC 11
+#define RKVENC_PERCENT 100
+
+/* HEVC weight i = S[i mod 3] * 2^(i div 3), S about 2^(7 + n / 3) */
+#define RKVENC_HEVC_KLUT_S0 128
+#define RKVENC_HEVC_KLUT_S1 161
+#define RKVENC_HEVC_KLUT_S2 204
+#define RKVENC_HEVC_LAMBDA_B_S0 112
+#define RKVENC_HEVC_LAMBDA_B_S1 137
+#define RKVENC_HEVC_LAMBDA_B_S2 176
+#define RKVENC_KLUT_ENTRY_BITS 18
+
+/* Three KLUT weights per word pair, the middle one split across both */
+#define RKVENC_KLUT_PAIRS (RKVENC_KLUT_WORDS / 2)
+#define RKVENC_KLUT_EVEN_WORD(n) (2 * (n))
+#define RKVENC_KLUT_ODD_WORD(n) (2 * (n) + 1)
+#define RKVENC_KLUT_SPLIT_BITS 9
+#define RKVENC_KLUT_SPLIT_EVEN_SHIFT 23
+#define RKVENC_KLUT_LAST_ODD_SHIFT 14
+
+static_assert(RKVENC_SIXTHS_PER_STEP * RKVENC_STEPS_PER_OCTAVE ==
+ RKVENC_SIXTHS_PER_OCTAVE);
+static_assert(RKVENC_HEVC_LAMBDA_QP_REF % RKVENC_SIXTHS_PER_OCTAVE == 0);
+
+u32 rkvenc_h264_klut[RKVENC_KLUT_WORDS] __ro_after_init;
+u32 rkvenc_hevc_klut[RKVENC_KLUT_WORDS] __ro_after_init;
+u32 rkvenc_h264_lambda[RKVENC_QP_MAX + 2] __ro_after_init;
+u32 rkvenc_hevc_lambda_satd[RKVENC_QP_MAX + 1] __ro_after_init;
+u32 rkvenc_hevc_lambda_a[RKVENC_QP_MAX + 1] __ro_after_init;
+u32 rkvenc_hevc_lambda_b[RKVENC_QP_MAX + 1] __ro_after_init;
+
+typedef u64 (*rkvenc_fx_pow_t)(u64 x);
+
+static u64 fx_mul(u64 a, u64 b)
+{
+ return mul_u64_u64_shr(a, b, RKVENC_FX_BITS);
+}
+
+static u64 fx_pow2(u64 x)
+{
+ return fx_mul(x, x);
+}
+
+static u64 fx_pow6(u64 x)
+{
+ u64 x3 = fx_mul(fx_pow2(x), x);
+
+ return fx_mul(x3, x3);
+}
+
+/* Largest x below 2.0 whose power does not exceed target, by bisection */
+static u64 fx_root(rkvenc_fx_pow_t pow, u64 target)
+{
+ u64 lo = 0, hi = RKVENC_FX_TWO, mid;
+
+ while (lo < hi) {
+ mid = (lo + hi + 1) / 2;
+ if (pow(mid) <= target)
+ lo = mid;
+ else
+ hi = mid - 1;
+ }
+
+ return lo;
+}
+
+/* x * 2^exp, rounded down or to nearest */
+static u32 fx_floor(u64 x, int exp)
+{
+ return x >> (RKVENC_FX_BITS - exp);
+}
+
+static u32 fx_round(u64 x, int exp)
+{
+ unsigned int s = RKVENC_FX_BITS - exp;
+
+ return (x + (1ULL << (s - 1))) >> s;
+}
+
+static void klut_pack(u32 *words, const u32 *weights)
+{
+ u32 n, e0, e1, e2, low, i = 0;
+
+ for (n = 0; n < RKVENC_KLUT_PAIRS; n++) {
+ e0 = weights[i++];
+ e1 = i < RKVENC_KLUT_ENTRIES ? weights[i++] : 0;
+ e2 = i < RKVENC_KLUT_ENTRIES ? weights[i++] : 0;
+ low = e1 & GENMASK(RKVENC_KLUT_SPLIT_BITS - 1, 0);
+ words[RKVENC_KLUT_EVEN_WORD(n)] =
+ e0 | low << RKVENC_KLUT_SPLIT_EVEN_SHIFT;
+ words[RKVENC_KLUT_ODD_WORD(n)] =
+ e1 >> RKVENC_KLUT_SPLIT_BITS |
+ e2 << RKVENC_KLUT_LAST_ODD_SHIFT;
+ }
+}
+
+static void h264_klut_init(const u64 *step_frac)
+{
+ u32 weights[RKVENC_KLUT_ENTRIES];
+ u64 x;
+ u32 k;
+
+ for (k = 0; k < RKVENC_KLUT_ENTRIES; k++) {
+ x = RKVENC_H264_KLUT_NEUTRAL *
+ step_frac[k % RKVENC_STEPS_PER_OCTAVE];
+ weights[k] = fx_floor(x, k / RKVENC_STEPS_PER_OCTAVE);
+ }
+
+ klut_pack(rkvenc_h264_klut, weights);
+}
+
+static void hevc_klut_init(void)
+{
+ static const u16 s[RKVENC_STEPS_PER_OCTAVE] = { RKVENC_HEVC_KLUT_S0,
+ RKVENC_HEVC_KLUT_S1,
+ RKVENC_HEVC_KLUT_S2 };
+ u32 weights[RKVENC_KLUT_ENTRIES];
+ u64 v;
+ u32 i;
+
+ for (i = 0; i < RKVENC_KLUT_ENTRIES; i++) {
+ v = (u64)s[i % RKVENC_STEPS_PER_OCTAVE]
+ << (i / RKVENC_STEPS_PER_OCTAVE);
+ weights[i] =
+ min_t(u64, v, GENMASK(RKVENC_KLUT_ENTRY_BITS - 1, 0));
+ }
+
+ klut_pack(rkvenc_hevc_klut, weights);
+}
+
+static void h264_lambda_init(const u64 *step_frac)
+{
+ u32 j, m;
+ u64 x;
+
+ for (j = 0; j < ARRAY_SIZE(rkvenc_h264_lambda); j++) {
+ m = j + RKVENC_H264_LAMBDA_J_OFFSET;
+ x = mul_u64_u32_div(step_frac[m % RKVENC_STEPS_PER_OCTAVE],
+ RKVENC_H264_LAMBDA_FACTOR_NUM,
+ RKVENC_H264_LAMBDA_FACTOR_DEN);
+ rkvenc_h264_lambda[j] =
+ fx_floor(x, m / RKVENC_STEPS_PER_OCTAVE);
+ }
+}
+
+/* The SATD lambda is the square root, sqrt(0.57) * 2^((q - 12) / 6) */
+static void hevc_lambda_init(const u64 *sixth, u64 sqrt_factor)
+{
+ u32 pct = (RKVENC_HEVC_LAMBDA_PCT << RKVENC_HEVC_LAMBDA_FRAC) >>
+ (RKVENC_HEVC_LAMBDA_QP_REF / RKVENC_STEPS_PER_OCTAVE);
+ int exp = RKVENC_HEVC_LAMBDA_FRAC -
+ RKVENC_HEVC_LAMBDA_QP_REF / RKVENC_SIXTHS_PER_OCTAVE;
+ static const u16 b[RKVENC_STEPS_PER_OCTAVE] = {
+ RKVENC_HEVC_LAMBDA_B_S0, RKVENC_HEVC_LAMBDA_B_S1,
+ RKVENC_HEVC_LAMBDA_B_S2
+ };
+ u32 q, r;
+ u64 x;
+
+ for (q = 0; q <= RKVENC_QP_MAX; q++) {
+ r = q % RKVENC_STEPS_PER_OCTAVE;
+ x = mul_u64_u32_div(sixth[RKVENC_SIXTHS_PER_STEP * r], pct,
+ RKVENC_PERCENT);
+ rkvenc_hevc_lambda_a[q] =
+ fx_round(x, q / RKVENC_STEPS_PER_OCTAVE);
+
+ x = fx_mul(sqrt_factor, sixth[q % RKVENC_SIXTHS_PER_OCTAVE]);
+ rkvenc_hevc_lambda_satd[q] =
+ fx_round(x, q / RKVENC_SIXTHS_PER_OCTAVE + exp);
+
+ rkvenc_hevc_lambda_b[q] = b[r] << (q / RKVENC_STEPS_PER_OCTAVE);
+ }
+}
+
+void __init rkvenc_tables_init(void)
+{
+ u64 sixth[RKVENC_SIXTHS_PER_OCTAVE];
+ u64 step_frac[RKVENC_STEPS_PER_OCTAVE];
+ u64 factor, c, sqrt_factor;
+ u32 r;
+
+ c = fx_root(fx_pow6, RKVENC_FX_TWO);
+ sixth[0] = RKVENC_FX_ONE;
+ for (r = 1; r < ARRAY_SIZE(sixth); r++)
+ sixth[r] = fx_mul(sixth[r - 1], c);
+
+ for (r = 0; r < ARRAY_SIZE(step_frac); r++)
+ step_frac[r] = sixth[RKVENC_SIXTHS_PER_STEP * r];
+
+ factor =
+ div_u64(RKVENC_HEVC_LAMBDA_PCT * RKVENC_FX_ONE, RKVENC_PERCENT);
+ sqrt_factor = fx_root(fx_pow2, factor);
+
+ h264_klut_init(step_frac);
+ hevc_klut_init();
+ h264_lambda_init(step_frac);
+ hevc_lambda_init(sixth, sqrt_factor);
+}
+
+/* Values without a closed form */
+const u8 rkvenc_h264_aq_thd[16] = {
+ 0, 0, 0, 0, 3, 3, 5, 5, 8, 8, 8, 15, 15, 20, 25, 25,
+};
+
+const s8 rkvenc_h264_aq_step_i[16] = {
+ -8, -7, -6, -5, -4, -3, -2, -1, 0, 1, 3, 3, 4, 5, 8, 8,
+};
+
+const s8 rkvenc_h264_aq_step_p[16] = {
+ -8, -7, -6, -5, -4, -3, -2, -1, 0, 1, 2, 3, 4, 5, 7, 8,
+};
+
+const u8 rkvenc_hevc_aq_thd[16] = {
+ 0, 0, 0, 0, 3, 3, 5, 5, 8, 8, 8, 15, 15, 20, 25, 35,
+};
+
+const s8 rkvenc_hevc_aq_step[16] = {
+ -8, -7, -6, -5, -4, -3, -2, -1, 0, 1, 2, 3, 4, 5, 7, 8,
+};
+
+const u16 rkvenc_hevc_intra_thd[4] = { 2, 6, 16, 36 };
+const u8 rkvenc_hevc_intra_wgt32[7] = { 23, 22, 21, 20, 22, 24, 26 };
+const u8 rkvenc_hevc_intra_wgt16[7] = { 17, 17, 17, 18, 17, 18, 18 };
diff --git a/drivers/media/platform/rockchip/rkvenc/rkvenc-vepu541.c b/drivers/media/platform/rockchip/rkvenc/rkvenc-vepu541.c
new file mode 100644
index 0000000000000..8bca4f6527347
--- /dev/null
+++ b/drivers/media/platform/rockchip/rkvenc/rkvenc-vepu541.c
@@ -0,0 +1,215 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * RKVENC v1 register image builder, codec independent part.
+ */
+
+#include <linux/math64.h>
+
+#include "rkvenc-vepu541.h"
+
+#define RKVENC_MIN_SIZE 8
+#define RKVENC_MAX_SIZE 4096
+
+static const u16 rkvenc_l1_gaps[][2] = {
+ { RKVENC_INT_CLR, 0x02c },
+ { 0x184, 0x190 },
+ { 0x1bc, 0x1bc },
+};
+
+bool rkvenc_vepu541_l1_written(u32 reg)
+{
+ u32 i;
+
+ if (reg < RKVENC_ENC_CLR || reg >= RKVENC_L1_CFG_END)
+ return false;
+
+ for (i = 0; i < ARRAY_SIZE(rkvenc_l1_gaps); i++)
+ if (reg >= rkvenc_l1_gaps[i][0] && reg <= rkvenc_l1_gaps[i][1])
+ return false;
+
+ return true;
+}
+
+void rkvenc_vepu541_slot_sizes(enum rkvenc_codec codec, u32 width, u32 height,
+ struct rkvenc_slot_sizes *sz)
+{
+ u32 aw, ah, wc, hx;
+
+ if (codec == RKVENC_CODEC_H264) {
+ aw = ALIGN(width, 64);
+ ah = ALIGN(height, 64) + 16;
+ sz->rfp_hdr = ALIGN(aw * ah / 64, 8192);
+ sz->rfp_body = aw * ah * 3 / 2;
+ sz->dsp = ALIGN(aw / 64 * ah / 64 * 256, 8192);
+ sz->cmv = 0;
+ return;
+ }
+
+ wc = DIV_ROUND_UP(width, 64);
+ hx = DIV_ROUND_UP(height, 64) + 1;
+ sz->rfp_hdr = ALIGN(wc * hx * 64, 8192);
+ sz->rfp_body = wc * hx * 4096 * 3 / 2;
+ sz->dsp = wc * hx * 256;
+ sz->cmv = ALIGN(wc * hx * 64, 256);
+}
+
+static void rkvenc_rc_build(const struct rkvenc_frame *f, u32 blocks,
+ u32 row_blocks, struct rkvenc_img *img)
+{
+ const struct rkvenc_rc *rc = f->rc;
+ u32 cfg, ebit, d, adj0, adj1, i;
+ s32 thd[RKVENC_RC_STEPS];
+ u64 ebit64;
+
+ cfg = 0;
+ if (f->aq)
+ cfg |= RKVENC_RC_CFG_AQ_EN;
+ if (f->aq && f->codec == RKVENC_CODEC_HEVC)
+ cfg |= RKVENC_RC_CFG_AQ_MODE;
+
+ rkvenc_img_l1(img, RKVENC_RC_QP,
+ FIELD_PREP(RKVENC_RC_QP_RANGE, rc ? rc->qp_range : 0) |
+ FIELD_PREP(RKVENC_RC_QP_MAX, f->qp_max) |
+ FIELD_PREP(RKVENC_RC_QP_MIN, f->qp_min));
+
+ if (!rc) {
+ rkvenc_img_l1(img, RKVENC_RC_CFG, cfg);
+ return;
+ }
+
+ cfg |= RKVENC_RC_CFG_RC_EN |
+ FIELD_PREP(RKVENC_RC_CFG_CTU_NUM, row_blocks);
+ rkvenc_img_l1(img, RKVENC_RC_CFG, cfg);
+
+ ebit64 = div_u64(16ULL * rc->target_bits, blocks);
+ ebit = min_t(u64, ebit64, RKVENC_RC_TGT_CTU_EBIT);
+ rkvenc_img_l1(img, RKVENC_RC_TGT, ebit);
+
+ /* The row rate control measures its deviation per row of blocks */
+ d = ebit * row_blocks / 16 * 5 / 16;
+ thd[0] = -2 * (s32)d;
+ thd[1] = -(s32)d;
+ thd[2] = d;
+ thd[3] = 2 * d;
+ for (i = 4; i < RKVENC_RC_STEPS; i++)
+ thd[i] = 0x7fffffff;
+
+ adj0 = 0;
+ adj1 = 0;
+ for (i = 0; i < RKVENC_RC_STEPS; i++) {
+ rkvenc_img_l1(img, RKVENC_RC_DTHD(i), (u32)thd[i]);
+ if (i < 5)
+ adj0 |= rkvenc_s2u(rc->step[i], 5) << (5 * i);
+ else
+ adj1 |= rkvenc_s2u(rc->step[i], 5) << (5 * (i - 5));
+ }
+ rkvenc_img_l1(img, RKVENC_RC_ADJ0, adj0);
+ rkvenc_img_l1(img, RKVENC_RC_ADJ1, adj1);
+}
+
+static void rkvenc_buffers_build(const struct rkvenc_frame *f,
+ struct rkvenc_img *img)
+{
+ const struct rkvenc_src *src = &f->src;
+ const struct rkvenc_bs *bs = &f->bs;
+
+ rkvenc_img_l1(img, RKVENC_SRC_FMT,
+ FIELD_PREP(RKVENC_SRC_FMT_CFMT,
+ RKVENC_SRC_FMT_CFMT_NV12));
+ rkvenc_img_l1(img, RKVENC_SRC_PROC, RKVENC_SRC_PROC_TXA_EN);
+ rkvenc_img_l1(img, RKVENC_SRC_STRID,
+ FIELD_PREP(RKVENC_SRC_STRID_STRD0, src->stride[0]) |
+ FIELD_PREP(RKVENC_SRC_STRID_STRD1, src->stride[1]));
+ rkvenc_img_l1(img, RKVENC_ADR_SRC0, src->plane[0]);
+ rkvenc_img_l1(img, RKVENC_ADR_SRC1, src->plane[1]);
+ rkvenc_img_l1(img, RKVENC_ADR_SRC2, src->plane[2]);
+
+ if (f->referenced) {
+ rkvenc_img_l1(img, RKVENC_ADR_RFPW_H, f->recon.rfp_hdr);
+ rkvenc_img_l1(img, RKVENC_ADR_RFPW_B, f->recon.rfp_body);
+ }
+ rkvenc_img_l1(img, RKVENC_ADR_DSPW, f->recon.dsp);
+ rkvenc_img_l1(img, RKVENC_ADR_RFPR_H, f->ref.rfp_hdr);
+ rkvenc_img_l1(img, RKVENC_ADR_RFPR_B, f->ref.rfp_body);
+ rkvenc_img_l1(img, RKVENC_ADR_DSPR, f->ref.dsp);
+ if (f->codec == RKVENC_CODEC_HEVC) {
+ rkvenc_img_l1(img, RKVENC_ADR_CMVW, f->recon.cmv);
+ rkvenc_img_l1(img, RKVENC_ADR_CMVR, f->ref.cmv);
+ }
+
+ rkvenc_img_l1(img, RKVENC_ADR_BSBB, bs->base);
+ rkvenc_img_l1(img, RKVENC_ADR_BSBR, bs->base);
+ rkvenc_img_l1(img, RKVENC_ADR_BSBT, bs->base + bs->size - 1);
+ rkvenc_img_l1(img, RKVENC_ADR_BSBS, bs->base + bs->offset);
+ rkvenc_img_l1(img, RKVENC_DTRNS_MAP,
+ FIELD_PREP(RKVENC_DTRNS_MAP_BSW_BUS_EDIN, 7));
+}
+
+static int rkvenc_frame_check(const struct rkvenc_frame *f)
+{
+ u32 w = f->src.width, h = f->src.height;
+
+ if (w < RKVENC_MIN_SIZE || h < RKVENC_MIN_SIZE ||
+ w > RKVENC_MAX_SIZE || h > RKVENC_MAX_SIZE)
+ return -EINVAL;
+
+ if (f->qp > RKVENC_QP_MAX || f->qp_min > f->qp_max ||
+ f->qp_max > RKVENC_QP_MAX)
+ return -EINVAL;
+
+ /* Hardware bug: H.264 at most 2 MBs wide and over 1 MB high hangs */
+ if (f->codec == RKVENC_CODEC_H264 && ALIGN(w, 16) <= 32 &&
+ ALIGN(h, 16) > 16)
+ return -EINVAL;
+
+ /* Hardware bug: HEVC P pictures one CTU row high decode wrongly */
+ if (f->codec == RKVENC_CODEC_HEVC && !f->intra && h <= 64)
+ return -EINVAL;
+
+ if (f->bs.size < 256 || f->bs.offset >= f->bs.size)
+ return -EINVAL;
+
+ return 0;
+}
+
+int rkvenc_vepu541_img_build(const struct rkvenc_frame *f,
+ struct rkvenc_img *img)
+{
+ u32 blocks, row_blocks, int_en;
+ int ret;
+
+ ret = rkvenc_frame_check(f);
+ if (ret)
+ return ret;
+
+ memset(img, 0, sizeof(*img));
+
+ int_en = RKVENC_INT_ENC_DONE | RKVENC_INT_SCLR_DONE | RKVENC_INT_OFLW |
+ RKVENC_INT_BRSP | RKVENC_INT_BERR | RKVENC_INT_RERR;
+ if (f->wdg_vs_load)
+ int_en |= RKVENC_INT_WDG;
+ rkvenc_img_l1(img, RKVENC_INT_EN, int_en);
+ rkvenc_img_l1(img, RKVENC_ENC_WDG,
+ FIELD_PREP(RKVENC_ENC_WDG_VS_LOAD_THD, f->wdg_vs_load));
+
+ rkvenc_buffers_build(f, img);
+
+ if (f->codec == RKVENC_CODEC_H264) {
+ row_blocks = DIV_ROUND_UP(f->src.width, 16);
+ blocks = row_blocks * DIV_ROUND_UP(f->src.height, 16);
+ rkvenc_vepu541_h264_build(f, img);
+ } else {
+ row_blocks = DIV_ROUND_UP(f->src.width, 64);
+ blocks = row_blocks * DIV_ROUND_UP(f->src.height, 64);
+ rkvenc_vepu541_hevc_build(f, img);
+ }
+
+ rkvenc_rc_build(f, blocks, row_blocks, img);
+
+ img->enc_strt = FIELD_PREP(RKVENC_ENC_STRT_CMD,
+ RKVENC_ENC_STRT_CMD_FRAME) |
+ RKVENC_ENC_STRT_CLK_GATE_EN |
+ RKVENC_ENC_STRT_DONE_TMVP_EN;
+
+ return 0;
+}
diff --git a/drivers/media/platform/rockchip/rkvenc/rkvenc-vepu541.h b/drivers/media/platform/rockchip/rkvenc/rkvenc-vepu541.h
new file mode 100644
index 0000000000000..33a13402d0ade
--- /dev/null
+++ b/drivers/media/platform/rockchip/rkvenc/rkvenc-vepu541.h
@@ -0,0 +1,201 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+#ifndef RKVENC_VEPU541_H
+#define RKVENC_VEPU541_H
+
+#include <linux/errno.h>
+#include <linux/string.h>
+#include <linux/types.h>
+#include <linux/bits.h>
+#include <linux/bitfield.h>
+#include <linux/kernel.h>
+#include <linux/minmax.h>
+#include <linux/math.h>
+#include <linux/log2.h>
+
+#include "rkvenc-vepu541-regs.h"
+
+enum rkvenc_codec {
+ RKVENC_CODEC_H264,
+ RKVENC_CODEC_HEVC,
+};
+
+struct rkvenc_src {
+ u32 plane[3];
+ u32 stride[2];
+ u32 width;
+ u32 height;
+};
+
+struct rkvenc_bs {
+ u32 base;
+ u32 size;
+ u32 offset;
+};
+
+struct rkvenc_slot {
+ u32 rfp_hdr;
+ u32 rfp_body;
+ u32 dsp;
+ u32 cmv;
+};
+
+struct rkvenc_slot_sizes {
+ u32 rfp_hdr;
+ u32 rfp_body;
+ u32 dsp;
+ u32 cmv;
+};
+
+struct rkvenc_h264_seq {
+ u32 width;
+ u32 height;
+ u8 profile_idc;
+ u8 level_idc;
+ u8 log2_max_frame_num_m4;
+ u8 log2_max_poc_lsb_m4;
+ u8 pic_init_qp;
+ s8 chroma_qp_offset;
+ bool cabac;
+ bool transform_8x8;
+ u8 dblk_idc;
+ s8 alpha_offset;
+ s8 beta_offset;
+ bool timing;
+ u32 fps;
+};
+
+struct rkvenc_h264_pic {
+ const struct rkvenc_h264_seq *seq;
+ u16 frame_num;
+ u16 poc_lsb;
+ u16 idr_pic_id;
+ u8 nal_ref_idc;
+};
+
+struct rkvenc_hevc_seq {
+ u32 width;
+ u32 height;
+ u8 level_idc;
+ u8 log2_max_poc_lsb_m4;
+ u8 pic_init_qp;
+ u8 max_merge_cand;
+ bool sao;
+ bool deblock_disabled;
+ s8 beta_offset;
+ s8 tc_offset;
+ bool strong_intra_smoothing;
+ bool cabac_init_present;
+};
+
+struct rkvenc_hevc_pic {
+ const struct rkvenc_hevc_seq *seq;
+ u16 poc_lsb;
+ u8 nal_unit_type;
+};
+
+struct rkvenc_rc {
+ u32 target_bits;
+ u8 qp_range;
+ s8 step[RKVENC_RC_STEPS];
+};
+
+struct rkvenc_frame {
+ enum rkvenc_codec codec;
+ bool intra;
+ bool referenced;
+ struct rkvenc_src src;
+ struct rkvenc_bs bs;
+ struct rkvenc_slot recon;
+ struct rkvenc_slot ref;
+ u8 qp;
+ u8 qp_min;
+ u8 qp_max;
+ bool aq;
+ const struct rkvenc_rc *rc;
+ u32 wdg_vs_load;
+ union {
+ struct rkvenc_h264_pic h264;
+ struct rkvenc_hevc_pic hevc;
+ };
+};
+
+#define RKVENC_L1_CFG_WORDS (RKVENC_L1_CFG_END / 4)
+
+struct rkvenc_img {
+ u32 l1[RKVENC_L1_CFG_WORDS];
+ u32 l2[RKVENC_L2_WORDS];
+ u32 l2_last;
+ u32 enc_strt;
+};
+
+static inline void rkvenc_img_l1(struct rkvenc_img *img, u32 reg, u32 val)
+{
+ img->l1[reg / 4] = val;
+}
+
+static inline void rkvenc_img_l2(struct rkvenc_img *img, u32 reg, u32 val)
+{
+ img->l2[reg / 4] = val;
+}
+
+static inline u32 rkvenc_s2u(s32 val, u32 bits)
+{
+ return (u32)val & GENMASK(bits - 1, 0);
+}
+
+static inline u32 rkvenc_pair(u32 lo, u32 hi)
+{
+ return lo | hi << 16;
+}
+
+static inline u32 rkvenc_bytes(u8 b0, u8 b1, u8 b2, u8 b3)
+{
+ return b0 | b1 << 8 | b2 << 16 | (u32)b3 << 24;
+}
+
+static inline void rkvenc_img_l2_u8(struct rkvenc_img *img, u32 reg,
+ const u8 *v, u32 n)
+{
+ u32 i;
+
+ for (i = 0; i < n; i++)
+ img->l2[reg / 4 + i / 4] |= (u32)v[i] << (8 * (i % 4));
+}
+
+static inline void rkvenc_img_l2_s6(struct rkvenc_img *img, u32 reg,
+ const s8 *v, u32 n)
+{
+ u32 i;
+
+ for (i = 0; i < n; i++)
+ img->l2[reg / 4 + i / 4] |= rkvenc_s2u(v[i], 6) << (8 * (i % 4));
+}
+
+bool rkvenc_vepu541_l1_written(u32 reg);
+void rkvenc_vepu541_slot_sizes(enum rkvenc_codec codec, u32 width, u32 height,
+ struct rkvenc_slot_sizes *sz);
+int rkvenc_vepu541_img_build(const struct rkvenc_frame *f,
+ struct rkvenc_img *img);
+
+void rkvenc_vepu541_h264_build(const struct rkvenc_frame *f,
+ struct rkvenc_img *img);
+void rkvenc_vepu541_hevc_build(const struct rkvenc_frame *f,
+ struct rkvenc_img *img);
+
+void rkvenc_tables_init(void);
+extern u32 rkvenc_h264_klut[RKVENC_KLUT_WORDS];
+extern u32 rkvenc_hevc_klut[RKVENC_KLUT_WORDS];
+extern u32 rkvenc_h264_lambda[RKVENC_QP_MAX + 2];
+extern u32 rkvenc_hevc_lambda_satd[RKVENC_QP_MAX + 1];
+extern u32 rkvenc_hevc_lambda_a[RKVENC_QP_MAX + 1];
+extern u32 rkvenc_hevc_lambda_b[RKVENC_QP_MAX + 1];
+extern const u8 rkvenc_h264_aq_thd[16];
+extern const s8 rkvenc_h264_aq_step_i[16];
+extern const s8 rkvenc_h264_aq_step_p[16];
+extern const u8 rkvenc_hevc_aq_thd[16];
+extern const s8 rkvenc_hevc_aq_step[16];
+extern const u16 rkvenc_hevc_intra_thd[4];
+extern const u8 rkvenc_hevc_intra_wgt32[7];
+extern const u8 rkvenc_hevc_intra_wgt16[7];
+
+#endif
diff --git a/drivers/media/platform/rockchip/rkvenc/rkvenc.c b/drivers/media/platform/rockchip/rkvenc/rkvenc.c
new file mode 100644
index 0000000000000..00778e97686d7
--- /dev/null
+++ b/drivers/media/platform/rockchip/rkvenc/rkvenc.c
@@ -0,0 +1,489 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Rockchip RKVENC v1 H.264/HEVC encoder: platform driver, interrupt and
+ * job handling.
+ */
+
+#include <linux/clk.h>
+#include <linux/completion.h>
+#include <linux/delay.h>
+#include <linux/interrupt.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/pm_runtime.h>
+#include <linux/reset.h>
+
+#include <media/v4l2-mem2mem.h>
+#include <media/videobuf2-dma-contig.h>
+
+#include "rkvenc-vepu541-regs.h"
+#include "rkvenc.h"
+
+#define RKVENC_WATCHDOG_MS 1000
+#define RKVENC_SAFE_CLEAR_MS 100
+#define RKVENC_RESET_PULSE_US 5
+
+#define RKVENC_L1_CFG_START 0x00c
+
+static const struct rkvenc_variant rkvenc_variant_rv1126 = {
+ .vepu540 = false,
+};
+
+static const struct rkvenc_variant rkvenc_variant_rk3568 = {
+ .vepu540 = true,
+};
+
+int rkvenc_hw_run(struct rkvenc_dev *dev, const struct rkvenc_frame *f)
+{
+ struct rkvenc_img *img = &dev->img;
+ void __iomem *regs = dev->regs;
+ unsigned int a;
+ int ret;
+
+ ret = rkvenc_vepu541_img_build(f, img);
+ if (WARN_ON(ret))
+ return ret;
+
+ /* INT_STA keeps its bits across a start */
+ writel(RKVENC_INT_ALL, regs + RKVENC_INT_CLR);
+
+ writel(RKVENC_L2_FIRST | RKVENC_L2CFG_ADDR_AUTO_INC,
+ regs + RKVENC_L2CFG_ADDR);
+ for (a = RKVENC_L2_FIRST; a <= img->l2_last; a += 4)
+ writel(img->l2[a / 4], regs + RKVENC_L2CFG_WDATA);
+
+ /* A write to ENC_CLR would clear the job, so L1 starts above it */
+ for (a = RKVENC_L1_CFG_END - 4; a >= RKVENC_L1_CFG_START; a -= 4)
+ if (rkvenc_vepu541_l1_written(a))
+ writel(img->l1[a / 4], regs + a);
+
+ /* A timed out job must not be judged by the previous status. */
+ dev->irq_sta = 0;
+ writel(img->enc_strt, regs + RKVENC_ENC_STRT);
+
+ schedule_delayed_work(&dev->watchdog,
+ msecs_to_jiffies(RKVENC_WATCHDOG_MS));
+ return 0;
+}
+
+void rkvenc_hw_status_read(struct rkvenc_dev *dev, u32 *int_sta, u32 *bytes,
+ u32 *qp_sum)
+{
+ void __iomem *regs = dev->regs;
+
+ *int_sta = dev->irq_sta;
+ *bytes = FIELD_GET(RKVENC_ST_BSL_LEN, readl(regs + RKVENC_ST_BSL));
+ *qp_sum = readl(regs + RKVENC_ST_SSE_QP) & GENMASK(23, 0);
+}
+
+static bool rkvenc_hw_force_clear(struct rkvenc_dev *dev)
+{
+ void __iomem *regs = dev->regs;
+
+ writel(RKVENC_INT_ALL, regs + RKVENC_INT_MSK);
+ writel(RKVENC_ENC_CLR_FORCE, regs + RKVENC_ENC_CLR);
+ writel(0, regs + RKVENC_ENC_CLR);
+ writel(RKVENC_INT_ALL, regs + RKVENC_INT_CLR);
+ readl(regs + RKVENC_INT_STA);
+ writel(0, regs + RKVENC_INT_MSK);
+
+ return !(readl(regs + RKVENC_ST_ENC) & GENMASK(1, 0));
+}
+
+void rkvenc_hw_recover(struct rkvenc_dev *dev)
+{
+ void __iomem *regs = dev->regs;
+
+ if (rkvenc_hw_force_clear(dev))
+ return;
+
+ dev_err(dev->dev, "job did not stop on force clear, resetting\n");
+
+ writel(RKVENC_INT_ALL & ~RKVENC_INT_SCLR_DONE, regs + RKVENC_INT_MSK);
+ writel(RKVENC_INT_ALL, regs + RKVENC_INT_CLR);
+
+ reinit_completion(&dev->sclr_done);
+ writel(RKVENC_ENC_CLR_SAFE, regs + RKVENC_ENC_CLR);
+ wait_for_completion_timeout(&dev->sclr_done,
+ msecs_to_jiffies(RKVENC_SAFE_CLEAR_MS));
+
+ if (dev->num_resets) {
+ reset_control_bulk_assert(dev->num_resets, dev->resets);
+ udelay(RKVENC_RESET_PULSE_US);
+ reset_control_bulk_deassert(dev->num_resets, dev->resets);
+ }
+
+ writel(0, regs + RKVENC_INT_MSK);
+}
+
+static irqreturn_t rkvenc_irq(int irq, void *priv)
+{
+ struct rkvenc_dev *dev = priv;
+ u32 sta;
+
+ sta = readl(dev->regs + RKVENC_INT_STA);
+ if (!sta)
+ return IRQ_NONE;
+
+ writel(sta, dev->regs + RKVENC_INT_CLR);
+ /* Flush the posted clear before the interrupt line is sampled again */
+ readl(dev->regs + RKVENC_INT_STA);
+
+ if (sta & RKVENC_INT_SCLR_DONE)
+ complete(&dev->sclr_done);
+
+ dev->irq_sta = sta;
+ return IRQ_WAKE_THREAD;
+}
+
+static void rkvenc_finish_job(struct rkvenc_dev *dev, struct rkvenc_ctx *ctx,
+ enum vb2_buffer_state state)
+{
+ struct vb2_v4l2_buffer *src, *dst;
+ u32 payload, int_sta, bytes, qp_sum;
+ u8 frame_type, qp_avg;
+ struct v4l2_ctrl *avg_qp;
+
+ rkvenc_hw_status_read(dev, &int_sta, &bytes, &qp_sum);
+
+ if (state == VB2_BUF_STATE_DONE) {
+ src = v4l2_m2m_next_src_buf(ctx->m2m_ctx);
+ dst = v4l2_m2m_next_dst_buf(ctx->m2m_ctx);
+
+ payload = ctx->cur_prepend + bytes;
+ frame_type = ctx->cur_intra ? 0 : 1;
+ if (ctx->qp_blocks)
+ qp_avg = (u8)(qp_sum / ctx->qp_blocks);
+ else
+ qp_avg = ctx->cur_qp;
+
+ if (dst) {
+ vb2_set_plane_payload(&dst->vb2_buf, 0, payload);
+ if (src)
+ v4l2_m2m_buf_copy_metadata(src, dst);
+ dst->flags |= ctx->cur_intra ? V4L2_BUF_FLAG_KEYFRAME
+ : V4L2_BUF_FLAG_PFRAME;
+ }
+
+ rkvenc_rc_update(ctx, payload, qp_avg, frame_type);
+ rkvenc_gop_commit(ctx);
+ ctx->consecutive_errors = 0;
+ ctx->overflow_retried = false;
+
+ avg_qp = v4l2_ctrl_find(&ctx->ctrl_hdl,
+ V4L2_CID_MPEG_VIDEO_AVERAGE_QP);
+ if (avg_qp)
+ v4l2_ctrl_s_ctrl(avg_qp, qp_avg);
+ } else if (ctx->rc_active && !ctx->overflow_retried &&
+ (int_sta & RKVENC_INT_ERRORS) == RKVENC_INT_OFLW) {
+ /* The job stays running for the framework: the frame is retried */
+ dst = v4l2_m2m_next_dst_buf(ctx->m2m_ctx);
+ rkvenc_hw_recover(dev);
+ rkvenc_rc_overflow(ctx,
+ dst ? vb2_plane_size(&dst->vb2_buf, 0) : 0,
+ ctx->cur_intra ? 0 : 1);
+ ctx->overflow_retried = true;
+ if (ctx->cur_prepend)
+ ctx->joined_header_done = false;
+ dev->job_ctx = NULL;
+ pm_runtime_put(dev->dev);
+ rkvenc_m2m_ops.device_run(ctx);
+ return;
+ } else {
+ rkvenc_hw_recover(dev);
+ rkvenc_gop_rollback(ctx);
+ ctx->overflow_retried = false;
+ ctx->consecutive_errors++;
+ if (ctx->consecutive_errors >= RKVENC_MAX_CONSEC_ERRORS) {
+ vb2_queue_error(&ctx->m2m_ctx->out_q_ctx.q);
+ vb2_queue_error(&ctx->m2m_ctx->cap_q_ctx.q);
+ }
+ }
+
+ src = v4l2_m2m_next_src_buf(ctx->m2m_ctx);
+ dst = v4l2_m2m_next_dst_buf(ctx->m2m_ctx);
+ if (src && dst &&
+ v4l2_m2m_is_last_draining_src_buf(ctx->m2m_ctx, src)) {
+ dst->flags |= V4L2_BUF_FLAG_LAST;
+ v4l2_m2m_mark_stopped(ctx->m2m_ctx);
+ }
+
+ dev->job_ctx = NULL;
+ pm_runtime_put(dev->dev);
+
+ v4l2_m2m_buf_done_and_job_finish(dev->m2m_dev, ctx->m2m_ctx, state);
+}
+
+static irqreturn_t rkvenc_irq_thread(int irq, void *priv)
+{
+ struct rkvenc_dev *dev = priv;
+ struct rkvenc_ctx *ctx;
+ enum vb2_buffer_state state;
+ u32 int_sta, bytes, qp_sum;
+ unsigned long flags;
+
+ /* Whoever clears job_running first, IRQ or watchdog, finishes the job */
+ spin_lock_irqsave(&dev->irq_lock, flags);
+ if (!dev->job_running) {
+ spin_unlock_irqrestore(&dev->irq_lock, flags);
+ return IRQ_HANDLED;
+ }
+ ctx = dev->job_ctx;
+ dev->job_running = false;
+ spin_unlock_irqrestore(&dev->irq_lock, flags);
+
+ cancel_delayed_work(&dev->watchdog);
+
+ rkvenc_hw_status_read(dev, &int_sta, &bytes, &qp_sum);
+
+ if ((int_sta & RKVENC_INT_ENC_DONE) &&
+ !(int_sta & RKVENC_INT_ERRORS))
+ state = VB2_BUF_STATE_DONE;
+ else
+ state = VB2_BUF_STATE_ERROR;
+
+ rkvenc_finish_job(dev, ctx, state);
+ return IRQ_HANDLED;
+}
+
+static void rkvenc_watchdog_work(struct work_struct *work)
+{
+ struct rkvenc_dev *dev = container_of(to_delayed_work(work),
+ struct rkvenc_dev, watchdog);
+ struct rkvenc_ctx *ctx;
+ unsigned long flags;
+
+ spin_lock_irqsave(&dev->irq_lock, flags);
+ if (!dev->job_running) {
+ spin_unlock_irqrestore(&dev->irq_lock, flags);
+ return;
+ }
+ ctx = dev->job_ctx;
+ dev->job_running = false;
+ spin_unlock_irqrestore(&dev->irq_lock, flags);
+
+ dev_err(dev->dev, "RKVENC encode timed out\n");
+ rkvenc_finish_job(dev, ctx, VB2_BUF_STATE_ERROR);
+}
+
+static int rkvenc_runtime_resume(struct device *dev)
+{
+ struct rkvenc_dev *rkvenc = dev_get_drvdata(dev);
+
+ return clk_bulk_prepare_enable(rkvenc->num_clocks, rkvenc->clocks);
+}
+
+static int rkvenc_runtime_suspend(struct device *dev)
+{
+ struct rkvenc_dev *rkvenc = dev_get_drvdata(dev);
+
+ clk_bulk_disable_unprepare(rkvenc->num_clocks, rkvenc->clocks);
+ return 0;
+}
+
+static int rkvenc_suspend(struct device *dev)
+{
+ struct rkvenc_dev *rkvenc = dev_get_drvdata(dev);
+
+ v4l2_m2m_suspend(rkvenc->m2m_dev);
+ return pm_runtime_force_suspend(dev);
+}
+
+static int rkvenc_resume(struct device *dev)
+{
+ struct rkvenc_dev *rkvenc = dev_get_drvdata(dev);
+ int ret;
+
+ ret = pm_runtime_force_resume(dev);
+ if (ret)
+ return ret;
+ /* Jobs rewrite every register and the references stay in memory */
+ v4l2_m2m_resume(rkvenc->m2m_dev);
+ return 0;
+}
+
+static void rkvenc_init_clocks_resets(struct rkvenc_dev *rkvenc)
+{
+ static const char *const clk_names[] = { "axi", "ahb", "core" };
+ static const char *const rst_names[] = { "axi", "ahb", "core" };
+ unsigned int i;
+
+ for (i = 0; i < RKVENC_NUM_CLKS; i++)
+ rkvenc->clocks[i].id = clk_names[i];
+ for (i = 0; i < RKVENC_NUM_RESETS; i++)
+ rkvenc->resets[i].id = rst_names[i];
+ rkvenc->num_clocks = RKVENC_NUM_CLKS;
+ rkvenc->num_resets = RKVENC_NUM_RESETS;
+}
+
+static int rkvenc_probe(struct platform_device *pdev)
+{
+ const struct rkvenc_variant *variant;
+ struct rkvenc_dev *rkvenc;
+ struct device *dev = &pdev->dev;
+ u32 ver, pic_size;
+ int ret, irq;
+
+ variant = of_device_get_match_data(dev);
+ if (!variant)
+ return -EINVAL;
+
+ rkvenc = devm_kzalloc(dev, sizeof(*rkvenc), GFP_KERNEL);
+ if (!rkvenc)
+ return -ENOMEM;
+
+ platform_set_drvdata(pdev, rkvenc);
+ rkvenc->dev = dev;
+ rkvenc->variant = variant;
+
+ spin_lock_init(&rkvenc->irq_lock);
+ mutex_init(&rkvenc->vdev_lock);
+ init_completion(&rkvenc->sclr_done);
+ INIT_DELAYED_WORK(&rkvenc->watchdog, rkvenc_watchdog_work);
+
+ rkvenc->regs = devm_platform_ioremap_resource(pdev, 0);
+ if (IS_ERR(rkvenc->regs))
+ return PTR_ERR(rkvenc->regs);
+
+ rkvenc_init_clocks_resets(rkvenc);
+
+ ret = devm_clk_bulk_get(dev, rkvenc->num_clocks, rkvenc->clocks);
+ if (ret)
+ return ret;
+
+ ret = devm_reset_control_bulk_get_exclusive(dev, rkvenc->num_resets,
+ rkvenc->resets);
+ if (ret)
+ return ret;
+
+ ret = dma_set_mask_and_coherent(dev, DMA_BIT_MASK(32));
+ if (ret) {
+ dev_err(dev, "Could not set DMA mask\n");
+ return ret;
+ }
+
+ ret = vb2_dma_contig_set_max_seg_size(dev, DMA_BIT_MASK(32));
+ if (ret)
+ return ret;
+
+ irq = platform_get_irq(pdev, 0);
+ if (irq < 0)
+ return irq;
+
+ ret = devm_request_threaded_irq(dev, irq, rkvenc_irq,
+ rkvenc_irq_thread, IRQF_ONESHOT,
+ dev_name(dev), rkvenc);
+ if (ret) {
+ dev_err(dev, "Could not request encoder IRQ\n");
+ return ret;
+ }
+
+ pm_runtime_enable(dev);
+
+ ret = pm_runtime_resume_and_get(dev);
+ if (ret)
+ return ret;
+
+ /* VERSION tells the 4K encoder from the 1080p one */
+ ver = readl(rkvenc->regs + RKVENC_VERSION);
+ pic_size = (ver >> 12) & 0xf;
+ switch (pic_size) {
+ case 0:
+ rkvenc->max_width = 4096;
+ rkvenc->max_height = 2304;
+ break;
+ case 1:
+ rkvenc->max_width = 1920;
+ rkvenc->max_height = 1088;
+ break;
+ default:
+ dev_warn(dev, "unknown VERSION pic_size %u (0x%08x)\n",
+ pic_size, ver);
+ rkvenc->max_width = 4096;
+ rkvenc->max_height = 2304;
+ break;
+ }
+ if (pic_size != 0)
+ dev_info(dev, "RKVENC VERSION 0x%08x (pic_size %u)\n",
+ ver, pic_size);
+
+ pm_runtime_put(dev);
+
+ ret = v4l2_device_register(dev, &rkvenc->v4l2_dev);
+ if (ret) {
+ dev_err(dev, "Failed to register V4L2 device\n");
+ goto err_pm_disable;
+ }
+
+ rkvenc->m2m_dev = v4l2_m2m_init(&rkvenc_m2m_ops);
+ if (IS_ERR(rkvenc->m2m_dev)) {
+ v4l2_err(&rkvenc->v4l2_dev, "Failed to init mem2mem device\n");
+ ret = PTR_ERR(rkvenc->m2m_dev);
+ goto err_unreg_v4l2;
+ }
+
+ ret = rkvenc_v4l2_init(rkvenc);
+ if (ret)
+ goto err_release_m2m;
+
+ return 0;
+
+err_release_m2m:
+ v4l2_m2m_release(rkvenc->m2m_dev);
+err_unreg_v4l2:
+ v4l2_device_unregister(&rkvenc->v4l2_dev);
+err_pm_disable:
+ pm_runtime_disable(dev);
+ return ret;
+}
+
+static void rkvenc_remove(struct platform_device *pdev)
+{
+ struct rkvenc_dev *rkvenc = platform_get_drvdata(pdev);
+
+ cancel_delayed_work_sync(&rkvenc->watchdog);
+ rkvenc_v4l2_cleanup(rkvenc);
+ v4l2_m2m_release(rkvenc->m2m_dev);
+ v4l2_device_unregister(&rkvenc->v4l2_dev);
+ pm_runtime_disable(&pdev->dev);
+}
+
+static const struct of_device_id rkvenc_dt_match[] = {
+ { .compatible = "rockchip,rv1126-rkvenc", .data = &rkvenc_variant_rv1126 },
+ { .compatible = "rockchip,rk3568-rkvenc", .data = &rkvenc_variant_rk3568 },
+ { /* sentinel */ },
+};
+MODULE_DEVICE_TABLE(of, rkvenc_dt_match);
+
+static const struct dev_pm_ops rkvenc_pm_ops = {
+ RUNTIME_PM_OPS(rkvenc_runtime_suspend, rkvenc_runtime_resume, NULL)
+ SYSTEM_SLEEP_PM_OPS(rkvenc_suspend, rkvenc_resume)
+};
+
+static struct platform_driver rkvenc_driver = {
+ .probe = rkvenc_probe,
+ .remove = rkvenc_remove,
+ .driver = {
+ .name = "rockchip-rkvenc",
+ .of_match_table = rkvenc_dt_match,
+ .pm = pm_ptr(&rkvenc_pm_ops),
+ },
+};
+
+static int __init rkvenc_init(void)
+{
+ rkvenc_tables_init();
+
+ return platform_driver_register(&rkvenc_driver);
+}
+module_init(rkvenc_init);
+
+static void __exit rkvenc_exit(void)
+{
+ platform_driver_unregister(&rkvenc_driver);
+}
+module_exit(rkvenc_exit);
+
+MODULE_AUTHOR("Daniel Golle <daniel@makrotopia.org>");
+MODULE_DESCRIPTION("Rockchip RKVENC v1 H.264/HEVC encoder driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/media/platform/rockchip/rkvenc/rkvenc.h b/drivers/media/platform/rockchip/rkvenc/rkvenc.h
new file mode 100644
index 0000000000000..df5905a329107
--- /dev/null
+++ b/drivers/media/platform/rockchip/rkvenc/rkvenc.h
@@ -0,0 +1,216 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Rockchip RKVENC v1 H.264 / HEVC encoder driver.
+ *
+ * struct rkvenc_frame carries no V4L2 types, so that a stateless front end
+ * can drive the same hardware back end.
+ */
+
+#ifndef RKVENC_H
+#define RKVENC_H
+
+#include <linux/clk.h>
+#include <linux/completion.h>
+#include <linux/delay.h>
+#include <linux/mutex.h>
+#include <linux/spinlock.h>
+#include <linux/workqueue.h>
+#include <linux/reset.h>
+
+#include <media/v4l2-ctrls.h>
+#include <media/v4l2-device.h>
+#include <media/v4l2-mem2mem.h>
+
+#include "rkvenc-vepu541.h"
+
+#define RKVENC_HDR_MAX 1024
+
+#define RKVENC_SLOTS 2
+#define RKVENC_SLOT_BUFS 4
+
+#define RKVENC_NUM_CLKS 3
+#define RKVENC_NUM_RESETS 3
+
+#define RKVENC_MAX_CONSEC_ERRORS 2
+
+struct rkvenc_dev;
+
+/**
+ * struct rkvenc_variant - per-SoC integration differences.
+ * @vepu540: RK3566/RK3568 "vepu540" rather than RV1109/RV1126 "vepu541".
+ */
+struct rkvenc_variant {
+ bool vepu540;
+};
+
+struct rkvenc_dev {
+ struct v4l2_device v4l2_dev;
+ struct media_device mdev;
+ struct video_device vdev;
+ struct v4l2_m2m_dev *m2m_dev;
+ struct device *dev;
+ void __iomem *regs;
+
+ struct clk_bulk_data clocks[RKVENC_NUM_CLKS];
+ int num_clocks;
+ struct reset_control_bulk_data resets[RKVENC_NUM_RESETS];
+ int num_resets;
+
+ const struct rkvenc_variant *variant;
+
+ struct rkvenc_img img;
+
+ struct mutex vdev_lock; /* serialises the video node ioctls */
+ spinlock_t irq_lock;
+ struct completion sclr_done;
+ u32 irq_sta;
+ struct delayed_work watchdog;
+ bool job_running;
+ struct rkvenc_ctx *job_ctx;
+ u32 max_width;
+ u32 max_height;
+};
+
+struct rkvenc_ctrls {
+ u32 bitrate_mode;
+ u32 bitrate;
+ u32 vbv_size;
+ bool frame_rc_enable;
+ u32 gop_size;
+ u32 i_period;
+ u32 header_mode;
+ bool prepend_spspps;
+ bool force_keyframe;
+
+ u8 profile;
+ u8 level;
+ u8 tier;
+ bool cabac;
+ bool transform_8x8;
+ u8 lf_mode;
+ s8 lf_alpha;
+ s8 lf_beta;
+ s8 hevc_beta_offset;
+ s8 hevc_tc_offset;
+ s8 chroma_qp_offset;
+ u8 min_qp;
+ u8 max_qp;
+ u8 i_frame_qp;
+ u8 p_frame_qp;
+
+ u8 hevc_max_merge;
+ bool hevc_sao;
+ bool hevc_strong_intra;
+ bool hevc_tmv;
+ bool hevc_cabac_init;
+
+ u32 framerate;
+};
+
+struct rkvenc_gop {
+ u32 frames_since_idr;
+ u32 idr_pic_id;
+ u16 frame_num;
+ u16 poc_lsb;
+};
+
+struct rkvenc_ctx {
+ struct v4l2_fh fh;
+ struct rkvenc_dev *dev;
+ struct v4l2_m2m_ctx *m2m_ctx;
+ struct v4l2_ctrl_handler ctrl_hdl;
+ struct mutex vlock;
+
+ u32 src_fourcc;
+ u32 coded_fourcc;
+ enum rkvenc_codec codec;
+ u32 width;
+ u32 height;
+ u32 bytesperline;
+ u32 src_cfmt;
+
+ struct rkvenc_ctrls c;
+
+ struct rkvenc_gop gop;
+ struct rkvenc_slot slot[RKVENC_SLOTS];
+ struct rkvenc_slot_sizes slot_sz;
+ void *slot_cpu[RKVENC_SLOTS][RKVENC_SLOT_BUFS];
+ bool slots_alloc;
+ int cur;
+ int last;
+ bool last_good_valid;
+ int last_good;
+ u32 consecutive_errors;
+ bool overflow_retried;
+
+ u32 src_num_planes;
+
+ bool joined_header_done;
+
+ s32 rc_buf; /* in bits */
+ u32 rc_tavg;
+ s8 rc_qp[2]; /* indexed by frame type, 0 = I, 1 = P */
+ u32 rc_x[2];
+ s32 rc_buf_gop;
+ u32 rc_p_qp_sum;
+ u32 rc_p_qp_cnt;
+ bool rc_active;
+ bool rc_first[2];
+
+ bool header_pending;
+ u8 hdr[RKVENC_HDR_MAX];
+ u32 hdr_len;
+
+ u32 fps;
+ u32 blocks;
+ u32 qp_blocks;
+
+ bool cur_intra;
+ u8 cur_qp;
+ u32 cur_prepend;
+
+ struct rkvenc_h264_seq h264_seq;
+ struct rkvenc_hevc_seq hevc_seq;
+};
+
+/* rkvenc.c */
+int rkvenc_hw_run(struct rkvenc_dev *dev, const struct rkvenc_frame *f);
+void rkvenc_hw_status_read(struct rkvenc_dev *dev, u32 *int_sta, u32 *bytes,
+ u32 *qp_sum);
+void rkvenc_hw_recover(struct rkvenc_dev *dev);
+int rkvenc_v4l2_init(struct rkvenc_dev *dev);
+void rkvenc_v4l2_cleanup(struct rkvenc_dev *dev);
+
+/* rkvenc-v4l2.c */
+extern const struct v4l2_m2m_ops rkvenc_m2m_ops;
+
+/* rkvenc-gop.c */
+void rkvenc_gop_init(struct rkvenc_ctx *ctx);
+bool rkvenc_gop_is_idr(struct rkvenc_ctx *ctx);
+void rkvenc_gop_commit(struct rkvenc_ctx *ctx);
+void rkvenc_gop_rollback(struct rkvenc_ctx *ctx);
+void rkvenc_gop_force_keyframe(struct rkvenc_ctx *ctx);
+
+/* rkvenc-ref.c */
+int rkvenc_ref_alloc(struct rkvenc_ctx *ctx);
+void rkvenc_ref_free(struct rkvenc_ctx *ctx);
+
+/* rkvenc-rc.c */
+void rkvenc_rc_init(struct rkvenc_ctx *ctx);
+void rkvenc_rc_pick(struct rkvenc_ctx *ctx, u8 *qp, u8 frame_type);
+void rkvenc_rc_update(struct rkvenc_ctx *ctx, u32 bytes, u32 qp_avg,
+ u8 frame_type);
+void rkvenc_rc_overflow(struct rkvenc_ctx *ctx, u32 bytes, u8 frame_type);
+
+/* rkvenc-h264.c / rkvenc-hevc.c */
+int rkvenc_h264_setup(struct rkvenc_ctx *ctx);
+int rkvenc_h264_write_headers(struct rkvenc_ctx *ctx, u8 *buf, u32 size);
+void rkvenc_h264_build_pic(struct rkvenc_ctx *ctx,
+ struct rkvenc_h264_pic *pic, bool idr);
+
+int rkvenc_hevc_setup(struct rkvenc_ctx *ctx);
+int rkvenc_hevc_write_headers(struct rkvenc_ctx *ctx, u8 *buf, u32 size);
+void rkvenc_hevc_build_pic(struct rkvenc_ctx *ctx,
+ struct rkvenc_hevc_pic *pic, bool idr);
+
+#endif /* RKVENC_H */
--
2.56.0
next prev parent reply other threads:[~2026-11-14 14:55 UTC|newest]
Thread overview: 7+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-11-14 14:54 [RFC PATCH 0/6] media: rockchip: RKVENC v1 H.264/HEVC encoder for the RV1126 Daniel Golle
2026-11-14 14:54 ` [RFC PATCH 1/6] dt-bindings: media: Add Rockchip RKVENC v1 video encoder Daniel Golle
2026-11-14 14:54 ` [RFC PATCH 2/6] clk: rockchip: rv1126: Add PD_VEPU clock tree Daniel Golle
2026-11-14 14:54 ` Daniel Golle [this message]
2026-11-14 14:55 ` [RFC PATCH 4/6] media: rockchip: rkvenc: Add frequency scaling and cooling Daniel Golle
2026-11-14 14:55 ` [RFC PATCH 5/6] ARM: dts: rockchip: Add RKVENC encoder to RV1126 Daniel Golle
2026-11-14 14:55 ` [RFC PATCH 6/6] ARM: dts: rockchip: Add RKVENC operating points " Daniel Golle
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=96b018d341e884c12e1d5c5c6d42880f134a644c.1794666330.git.daniel@makrotopia.org \
--to=daniel@makrotopia.org \
--cc=bmasney+clk@redhat.com \
--cc=conor+dt@kernel.org \
--cc=detlev.casanova@collabora.com \
--cc=devicetree@vger.kernel.org \
--cc=ezequiel@vanguardiasur.com.ar \
--cc=gerald.loacker@wolfvision.net \
--cc=heiko@sntech.de \
--cc=hverkuil+cisco@kernel.org \
--cc=jbrunet+clk@baylibre.com \
--cc=krzk+dt@kernel.org \
--cc=linux-arm-kernel@lists.infradead.org \
--cc=linux-clk@vger.kernel.org \
--cc=linux-kernel@vger.kernel.org \
--cc=linux-media@vger.kernel.org \
--cc=linux-rockchip@lists.infradead.org \
--cc=mchehab@kernel.org \
--cc=mehdi.djait@linux.intel.com \
--cc=michael.riesch@collabora.com \
--cc=nicolas.dufresne@collabora.com \
--cc=p.zabel@pengutronix.de \
--cc=paulk@sys-base.io \
--cc=robh@kernel.org \
--cc=sakari.ailus@linux.intel.com \
--cc=sboyd@kernel.org \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox
all inboxes | Powered by JetHome®