Linux-ARM-Kernel Archive on lore.kernel.org
 help / color / mirror / Atom feed
* [RFC PATCH 0/6] media: rockchip: RKVENC v1 H.264/HEVC encoder for the RV1126
@ 2026-11-14 14:54 Daniel Golle
  2026-11-14 14:54 ` [RFC PATCH 1/6] dt-bindings: media: Add Rockchip RKVENC v1 video encoder Daniel Golle
                   ` (4 more replies)
  0 siblings, 5 replies; 6+ messages in thread
From: Daniel Golle @ 2026-11-14 14:54 UTC (permalink / raw)
  To: Daniel Golle, Mauro Carvalho Chehab, Rob Herring,
	Krzysztof Kozlowski, Conor Dooley, Heiko Stuebner, Stephen Boyd,
	Brian Masney, Jerome Brunet, Philipp Zabel, Sakari Ailus,
	Michael Riesch, Mehdi Djait, Hans Verkuil, Gerald Loacker,
	linux-media, linux-rockchip, devicetree, linux-arm-kernel,
	linux-kernel, linux-clk
  Cc: nicolas.dufresne, detlev.casanova, paulk, ezequiel

This RFC adds a driver for RKVENC v1, the H.264 and HEVC video encoder
of the Rockchip RV1109 and RV1126 (Rockchip calls this generation
"vepu541"; the RK3566 and RK3568 carry the "vepu540" variant). It is a
stateful V4L2 memory-to-memory encoder: NV12 or NV12M in, H.264 or HEVC
Annex B out, low-delay IDR plus P pictures with a single reference, no
B frames. The driver writes the parameter sets (SPS, PPS and VPS), the
hardware writes the slice headers and data. Rate control is frame
level, in the driver: CBR with one QP per picture, the hardware row
rate control stays off. The core clock follows the load through
devfreq and the encoder registers as a cooling device. No new uAPI is
added; the driver uses the existing stateful encoder controls and
formats only.

The series:
  1  binding (with MAINTAINERS entry)
  2  RV1126 clock tree of the VEPU power domain
  3  the driver
  4  frequency scaling and cooling
  5  RV1126 device tree node, IOMMU, power domain and QoS nodes
  6  RV1126 operating points

It is an RFC because of the questions below, which decide where and
in which shape this can go.

Hardware and testing:

I have tested H.264 and H.265 encoding extensively with this driver on
Linux 6.18 in an RV1126 IP camera (Herospeed board, Sony IMX415
sensor) behind the RV1126 ISP, including 3856x2192 and 1280x720 H.265
at 30 fps streamed continuously over RTSP. The driver has been part of
the OpenWrt rockchip/armv7 target since 2026-10-17
(target/linux/rockchip/patches-6.18/183-*), so that testing can be
reproduced by anyone with the hardware.

The port to linux-next in this series is compile-tested only: gcc 16.2
and clang 23, both with W=1, ARM multi_v7_defconfig plus the RKVENC
options; dt_binding_check on the binding and dtbs_check on the RV1109
and RV1126 boards in tree, all without warnings (dtschema 2026.9).
Against the 6.18 code the driver differs in the cooling device
registration signature of the base, kzalloc_obj(), one min_t(), a few
named constants and its comments.

Design questions:

There is no stateless encoder framework upstream yet. The driver
implements a stateful front end only, and keeps the hardware back end
apart from it so that it can move to stateless infrastructure once
that exists:

- Hardware back end: rkvenc.c (job start, interrupt, recovery,
  watchdog, power), rkvenc-vepu541*.c (the register image, built from
  struct rkvenc_frame, which carries no V4L2 types), the tuning tables,
  rkvenc-ref.c (reconstruction and reference buffers) and
  rkvenc-dvfs.c.
- Codec front end: rkvenc-gop.c (IDR cadence, forced key frames, which
  slot is the reference), rkvenc-h264.c and rkvenc-hevc.c (frame_num,
  POC, idr_pic_id and NAL type of each picture), rkvenc-nal.c (the
  parameter set writer) and rkvenc-rc.c (the picture QP).
- V4L2 binding: rkvenc-v4l2.c.

Under the H.264 stateless encode series Paul posted in May, userspace
decides the frame type, frame_num, POC and references and passes them
in V4L2_CID_STATELESS_H264_ENCODE_PARAMS, while header writing and rate
control stay in the kernel as shared helpers. Applied to this driver,
rkvenc-gop.c and the picture fields of rkvenc-h264.c become userspace
input, rkvenc-nal.c and rkvenc-rc.c give way to the shared helpers,
and the back end stays as it is: struct rkvenc_frame is already the
per-picture state such a core hands to a driver. The single reference
with sliding window marking in that series is what the two-slot
reference handling here implements. HEVC has no posted stateless
encode uAPI at the moment. Still to be done for a conversion: the GOP,
reference and rate control code works on struct rkvenc_ctx, which also
carries the V4L2 file handle and control state, and the parameter set
code reads V4L2 control enums; both need a codec state of their own.

Questions:

1. Conformance of a stateful encoder.

   The maintainer entry profile requires v4l2-compliance to pass, and
   dev-encoder.rst defines the interface, but there is no fluster
   equivalent for encoders, and Nicolas noted in January 2025 that we
   have no metric of how good or bad the in-tree encoders are. The plan
   for the next version is: v4l2-compliance -s with streaming from a
   file, plus every stream decoded by FFmpeg and by the H.264 JM and
   HEVC HM reference decoders, the decoded pictures required to be
   bit-identical between the decoders, PSNR reported per picture
   against the source, a static-scene sequence to catch
   encoder/decoder drift, and the bitrate of CBR runs reported against
   the target. Is that what you expect to see for a new encoder, is
   there a test corpus or tool you would rather have used, and should
   the results go into the cover letter or somewhere else?

2. Staging or drivers/media/platform/rockchip.

   hantro and rkvdec were staged because the stateless uAPI was not
   stable yet, see commit fbb6c848dd89 ("media: destage Hantro VPU
   driver"), and allegro left staging once the stateful encoder API
   was final, see commit d74d4e2359ec ("media: allegro: move driver
   out of staging"). This driver uses only the stable stateful encoder
   uAPI and adds no controls, formats or ioctls of its own, so it is
   posted for drivers/media/platform/rockchip/rkvenc. The in-kernel
   rate control and the driver-written parameter sets are the parts
   that differ from the firmware-based stateful encoders in tree. Do
   either of them, or anything else, argue for staging in your view?

3. Stateful now, and the back end for the stateless work?

   dev-encoder.rst strongly discourages header generation in the
   driver for a stateful encoder and advises the stateless encoder
   interface instead, and in the VEPU510 thread Nicolas asked not to
   offer two interfaces for the same hardware. RKVENC v1 is the
   generation before VEPU510/VEPU580, its main users are 32-bit camera
   SoCs whose userspace is GStreamer and v4l2 RTSP servers, and no
   other interface for it has been posted. Nicolas also asked for a
   second implementation of the stateless encoder spec, from another
   vendor than the VC8000E. Would you take the stateful driver now,
   with its back end offered as that second implementation and the
   driver converted once the spec is merged, or should RKVENC v1 only
   come as a stateless driver?

4. Naming. The binding is rockchip,rv1126-rkvenc.yaml with the
   compatibles rockchip,rv1126-rkvenc and rockchip,rk3568-rkvenc, and
   the driver directory rockchip/rkvenc. The RK3576 and RK3588 RFCs
   below used rockchip/rkvenc for a different generation of the
   block. Should RKVENC v1 keep that directory and those names, or
   take generation specific ones (for example vepu541)?

5. RK3566/RK3568. The driver matches rockchip,rk3568-rkvenc with the
   vepu540 variant data, but I don't have hardware to actuall test
   that. Keep the compatible for a later RK356x series, or drop it
   until someone with the hardware has done some testing?

Related postings:

- Jiaxing Hu, "[RFC PATCH 0/3] media: rockchip: VEPU510 H.264 encoder
  for RK3576", stateful, I frames only; the author moved to the Vulkan
  Video direction in the thread:
  https://lore.kernel.org/all/20260722073417.2064667-1-gahing@gahingwoo.com/
- Arun Iyengar, "[PATCH RFC 00/12] media: rockchip: Add a VEPU580
  stateless H.264/HEVC encoder driver for the RK3588", withdrawn by
  the author on 2026-11-13:
  https://lore.kernel.org/all/20261110-rkvenc-v1-0-ea8b47122d8c@gmail.com/
- Paul Kocialkowski, "[PATCH 00/14] media: Add V4L2 H.264 stateless
  encode and VC8000E support":
  https://lore.kernel.org/all/20260522101653.2565125-1-paulk@sys-base.io/

Not in this series:

- Binding the encoder to the RV1126 NPU thermal zone, the zone that
  covers the VEPU power domain. It needs RV1126 TSADC support, which is
  not in mainline yet, and follows with it.
- venc-supply on the RV1126 boards in tree. Their encoder shares the
  vdd_npu_vepu rail with the NPU.

Provenance:

There is no public documentation of this block. The driver was written
by reverse engineering the hardware, with the Rockchip MPP userspace
library as a behavioural reference. A spec was derived by an LLM
agent, and that spec was then used to create the driver

Tool use:

Most of this series was written with multiple LLM coding assistants:
The spec was derived by Claude Sonnet 5.5, handed to a locally run
Qwen3.8-37B which carried out the initial implementation. Claude
Opus 5.5 then did the initial testing and bug fixing, and pre-submission
polishment under my micro-management.
The full test coverage, and long-term testing on the off-the-shelf camera
was done by me.

Daniel Golle (6):
  dt-bindings: media: Add Rockchip RKVENC v1 video encoder
  clk: rockchip: rv1126: Add PD_VEPU clock tree
  media: rockchip: Add RKVENC v1 video encoder driver
  media: rockchip: rkvenc: Add frequency scaling and cooling
  ARM: dts: rockchip: Add RKVENC encoder to RV1126
  ARM: dts: rockchip: Add RKVENC operating points to RV1126

 .../media/rockchip,rv1126-rkvenc.yaml         |  104 ++
 MAINTAINERS                                   |    8 +
 arch/arm/boot/dts/rockchip/rv1126.dtsi        |   80 ++
 drivers/clk/rockchip/clk-rv1126.c             |   21 +
 drivers/media/platform/rockchip/Kconfig       |    1 +
 drivers/media/platform/rockchip/Makefile      |    1 +
 .../media/platform/rockchip/rkvenc/Kconfig    |   20 +
 .../media/platform/rockchip/rkvenc/Makefile   |   16 +
 .../platform/rockchip/rkvenc/rkvenc-dvfs.c    |  364 ++++++
 .../platform/rockchip/rkvenc/rkvenc-gop.c     |   72 ++
 .../platform/rockchip/rkvenc/rkvenc-h264.c    |   82 ++
 .../platform/rockchip/rkvenc/rkvenc-hevc.c    |   63 +
 .../platform/rockchip/rkvenc/rkvenc-nal.c     |  351 ++++++
 .../platform/rockchip/rkvenc/rkvenc-nal.h     |   12 +
 .../platform/rockchip/rkvenc/rkvenc-rc.c      |  214 ++++
 .../platform/rockchip/rkvenc/rkvenc-ref.c     |  114 ++
 .../platform/rockchip/rkvenc/rkvenc-v4l2.c    | 1085 +++++++++++++++++
 .../rockchip/rkvenc/rkvenc-vepu541-h264.c     |  206 ++++
 .../rockchip/rkvenc/rkvenc-vepu541-hevc.c     |  182 +++
 .../rockchip/rkvenc/rkvenc-vepu541-regs.h     |  283 +++++
 .../rockchip/rkvenc/rkvenc-vepu541-tables.c   |  256 ++++
 .../platform/rockchip/rkvenc/rkvenc-vepu541.c |  215 ++++
 .../platform/rockchip/rkvenc/rkvenc-vepu541.h |  201 +++
 .../media/platform/rockchip/rkvenc/rkvenc.c   |  498 ++++++++
 .../media/platform/rockchip/rkvenc/rkvenc.h   |  263 ++++
 25 files changed, 4712 insertions(+)
 create mode 100644 Documentation/devicetree/bindings/media/rockchip,rv1126-rkvenc.yaml
 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-dvfs.c
 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


base-commit: 84abb955b1015477aa11a96e2f3ac1b938b77f48
-- 
2.56.0


^ permalink raw reply	[flat|nested] 6+ messages in thread

* [RFC PATCH 1/6] dt-bindings: media: Add Rockchip RKVENC v1 video encoder
  2026-11-14 14:54 [RFC PATCH 0/6] media: rockchip: RKVENC v1 H.264/HEVC encoder for the RV1126 Daniel Golle
@ 2026-11-14 14:54 ` Daniel Golle
  2026-11-14 14:54 ` [RFC PATCH 2/6] clk: rockchip: rv1126: Add PD_VEPU clock tree Daniel Golle
                   ` (3 subsequent siblings)
  4 siblings, 0 replies; 6+ messages in thread
From: Daniel Golle @ 2026-11-14 14:54 UTC (permalink / raw)
  To: Daniel Golle, Mauro Carvalho Chehab, Rob Herring,
	Krzysztof Kozlowski, Conor Dooley, Heiko Stuebner, Stephen Boyd,
	Brian Masney, Jerome Brunet, Philipp Zabel, Sakari Ailus,
	Michael Riesch, Mehdi Djait, Hans Verkuil, Gerald Loacker,
	linux-media, linux-rockchip, devicetree, linux-arm-kernel,
	linux-kernel, linux-clk
  Cc: nicolas.dufresne, detlev.casanova, paulk, ezequiel

Add the binding of the RKVENC v1 video encoder found in the RV1109 and
RV1126 ("vepu541") and in the RK3566 and RK3568 ("vepu540"). The block
encodes H.264 and HEVC from raw frames and reaches memory through an
IOMMU.

Describe the register space, the interrupt, the AXI, AHB and core
clocks and resets, the power domain and the IOMMU. The core clock is
scaled between operating points with an optional supply, and the
encoder is a cooling device of the thermal zones.

Signed-off-by: Daniel Golle <daniel@makrotopia.org>
---
 .../media/rockchip,rv1126-rkvenc.yaml         | 104 ++++++++++++++++++
 MAINTAINERS                                   |   7 ++
 2 files changed, 111 insertions(+)
 create mode 100644 Documentation/devicetree/bindings/media/rockchip,rv1126-rkvenc.yaml

diff --git a/Documentation/devicetree/bindings/media/rockchip,rv1126-rkvenc.yaml b/Documentation/devicetree/bindings/media/rockchip,rv1126-rkvenc.yaml
new file mode 100644
index 0000000000000..9a83ce1499493
--- /dev/null
+++ b/Documentation/devicetree/bindings/media/rockchip,rv1126-rkvenc.yaml
@@ -0,0 +1,104 @@
+# 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 <daniel@makrotopia.org>
+
+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
+
+  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
+      - 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 <dt-bindings/clock/rockchip,rv1126-cru.h>
+    #include <dt-bindings/interrupt-controller/arm-gic.h>
+    #include <dt-bindings/power/rockchip,rv1126-power.h>
+
+    video-codec@ffbb0000 {
+        compatible = "rockchip,rv1126-rkvenc";
+        reg = <0xffbb0000 0xf00>;
+        interrupts = <GIC_SPI 68 IRQ_TYPE_LEVEL_HIGH>;
+        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/MAINTAINERS b/MAINTAINERS
index 9cd098f0d91f3..26991dd3a3569 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -24544,6 +24544,13 @@ S:	Maintained
 F:	Documentation/devicetree/bindings/media/rockchip,vdec.yaml
 F:	drivers/media/platform/rockchip/rkvdec/
 
+ROCKCHIP RKVENC VIDEO ENCODER DRIVER
+M:	Daniel Golle <daniel@makrotopia.org>
+L:	linux-media@vger.kernel.org
+L:	linux-rockchip@lists.infradead.org
+S:	Maintained
+F:	Documentation/devicetree/bindings/media/rockchip,rv1126-rkvenc.yaml
+
 ROCKCHIP RK3308 INTERNAL AUDIO CODEC
 M:	Luca Ceresoli <luca.ceresoli@bootlin.com>
 S:	Maintained
-- 
2.56.0


^ permalink raw reply related	[flat|nested] 6+ messages in thread

* [RFC PATCH 2/6] clk: rockchip: rv1126: Add PD_VEPU clock tree
  2026-11-14 14:54 [RFC PATCH 0/6] media: rockchip: RKVENC v1 H.264/HEVC encoder for the RV1126 Daniel Golle
  2026-11-14 14:54 ` [RFC PATCH 1/6] dt-bindings: media: Add Rockchip RKVENC v1 video encoder Daniel Golle
@ 2026-11-14 14:54 ` Daniel Golle
  2026-11-14 14:55 ` [RFC PATCH 4/6] media: rockchip: rkvenc: Add frequency scaling and cooling Daniel Golle
                   ` (2 subsequent siblings)
  4 siblings, 0 replies; 6+ messages in thread
From: Daniel Golle @ 2026-11-14 14:54 UTC (permalink / raw)
  To: Daniel Golle, Mauro Carvalho Chehab, Rob Herring,
	Krzysztof Kozlowski, Conor Dooley, Heiko Stuebner, Stephen Boyd,
	Brian Masney, Jerome Brunet, Philipp Zabel, Sakari Ailus,
	Michael Riesch, Mehdi Djait, Hans Verkuil, Gerald Loacker,
	linux-media, linux-rockchip, devicetree, linux-arm-kernel,
	linux-kernel, linux-clk
  Cc: nicolas.dufresne, detlev.casanova, paulk, ezequiel

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 and the two parent selections of the bus and
core clocks.

Signed-off-by: Daniel Golle <daniel@makrotopia.org>
---
 drivers/clk/rockchip/clk-rv1126.c | 21 +++++++++++++++++++++
 1 file changed, 21 insertions(+)

diff --git a/drivers/clk/rockchip/clk-rv1126.c b/drivers/clk/rockchip/clk-rv1126.c
index f35bc9670c274..111595c9d990d 100644
--- a/drivers/clk/rockchip/clk-rv1126.c
+++ b/drivers/clk/rockchip/clk-rv1126.c
@@ -176,6 +176,8 @@ PNAME(mux_i2s2_out2io_p)		= { "mclk_i2s2", "xin12m" };
 PNAME(mux_gpll_cpll_xin24m_p)		= { "gpll", "cpll", "xin24m" };
 PNAME(mux_audpwm_p)			= { "sclk_audpwm_div", "sclk_audpwm_fracdiv", "xin24m" };
 PNAME(mux_dclk_vop_p)			= { "dclk_vop_div", "dclk_vop_fracdiv", "xin24m" };
+PNAME(mux_cpll_gpll_hpll_p) = { "cpll", "gpll", "hpll" };
+PNAME(mux_cpll_hpll_gpll_p) = { "cpll", "hpll", "gpll" };
 PNAME(mux_usb480m_gpll_p)		= { "usb480m", "gpll" };
 PNAME(clk_gmac_src_m0_p)		= { "clk_gmac_div", "clk_gmac_rgmii_m0" };
 PNAME(clk_gmac_src_m1_p)		= { "clk_gmac_div", "clk_gmac_rgmii_m1" };
@@ -719,6 +721,25 @@ static struct rockchip_clk_branch rv1126_clk_branches[] __initdata = {
 			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 9
 	 */
-- 
2.56.0


^ permalink raw reply related	[flat|nested] 6+ messages in thread

* [RFC PATCH 4/6] media: rockchip: rkvenc: Add frequency scaling and cooling
  2026-11-14 14:54 [RFC PATCH 0/6] media: rockchip: RKVENC v1 H.264/HEVC encoder for the RV1126 Daniel Golle
  2026-11-14 14:54 ` [RFC PATCH 1/6] dt-bindings: media: Add Rockchip RKVENC v1 video encoder Daniel Golle
  2026-11-14 14:54 ` [RFC PATCH 2/6] clk: rockchip: rv1126: Add PD_VEPU clock tree Daniel Golle
@ 2026-11-14 14:55 ` Daniel Golle
  2026-11-14 14:55 ` [RFC PATCH 5/6] ARM: dts: rockchip: Add RKVENC encoder to RV1126 Daniel Golle
  2026-11-14 14:55 ` [RFC PATCH 6/6] ARM: dts: rockchip: Add RKVENC operating points " Daniel Golle
  4 siblings, 0 replies; 6+ messages in thread
From: Daniel Golle @ 2026-11-14 14:55 UTC (permalink / raw)
  To: Daniel Golle, Mauro Carvalho Chehab, Rob Herring,
	Krzysztof Kozlowski, Conor Dooley, Heiko Stuebner, Stephen Boyd,
	Brian Masney, Jerome Brunet, Philipp Zabel, Sakari Ailus,
	Michael Riesch, Mehdi Djait, Hans Verkuil, Gerald Loacker,
	linux-media, linux-rockchip, devicetree, linux-arm-kernel,
	linux-kernel, linux-clk
  Cc: nicolas.dufresne, detlev.casanova, paulk, ezequiel

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
takes the busy time of the jobs as load and runs once per round of the
streaming contexts: a job end runs it when the context that opened the
round completes again, so the load covers one job of every context and
the clock settles at the lowest point that sustains all of them
together. 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.

Signed-off-by: Daniel Golle <daniel@makrotopia.org>
---
 .../media/platform/rockchip/rkvenc/Kconfig    |   3 +
 .../media/platform/rockchip/rkvenc/Makefile   |   1 +
 .../platform/rockchip/rkvenc/rkvenc-dvfs.c    | 364 ++++++++++++++++++
 .../platform/rockchip/rkvenc/rkvenc-v4l2.c    |  20 +-
 .../media/platform/rockchip/rkvenc/rkvenc.c   |  11 +-
 .../media/platform/rockchip/rkvenc/rkvenc.h   |  47 +++
 6 files changed, 443 insertions(+), 3 deletions(-)
 create mode 100644 drivers/media/platform/rockchip/rkvenc/rkvenc-dvfs.c

diff --git a/drivers/media/platform/rockchip/rkvenc/Kconfig b/drivers/media/platform/rockchip/rkvenc/Kconfig
index 1d76314a2ea6d..f4dd8d5dec37c 100644
--- 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
diff --git a/drivers/media/platform/rockchip/rkvenc/Makefile b/drivers/media/platform/rockchip/rkvenc/Makefile
index 026c81aec492c..20987b293bded 100644
--- 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 \
diff --git a/drivers/media/platform/rockchip/rkvenc/rkvenc-dvfs.c b/drivers/media/platform/rockchip/rkvenc/rkvenc-dvfs.c
new file mode 100644
index 0000000000000..8b59122d5d983
--- /dev/null
+++ b/drivers/media/platform/rockchip/rkvenc/rkvenc-dvfs.c
@@ -0,0 +1,364 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Frequency scaling and cooling of the RKVENC v1 encoder. devfreq is
+ * evaluated once per round of the streaming contexts, so no polling
+ * interval is involved. Below the lowest operating point, cooling
+ * stretches the period between jobs.
+ */
+
+#include <linux/devfreq.h>
+#include <linux/ktime.h>
+#include <linux/math64.h>
+#include <linux/pm_opp.h>
+#include <linux/pm_qos.h>
+#include <linux/thermal.h>
+#include <linux/units.h>
+
+#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_ctx *ctx)
+{
+	struct rkvenc_dvfs *dvfs = &rkvenc->dvfs;
+	struct devfreq *devfreq = dvfs->devfreq;
+	unsigned long flags;
+	bool evaluate;
+	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;
+	/* A round ends with the context that opened it, covering every other one */
+	evaluate = dvfs->round_ctx == ctx;
+	if (!dvfs->round_ctx)
+		dvfs->round_ctx = ctx;
+	spin_unlock_irqrestore(&dvfs->lock, flags);
+	wake_up(&dvfs->idle_wq);
+
+	if (!devfreq || !evaluate)
+		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_idle(struct rkvenc_dev *rkvenc, struct rkvenc_ctx *ctx)
+{
+	struct rkvenc_dvfs *dvfs = &rkvenc->dvfs;
+	unsigned long flags;
+
+	spin_lock_irqsave(&dvfs->lock, flags);
+	if (dvfs->round_ctx == ctx)
+		dvfs->round_ctx = NULL;
+	spin_unlock_irqrestore(&dvfs->lock, flags);
+}
+
+void rkvenc_dvfs_ctx_del(struct rkvenc_dev *rkvenc, struct rkvenc_ctx *ctx)
+{
+	struct rkvenc_dvfs *dvfs = &rkvenc->dvfs;
+
+	rkvenc_dvfs_ctx_idle(rkvenc, ctx);
+	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, 0, "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;
+}
diff --git a/drivers/media/platform/rockchip/rkvenc/rkvenc-v4l2.c b/drivers/media/platform/rockchip/rkvenc/rkvenc-v4l2.c
index 3be496f5cf0c1..8143f79baa551 100644
--- a/drivers/media/platform/rockchip/rkvenc/rkvenc-v4l2.c
+++ b/drivers/media/platform/rockchip/rkvenc/rkvenc-v4l2.c
@@ -351,6 +351,7 @@ static void rkvenc_stop_streaming(struct vb2_queue *q)
 	struct vb2_v4l2_buffer *vbuf;
 
 	v4l2_m2m_update_stop_streaming_state(ctx->m2m_ctx, q);
+	rkvenc_dvfs_ctx_idle(ctx->dev, ctx);
 
 	if (V4L2_TYPE_IS_OUTPUT(q->type)) {
 		while ((vbuf = v4l2_m2m_src_buf_remove(ctx->m2m_ctx)))
@@ -457,17 +458,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, ctx);
+
 finish_error:
 	dev->job_running = false;
 	dev->job_ctx = NULL;
@@ -478,8 +484,16 @@ static void rkvenc_device_run(void *priv)
 		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,
 };
 
 static int rkvenc_s_ctrl(struct v4l2_ctrl *ctrl)
@@ -944,6 +958,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;
 
@@ -959,6 +974,7 @@ static int rkvenc_release(struct file *filp)
 {
 	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);
diff --git a/drivers/media/platform/rockchip/rkvenc/rkvenc.c b/drivers/media/platform/rockchip/rkvenc/rkvenc.c
index 00778e97686d7..f15a0922059f1 100644
--- a/drivers/media/platform/rockchip/rkvenc/rkvenc.c
+++ b/drivers/media/platform/rockchip/rkvenc/rkvenc.c
@@ -187,6 +187,7 @@ static void rkvenc_finish_job(struct rkvenc_dev *dev, struct rkvenc_ctx *ctx,
 			ctx->joined_header_done = false;
 		dev->job_ctx = NULL;
 		pm_runtime_put(dev->dev);
+		rkvenc_dvfs_job_end(dev, ctx);
 		rkvenc_m2m_ops.device_run(ctx);
 		return;
 	} else {
@@ -210,6 +211,7 @@ static void rkvenc_finish_job(struct rkvenc_dev *dev, struct rkvenc_ctx *ctx,
 
 	dev->job_ctx = NULL;
 	pm_runtime_put(dev->dev);
+	rkvenc_dvfs_job_end(dev, ctx);
 
 	v4l2_m2m_buf_done_and_job_finish(dev->m2m_dev, ctx->m2m_ctx, state);
 }
@@ -422,12 +424,18 @@ static int rkvenc_probe(struct platform_device *pdev)
 		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:
@@ -442,6 +450,7 @@ static void rkvenc_remove(struct platform_device *pdev)
 	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);
diff --git a/drivers/media/platform/rockchip/rkvenc/rkvenc.h b/drivers/media/platform/rockchip/rkvenc/rkvenc.h
index df5905a329107..01311c31bbd2a 100644
--- a/drivers/media/platform/rockchip/rkvenc/rkvenc.h
+++ b/drivers/media/platform/rockchip/rkvenc/rkvenc.h
@@ -12,8 +12,14 @@
 #include <linux/clk.h>
 #include <linux/completion.h>
 #include <linux/delay.h>
+#include <linux/devfreq.h>
+#include <linux/ktime.h>
+#include <linux/list.h>
 #include <linux/mutex.h>
+#include <linux/pm_qos.h>
 #include <linux/spinlock.h>
+#include <linux/thermal.h>
+#include <linux/wait.h>
 #include <linux/workqueue.h>
 #include <linux/reset.h>
 
@@ -30,11 +36,40 @@
 
 #define RKVENC_NUM_CLKS		3
 #define RKVENC_NUM_RESETS	3
+#define RKVENC_CLK_CORE 2
 
 #define RKVENC_MAX_CONSEC_ERRORS	2
 
 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_ctx *round_ctx;
+};
+
 /**
  * struct rkvenc_variant - per-SoC integration differences.
  * @vepu540: RK3566/RK3568 "vepu540" rather than RV1109/RV1126 "vepu541".
@@ -59,6 +94,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;
@@ -120,6 +156,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;
@@ -181,6 +218,16 @@ 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-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, struct rkvenc_ctx *ctx);
+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_idle(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;
 
-- 
2.56.0


^ permalink raw reply related	[flat|nested] 6+ messages in thread

* [RFC PATCH 5/6] ARM: dts: rockchip: Add RKVENC encoder to RV1126
  2026-11-14 14:54 [RFC PATCH 0/6] media: rockchip: RKVENC v1 H.264/HEVC encoder for the RV1126 Daniel Golle
                   ` (2 preceding siblings ...)
  2026-11-14 14:55 ` [RFC PATCH 4/6] media: rockchip: rkvenc: Add frequency scaling and cooling Daniel Golle
@ 2026-11-14 14:55 ` Daniel Golle
  2026-11-14 14:55 ` [RFC PATCH 6/6] ARM: dts: rockchip: Add RKVENC operating points " Daniel Golle
  4 siblings, 0 replies; 6+ messages in thread
From: Daniel Golle @ 2026-11-14 14:55 UTC (permalink / raw)
  To: Daniel Golle, Mauro Carvalho Chehab, Rob Herring,
	Krzysztof Kozlowski, Conor Dooley, Heiko Stuebner, Stephen Boyd,
	Brian Masney, Jerome Brunet, Philipp Zabel, Sakari Ailus,
	Michael Riesch, Mehdi Djait, Hans Verkuil, Gerald Loacker,
	linux-media, linux-rockchip, devicetree, linux-arm-kernel,
	linux-kernel, linux-clk
  Cc: nicolas.dufresne, detlev.casanova, paulk, ezequiel

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.

Signed-off-by: Daniel Golle <daniel@makrotopia.org>
---
 arch/arm/boot/dts/rockchip/rv1126.dtsi | 54 ++++++++++++++++++++++++++
 1 file changed, 54 insertions(+)

diff --git a/arch/arm/boot/dts/rockchip/rv1126.dtsi b/arch/arm/boot/dts/rockchip/rv1126.dtsi
index 74f238e13533c..63705291dd3ce 100644
--- a/arch/arm/boot/dts/rockchip/rv1126.dtsi
+++ b/arch/arm/boot/dts/rockchip/rv1126.dtsi
@@ -139,6 +139,21 @@ qos_sdio: qos@fe86c000 {
 		reg = <0xfe86c000 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_iep: qos@fe8a0000 {
 		compatible = "rockchip,rv1126-qos", "syscon";
 		reg = <0xfe8a0000 0x20>;
@@ -223,6 +238,17 @@ power-domain@RV1126_PD_VO {
 					 <&qos_iep>;
 				#power-domain-cells = <0>;
 			};
+
+			power-domain@RV1126_PD_VEPU {
+				reg = <RV1126_PD_VEPU>;
+				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>;
+			};
 		};
 	};
 
@@ -630,6 +656,34 @@ vop_mmu: iommu@ffb00f00 {
 		status = "disabled";
 	};
 
+	venc: video-codec@ffbb0000 {
+		compatible = "rockchip,rv1126-rkvenc";
+		reg = <0xffbb0000 0xf00>;
+		interrupts = <GIC_SPI 68 IRQ_TYPE_LEVEL_HIGH>;
+		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 = <GIC_SPI 69 IRQ_TYPE_LEVEL_HIGH>,
+			     <GIC_SPI 70 IRQ_TYPE_LEVEL_HIGH>;
+		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>;
-- 
2.56.0


^ permalink raw reply related	[flat|nested] 6+ messages in thread

* [RFC PATCH 6/6] ARM: dts: rockchip: Add RKVENC operating points to RV1126
  2026-11-14 14:54 [RFC PATCH 0/6] media: rockchip: RKVENC v1 H.264/HEVC encoder for the RV1126 Daniel Golle
                   ` (3 preceding siblings ...)
  2026-11-14 14:55 ` [RFC PATCH 5/6] ARM: dts: rockchip: Add RKVENC encoder to RV1126 Daniel Golle
@ 2026-11-14 14:55 ` Daniel Golle
  4 siblings, 0 replies; 6+ messages in thread
From: Daniel Golle @ 2026-11-14 14:55 UTC (permalink / raw)
  To: Daniel Golle, Mauro Carvalho Chehab, Rob Herring,
	Krzysztof Kozlowski, Conor Dooley, Heiko Stuebner, Stephen Boyd,
	Brian Masney, Jerome Brunet, Philipp Zabel, Sakari Ailus,
	Michael Riesch, Mehdi Djait, Hans Verkuil, Gerald Loacker,
	linux-media, linux-rockchip, devicetree, linux-arm-kernel,
	linux-kernel, linux-clk
  Cc: nicolas.dufresne, detlev.casanova, paulk, ezequiel

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.

Signed-off-by: Daniel Golle <daniel@makrotopia.org>
---
 arch/arm/boot/dts/rockchip/rv1126.dtsi | 26 ++++++++++++++++++++++++++
 1 file changed, 26 insertions(+)

diff --git a/arch/arm/boot/dts/rockchip/rv1126.dtsi b/arch/arm/boot/dts/rockchip/rv1126.dtsi
index 63705291dd3ce..7e37fd89609a7 100644
--- a/arch/arm/boot/dts/rockchip/rv1126.dtsi
+++ b/arch/arm/boot/dts/rockchip/rv1126.dtsi
@@ -104,6 +104,30 @@ xin24m: oscillator {
 		#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>;
@@ -665,6 +689,8 @@ venc: video-codec@ffbb0000 {
 		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";
-- 
2.56.0


^ permalink raw reply related	[flat|nested] 6+ messages in thread

end of thread, other threads:[~2026-11-14 14:55 UTC | newest]

Thread overview: 6+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-11-14 14:54 [RFC PATCH 0/6] media: rockchip: RKVENC v1 H.264/HEVC encoder for the RV1126 Daniel Golle
2026-11-14 14:54 ` [RFC PATCH 1/6] dt-bindings: media: Add Rockchip RKVENC v1 video encoder Daniel Golle
2026-11-14 14:54 ` [RFC PATCH 2/6] clk: rockchip: rv1126: Add PD_VEPU clock tree Daniel Golle
2026-11-14 14:55 ` [RFC PATCH 4/6] media: rockchip: rkvenc: Add frequency scaling and cooling Daniel Golle
2026-11-14 14:55 ` [RFC PATCH 5/6] ARM: dts: rockchip: Add RKVENC encoder to RV1126 Daniel Golle
2026-11-14 14:55 ` [RFC PATCH 6/6] ARM: dts: rockchip: Add RKVENC operating points " Daniel Golle

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox