From 64129c428221712fe2ee4e4472bcdd81e4a4aa61 Mon Sep 17 00:00:00 2001 From: Daniel Golle Date: Sat, 17 Oct 2026 00:15:39 +0100 Subject: [PATCH] rockchip: add the RKVENC H.264 and HEVC encoder Add the stateful V4L2 encoder driver for the RKVENC v1 of the RV1126 and RK3568 as patches 183: its binding, the PD_VEPU clock tree, the driver and the device tree node, followed by the frequency scaling with a cooling device and the operating points that go with it. The operating points carry the vendor voltages, which apply once a board names the supply of the encoder. The NPU thermal zone gets two passive trips and cools the encoder, which lowers its clock and voltage and then its frame rate. The zone is polled every second, since the temperature trend decides when the cooling device is released. Build the devfreq core and the simple_ondemand governor into the ARMv7 kernel for the frequency scaling, and let the 38x38 v10 board name vdd_logic_npu_vepu as the supply of the encoder. Add kmod-video-rockchip-rkvenc and install it on the Herospeed RV1126 board. Signed-off-by: Daniel Golle --- target/linux/rockchip/armv7/config-6.18 | 2 + .../rockchip/dts/rv1126-38x38-v10-emmc.dtsi | 4 + target/linux/rockchip/image/armv7.mk | 2 +- target/linux/rockchip/modules.mk | 16 + ...edia-rockchip-add-rockchip-rkvenc-v1.patch | 127 + ...ckchip-rv1126-add-pd-vepu-clock-tree.patch | 42 + ...ockchip-add-rkvenc-v1-encoder-driver.patch | 4394 +++++++++++++++++ ...-rv1126-add-the-rkvenc-video-encoder.patch | 92 + ...-media-rockchip-rkvenc-add-operating.patch | 31 + ...hip-rkvenc-add-frequency-scaling-and.patch | 601 +++ ...chip-rv1126-add-the-rkvenc-operating.patch | 60 + ...ip-rv1126-bind-the-rkvenc-to-the-npu.patch | 58 + 12 files changed, 5428 insertions(+), 1 deletion(-) create mode 100644 target/linux/rockchip/patches-6.18/183-01-dt-bindings-media-rockchip-add-rockchip-rkvenc-v1.patch create mode 100644 target/linux/rockchip/patches-6.18/183-02-clk-rockchip-rv1126-add-pd-vepu-clock-tree.patch create mode 100644 target/linux/rockchip/patches-6.18/183-03-media-platform-rockchip-add-rkvenc-v1-encoder-driver.patch create mode 100644 target/linux/rockchip/patches-6.18/183-04-arm-dts-rockchip-rv1126-add-the-rkvenc-video-encoder.patch create mode 100644 target/linux/rockchip/patches-6.18/183-05-dt-bindings-media-rockchip-rkvenc-add-operating.patch create mode 100644 target/linux/rockchip/patches-6.18/183-06-media-rockchip-rkvenc-add-frequency-scaling-and.patch create mode 100644 target/linux/rockchip/patches-6.18/183-07-arm-dts-rockchip-rv1126-add-the-rkvenc-operating.patch create mode 100644 target/linux/rockchip/patches-6.18/183-08-arm-dts-rockchip-rv1126-bind-the-rkvenc-to-the-npu.patch diff --git a/target/linux/rockchip/armv7/config-6.18 b/target/linux/rockchip/armv7/config-6.18 index d6c042492cd..4c3f5c4e4bf 100644 --- a/target/linux/rockchip/armv7/config-6.18 +++ b/target/linux/rockchip/armv7/config-6.18 @@ -111,6 +111,7 @@ CONFIG_CRYPTO_LIB_UTILS=y CONFIG_CURRENT_POINTER_IN_TPIDRURO=y CONFIG_DCACHE_WORD_ACCESS=y CONFIG_DEBUG_LL_INCLUDE="mach/debug-macro.S" +CONFIG_DEVFREQ_GOV_SIMPLE_ONDEMAND=y CONFIG_DMADEVICES=y CONFIG_DMA_CMA=y CONFIG_DMA_ENGINE=y @@ -284,6 +285,7 @@ CONFIG_PINCTRL_ROCKCHIP=y CONFIG_PL330_DMA=y CONFIG_PM=y CONFIG_PM_CLK=y +CONFIG_PM_DEVFREQ=y CONFIG_PM_GENERIC_DOMAINS=y CONFIG_PM_GENERIC_DOMAINS_OF=y CONFIG_PM_OPP=y diff --git a/target/linux/rockchip/dts/rv1126-38x38-v10-emmc.dtsi b/target/linux/rockchip/dts/rv1126-38x38-v10-emmc.dtsi index 7b8132e8916..7b2bc210228 100644 --- a/target/linux/rockchip/dts/rv1126-38x38-v10-emmc.dtsi +++ b/target/linux/rockchip/dts/rv1126-38x38-v10-emmc.dtsi @@ -253,6 +253,10 @@ status = "okay"; }; +&venc { + venc-supply = <&vdd_logic_npu_vepu>; +}; + &wdt { status = "okay"; }; diff --git a/target/linux/rockchip/image/armv7.mk b/target/linux/rockchip/image/armv7.mk index 2eefaf50945..60e1a7a4d3f 100644 --- a/target/linux/rockchip/image/armv7.mk +++ b/target/linux/rockchip/image/armv7.mk @@ -47,7 +47,7 @@ define Device/herospeed_rv1126-imx415 DEVICE_PACKAGES := kmod-rtc-pcf8563 kmod-video-core kmod-video-imx415 \ kmod-video-rockchip-cif kmod-phy-rockchip-inno-csidphy \ kmod-leds-pwm-flash kmod-crypto-hw-rockchip2 kmod-video-hantro \ - kmod-video-rockchip-isp2 video-support + kmod-video-rockchip-isp2 kmod-video-rockchip-rkvenc video-support IMAGES := sysupgrade.bin factory-RV1126_IMX415.fls IMAGE/sysupgrade.bin := sysupgrade-tar | append-metadata IMAGE/factory-RV1126_IMX415.fls := herospeed-fls diff --git a/target/linux/rockchip/modules.mk b/target/linux/rockchip/modules.mk index 57f45986119..9f400feb64b 100644 --- a/target/linux/rockchip/modules.mk +++ b/target/linux/rockchip/modules.mk @@ -282,3 +282,19 @@ define KernelPackage/video-rockchip-isp2/description endef $(eval $(call KernelPackage,video-rockchip-isp2)) + +define KernelPackage/video-rockchip-rkvenc + TITLE:=Rockchip RKVENC H.264/HEVC video encoder + DEPENDS:=@TARGET_rockchip +kmod-video-mem2mem +kmod-video-dma-contig + KCONFIG:=CONFIG_VIDEO_ROCKCHIP_RKVENC + FILES:=$(LINUX_DIR)/drivers/media/platform/rockchip/rkvenc/rockchip-rkvenc.ko + AUTOLOAD:=$(call AutoProbe,rockchip-rkvenc) + $(call AddDepends/video) +endef + +define KernelPackage/video-rockchip-rkvenc/description + Stateful V4L2 memory-to-memory encoder driver for the RKVENC v1 video + encoder of the Rockchip RV1126 and RK3568 SoCs (H.264 and HEVC). +endef + +$(eval $(call KernelPackage,video-rockchip-rkvenc)) diff --git a/target/linux/rockchip/patches-6.18/183-01-dt-bindings-media-rockchip-add-rockchip-rkvenc-v1.patch b/target/linux/rockchip/patches-6.18/183-01-dt-bindings-media-rockchip-add-rockchip-rkvenc-v1.patch new file mode 100644 index 00000000000..1c31f4c5c3c --- /dev/null +++ b/target/linux/rockchip/patches-6.18/183-01-dt-bindings-media-rockchip-add-rockchip-rkvenc-v1.patch @@ -0,0 +1,127 @@ +From a18e130d6caff08ebc57a665e7ff8e4aa3c7cb3e Mon Sep 17 00:00:00 2001 +From: Daniel Golle +Date: Mon, 12 Oct 2026 17:10:27 +0100 +Subject: [PATCH] dt-bindings: media: rockchip: Add Rockchip RKVENC v1 encoder + binding + +Add the device tree binding for the Rockchip RKVENC v1 video encoder +found in the RV1126/RV1109 ("vepu541") and RK3566/RK3568 ("vepu540") +SoCs. The block encodes H.264 (AVC) and H.265 (HEVC) from raw +YUV/RGB frames and reaches memory through an IOMMU. + +Document the two compatible strings, the register space, the single +interrupt, the axi/ahb/core clock and reset triplets, the power +domain and the IOMMU phandle. + +Assisted-by: LLM +Signed-off-by: Daniel Golle +--- + .../media/rockchip,rv1126-rkvenc.yaml | 102 ++++++++++++++++++ + 1 file changed, 102 insertions(+) + create mode 100644 Documentation/devicetree/bindings/media/rockchip,rv1126-rkvenc.yaml + +--- /dev/null ++++ b/Documentation/devicetree/bindings/media/rockchip,rv1126-rkvenc.yaml +@@ -0,0 +1,102 @@ ++# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) ++%YAML 1.2 ++--- ++$id: http://devicetree.org/schemas/media/rockchip,rv1126-rkvenc.yaml# ++$schema: http://devicetree.org/meta-schemas/core.yaml# ++ ++title: Rockchip RKVENC v1 H.264/HEVC video encoder ++ ++maintainers: ++ - Daniel Golle ++ ++description: ++ The RKVENC v1 encoder found in the RV1109, RV1126, RK3566 and RK3568 ++ encodes H.264 and HEVC from raw YUV or RGB frames. It reads and writes ++ memory through an IOMMU with two MMU instances. ++ ++properties: ++ compatible: ++ enum: ++ - rockchip,rk3568-rkvenc ++ - rockchip,rv1126-rkvenc ++ ++ reg: ++ maxItems: 1 ++ ++ interrupts: ++ maxItems: 1 ++ ++ clocks: ++ items: ++ - description: AXI master clock ++ - description: AHB register interface clock ++ - description: Encoder core clock ++ ++ clock-names: ++ items: ++ - const: axi ++ - const: ahb ++ - const: core ++ ++ assigned-clocks: true ++ ++ assigned-clock-rates: true ++ ++ resets: ++ items: ++ - description: AXI reset ++ - description: AHB reset ++ - description: Encoder core reset ++ ++ reset-names: ++ items: ++ - const: axi ++ - const: ahb ++ - const: core ++ ++ power-domains: ++ maxItems: 1 ++ ++ iommus: ++ maxItems: 1 ++ ++required: ++ - compatible ++ - reg ++ - interrupts ++ - clocks ++ - clock-names ++ - resets ++ - reset-names ++ - power-domains ++ - iommus ++ ++additionalProperties: false ++ ++examples: ++ - | ++ #include ++ #include ++ #include ++ ++ venc_mmu: iommu@ffbb0800 { ++ compatible = "rockchip,rv1126-iommu"; ++ reg = <0xffbb0800 0x100>; ++ #iommu-cells = <0>; ++ }; ++ ++ video-codec@ffbb0000 { ++ compatible = "rockchip,rv1126-rkvenc"; ++ reg = <0xffbb0000 0xf00>; ++ interrupts = ; ++ clocks = <&cru ACLK_VENC>, <&cru HCLK_VENC>, <&cru CLK_VENC_CORE>; ++ clock-names = "axi", "ahb", "core"; ++ assigned-clocks = <&cru ACLK_VENC>, <&cru CLK_VENC_CORE>; ++ assigned-clock-rates = <297000000>, <396000000>; ++ resets = <&cru SRST_VENC_A>, <&cru SRST_VENC_H>, ++ <&cru SRST_VENC_CORE>; ++ reset-names = "axi", "ahb", "core"; ++ iommus = <&venc_mmu>; ++ power-domains = <&power RV1126_PD_VEPU>; ++ }; ++... diff --git a/target/linux/rockchip/patches-6.18/183-02-clk-rockchip-rv1126-add-pd-vepu-clock-tree.patch b/target/linux/rockchip/patches-6.18/183-02-clk-rockchip-rv1126-add-pd-vepu-clock-tree.patch new file mode 100644 index 00000000000..45e2b1c7830 --- /dev/null +++ b/target/linux/rockchip/patches-6.18/183-02-clk-rockchip-rv1126-add-pd-vepu-clock-tree.patch @@ -0,0 +1,42 @@ +From 1a3e37de23fd12f452c6939d37d0dd62a77a4bf0 Mon Sep 17 00:00:00 2001 +From: Daniel Golle +Date: Fri, 16 Oct 2026 17:39:42 +0100 +Subject: [PATCH] clk: rockchip: rv1126: Add PD_VEPU clock tree + +The video encoder power domain of the RV1126 contains the RKVENC +encoder. Add its AXI and AHB bus clocks, the AXI and AHB clocks of the +encoder and the encoder core clock, together with the two interconnect +gates that stay enabled. + +Assisted-by: LLM +Signed-off-by: Daniel Golle +--- + +--- a/drivers/clk/rockchip/clk-rv1126.c ++++ b/drivers/clk/rockchip/clk-rv1126.c +@@ -756,6 +756,25 @@ static struct rockchip_clk_branch rv1126 + RV1126_CLKSEL_CON(72), 8, 1, MFLAGS, 0, 7, DFLAGS, + RV1126_CLKGATE_CON(11), 1, GFLAGS), + ++ /* PD_VEPU */ ++ COMPOSITE(ACLK_PDVEPU, "aclk_pdvepu", mux_cpll_hpll_gpll_p, 0, ++ RV1126_CLKSEL_CON(40), 6, 2, MFLAGS, 0, 5, DFLAGS, ++ RV1126_CLKGATE_CON(12), 0, GFLAGS), ++ COMPOSITE_NOMUX(HCLK_PDVEPU, "hclk_pdvepu", "aclk_pdvepu", 0, ++ RV1126_CLKSEL_CON(41), 0, 5, DFLAGS, ++ RV1126_CLKGATE_CON(12), 2, GFLAGS), ++ GATE(ACLK_VENC, "aclk_venc", "aclk_pdvepu", 0, ++ RV1126_CLKGATE_CON(12), 5, GFLAGS), ++ GATE(HCLK_VENC, "hclk_venc", "hclk_pdvepu", 0, ++ RV1126_CLKGATE_CON(12), 6, GFLAGS), ++ COMPOSITE(CLK_VENC_CORE, "clk_venc_core", mux_cpll_gpll_hpll_p, 0, ++ RV1126_CLKSEL_CON(40), 14, 2, MFLAGS, 8, 5, DFLAGS, ++ RV1126_CLKGATE_CON(12), 1, GFLAGS), ++ GATE(0, "aclk_pdvepu_niu", "aclk_pdvepu", CLK_IGNORE_UNUSED, ++ RV1126_CLKGATE_CON(12), 3, GFLAGS), ++ GATE(0, "hclk_pdvepu_niu", "hclk_pdvepu", CLK_IGNORE_UNUSED, ++ RV1126_CLKGATE_CON(12), 4, GFLAGS), ++ + /* + * Clock-Architecture Diagram 8 + */ diff --git a/target/linux/rockchip/patches-6.18/183-03-media-platform-rockchip-add-rkvenc-v1-encoder-driver.patch b/target/linux/rockchip/patches-6.18/183-03-media-platform-rockchip-add-rkvenc-v1-encoder-driver.patch new file mode 100644 index 00000000000..c6422e78444 --- /dev/null +++ b/target/linux/rockchip/patches-6.18/183-03-media-platform-rockchip-add-rkvenc-v1-encoder-driver.patch @@ -0,0 +1,4394 @@ +From 4478c9d81c72508da867630331ae5d6e7fa31b9f Mon Sep 17 00:00:00 2001 +From: Daniel Golle +Date: Fri, 16 Oct 2026 23:57:02 +0100 +Subject: [PATCH] media: platform: rockchip: Add RKVENC v1 encoder driver + +Add a stateful V4L2 memory-to-memory encoder driver for the Rockchip +RKVENC v1 video encoder, found in the RV1126/RV1109 ("vepu541") and +RK3566/RK3568 ("vepu540") SoCs. The hardware produces H.264 (AVC) +and H.265 (HEVC) bitstreams; this driver exposes them through the +standard V4L2 stateful encoder interface. + +The code is split into an engine that depends on plain C types only and +a V4L2 front end: + + * The engine (rkvenc-vepu541*.c and rkvenc-{h264,hevc,nal,gop,ref,rc}.c) + builds the hardware register image, writes the parameter sets + (SPS/PPS/VPS), manages the GOP structure (a single short-term + reference, low-delay IDR+P, POC type 0, no B-frames), the two-slot + reference ping-pong and the per-frame rate control. Pictures whose + size does not allow P frames are coded as IDR frames only. + + * The front end (rkvenc.c, rkvenc-v4l2.c) binds the engine to the V4L2 + mem2mem framework: format negotiation with SPS/VPS cropping for + sizes that are not a multiple of the coding block, the control set + (H.264 profile, level, CABAC or CAVLC, 8x8 transform and loop filter + controls, the HEVC counterparts, CBR bitrate, GOP size, force + keyframe), buffer queueing, the device_run to hardware job mapping + and the hard-IRQ/threaded-IRQ completion model with claim-under-lock + job ownership and a watchdog. A failed job is cleared with a force + clear and resets the block only when that does not stop it. + +Both V4L2_MPEG_VIDEO_HEADER_MODE_SEPARATE and JOINED_WITH_1ST_FRAME +(with PREPEND_SPSPPS_TO_IDR) are supported. Hardware row-level rate +control is left disabled; the driver varies the per-picture QP only. +The raw frames are imported without a kernel mapping and a capture +buffer holds half as many bytes as the picture has pixels, so that 4K +streams fit the vmalloc area of 32-bit systems. + +The RK3568 HEVC tiles wider than 1920 pixels and the Q6 VEPU variant +are out of scope. + +Assisted-by: LLM +Signed-off-by: Daniel Golle +--- + 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 | 82 ++ + .../platform/rockchip/rkvenc/rkvenc-h264.c | 90 ++ + .../platform/rockchip/rkvenc/rkvenc-hevc.c | 63 + + .../platform/rockchip/rkvenc/rkvenc-nal.c | 353 +++++ + .../platform/rockchip/rkvenc/rkvenc-nal.h | 12 + + .../platform/rockchip/rkvenc/rkvenc-rc.c | 180 +++ + .../platform/rockchip/rkvenc/rkvenc-ref.c | 127 ++ + .../platform/rockchip/rkvenc/rkvenc-v4l2.c | 1149 +++++++++++++++++ + .../rockchip/rkvenc/rkvenc-vepu541-h264.c | 207 +++ + .../rockchip/rkvenc/rkvenc-vepu541-hevc.c | 211 +++ + .../rockchip/rkvenc/rkvenc-vepu541-regs.h | 283 ++++ + .../rockchip/rkvenc/rkvenc-vepu541-tables.c | 97 ++ + .../platform/rockchip/rkvenc/rkvenc-vepu541.c | 218 ++++ + .../platform/rockchip/rkvenc/rkvenc-vepu541.h | 198 +++ + .../media/platform/rockchip/rkvenc/rkvenc.c | 520 ++++++++ + .../media/platform/rockchip/rkvenc/rkvenc.h | 249 ++++ + 20 files changed, 4073 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 + +--- a/drivers/media/platform/rockchip/Kconfig ++++ b/drivers/media/platform/rockchip/Kconfig +@@ -7,3 +7,4 @@ source "drivers/media/platform/rockchip/ + source "drivers/media/platform/rockchip/rkisp1/Kconfig" + source "drivers/media/platform/rockchip/rkvdec/Kconfig" + source "drivers/media/platform/rockchip/rkisp2/Kconfig" ++source "drivers/media/platform/rockchip/rkvenc/Kconfig" +--- a/drivers/media/platform/rockchip/Makefile ++++ b/drivers/media/platform/rockchip/Makefile +@@ -4,3 +4,4 @@ obj-y += rkcif/ + obj-y += rkisp1/ + obj-y += rkvdec/ + obj-y += rkisp2/ ++obj-y += rkvenc/ +--- /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. +--- /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 +--- /dev/null ++++ b/drivers/media/platform/rockchip/rkvenc/rkvenc-gop.c +@@ -0,0 +1,82 @@ ++// SPDX-License-Identifier: GPL-2.0-only ++/* ++ * GOP / frame-type state machine for the RKVENC v1 encoder. ++ * ++ * One short-term reference, low-delay IDR+P, POC type 0, no B frames. ++ * The slot ping-pong (cur writes, last is the reference; after a good ++ * frame last = cur; cur ^= 1) mirrors the validated lab harness exactly ++ * so the driver produces the same coding structure the hardware was ++ * measured against. 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; ++} ++ ++/* Pictures too small for the hardware to code P frames correctly. */ ++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)); ++} ++ ++/* Decide whether the next frame is an IDR. Called from device_run(). */ ++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; ++ /* After a recovery with no usable reference, resync with an IDR. */ ++ 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; ++ /* A new GOP starts; the next (P) frame is #1 of it. */ ++ 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) ++{ ++ /* The failed frame did not touch the reference slot, so there is ++ * nothing to undo except to drop the consumed force-key-frame flag. ++ */ ++ ctx->c.force_keyframe = false; ++} ++ ++void rkvenc_gop_force_keyframe(struct rkvenc_ctx *ctx) ++{ ++ ctx->c.force_keyframe = true; ++} +--- /dev/null ++++ b/drivers/media/platform/rockchip/rkvenc/rkvenc-h264.c +@@ -0,0 +1,90 @@ ++// SPDX-License-Identifier: GPL-2.0-only ++/* ++ * H.264 syntax state and parameter-set generation for the RKVENC v1 ++ * encoder. The same struct rkvenc_h264_seq feeds both the hardware ++ * SYNT_* registers (through the engine) and the software SPS/PPS writer, ++ * so the slice header the hardware emits and the SPS/PPS cannot disagree. ++ * ++ * POC type 0, one short-term reference, max_num_ref_frames 1, every intra ++ * picture is an IDR (spec/05 2, 8.2). The parameter sets are cached at ++ * stream configuration and copied (not regenerated) per IDR. ++ */ ++ ++#include ++ ++#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; ++ } ++} ++ ++/* Build the per-stream SPS/PPS values from the control snapshot. */ ++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); ++ /* Smallest frame-num / POC ranges that exceed any reachable GOP. */ ++ seq->log2_max_frame_num_m4 = 4; /* 2^8 = 256 */ ++ seq->log2_max_poc_lsb_m4 = 5; /* 2^9 = 512 */ ++ 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); ++} ++ ++/* Copy the cached parameter sets into a capture buffer. */ ++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; ++} +--- /dev/null ++++ b/drivers/media/platform/rockchip/rkvenc/rkvenc-hevc.c +@@ -0,0 +1,63 @@ ++// SPDX-License-Identifier: GPL-2.0-only ++/* ++ * HEVC syntax state and parameter-set generation for the RKVENC v1 ++ * encoder. Same discipline as rkvenc-h264.c: one struct rkvenc_hevc_seq ++ * feeds both the engine and the VPS/SPS/PPS writer. Fixed coding ++ * structure of spec/06 1.2: Main profile, one short-term reference, POC ++ * type 0, nal_unit_type 19 for IDR and 1 for P (the reference value; ++ * question Q12 on type 20 is open). ++ */ ++ ++#include ++ ++#include "rkvenc.h" ++#include "rkvenc-nal.h" ++ ++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; /* 2^8 = 256 */ ++ 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 ? 19 : 1; ++ pic->poc_lsb = ctx->gop.frames_since_idr & 0xff; ++} +--- /dev/null ++++ b/drivers/media/platform/rockchip/rkvenc/rkvenc-nal.c +@@ -0,0 +1,353 @@ ++// SPDX-License-Identifier: GPL-2.0-only ++/* ++ * Parameter-set writer for the vepu541 lab harness: H.264 SPS/PPS ++ * (ITU-T H.264 7.3.2) and HEVC VPS/SPS/PPS (ITU-T H.265 7.3.2), with ++ * the fixed coding structure the hardware assumes (spec/05 2, spec/06 ++ * 1.2, 2.2, 2.3). ++ */ ++ ++#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); ++} ++ ++/* Start code, NAL header, RBSP with emulation prevention (H.264 7.4.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]; ++ ++ 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); /* aspect, overscan, video signal, chroma loc */ ++ 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); /* nal hrd, vcl hrd, pic_struct */ ++ 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); /* frame_mbs_only_flag */ ++ bw_put(bw, 1, 1); /* direct_8x8_inference_flag */ ++ 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); /* weighted_pred_flag, weighted_bipred_idc */ ++ bw_se(bw, seq->pic_init_qp - 26); ++ bw_se(bw, 0); ++ bw_se(bw, seq->chroma_qp_offset); ++ bw_put(bw, 1, 1); /* deblocking_filter_control_present_flag */ ++ bw_put(bw, 0, 2); ++ if (seq->transform_8x8) { ++ bw_put(bw, 1, 1); /* transform_8x8_mode_flag */ ++ bw_put(bw, 0, 1); /* pic_scaling_matrix_present_flag */ ++ 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); /* general_profile_space */ ++ bw_put(bw, 0, 1); /* general_tier_flag: Main */ ++ bw_put(bw, 1, 5); /* general_profile_idc: Main */ ++ bw_put(bw, 0x60000000, 32); /* compatible with profiles 1, 2 */ ++ bw_put(bw, 1, 1); /* progressive_source */ ++ bw_put(bw, 0, 1); /* interlaced_source */ ++ bw_put(bw, 0, 1); /* non_packed_constraint */ ++ bw_put(bw, 1, 1); /* frame_only_constraint */ ++ 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); /* scaling_list_enabled_flag */ ++ bw_put(bw, 0, 1); /* amp_enabled_flag */ ++ bw_put(bw, seq->sao, 1); ++ bw_put(bw, 0, 1); /* pcm_enabled_flag */ ++ bw_ue(bw, 0); /* num_short_term_ref_pic_sets */ ++ bw_put(bw, 0, 1); /* long_term_ref_pics_present_flag */ ++ bw_put(bw, 0, 1); /* sps_temporal_mvp_enabled_flag */ ++ bw_put(bw, seq->strong_intra_smoothing, 1); ++ bw_put(bw, 0, 1); /* vui_parameters_present_flag */ ++ 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); /* dependent_slice_segments_enabled */ ++ bw_put(bw, 0, 1); /* output_flag_present_flag */ ++ bw_put(bw, 0, 3); /* num_extra_slice_header_bits */ ++ bw_put(bw, 0, 1); /* sign_data_hiding_enabled_flag */ ++ 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); /* constrained_intra_pred_flag */ ++ bw_put(bw, 0, 1); /* transform_skip_enabled_flag */ ++ 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); /* slice chroma qp offsets */ ++ bw_put(bw, 0, 1); /* weighted_pred_flag */ ++ bw_put(bw, 0, 1); /* weighted_bipred_flag */ ++ bw_put(bw, 0, 1); /* transquant_bypass_enabled_flag */ ++ bw_put(bw, 0, 1); /* tiles_enabled_flag */ ++ bw_put(bw, 0, 1); /* entropy_coding_sync_enabled_flag */ ++ bw_put(bw, 0, 1); /* loop filter across slices */ ++ bw_put(bw, 1, 1); /* deblocking_filter_control_present_flag */ ++ bw_put(bw, 0, 1); /* deblocking_filter_override_enabled_flag */ ++ 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); /* pps_scaling_list_data_present_flag */ ++ bw_put(bw, 0, 1); /* lists_modification_present_flag */ ++ bw_ue(bw, 0); ++ bw_put(bw, 0, 1); /* slice header extension */ ++ bw_put(bw, 0, 1); /* pps_extension_present_flag */ ++ 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; ++} +--- /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 +--- /dev/null ++++ b/drivers/media/platform/rockchip/rkvenc/rkvenc-rc.c +@@ -0,0 +1,180 @@ ++// SPDX-License-Identifier: GPL-2.0-only ++/* ++ * Frame-level integer rate controller for the RKVENC v1 encoder ++ * (design/02 section 12.3). ++ * ++ * The hardware row-level RC is intentionally left disabled in this series ++ * (the engine is given f->rc == NULL), so the controller only varies the ++ * per-picture QP; the picture itself is encoded at a constant QP. The QP ++ * scale qs[] is shared by H.264 and HEVC (quantiser step doubles every ++ * six QP) and is derived exactly from round(256 * 2^((q-4)/6)). ++ * ++ * The policy constants the design marks "needs approval" (C11 VBV size, ++ * C12 I/P ratio clamp, C13 QP step, D horizon, C6 default fps) are ++ * replaced here by derived safe values, each flagged in a comment. They ++ * are not hardware facts and can be tightened once measured on real ++ * camera content. ++ */ ++ ++#include ++#include ++ ++#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 } ++ ++/* Derived safe policy values (design/04 "needs approval" boxes). */ ++#define RKVENC_RC_W_CLAMP 4096u /* C12: I/P complexity ratio cap */ ++#define RKVENC_RC_QP_STEP 4u /* C13: max QP change per frame/type */ ++#define RKVENC_RC_DEFAULT_FPS 30u /* C6: default frame rate (S_PARM) */ ++ ++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; ++ } ++} ++ ++void rkvenc_rc_init(struct rkvenc_ctx *ctx) ++{ ++ u32 bitrate = ctx->c.bitrate ?: 4000000; ++ u32 fps = ctx->fps ?: RKVENC_RC_DEFAULT_FPS; ++ u32 gop = ctx->c.gop_size; ++ ++ rkvenc_qs_build(); ++ ++ ctx->rc_tavg = div_u64((u64)bitrate, fps); ++ if (!ctx->rc_tavg) ++ ctx->rc_tavg = 1; ++ /* C11: Bs = T_avg * GOP length (one GOP of bits). */ ++ if (ctx->c.vbv_size) ++ ctx->rc_buf = (s32)min_t(u64, (u64)ctx->c.vbv_size, ++ (u64)S32_MAX); ++ else ++ ctx->rc_buf = (s32)(ctx->rc_tavg * max_t(u32, gop, 1)); ++ 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; ++} ++ ++/* ++ * Pick the QP for the next frame of type t (0 = I, 1 = P). Called from ++ * device_run(); the chosen value is written to *qp. ++ */ ++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; ++ unsigned int q; ++ ++ if (!ctx->rc_active) { ++ *qp = frame_type ? ctx->c.p_frame_qp : ctx->c.i_frame_qp; ++ return; ++ } ++ ++ /* First frame of a type: no model yet, use the control QP. */ ++ if (ctx->rc_first[t]) { ++ *qp = frame_type ? ctx->c.p_frame_qp : ctx->c.i_frame_qp; ++ return; ++ } ++ ++ /* ++ * T_I/T_P keep the GOP average at T_avg; the buffer correction ++ * over D frames pulls the rate back toward target. qs_target is ++ * the quantiser step that, at the measured complexity, would ++ * have produced T bits; the smallest QP whose qs[] reaches it ++ * wins. ++ */ ++ 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); ++ u32 d = max_t(u32, (u32)(ctx->rc_buf < 0 ? -ctx->rc_buf : ++ ctx->rc_buf) / ctx->rc_tavg, 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; ++ /* Buffer correction: spend down an excess, save a deficit. */ ++ if (ctx->rc_buf < 0) ++ t_t += (u32)div_s64(div_s64((s64)-ctx->rc_buf, d), ++ ctx->blocks); ++ else ++ t_t -= (u32)div_s64(div_s64((s64)ctx->rc_buf, d), ++ ctx->blocks); ++ if (t_t < 1) ++ t_t = 1; ++ ++ 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); ++ /* C13: limit the per-type QP swing to avoid oscillation. */ ++ 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; ++ *qp = q; ++} ++ ++/* ++ * Update the controller after a completed frame of type t produced ++ * bytes = hdr + ST_BSL bits of output at average QP qp_avg. ++ */ ++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; /* frame size in bits */ ++ u32 vbv = ctx->c.vbv_size ?: ctx->rc_tavg; ++ s64 cap = max_t(u64, vbv, 1); ++ ++ if (!ctx->rc_active) ++ return; ++ ++ /* X_t = S * qs[q_avg] / 256. */ ++ if (qp_avg <= RKVENC_QP_MAX) ++ ctx->rc_x[t] = (u32)div_u64(s * rkvenc_qs[qp_avg], 256); ++ else ++ ctx->rc_x[t] = (u32)div_u64(s * rkvenc_qs[RKVENC_QP_MAX], 256); ++ ++ /* Leaky bucket: f += S - T_avg, clamped to [-Bs, Bs]. */ ++ 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; ++} +--- /dev/null ++++ b/drivers/media/platform/rockchip/rkvenc/rkvenc-ref.c +@@ -0,0 +1,127 @@ ++// SPDX-License-Identifier: GPL-2.0-only ++/* ++ * Reconstruction / reference slot pool for the RKVENC v1 encoder. ++ * ++ * Two slots alternate in the single short-term reference (spec/05 8.4, ++ * spec/06 3.10): a failed frame never touches a reference slot, so ++ * rollback is free. The buffers are sized by the validated formulas in ++ * rkvenc_vepu541_slot_sizes() and allocated as separate DMA regions so an ++ * overrun of one cannot silently corrupt another. ++ */ ++ ++#include ++#include ++ ++#include "rkvenc.h" ++ ++/* Index into slot_cpu[][4] / the per-slot allocation order. */ ++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[4]; ++ u32 handles[4]; ++ 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 < 4; 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; ++ size_t sizes[4]; ++ int ret = 0; ++ ++ if (ctx->slots_alloc) ++ return 0; ++ ++ rkvenc_vepu541_slot_sizes(ctx->codec, ctx->width, ctx->height, sz); ++ ++ sizes[RKVENC_BUF_HDR] = sz->rfp_hdr; ++ sizes[RKVENC_BUF_BODY] = sz->rfp_body; ++ sizes[RKVENC_BUF_DSP] = sz->dsp; ++ sizes[RKVENC_BUF_CMV] = sz->cmv; ++ ++ for (i = 0; i < RKVENC_SLOTS; i++) { ++ /* Reconstruction header. Body follows it with a 4 KiB step. */ ++ 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; ++} +--- /dev/null ++++ b/drivers/media/platform/rockchip/rkvenc/rkvenc-v4l2.c +@@ -0,0 +1,1149 @@ ++// SPDX-License-Identifier: GPL-2.0-only ++/* ++ * V4L2 stateful mem-to-mem front end for the Rockchip RKVENC v1 encoder. ++ * ++ * This file is the thin V4L2 (and only V4L2) layer: it exposes the OUTPUT ++ * (raw) and CAPTURE (coded) queues, the standard encoder controls, the ++ * format/sizing negotiation and the m2m job hook that hands a fully formed ++ * struct rkvenc_frame to the codec-neutral engine. All coding-structure ++ * knowledge (GOP, reference picture management, rate control, parameter ++ * sets) lives in the engine and is driven here through rkvenc_*.c. ++ * ++ * Coding structure (see design/02-architecture.md): low-delay IDR+P, one ++ * short-term reference, POC type 0, no B frames. The parameter sets for a ++ * stream are generated once and copied (never regenerated differently) ++ * into every IDR; in SEPARATE_HEADER_MODE they are emitted as the first ++ * CAPTURE buffer, in JOINED_WITH_1ST_FRAME they are prepended to the first ++ * IDR's coded slice data. ++ */ ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#include "rkvenc.h" ++ ++static inline struct rkvenc_ctx *fh_to_rkvenc_ctx(void *priv) ++{ ++ return container_of(priv, struct rkvenc_ctx, fh); ++} ++ ++/* ----------------------------------------------------------------------- */ ++/* Format tables */ ++/* ----------------------------------------------------------------------- */ ++ ++struct rkvenc_fmt { ++ u32 fourcc; ++ u8 num_planes; ++}; ++ ++/* OUTPUT (raw) formats: the engine only understands NV12. */ ++static const struct rkvenc_fmt rkvenc_output_fmts[] = { ++ { V4L2_PIX_FMT_NV12, 1 }, ++ { V4L2_PIX_FMT_NV12M, 2 }, ++}; ++ ++/* CAPTURE (coded) formats. */ ++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; ++} ++ ++/* ----------------------------------------------------------------------- */ ++/* Control snapshot helpers */ ++/* ----------------------------------------------------------------------- */ ++ ++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; ++} ++ ++/* ++ * Read the live control values into the per-stream snapshot ctx->c and ++ * derive rc_active. Called from device_run() (every frame) so runtime ++ * control changes (QP limits, GOP size, level, ...) take effect on the ++ * next frame. ++ */ ++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); ++ /* Either GOP_SIZE or I_PERIOD may drive IDR cadence. */ ++ 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); ++ /* No HEVC_SAO control in this uAPI revision; SAO tracks the ++ * loop-filter mode (a disabled loop filter implies no SAO). */ ++ 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); ++ /* HEVC bitstreams are always CABAC; no cabac_init control. */ ++ c->hevc_cabac_init = 0; ++ /* Either GOP_SIZE or REFRESH_PERIOD may drive IDR cadence. */ ++ 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; ++} ++ ++/* Generate (or refresh) the parameter sets cached in ctx->hdr/hdr_len. */ ++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); ++ } ++} ++ ++/* ----------------------------------------------------------------------- */ ++/* Sizing */ ++/* ----------------------------------------------------------------------- */ ++ ++static u32 rkvenc_output_plane_size(struct rkvenc_ctx *ctx, unsigned int plane) ++{ ++ u32 bpl = ctx->bytesperline; ++ u32 h = ctx->height; ++ ++ /* NV12: luma + interleaved chroma share the buffer; the chroma ++ * starts at bpl * h. NV12M: two distinct planes. */ ++ 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; ++} ++ ++/* ----------------------------------------------------------------------- */ ++/* vb2 queue callbacks */ ++/* ----------------------------------------------------------------------- */ ++ ++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)) { ++ /* Drain: mark the last queued capture buffer with LAST. */ ++ 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; ++ } ++ ++ /* SEPARATE header: emit the cached parameter sets as the ++ * first CAPTURE buffer, without consuming a source frame. */ ++ 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; ++ ++ /* Both formats must be set before the capture queue streams on. */ ++ if (!ctx->coded_fourcc || !ctx->src_fourcc) { ++ rkvenc_return_dst_bufs(ctx); ++ return -EINVAL; ++ } ++ ++ /* H.264 pictures up to two macroblocks wide and two rows high hang. */ ++ 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, ++}; ++ ++/* ----------------------------------------------------------------------- */ ++/* m2m job hook */ ++/* ----------------------------------------------------------------------- */ ++ ++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); ++ ++ /* ++ * The IDR frame is frame #0 of its GOP, so the per-picture ++ * numbering (frame_num / poc_lsb) must start from zero. Reset the ++ * counter here, before build_pic() reads it (design/02 12.2). ++ */ ++ 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); ++ ++ /* JOINED_WITH_1ST_FRAME: prepend the parameter sets to the first ++ * (or, with PREPEND_SPSPPS_TO_IDR, every requested) IDR. */ ++ 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)); ++ ++ 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; ++ ++ /* Slot ping-pong (design/02 12.2): write to cur, reference last ++ * (or cur for an IDR); gop_commit flips them after a good frame. */ ++ 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; ++ /* Hardware row-level RC is left disabled in this series. */ ++ 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, ++}; ++ ++/* ----------------------------------------------------------------------- */ ++/* Controls */ ++/* ----------------------------------------------------------------------- */ ++ ++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: ++ /* Switching to SEPARATE_HEADER_MODE re-emits the parameter sets ++ * on the next stream; JOINED stays valid for the running one. */ ++ 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); ++ ++ /* Bitrate / rate control. Only CBR is wired to the engine. */ ++ 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); ++ ++ /* GOP / key frames. */ ++ 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); ++ ++ /* Header placement. */ ++ 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); ++ ++ /* H.264 specific. */ ++ 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); ++ ++ /* HEVC specific. */ ++ 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); ++ ++ /* QP controls (per codec; the driver applies only the one matching ++ * the active coded format). */ ++ 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); ++ ++ /* Read-only average QP (updated by the IRQ thread). */ ++ 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); ++ ++ /* With SEPARATE the first CAPTURE buffer carries the parameter ++ * sets. */ ++ 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; ++} ++ ++/* ----------------------------------------------------------------------- */ ++/* Format negotiation */ ++/* ----------------------------------------------------------------------- */ ++ ++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(sizeof(*ctx), GFP_KERNEL); ++ 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); ++} +\ No newline at end of file +--- /dev/null ++++ b/drivers/media/platform/rockchip/rkvenc/rkvenc-vepu541-h264.c +@@ -0,0 +1,207 @@ ++// SPDX-License-Identifier: GPL-2.0-only ++/* ++ * vepu541 register image builder, H.264 view (spec/05, spec/03 4, ++ * spec/04 2.8 default scene). ++ */ ++ ++#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, vepu541 (spec/05 6.1). */ ++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 (spec/11 10.7). */ ++ 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); ++} +--- /dev/null ++++ b/drivers/media/platform/rockchip/rkvenc/rkvenc-vepu541-hevc.c +@@ -0,0 +1,183 @@ ++// SPDX-License-Identifier: GPL-2.0-only ++/* ++ * vepu541 register image builder, HEVC view (spec/06, spec/03b, ++ * spec/04 2.8 default scene). ++ */ ++ ++#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); ++ ++ /* pic_qp quantises, sli_qp is the header QP: keep them equal (spec/11 10.3). */ ++ 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, bits [8:0] used (spec/11 10.7). */ ++ 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); ++} +--- /dev/null ++++ b/drivers/media/platform/rockchip/rkvenc/rkvenc-vepu541-regs.h +@@ -0,0 +1,284 @@ ++/* SPDX-License-Identifier: GPL-2.0-only */ ++#ifndef RKVENC_VEPU541_REGS_H ++#define RKVENC_VEPU541_REGS_H ++ ++#include ++#include ++ ++#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 ++ ++/* L2 space of vepu541: words 0x004..0x30c (spec/11 2.3). */ ++#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 +--- /dev/null ++++ b/drivers/media/platform/rockchip/rkvenc/rkvenc-vepu541-tables.c +@@ -0,0 +1,278 @@ ++// SPDX-License-Identifier: GPL-2.0-only ++/* ++ * Tuning tables of the vepu541 encoder (spec/04). Where the spec gives a ++ * closed form that reproduces a table exactly, the entries are computed ++ * from it once at module load, with integer fixed-point arithmetic. The ++ * tables are read-only afterwards, so the encoding path only copies them. ++ */ ++ ++#include ++#include ++#include ++ ++#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 ++ ++/* T01: weight k = floor(16 * 2^(k / 3)); 16 is the neutral weight */ ++#define RKVENC_H264_KLUT_NEUTRAL 16 ++ ++/* T03: entry 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 ++ ++/* ++ * T04, T05: HM lambda 0.57 * 2^((q - 12) / 3) as unsigned fixed point with ++ * 11 fraction bits. The factor is given in percent, the QP reference is the ++ * QP at which the lambda equals the factor. ++ */ ++#define RKVENC_HEVC_LAMBDA_PCT 57 ++#define RKVENC_HEVC_LAMBDA_QP_REF 12 ++#define RKVENC_HEVC_LAMBDA_FRAC 11 ++#define RKVENC_PERCENT 100 ++ ++/* ++ * T02, T06: weight i = S[i mod 3] * 2^(i div 3), with the first octave of ++ * the chroma cost weights S0..S2 (128 = 2^7, then about 2^(7 + 1/3) and ++ * 2^(7 + 2/3)) and of the lambda B S0..S2. T02 saturates at the 18 bits of ++ * a table entry. ++ */ ++#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 ++ ++/* ++ * Packing of the KLUT weights (spec/04 section 2.1): three weights per ++ * word pair. The middle weight is split, its low part in the bits above ++ * RKVENC_KLUT_SPLIT_EVEN_SHIFT of the even word and its high part in the low ++ * bits of the odd word, which carries the third weight above ++ * RKVENC_KLUT_LAST_ODD_SHIFT. ++ */ ++#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); ++ } ++} ++ ++/* ++ * T05 is 0.57 * 2^((q - 12) / 3) * 2048, which is 72.96 * 2^(q / 3), and ++ * T04 is sqrt(0.57) * 2^((q - 12) / 6) * 2048 with the QP reference and ++ * the scale folded into the integer exponent. ++ */ ++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); ++} ++ ++/* spec/04 T07..T11 */ ++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, ++}; ++ ++/* spec/04 T12..T15 */ ++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 }; +--- /dev/null ++++ b/drivers/media/platform/rockchip/rkvenc/rkvenc-vepu541.c +@@ -0,0 +1,218 @@ ++// SPDX-License-Identifier: GPL-2.0-only ++/* ++ * vepu541 register image builder, codec-independent part. ++ * ++ * Pure functions of struct rkvenc_frame into struct rkvenc_img; shared ++ * unchanged by the lab harness and the driver. ++ */ ++ ++#include ++ ++#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(ebit64, (u64)RKVENC_RC_TGT_CTU_EBIT); ++ rkvenc_img_l1(img, RKVENC_RC_TGT, ebit); ++ ++ /* Deviation is per interval of row_blocks blocks (spec/11 9.1). */ ++ 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; ++ ++ /* H.264 pictures one or two MBs wide with two MB rows hang (spec/11 8.2). */ ++ if (f->codec == RKVENC_CODEC_H264 && ALIGN(w, 16) <= 32 && ++ ALIGN(h, 16) > 16) ++ return -EINVAL; ++ ++ /* HEVC P pictures one CTU row high decode wrongly (spec/11 8.3). */ ++ 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; ++} +--- /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 ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#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 +--- /dev/null ++++ b/drivers/media/platform/rockchip/rkvenc/rkvenc.c +@@ -0,0 +1,533 @@ ++// SPDX-License-Identifier: GPL-2.0-only ++/* ++ * Rockchip RKVENC v1 platform driver: probe/remove, clocks, resets, power ++ * management, the interrupt (hard + threaded) model, the job watchdog and ++ * the hardware programming of one frame. ++ * ++ * The validated register image is built by the codec-neutral engine in ++ * rkvenc-vepu541*.c (rkvenc_vepu541_img_build()); this file only writes ++ * that image to the device and reacts to its completion. See ++ * design/02-architecture.md section 7 (interrupt/job/watchdog) and ++ * section 10 (register programming). ++ */ ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#include ++#include ++ ++#include "rkvenc-vepu541-regs.h" ++#include "rkvenc.h" ++ ++/* ++ * Job watchdog delay. design/04 section 1.2 lists this as C1, "needs ++ * approval": a bound is unavoidable because the awaited event (the ++ * frame-done interrupt) is exactly the one that failed to arrive. The ++ * lab harness measures the worst frame at 4096x2304 HEVC P as 40.8 ms; ++ * one second leaves a wide safety factor for lower core clocks and ++ * scheduling variance. Replace with the owner-approved value when known. ++ */ ++#define RKVENC_WATCHDOG_MS 1000 ++ ++/* ++ * Safe-clear completion bound (design/04 C3, "needs approval"). Only a ++ * safety net for the wait on sclr_done during recovery; derived generous ++ * placeholder until measured. ++ */ ++#define RKVENC_SAFE_CLEAR_MS 100 ++ ++/* ++ * CRU reset pulse (design/04 C4, "needs approval"). Rockchip soft-reset ++ * ops have assert/deassert only, so this is the delay between the two. ++ * Derived placeholder; the vendor's 5 us has no stated derivation. ++ */ ++#define RKVENC_RESET_PULSE_US 5 ++ ++static const struct rkvenc_variant rkvenc_variant_rv1126 = { ++ .vepu540 = false, ++}; ++ ++static const struct rkvenc_variant rkvenc_variant_rk3568 = { ++ .vepu540 = true, ++}; ++ ++/* rkvenc_hw_run(): build and program one frame, arm the watchdog. */ ++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 latches across a start; clear it first (lab harness ++ * spec/11 4.1, design/02 7). The relaxed writes below are ordered ++ * before the final ENC_STRT writel() (design/02 10.2 step 5). ++ */ ++ writel(RKVENC_INT_ALL, regs + RKVENC_INT_CLR); ++ ++ /* L2 window, ascending, with address auto-increment (spec/01 2.2). */ ++ 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); ++ ++ /* ++ * L1 configuration, descending. Skip VERSION/ENC_STRT (below the ++ * window), ENC_CLR and the gaps of spec/02 4, which ++ * rkvenc_vepu541_l1_written() already excludes; ENC_CLR (0x008) ++ * is intentionally omitted (design/02 10.2). ++ */ ++ for (a = RKVENC_L1_CFG_END - 4; a >= 0x00c; a -= 4) ++ if (rkvenc_vepu541_l1_written(a)) ++ writel(img->l1[a / 4], regs + a); ++ ++ /* Order every relaxed write before the start (spec/01 5.4). */ ++ 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"); ++ ++ /* ++ * Mask every source except safe-clear, clear all latched status ++ * (design/02 13 step 1). ++ */ ++ writel(RKVENC_INT_ALL & ~RKVENC_INT_SCLR_DONE, regs + RKVENC_INT_MSK); ++ writel(RKVENC_INT_ALL, regs + RKVENC_INT_CLR); ++ ++ /* Safe clear; wait for sclr_done through the hard-IRQ completion. */ ++ 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)); ++ ++ /* Full CRU reset (design/02 13 step 3a). */ ++ 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); ++ } ++ ++ /* Re-enable all interrupt sources. */ ++ writel(0, regs + RKVENC_INT_MSK); ++} ++ ++/* Hard IRQ: latch INT_STA, clear it, wake the threaded handler. */ ++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); ++ /* Read INT_STA back to flush the posted clear write (design/02 7). */ ++ readl(dev->regs + RKVENC_INT_STA); ++ ++ /* ++ * If recovery is waiting on a safe clear, let it know (design/02 7 ++ * step 3). Harmless if nobody is waiting. ++ */ ++ 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; ++ ++ 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 { ++ rkvenc_hw_recover(dev); ++ rkvenc_gop_rollback(ctx); ++ 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; ++ ++ /* ++ * Claim the job under the IRQ lock: whoever clears job_running ++ * first (this thread or the watchdog) owns the completion, so the ++ * other path backs off. This prevents a double finish. ++ */ ++ 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); ++ ++ /* We own the job now; stop the watchdog from finishing it too. */ ++ cancel_delayed_work(&dev->watchdog); ++ ++ rkvenc_hw_status_read(dev, &int_sta, &bytes, &qp_sum); ++ ++ /* Success iff frame done (bit 0) and no error bits 4..8 (design/02 7). */ ++ 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; ++} ++ ++/* Watchdog: the frame-done interrupt never arrived. */ ++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; ++ /* ++ * Every job rewrites L1 and L2 and the reconstruction buffers keep ++ * their content, so the GOP continues without an IDR (design/02 5). ++ */ ++ 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); ++ ++ /* One register window 0x000..0xeff (design/03, spec/01 2.1). */ ++ 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); ++ ++ /* ++ * Read VERSION to derive the size limit (design/03 step 6). Keep ++ * the device on only for the read. ++ */ ++ ret = pm_runtime_resume_and_get(dev); ++ if (ret) ++ return ret; ++ ++ 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 "); ++MODULE_DESCRIPTION("Rockchip RKVENC v1 H.264/HEVC encoder driver"); ++MODULE_LICENSE("GPL"); +--- /dev/null ++++ b/drivers/media/platform/rockchip/rkvenc/rkvenc.h +@@ -0,0 +1,249 @@ ++/* SPDX-License-Identifier: GPL-2.0-only */ ++/* ++ * Rockchip RKVENC v1 H.264 / HEVC encoder driver. ++ * ++ * Stateful V4L2 memory-to-memory encoder for the RV1109/RV1126 ++ * ("vepu541") and RK3566/RK3568 ("vepu540") encoder. The hardware ++ * programming, reference management, rate control and parameter-set ++ * writing are kept free of V4L2 types so a later stateless or hybrid ++ * front end can reuse them (see design/02-architecture.md). ++ */ ++ ++#ifndef RKVENC_H ++#define RKVENC_H ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#include ++#include ++#include ++ ++#include "rkvenc-vepu541.h" ++ ++/* Worst-case parameter-set bytes written ahead of the hardware output. ++ * H.264 SPS+PPS and HEVC VPS+SPS+PPS fit well within this; the design ++ * notes it is a safe upper bound to be tightened by a KUnit test. ++ */ ++#define RKVENC_HDR_MAX 1024 ++ ++#define RKVENC_SLOTS 2 ++ ++/* Clock and reset names, in bulk-programming order (design/02 3). */ ++#define RKVENC_NUM_CLKS 3 ++#define RKVENC_NUM_RESETS 3 ++ ++/* Consecutive-job failure policy (design/04 C10: "needs approval"). ++ * A second consecutive failed job of one context ends the stream. Until ++ * the owner approves a different value, 2 is the safe, conventional ++ * choice (more retries would hide a persistent fault). ++ */ ++#define RKVENC_MAX_CONSEC_ERRORS 2 ++ ++struct rkvenc_dev; ++ ++/** ++ * struct rkvenc_variant - per-SoC integration differences. ++ * @vepu540: true for the RK3566/RK3568 "vepu540" (longer HEVC L2, tiles, ++ * scaling-list bit 20, OSD_INV_CFG); false for RV1109/RV1126 ++ * "vepu541". The shared register range is identical; the vepu540 ++ * extras are added in series D. ++ */ ++struct rkvenc_variant { ++ bool vepu540; ++}; ++ ++/** ++ * struct rkvenc_dev - device-wide state. ++ * @img: register image of the single in-flight job (one job at a time). ++ */ ++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; ++}; ++ ++/* Snapshot of the codec controls, taken at device_run() time. */ ++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; ++}; ++ ++/* GOP / frame-number state machine (design/02 section 12.2). */ ++struct rkvenc_gop { ++ u32 frames_since_idr; ++ u32 idr_pic_id; ++ u16 frame_num; ++ u16 poc_lsb; ++}; ++ ++/** ++ * struct rkvenc_ctx - per-file-handle (per-stream) state. ++ */ ++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; ++ /* Allocation cookies (hdr, body, dsp, cmv) for freeing; the ++ * hardware addresses live in slot[]. */ ++ void *slot_cpu[RKVENC_SLOTS][4]; ++ bool slots_alloc; ++ int cur; ++ int last; ++ bool last_good_valid; ++ int last_good; ++ u32 consecutive_errors; ++ ++ /* OUTPUT (raw) format details. */ ++ u32 src_num_planes; ++ ++ /* JOINED_WITH_1ST_FRAME header bookkeeping: once the first IDR ++ * has prepended its parameter sets, no later IDR in this stream ++ * does (PREPEND_SPSPPS_TO_IDR additionally forces it). */ ++ bool joined_header_done; ++ ++ /* Driver-side frame-level rate-control state. The hardware ++ * row-level RC is left disabled (the engine gets f->rc == NULL), ++ * so only the per-picture QP is varied, exactly as a fixed-QP ++ * stream with a software-chosen QP per frame. ++ */ ++ s32 rc_buf; /* virtual buffer fullness, in bits */ ++ u32 rc_tavg; /* target bits per frame */ ++ s8 rc_qp[2]; /* last chosen QP per type: 0=I, 1=P */ ++ u32 rc_x[2]; /* last measured complexity per type */ ++ bool rc_active; ++ bool rc_first[2]; /* first frame of each type seen */ ++ ++ bool header_pending; ++ u8 hdr[RKVENC_HDR_MAX]; ++ u32 hdr_len; ++ ++ u32 fps; /* frame rate for RC and VUI timing */ ++ u32 blocks; /* coding-block count of the current stream */ ++ u32 qp_blocks; /* blocks summed into the hardware QP sum */ ++ ++ /* Per-job bookkeeping for the IRQ thread / watchdog. Set in ++ * rkvenc_device_run(), read in the completion path. ++ */ ++ bool cur_intra; /* IDR (keyframe) vs P */ ++ u8 cur_qp; /* QP chosen for the running frame */ ++ u32 cur_prepend; /* header bytes prepended before this frame */ ++ ++ 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); ++ ++/* 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 */ diff --git a/target/linux/rockchip/patches-6.18/183-04-arm-dts-rockchip-rv1126-add-the-rkvenc-video-encoder.patch b/target/linux/rockchip/patches-6.18/183-04-arm-dts-rockchip-rv1126-add-the-rkvenc-video-encoder.patch new file mode 100644 index 00000000000..dcd098231c4 --- /dev/null +++ b/target/linux/rockchip/patches-6.18/183-04-arm-dts-rockchip-rv1126-add-the-rkvenc-video-encoder.patch @@ -0,0 +1,92 @@ +From de2e57e70ad2eb96fa88193bd700b4a7e68ff75c Mon Sep 17 00:00:00 2001 +From: Daniel Golle +Date: Fri, 16 Oct 2026 17:39:58 +0100 +Subject: [PATCH] ARM: dts: rockchip: rv1126: Add the RKVENC video encoder + +Add the encoder with its IOMMU, the VEPU power domain and the three +QoS nodes of the encoder ports. The encoder AXI clock is set to +297 MHz and the core clock to 396 MHz. + +Assisted-by: LLM +Signed-off-by: Daniel Golle +--- + arch/arm/boot/dts/rockchip/rv1126.dtsi | 54 ++++++++++++++++++++++++++ + 1 file changed, 54 insertions(+) + +--- a/arch/arm/boot/dts/rockchip/rv1126.dtsi ++++ b/arch/arm/boot/dts/rockchip/rv1126.dtsi +@@ -331,6 +331,21 @@ + reg = <0xfe8d0000 0x20>; + }; + ++ qos_venc_rd_m0: qos@fe870000 { ++ compatible = "rockchip,rv1126-qos", "syscon"; ++ reg = <0xfe870000 0x20>; ++ }; ++ ++ qos_venc_rd_m1: qos@fe870080 { ++ compatible = "rockchip,rv1126-qos", "syscon"; ++ reg = <0xfe870080 0x20>; ++ }; ++ ++ qos_venc_wr: qos@fe870100 { ++ compatible = "rockchip,rv1126-qos", "syscon"; ++ reg = <0xfe870100 0x20>; ++ }; ++ + qos_ispp_m0: qos@fe880000 { + compatible = "rockchip,rv1126-qos", "syscon"; + reg = <0xfe880000 0x20>; +@@ -472,6 +487,17 @@ + <&qos_iep>; + #power-domain-cells = <0>; + }; ++ ++ power-domain@RV1126_PD_VEPU { ++ reg = ; ++ clocks = <&cru ACLK_VENC>, ++ <&cru HCLK_VENC>, ++ <&cru CLK_VENC_CORE>; ++ pm_qos = <&qos_venc_rd_m0>, ++ <&qos_venc_rd_m1>, ++ <&qos_venc_wr>; ++ #power-domain-cells = <0>; ++ }; + }; + }; + +@@ -1315,6 +1341,34 @@ + status = "disabled"; + }; + ++ venc: video-codec@ffbb0000 { ++ compatible = "rockchip,rv1126-rkvenc"; ++ reg = <0xffbb0000 0xf00>; ++ interrupts = ; ++ clocks = <&cru ACLK_VENC>, <&cru HCLK_VENC>, ++ <&cru CLK_VENC_CORE>; ++ clock-names = "axi", "ahb", "core"; ++ assigned-clocks = <&cru ACLK_VENC>, <&cru CLK_VENC_CORE>; ++ assigned-clock-rates = <297000000>, <396000000>; ++ resets = <&cru SRST_VENC_A>, <&cru SRST_VENC_H>, ++ <&cru SRST_VENC_CORE>; ++ reset-names = "axi", "ahb", "core"; ++ iommus = <&venc_mmu>; ++ power-domains = <&power RV1126_PD_VEPU>; ++ }; ++ ++ venc_mmu: iommu@ffbb0f00 { ++ compatible = "rockchip,iommu"; ++ reg = <0xffbb0f00 0x40>, <0xffbb0f40 0x40>; ++ interrupts = , ++ ; ++ clocks = <&cru ACLK_VENC>, <&cru HCLK_VENC>; ++ clock-names = "aclk", "iface"; ++ #iommu-cells = <0>; ++ power-domains = <&power RV1126_PD_VEPU>; ++ rockchip,disable-mmu-reset; ++ }; ++ + gmac: ethernet@ffc40000 { + compatible = "rockchip,rv1126-gmac", "snps,dwmac-4.20a"; + reg = <0xffc40000 0x4000>; diff --git a/target/linux/rockchip/patches-6.18/183-05-dt-bindings-media-rockchip-rkvenc-add-operating.patch b/target/linux/rockchip/patches-6.18/183-05-dt-bindings-media-rockchip-rkvenc-add-operating.patch new file mode 100644 index 00000000000..fd8e1ea8ed7 --- /dev/null +++ b/target/linux/rockchip/patches-6.18/183-05-dt-bindings-media-rockchip-rkvenc-add-operating.patch @@ -0,0 +1,31 @@ +From 3abba483e809d697f09aab1bc7e1f1e9d2175fc3 Mon Sep 17 00:00:00 2001 +From: Daniel Golle +Date: Fri, 16 Oct 2026 21:16:15 +0100 +Subject: [PATCH] dt-bindings: media: rockchip,rkvenc: Add operating points + +The encoder core clock and its supply are scaled between operating +points, and the encoder is a cooling device of the thermal zones. + +Assisted-by: LLM +Signed-off-by: Daniel Golle +--- + .../devicetree/bindings/media/rockchip,rv1126-rkvenc.yaml | 8 ++++++++ + 1 file changed, 8 insertions(+) + +--- a/Documentation/devicetree/bindings/media/rockchip,rv1126-rkvenc.yaml ++++ b/Documentation/devicetree/bindings/media/rockchip,rv1126-rkvenc.yaml +@@ -42,6 +42,14 @@ properties: + + assigned-clock-rates: true + ++ operating-points-v2: true ++ ++ venc-supply: ++ description: Supply of the encoder core, scaled with the operating points. ++ ++ "#cooling-cells": ++ const: 2 ++ + resets: + items: + - description: AXI reset diff --git a/target/linux/rockchip/patches-6.18/183-06-media-rockchip-rkvenc-add-frequency-scaling-and.patch b/target/linux/rockchip/patches-6.18/183-06-media-rockchip-rkvenc-add-frequency-scaling-and.patch new file mode 100644 index 00000000000..98ad678f09b --- /dev/null +++ b/target/linux/rockchip/patches-6.18/183-06-media-rockchip-rkvenc-add-frequency-scaling-and.patch @@ -0,0 +1,601 @@ +From 199c03b142bb66890d48de1685b42d54290a5394 Mon Sep 17 00:00:00 2001 +From: Daniel Golle +Date: Fri, 16 Oct 2026 23:57:17 +0100 +Subject: [PATCH] media: rockchip: rkvenc: Add frequency scaling and cooling + +The encoder core runs at the fixed 396 MHz of the clock tree setup, +which is a little short of a 4K stream at 30 frames per second. + +Register the core clock with devfreq. The simple_ondemand governor +runs whenever a job completes and takes the busy time of the jobs as +load, so no polling interval is involved. The clock changes only +while no job is on the hardware: a job start waits for a clock change +in progress and a clock change waits for the running job. A venc-supply +in the node is scaled with the operating points. + +Register a cooling device on top. Its first states lower the highest +allowed operating point through a PM QoS frequency limit, one point +per state. The remaining states stretch the period between jobs so +that the busy time stays at 75, 50 and 25 percent of it, which limits +the frame rate once the lowest point is reached. The cooling device, +the QoS request and the devfreq device are removed from remove(), +before the driver data goes away, since their callbacks use it. + +Assisted-by: LLM +Signed-off-by: Daniel Golle +--- + .../media/platform/rockchip/rkvenc/Kconfig | 3 + + .../media/platform/rockchip/rkvenc/Makefile | 1 + + .../platform/rockchip/rkvenc/rkvenc-dvfs.c | 352 ++++++++++++++++++ + .../platform/rockchip/rkvenc/rkvenc-v4l2.c | 19 +- + .../media/platform/rockchip/rkvenc/rkvenc.c | 10 +- + .../media/platform/rockchip/rkvenc/rkvenc.h | 45 +++ + 6 files changed, 427 insertions(+), 3 deletions(-) + create mode 100644 drivers/media/platform/rockchip/rkvenc/rkvenc-dvfs.c + +--- a/drivers/media/platform/rockchip/rkvenc/Kconfig ++++ b/drivers/media/platform/rockchip/rkvenc/Kconfig +@@ -3,7 +3,10 @@ config VIDEO_ROCKCHIP_RKVENC + tristate "Rockchip RKVENC (vepu540/vepu541) H.264/HEVC encoder" + depends on ARCH_ROCKCHIP || COMPILE_TEST + depends on VIDEO_DEV ++ select DEVFREQ_GOV_SIMPLE_ONDEMAND + select MEDIA_CONTROLLER ++ select PM_DEVFREQ ++ select PM_OPP + select VIDEOBUF2_DMA_CONTIG + select V4L2_MEM2MEM_DEV + help +--- a/drivers/media/platform/rockchip/rkvenc/Makefile ++++ b/drivers/media/platform/rockchip/rkvenc/Makefile +@@ -1,5 +1,6 @@ + # SPDX-License-Identifier: GPL-2.0-only + rockchip-rkvenc-y += rkvenc.o \ ++ rkvenc-dvfs.o \ + rkvenc-v4l2.o \ + rkvenc-gop.o \ + rkvenc-ref.o \ +--- /dev/null ++++ b/drivers/media/platform/rockchip/rkvenc/rkvenc-dvfs.c +@@ -0,0 +1,352 @@ ++// SPDX-License-Identifier: GPL-2.0-only ++/* ++ * Rockchip RKVENC v1 frequency scaling and thermal cooling. ++ * ++ * The core clock follows the load through devfreq. The governor is ++ * evaluated whenever a job completes, so there is no polling interval. ++ * The clock only changes while no job is running. ++ * ++ * The cooling device lowers the highest allowed frequency state by state. ++ * Once the lowest operating point is reached, further states stretch the ++ * period between jobs so that the busy time stays within a share of it. ++ */ ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#include "rkvenc.h" ++ ++#define RKVENC_THROTTLE_STEPS 3 ++#define RKVENC_THROTTLE_STEP_PCT 25 ++ ++static unsigned long rkvenc_dvfs_us(ktime_t t) ++{ ++ return min_t(u64, ktime_to_us(t), ULONG_MAX); ++} ++ ++static int rkvenc_dvfs_target(struct device *dev, unsigned long *freq, ++ u32 flags) ++{ ++ struct rkvenc_dev *rkvenc = dev_get_drvdata(dev); ++ struct rkvenc_dvfs *dvfs = &rkvenc->dvfs; ++ struct dev_pm_opp *opp; ++ int ret; ++ ++ opp = devfreq_recommended_opp(dev, freq, flags); ++ if (IS_ERR(opp)) ++ return PTR_ERR(opp); ++ ++ *freq = dev_pm_opp_get_freq(opp); ++ dev_pm_opp_put(opp); ++ ++ mutex_lock(&dvfs->freq_lock); ++ wait_event(dvfs->idle_wq, !READ_ONCE(dvfs->busy)); ++ ret = dev_pm_opp_set_rate(dev, *freq); ++ mutex_unlock(&dvfs->freq_lock); ++ ++ return ret; ++} ++ ++static int rkvenc_dvfs_cur_freq(struct device *dev, unsigned long *freq) ++{ ++ struct rkvenc_dev *rkvenc = dev_get_drvdata(dev); ++ ++ *freq = clk_get_rate(rkvenc->clocks[RKVENC_CLK_CORE].clk); ++ return 0; ++} ++ ++static int rkvenc_dvfs_status(struct device *dev, ++ struct devfreq_dev_status *stat) ++{ ++ struct rkvenc_dev *rkvenc = dev_get_drvdata(dev); ++ struct rkvenc_dvfs *dvfs = &rkvenc->dvfs; ++ unsigned long flags; ++ ktime_t now, busy; ++ ++ spin_lock_irqsave(&dvfs->lock, flags); ++ now = ktime_get(); ++ busy = dvfs->busy_total; ++ if (dvfs->busy) { ++ busy = ktime_add(busy, ktime_sub(now, dvfs->busy_since)); ++ dvfs->busy_since = now; ++ } ++ stat->total_time = rkvenc_dvfs_us(ktime_sub(now, dvfs->last_eval)); ++ stat->busy_time = min(rkvenc_dvfs_us(busy), stat->total_time); ++ dvfs->busy_total = 0; ++ dvfs->last_eval = now; ++ spin_unlock_irqrestore(&dvfs->lock, flags); ++ ++ rkvenc_dvfs_cur_freq(dev, &stat->current_frequency); ++ return 0; ++} ++ ++void rkvenc_dvfs_job_begin(struct rkvenc_dev *rkvenc) ++{ ++ struct rkvenc_dvfs *dvfs = &rkvenc->dvfs; ++ unsigned long flags; ++ ++ mutex_lock(&dvfs->freq_lock); ++ spin_lock_irqsave(&dvfs->lock, flags); ++ dvfs->busy = true; ++ dvfs->job_start = ktime_get(); ++ dvfs->busy_since = dvfs->job_start; ++ spin_unlock_irqrestore(&dvfs->lock, flags); ++ mutex_unlock(&dvfs->freq_lock); ++} ++ ++bool rkvenc_dvfs_start_allowed(struct rkvenc_dev *rkvenc) ++{ ++ struct rkvenc_dvfs *dvfs = &rkvenc->dvfs; ++ unsigned long flags, delay; ++ s64 delay_us; ++ bool allowed; ++ ++ spin_lock_irqsave(&dvfs->lock, flags); ++ delay_us = ktime_to_us(ktime_sub(dvfs->next_start, ktime_get())); ++ allowed = dvfs->duty_pct >= 100 || delay_us <= 0; ++ delay = usecs_to_jiffies(clamp_t(s64, delay_us, 0, UINT_MAX)); ++ if (!allowed) ++ queue_delayed_work(system_wq, &dvfs->throttle_work, delay); ++ spin_unlock_irqrestore(&dvfs->lock, flags); ++ ++ return allowed; ++} ++ ++void rkvenc_dvfs_job_end(struct rkvenc_dev *rkvenc) ++{ ++ struct rkvenc_dvfs *dvfs = &rkvenc->dvfs; ++ struct devfreq *devfreq = dvfs->devfreq; ++ unsigned long flags; ++ ktime_t now; ++ ++ spin_lock_irqsave(&dvfs->lock, flags); ++ now = ktime_get(); ++ dvfs->busy_total = ++ ktime_add(dvfs->busy_total, ktime_sub(now, dvfs->busy_since)); ++ dvfs->next_start = ktime_add_ns( ++ dvfs->job_start, ++ div_u64(ktime_to_ns(ktime_sub(now, dvfs->job_start)) * 100, ++ dvfs->duty_pct)); ++ dvfs->busy = false; ++ spin_unlock_irqrestore(&dvfs->lock, flags); ++ wake_up(&dvfs->idle_wq); ++ ++ if (!devfreq) ++ return; ++ ++ mutex_lock(&devfreq->lock); ++ update_devfreq(devfreq); ++ mutex_unlock(&devfreq->lock); ++} ++ ++static int rkvenc_dvfs_freqs_get(struct rkvenc_dev *rkvenc) ++{ ++ struct rkvenc_dvfs *dvfs = &rkvenc->dvfs; ++ struct device *dev = rkvenc->dev; ++ struct dev_pm_opp *opp; ++ unsigned long freq = 0; ++ int num, i; ++ ++ num = dev_pm_opp_get_opp_count(dev); ++ if (num <= 0) ++ return num ? num : -ENODEV; ++ ++ dvfs->freqs = devm_kcalloc(dev, num, sizeof(*dvfs->freqs), GFP_KERNEL); ++ if (!dvfs->freqs) ++ return -ENOMEM; ++ ++ for (i = 0; i < num; i++, freq++) { ++ opp = dev_pm_opp_find_freq_ceil(dev, &freq); ++ if (IS_ERR(opp)) ++ return PTR_ERR(opp); ++ dvfs->freqs[i] = freq; ++ dev_pm_opp_put(opp); ++ } ++ ++ dvfs->num_freqs = num; ++ return 0; ++} ++ ++static int rkvenc_cdev_max_state(struct thermal_cooling_device *cdev, ++ unsigned long *state) ++{ ++ struct rkvenc_dvfs *dvfs = cdev->devdata; ++ ++ *state = dvfs->num_freqs - 1 + RKVENC_THROTTLE_STEPS; ++ return 0; ++} ++ ++static int rkvenc_cdev_cur_state(struct thermal_cooling_device *cdev, ++ unsigned long *state) ++{ ++ struct rkvenc_dvfs *dvfs = cdev->devdata; ++ ++ *state = dvfs->cooling_state; ++ return 0; ++} ++ ++static int rkvenc_cdev_set_state(struct thermal_cooling_device *cdev, ++ unsigned long state) ++{ ++ struct rkvenc_dvfs *dvfs = cdev->devdata; ++ unsigned long idx = min_t(unsigned long, state, dvfs->num_freqs - 1); ++ unsigned long freq = dvfs->freqs[dvfs->num_freqs - 1 - idx]; ++ unsigned long flags; ++ int ret; ++ ++ ret = dev_pm_qos_update_request(&dvfs->max_freq_req, freq / HZ_PER_KHZ); ++ if (ret < 0) ++ return ret; ++ ++ spin_lock_irqsave(&dvfs->lock, flags); ++ dvfs->duty_pct = 100 - RKVENC_THROTTLE_STEP_PCT * (state - idx); ++ spin_unlock_irqrestore(&dvfs->lock, flags); ++ dvfs->cooling_state = state; ++ ++ return 0; ++} ++ ++static const struct thermal_cooling_device_ops rkvenc_cdev_ops = { ++ .get_max_state = rkvenc_cdev_max_state, ++ .get_cur_state = rkvenc_cdev_cur_state, ++ .set_cur_state = rkvenc_cdev_set_state, ++}; ++ ++static void rkvenc_dvfs_throttle_work(struct work_struct *work) ++{ ++ struct rkvenc_dvfs *dvfs = container_of( ++ to_delayed_work(work), struct rkvenc_dvfs, throttle_work); ++ struct rkvenc_ctx *ctx; ++ ++ mutex_lock(&dvfs->ctx_lock); ++ list_for_each_entry(ctx, &dvfs->ctx_list, dvfs_node) ++ v4l2_m2m_try_schedule(ctx->m2m_ctx); ++ mutex_unlock(&dvfs->ctx_lock); ++} ++ ++void rkvenc_dvfs_ctx_add(struct rkvenc_dev *rkvenc, struct rkvenc_ctx *ctx) ++{ ++ struct rkvenc_dvfs *dvfs = &rkvenc->dvfs; ++ ++ mutex_lock(&dvfs->ctx_lock); ++ list_add_tail(&ctx->dvfs_node, &dvfs->ctx_list); ++ mutex_unlock(&dvfs->ctx_lock); ++} ++ ++void rkvenc_dvfs_ctx_del(struct rkvenc_dev *rkvenc, struct rkvenc_ctx *ctx) ++{ ++ struct rkvenc_dvfs *dvfs = &rkvenc->dvfs; ++ ++ mutex_lock(&dvfs->ctx_lock); ++ list_del(&ctx->dvfs_node); ++ mutex_unlock(&dvfs->ctx_lock); ++} ++ ++void rkvenc_dvfs_exit(struct rkvenc_dev *rkvenc) ++{ ++ struct rkvenc_dvfs *dvfs = &rkvenc->dvfs; ++ ++ thermal_cooling_device_unregister(dvfs->cdev); ++ dvfs->cdev = NULL; ++ ++ if (dvfs->qos_added) ++ dev_pm_qos_remove_request(&dvfs->max_freq_req); ++ dvfs->qos_added = false; ++ ++ if (dvfs->devfreq) ++ devfreq_remove_device(dvfs->devfreq); ++ dvfs->devfreq = NULL; ++ ++ cancel_delayed_work_sync(&dvfs->throttle_work); ++} ++ ++static int rkvenc_dvfs_cooling_init(struct rkvenc_dev *rkvenc) ++{ ++ struct rkvenc_dvfs *dvfs = &rkvenc->dvfs; ++ struct device *dev = rkvenc->dev; ++ int ret; ++ ++ if (!of_property_present(dev->of_node, "#cooling-cells")) ++ return 0; ++ ++ ret = dev_pm_qos_add_request(dev, &dvfs->max_freq_req, ++ DEV_PM_QOS_MAX_FREQUENCY, ++ PM_QOS_MAX_FREQUENCY_DEFAULT_VALUE); ++ if (ret < 0) ++ return ret; ++ dvfs->qos_added = true; ++ ++ dvfs->cdev = thermal_of_cooling_device_register(dev->of_node, "rkvenc", ++ dvfs, &rkvenc_cdev_ops); ++ if (IS_ERR(dvfs->cdev)) { ++ ret = PTR_ERR(dvfs->cdev); ++ dvfs->cdev = NULL; ++ return dev_err_probe(dev, ret, ++ "could not add the cooling device\n"); ++ } ++ ++ return 0; ++} ++ ++int rkvenc_dvfs_init(struct rkvenc_dev *rkvenc) ++{ ++ static const char *const clk_names[] = { "core", NULL }; ++ static const char *const supply_names[] = { "venc", NULL }; ++ struct dev_pm_opp_config opp_config = { .clk_names = clk_names }; ++ struct rkvenc_dvfs *dvfs = &rkvenc->dvfs; ++ struct device *dev = rkvenc->dev; ++ struct devfreq *devfreq; ++ int ret; ++ ++ spin_lock_init(&dvfs->lock); ++ mutex_init(&dvfs->freq_lock); ++ mutex_init(&dvfs->ctx_lock); ++ INIT_LIST_HEAD(&dvfs->ctx_list); ++ INIT_DELAYED_WORK(&dvfs->throttle_work, rkvenc_dvfs_throttle_work); ++ init_waitqueue_head(&dvfs->idle_wq); ++ dvfs->last_eval = ktime_get(); ++ dvfs->duty_pct = 100; ++ ++ if (!of_property_present(dev->of_node, "operating-points-v2")) ++ return 0; ++ ++ if (of_property_present(dev->of_node, "venc-supply")) ++ opp_config.regulator_names = supply_names; ++ ++ ret = devm_pm_opp_set_config(dev, &opp_config); ++ if (ret) ++ return ret; ++ ++ ret = devm_pm_opp_of_add_table(dev); ++ if (ret) ++ return ret; ++ ++ ret = rkvenc_dvfs_freqs_get(rkvenc); ++ if (ret) ++ return ret; ++ ++ dvfs->profile.target = rkvenc_dvfs_target; ++ dvfs->profile.get_dev_status = rkvenc_dvfs_status; ++ dvfs->profile.get_cur_freq = rkvenc_dvfs_cur_freq; ++ dvfs->profile.freq_table = dvfs->freqs; ++ dvfs->profile.max_state = dvfs->num_freqs; ++ rkvenc_dvfs_cur_freq(dev, &dvfs->profile.initial_freq); ++ ++ devfreq = devfreq_add_device(dev, &dvfs->profile, ++ DEVFREQ_GOV_SIMPLE_ONDEMAND, NULL); ++ if (IS_ERR(devfreq)) ++ return dev_err_probe(dev, PTR_ERR(devfreq), ++ "could not add the devfreq device\n"); ++ ++ dvfs->devfreq = devfreq; ++ ret = rkvenc_dvfs_cooling_init(rkvenc); ++ if (ret) ++ rkvenc_dvfs_exit(rkvenc); ++ ++ return ret; ++} +--- a/drivers/media/platform/rockchip/rkvenc/rkvenc-v4l2.c ++++ b/drivers/media/platform/rockchip/rkvenc/rkvenc-v4l2.c +@@ -517,17 +517,22 @@ static void rkvenc_device_run(void *priv + else + rkvenc_hevc_build_pic(ctx, &f.hevc, idr); + ++ rkvenc_dvfs_job_begin(dev); ++ + ret = pm_runtime_resume_and_get(dev->dev); + if (ret) +- goto finish_error; ++ goto finish_error_idle; + + ret = rkvenc_hw_run(dev, &f); + if (ret) { + pm_runtime_put(dev->dev); +- goto finish_error; ++ goto finish_error_idle; + } + return; + ++finish_error_idle: ++ rkvenc_dvfs_job_end(dev); ++ + finish_error: + dev->job_running = false; + dev->job_ctx = NULL; +@@ -538,8 +543,16 @@ finish_error: + v4l2_m2m_job_finish(dev->m2m_dev, ctx->m2m_ctx); + } + ++static int rkvenc_job_ready(void *priv) ++{ ++ struct rkvenc_ctx *ctx = priv; ++ ++ return rkvenc_dvfs_start_allowed(ctx->dev); ++} ++ + const struct v4l2_m2m_ops rkvenc_m2m_ops = { + .device_run = rkvenc_device_run, ++ .job_ready = rkvenc_job_ready, + }; + + /* ----------------------------------------------------------------------- */ +@@ -1024,6 +1037,7 @@ static int rkvenc_open(struct file *filp + if (ret) + goto err_m2m; + ++ rkvenc_dvfs_ctx_add(dev, ctx); + v4l2_fh_add(&ctx->fh, filp); + return 0; + +@@ -1039,6 +1053,7 @@ static int rkvenc_release(struct file *f + { + struct rkvenc_ctx *ctx = fh_to_rkvenc_ctx(filp->private_data); + ++ rkvenc_dvfs_ctx_del(ctx->dev, ctx); + v4l2_m2m_ctx_release(ctx->m2m_ctx); + v4l2_ctrl_handler_free(&ctx->ctrl_hdl); + v4l2_fh_del(&ctx->fh, filp); +--- a/drivers/media/platform/rockchip/rkvenc/rkvenc.c ++++ b/drivers/media/platform/rockchip/rkvenc/rkvenc.c +@@ -240,6 +240,7 @@ static void rkvenc_finish_job(struct rkv + + dev->job_ctx = NULL; + pm_runtime_put(dev->dev); ++ rkvenc_dvfs_job_end(dev); + + v4l2_m2m_buf_done_and_job_finish(dev->m2m_dev, ctx->m2m_ctx, state); + } +@@ -466,12 +467,18 @@ static int rkvenc_probe(struct platform_ + goto err_unreg_v4l2; + } + +- ret = rkvenc_v4l2_init(rkvenc); ++ ret = rkvenc_dvfs_init(rkvenc); + if (ret) + goto err_release_m2m; + ++ ret = rkvenc_v4l2_init(rkvenc); ++ if (ret) ++ goto err_dvfs; ++ + return 0; + ++err_dvfs: ++ rkvenc_dvfs_exit(rkvenc); + err_release_m2m: + v4l2_m2m_release(rkvenc->m2m_dev); + err_unreg_v4l2: +@@ -486,6 +493,7 @@ static void rkvenc_remove(struct platfor + struct rkvenc_dev *rkvenc = platform_get_drvdata(pdev); + + cancel_delayed_work_sync(&rkvenc->watchdog); ++ rkvenc_dvfs_exit(rkvenc); + rkvenc_v4l2_cleanup(rkvenc); + v4l2_m2m_release(rkvenc->m2m_dev); + v4l2_device_unregister(&rkvenc->v4l2_dev); +--- a/drivers/media/platform/rockchip/rkvenc/rkvenc.h ++++ b/drivers/media/platform/rockchip/rkvenc/rkvenc.h +@@ -15,8 +15,14 @@ + #include + #include + #include ++#include ++#include ++#include + #include ++#include + #include ++#include ++#include + #include + #include + +@@ -37,6 +43,7 @@ + /* Clock and reset names, in bulk-programming order (design/02 3). */ + #define RKVENC_NUM_CLKS 3 + #define RKVENC_NUM_RESETS 3 ++#define RKVENC_CLK_CORE 2 + + /* Consecutive-job failure policy (design/04 C10: "needs approval"). + * A second consecutive failed job of one context ends the stream. Until +@@ -47,6 +54,33 @@ + + struct rkvenc_dev; + ++struct rkvenc_dvfs { ++ struct devfreq *devfreq; ++ struct devfreq_dev_profile profile; ++ unsigned long *freqs; ++ unsigned int num_freqs; ++ ++ struct mutex freq_lock; ++ wait_queue_head_t idle_wq; ++ ++ struct thermal_cooling_device *cdev; ++ struct dev_pm_qos_request max_freq_req; ++ bool qos_added; ++ unsigned int cooling_state; ++ struct delayed_work throttle_work; ++ struct mutex ctx_lock; /* protects ctx_list */ ++ struct list_head ctx_list; ++ ++ spinlock_t lock; /* protects the fields below */ ++ bool busy; ++ ktime_t job_start; ++ ktime_t busy_since; ++ ktime_t busy_total; ++ ktime_t last_eval; ++ ktime_t next_start; ++ unsigned int duty_pct; ++}; ++ + /** + * struct rkvenc_variant - per-SoC integration differences. + * @vepu540: true for the RK3566/RK3568 "vepu540" (longer HEVC L2, tiles, +@@ -78,6 +112,7 @@ struct rkvenc_dev { + const struct rkvenc_variant *variant; + + struct rkvenc_img img; ++ struct rkvenc_dvfs dvfs; + + struct mutex vdev_lock; /* serialises the video node ioctls */ + spinlock_t irq_lock; +@@ -144,6 +179,7 @@ struct rkvenc_ctx { + struct v4l2_m2m_ctx *m2m_ctx; + struct v4l2_ctrl_handler ctrl_hdl; + struct mutex vlock; ++ struct list_head dvfs_node; + + u32 src_fourcc; + u32 coded_fourcc; +@@ -215,6 +251,15 @@ void rkvenc_hw_recover(struct rkvenc_dev + int rkvenc_v4l2_init(struct rkvenc_dev *dev); + void rkvenc_v4l2_cleanup(struct rkvenc_dev *dev); + ++/* rkvenc-dvfs.c */ ++int rkvenc_dvfs_init(struct rkvenc_dev *dev); ++void rkvenc_dvfs_exit(struct rkvenc_dev *dev); ++void rkvenc_dvfs_job_begin(struct rkvenc_dev *dev); ++void rkvenc_dvfs_job_end(struct rkvenc_dev *dev); ++bool rkvenc_dvfs_start_allowed(struct rkvenc_dev *dev); ++void rkvenc_dvfs_ctx_add(struct rkvenc_dev *dev, struct rkvenc_ctx *ctx); ++void rkvenc_dvfs_ctx_del(struct rkvenc_dev *dev, struct rkvenc_ctx *ctx); ++ + /* rkvenc-v4l2.c */ + extern const struct v4l2_m2m_ops rkvenc_m2m_ops; + diff --git a/target/linux/rockchip/patches-6.18/183-07-arm-dts-rockchip-rv1126-add-the-rkvenc-operating.patch b/target/linux/rockchip/patches-6.18/183-07-arm-dts-rockchip-rv1126-add-the-rkvenc-operating.patch new file mode 100644 index 00000000000..c1f5ad398ae --- /dev/null +++ b/target/linux/rockchip/patches-6.18/183-07-arm-dts-rockchip-rv1126-add-the-rkvenc-operating.patch @@ -0,0 +1,60 @@ +From bb8ac0eccd38910345f8a60201906a40bed4c1b5 Mon Sep 17 00:00:00 2001 +From: Daniel Golle +Date: Fri, 16 Oct 2026 21:16:15 +0100 +Subject: [PATCH] ARM: dts: rockchip: rv1126: Add the RKVENC operating points + +Add the four core clock rates of the encoder, 297, 396, 500 and 594 +MHz, which the clock tree provides as GPLL/4, GPLL/3, CPLL and GPLL/2, +with the voltages of 725, 725, 750 and 825 mV that the vendor device +tree gives them. The voltages apply on boards that describe the supply +of the encoder as venc-supply. Make the encoder a cooling device so +that thermal zones can use it. + +Assisted-by: LLM +Signed-off-by: Daniel Golle +--- + arch/arm/boot/dts/rockchip/rv1126.dtsi | 26 ++++++++++++++++++++++++++ + 1 file changed, 26 insertions(+) + +--- a/arch/arm/boot/dts/rockchip/rv1126.dtsi ++++ b/arch/arm/boot/dts/rockchip/rv1126.dtsi +@@ -236,6 +236,30 @@ + #clock-cells = <0>; + }; + ++ venc_opp_table: opp-table-venc { ++ compatible = "operating-points-v2"; ++ ++ opp-297000000 { ++ opp-hz = /bits/ 64 <297000000>; ++ opp-microvolt = <725000 725000 1000000>; ++ }; ++ ++ opp-396000000 { ++ opp-hz = /bits/ 64 <396000000>; ++ opp-microvolt = <725000 725000 1000000>; ++ }; ++ ++ opp-500000000 { ++ opp-hz = /bits/ 64 <500000000>; ++ opp-microvolt = <750000 750000 1000000>; ++ }; ++ ++ opp-594000000 { ++ opp-hz = /bits/ 64 <594000000>; ++ opp-microvolt = <825000 825000 1000000>; ++ }; ++ }; ++ + grf: syscon@fe000000 { + compatible = "rockchip,rv1126-grf", "syscon", "simple-mfd"; + reg = <0xfe000000 0x20000>; +@@ -1350,6 +1374,8 @@ + clock-names = "axi", "ahb", "core"; + assigned-clocks = <&cru ACLK_VENC>, <&cru CLK_VENC_CORE>; + assigned-clock-rates = <297000000>, <396000000>; ++ operating-points-v2 = <&venc_opp_table>; ++ #cooling-cells = <2>; + resets = <&cru SRST_VENC_A>, <&cru SRST_VENC_H>, + <&cru SRST_VENC_CORE>; + reset-names = "axi", "ahb", "core"; diff --git a/target/linux/rockchip/patches-6.18/183-08-arm-dts-rockchip-rv1126-bind-the-rkvenc-to-the-npu.patch b/target/linux/rockchip/patches-6.18/183-08-arm-dts-rockchip-rv1126-bind-the-rkvenc-to-the-npu.patch new file mode 100644 index 00000000000..3232d17e863 --- /dev/null +++ b/target/linux/rockchip/patches-6.18/183-08-arm-dts-rockchip-rv1126-bind-the-rkvenc-to-the-npu.patch @@ -0,0 +1,58 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Daniel Golle +Date: Sat, 17 Oct 2026 02:00:00 +0100 +Subject: [PATCH] ARM: dts: rockchip: rv1126: bind the RKVENC to the NPU + thermal zone + +The encoder sits in the VEPU and NPU power region, whose temperature the +NPU TSADC reports. Give the NPU thermal zone passive trips at 85 and 95 +degrees Celsius like the CPU zone has, and let the encoder cool it. At +the first trip the encoder core clock and supply come down to the lowest +operating point, which are the cooling states 0 to 3, and at the second +trip the frame rate is limited as well. The zone is polled every second, +since the step_wise governor releases the cooling device on a falling +trend. + +Assisted-by: LLM +Signed-off-by: Daniel Golle +--- +--- a/arch/arm/boot/dts/rockchip/rv1126.dtsi ++++ b/arch/arm/boot/dts/rockchip/rv1126.dtsi +@@ -148,11 +148,35 @@ + }; + + npu_thermal: npu-thermal { +- polling-delay-passive = <0>; +- polling-delay = <0>; ++ polling-delay-passive = <1000>; ++ polling-delay = <1000>; + thermal-sensors = <&npu_tsadc 0>; + ++ cooling-maps { ++ map0 { ++ trip = <&npu_alert0>; ++ cooling-device = <&venc THERMAL_NO_LIMIT 3>; ++ }; ++ ++ map1 { ++ trip = <&npu_alert1>; ++ cooling-device = <&venc THERMAL_NO_LIMIT THERMAL_NO_LIMIT>; ++ }; ++ }; ++ + trips { ++ npu_alert0: npu-alert0 { ++ temperature = <85000>; ++ hysteresis = <2000>; ++ type = "passive"; ++ }; ++ ++ npu_alert1: npu-alert1 { ++ temperature = <95000>; ++ hysteresis = <2000>; ++ type = "passive"; ++ }; ++ + npu_crit: npu-crit { + temperature = <125000>; + hysteresis = <2000>;