mirror of https://lore.kernel.org/lkml/
 help / color / mirror / Atom feed
* [PATCH RFC 00/10] media: Add a V4L2 HEVC stateless encode uAPI and helpers
@ 2026-11-10 10:07 Arun Iyengar via B4 Relay
  2026-11-10 10:07 ` [PATCH RFC 01/10] media: hevc: Add H.265 syntax definitions Arun Iyengar via B4 Relay
                   ` (9 more replies)
  0 siblings, 10 replies; 11+ messages in thread
From: Arun Iyengar via B4 Relay @ 2026-11-10 10:07 UTC (permalink / raw)
  To: Mauro Carvalho Chehab
  Cc: linux-media, linux-kernel, Paul Kocialkowski, Nicolas Dufresne,
	Hans Verkuil, Arun Iyengar

Hi everyone,

This RFC proposes a V4L2 uAPI for stateless HEVC (H.265) encoders, built
on the kernel-driven design of Paul Kocialkowski's H.264 stateless
encode series, "[PATCH 00/14] media: Add V4L2 H.264 stateless encode and
VC8000E support" (v1, 2026-05-22):
https://lore.kernel.org/all/20260522101653.2565125-1-paulk@sys-base.io/
(https://patchwork.kernel.org/project/linux-media/list/?series=1099281)

It applies on media next 6628e7d4c6b2 (v7.4-rc1) with, in this order,
patches 1-7 of that series and my "[PATCH RFC 00/11] media: h264:
Stateless encode core follow-ups" applied first (the prerequisite lines
below). Only patches 7-9 here need them: they use the codec-independent
constant bitrate helper (v4l2-enc-cbr) of the follow-ups; patches 1-6
and 10 only share Kconfig/Makefile context. As far as I can tell, this is
the first HEVC stateless encode proposal. It is meant to help the H.264
discussion as much as to add HEVC: HEVC makes one answer to reference
handling concrete. I expect to adapt it to the rework of the H.264 core helpers
that Paul announced for v2.

What it reuses from the H.264 design:
- V4L2_PIX_FMT_HEVC_SLICE on the CAPTURE queue, with one access unit per
  buffer;
- the existing V4L2_CID_STATELESS_HEVC_SPS/PPS controls and structures
  for the coding tools. The parts a decoder never needed (profile, tier
  and level, conformance window, VUI, VPS) are derived in the kernel
  from the format, S_PARM, S_SELECTION and the stateful HEVC
  profile/level/tier controls;
- the kernel writes the VPS, SPS, PPS, AUD and, unless the hardware does
  it, the slice segment header;
- the QP comes from the stateful QP/rate control controls: direct QP,
  constant quality and constant bitrate in the core;
- the driver API: init/step/complete/exit, state_constrain and
  reconstruction buffer callbacks.

What is new:
- V4L2_CID_STATELESS_HEVC_ENCODE_PARAMS: the NAL unit type, TemporalId,
  slice type and POC of the picture, the slice header syntax elements
  that the SPS/PPS enable, and its reference picture set, given as a
  list of OUTPUT buffer timestamps, each marked used-by-current-picture
  and/or long-term. The kernel keeps the reconstructed pictures,
  computes the POC deltas, codes the RPS, builds the default reference
  lists and drops every picture that is not listed, exactly as a decoder
  does (8.3.2). Userspace keeps full control of the GOP structure,
  long-term references and temporal layers without any MMCO-like
  syntax. Hardware with one reference read port uses the single entry
  marked as used.
- V4L2_CID_STATELESS_HEVC_ENCODE_FEEDBACK, read-only: the values the
  kernel settled on (QP, resolved reference lists) and hardware
  statistics, in a codec-agnostic struct v4l2_stateless_encode_stats
  (QP sum/min/max, luma SSE, block counts) that H.264 could share.
  Applications never set it, so v4l2_ctrl_request_complete() stores the
  value the driver set at the end of the run in the request, and it can
  be read per request today, without read-only requests.
- A kernel HEVC NAL unit writer (v4l2-hevc-enc-rbsp) with byte-wise
  emulation prevention, and an encode core (v4l2-hevc-enc) for the
  RPS/DPB, accommodation, headers and feedback.
- KUnit tests for both: byte-exact VPS/SPS/PPS/AUD/slice headers for
  three configurations (vectors from an independent reference
  implementation, see the commit message), and GOP sequences through
  the core.
- Patches 7-9: the constant bitrate mode of the core on v4l2-enc-cbr,
  QP limits for I pictures and a re-encode hook for drivers whose
  hardware writes the slice header.

The uAPI was checked against two unrelated encoders: the Rockchip
VEPU580 (RK3588; its driver is posted separately as an RFC) and the
VeriSilicon VC8000E (its HEVC registers are in Paul's series). Both
write slice headers from registers and take CPU-written parameter sets,
and both map one-to-one onto the resolved slice header structure.

Questions where I would particularly like feedback:
1. Explicit references for HEVC versus the sliding window of the H.264
   v1 series: is the asymmetry fine, or should H.264 go explicit too?
2. Should the H.264 and HEVC bit writers become one shared helper, as
   the rate control now is?
3. Is a read-only feedback control the right pattern, and should the
   stats be their own codec-agnostic control?
4. Capability discovery: reject-on-use plus written-back accommodation,
   or a dedicated caps control?
5. HEVC_SLICE reuse, or HEVC_SL alongside the H264_SL rename?

Not covered yet: multi-slice, tiles/WPP with kernel-written headers
(a driver can still signal uniform tile columns, as the VEPU580 driver
does above 4096 pixels), RPS candidates in the SPS, custom scaling
lists, weighted prediction, SEI/HRD, 4:2:2/4:4:4 and 10-bit beyond what
the SPS can express (Main 10 is signalled but untested).

Testing: the touched files build with W=1 on arm64. The KUnit tests
pass on UML (23 tests: 19 for the RBSP writer, 4 for the core).
"make SPHINXDIRS=userspace-api/media htmldocs" gives no new warnings.
On hardware, the uAPI and helpers ran on an Orange Pi 5 (RK3588S)
through the VEPU580 driver, on a v7.3-based kernel whose HEVC encode
files are byte-identical to this series:
- constant bitrate, IDR period 120: 1080p60 (300 pictures of two clips)
  at 2, 4, 8 and 16 Mbit/s and 2160p30 (120 pictures of two clips) at
  20 and 40 Mbit/s, within 3.4% of the target, the largest one second
  window at most 1.35 times the bitrate; PSNR at equal bitrate against
  the driver's earlier private rate control: +0.27 to +1.0 dB on the
  animation clips, -0.05 to -0.13 dB on the natural ones (+0.27 dB on
  average); 4104x2160, 5120x2880 and 8192x4320 in tile columns; every
  stream decodes in FFmpeg;
- with earlier revisions of these patches: 1080p and 4K at
  fixed QP and constant quality with GOP 1/10/30, long-term references,
  TMVP on/off and SAO off, plus two 4K streams in parallel, decode in
  FFmpeg without errors at 38-59 dB PSNR, and the feedback control
  matched the expected NAL type, POC, QP and reference list on every
  picture.

This series was developed with Claude (Anthropic) as a coding assistant:
it wrote the code, the documentation and the commit messages, and I
reviewed and tested every change as described above.

Signed-off-by: Arun Iyengar <aiyengar1990@gmail.com>
---
Arun Iyengar (10):
      media: hevc: Add H.265 syntax definitions
      media: uapi: Add HEVC stateless encode support
      media: hevc: Add stateless encode RBSP writer
      media: hevc: Add KUnit tests for the stateless encode RBSP writer
      media: hevc: Add stateless encode core
      media: hevc: Add KUnit tests for the stateless encode core
      media: v4l2-hevc-enc: Add a constant bitrate mode
      media: v4l2-hevc-enc: Add QP limits for the I pictures
      media: v4l2-hevc-enc: Re-encode oversized pictures
      media: docs: Document the HEVC stateless encode uAPI

 .../media/v4l/ext-ctrls-codec-stateless.rst        |  411 ++++++
 .../userspace-api/media/v4l/pixfmt-compressed.rst  |    3 +
 .../media/v4l/videodev2.h.rst.exceptions           |    2 +
 .../userspace-api/media/v4l/vidioc-g-ext-ctrls.rst |    8 +
 .../userspace-api/media/v4l/vidioc-queryctrl.rst   |   12 +
 drivers/media/v4l2-core/.kunitconfig               |    6 +
 drivers/media/v4l2-core/Kconfig                    |   37 +
 drivers/media/v4l2-core/Makefile                   |    4 +
 drivers/media/v4l2-core/v4l2-ctrls-core.c          |  194 +++
 drivers/media/v4l2-core/v4l2-ctrls-defs.c          |    9 +
 drivers/media/v4l2-core/v4l2-hevc-enc-kunit.c      |  554 +++++++
 drivers/media/v4l2-core/v4l2-hevc-enc-rbsp-kunit.c |  696 +++++++++
 drivers/media/v4l2-core/v4l2-hevc-enc-rbsp.c       |  977 +++++++++++++
 drivers/media/v4l2-core/v4l2-hevc-enc.c            | 1510 ++++++++++++++++++++
 include/media/v4l2-ctrls.h                         |    4 +
 include/media/v4l2-hevc-enc-rbsp.h                 |  279 ++++
 include/media/v4l2-hevc-enc.h                      |  300 ++++
 include/media/v4l2-hevc.h                          |  132 +-
 include/uapi/linux/v4l2-controls.h                 |  188 +++
 include/uapi/linux/videodev2.h                     |    2 +
 20 files changed, 5327 insertions(+), 1 deletion(-)
---
base-commit: 6628e7d4c6b25c8d18f1345e3a20d21829aae7e4
change-id: 20261110-hevc-encode-uapi-a26990b358b3
prerequisite-patch-id: fd11f19266754d1bd40bf61dd5d1da74a0dc13c2
prerequisite-patch-id: 7c7edc7344998d520efaf7b430c90fd527c704c7
prerequisite-patch-id: 4689de770eb1bcdc153d6beda2e392d0ce6f344d
prerequisite-patch-id: 0afdcc24892eac773036d05d8f504e5ccb35eb62
prerequisite-patch-id: ecc849b3328d4396380f0cfc331536c16d2e0d28
prerequisite-patch-id: c62d0fd16706c4328624948805eb2f8d7b223ae0
prerequisite-patch-id: e955d15603fd070034960a1362e4867c2f124d69
prerequisite-patch-id: 76eb64d7920ddc1f268afa6f13df14cf9d1931c8
prerequisite-patch-id: 654c303a6e9e4a3108e9ae9b7c9aa78f95b82c0c
prerequisite-patch-id: 96f470c1f8b2ca83ffc496f41fd173e925180501
prerequisite-patch-id: fa3430fcef00637bb84f9401bb9045dd46194298
prerequisite-patch-id: da666def8ed2b5296da8dee1be387b2818ba3cc3
prerequisite-patch-id: 7e532c86fbd0f7bd6705ac4f1544b7d7d27cbc26
prerequisite-patch-id: 09e1836b24606834b9ac4aa3c851d39f564cfef6
prerequisite-patch-id: fd8fcc747458e52344df760a17b61748361bfbb1
prerequisite-patch-id: 883eefffa7ea70d39d357467780cb4f2b69c8d36
prerequisite-patch-id: aa018c975f9b1f0555164d46446dea01d6496823
prerequisite-patch-id: d8d6323a5177f6b1ce661a86ee5813d55b4a3570

Best regards,
--  
Arun Iyengar <aiyengar1990@gmail.com>



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

* [PATCH RFC 01/10] media: hevc: Add H.265 syntax definitions
  2026-11-10 10:07 [PATCH RFC 00/10] media: Add a V4L2 HEVC stateless encode uAPI and helpers Arun Iyengar via B4 Relay
@ 2026-11-10 10:07 ` Arun Iyengar via B4 Relay
  2026-11-10 10:07 ` [PATCH RFC 02/10] media: uapi: Add HEVC stateless encode support Arun Iyengar via B4 Relay
                   ` (8 subsequent siblings)
  9 siblings, 0 replies; 11+ messages in thread
From: Arun Iyengar via B4 Relay @ 2026-11-10 10:07 UTC (permalink / raw)
  To: Mauro Carvalho Chehab
  Cc: linux-media, linux-kernel, Paul Kocialkowski, Nicolas Dufresne,
	Hans Verkuil, Arun Iyengar

From: Arun Iyengar <aiyengar1990@gmail.com>

Add the ITU-T Rec. H.265 constants that a stateless HEVC encoder needs
to produce a bitstream: NAL unit type codes (Table 7-1), profile IDCs
(Annex A), access unit delimiter picture types (Table 7-2) and the VUI
colour description code points (Tables E.3 to E.5, shared with ITU-T
H.273).

Also add small helpers that classify NAL unit types (IRAP, IDR, BLA,
leading and sub-layer non-reference pictures), since the reference
picture set and picture order count rules depend on them.

These are kernel-internal definitions: the uAPI carries nal_unit_type as
the raw H.265 value, like the HEVC decoding controls already do.

Signed-off-by: Arun Iyengar <aiyengar1990@gmail.com>
---
 include/media/v4l2-hevc.h | 132 +++++++++++++++++++++++++++++++++++++++++++++-
 1 file changed, 131 insertions(+), 1 deletion(-)

diff --git a/include/media/v4l2-hevc.h b/include/media/v4l2-hevc.h
index 973c96be16be..cc649b212a6d 100644
--- a/include/media/v4l2-hevc.h
+++ b/include/media/v4l2-hevc.h
@@ -1,6 +1,7 @@
 /* SPDX-License-Identifier: GPL-2.0-or-later */
 /*
- * Helper functions for HEVC stateless codecs.
+ * Helper functions and ITU-T Rec. H.265 syntax definitions for HEVC stateless
+ * codecs.
  */
 
 #ifndef _MEDIA_V4L2_HEVC_H
@@ -38,4 +39,133 @@ v4l2_hevc_pps_num_tile_rows(const struct v4l2_ctrl_hevc_pps *pps)
 		     ARRAY_SIZE(pps->row_height_minus1));
 }
 
+/* ITU-T Rec. H.265 Table 7-1: NAL unit type codes */
+#define V4L2_HEVC_NAL_TRAIL_N		0
+#define V4L2_HEVC_NAL_TRAIL_R		1
+#define V4L2_HEVC_NAL_TSA_N		2
+#define V4L2_HEVC_NAL_TSA_R		3
+#define V4L2_HEVC_NAL_STSA_N		4
+#define V4L2_HEVC_NAL_STSA_R		5
+#define V4L2_HEVC_NAL_RADL_N		6
+#define V4L2_HEVC_NAL_RADL_R		7
+#define V4L2_HEVC_NAL_RASL_N		8
+#define V4L2_HEVC_NAL_RASL_R		9
+#define V4L2_HEVC_NAL_RSV_VCL_N14	14
+#define V4L2_HEVC_NAL_BLA_W_LP		16
+#define V4L2_HEVC_NAL_BLA_W_RADL	17
+#define V4L2_HEVC_NAL_BLA_N_LP		18
+#define V4L2_HEVC_NAL_IDR_W_RADL	19
+#define V4L2_HEVC_NAL_IDR_N_LP		20
+#define V4L2_HEVC_NAL_CRA_NUT		21
+#define V4L2_HEVC_NAL_RSV_IRAP_VCL23	23
+#define V4L2_HEVC_NAL_VPS		32
+#define V4L2_HEVC_NAL_SPS		33
+#define V4L2_HEVC_NAL_PPS		34
+#define V4L2_HEVC_NAL_AUD		35
+#define V4L2_HEVC_NAL_EOS		36
+#define V4L2_HEVC_NAL_EOB		37
+#define V4L2_HEVC_NAL_FD		38
+#define V4L2_HEVC_NAL_PREFIX_SEI	39
+#define V4L2_HEVC_NAL_SUFFIX_SEI	40
+
+/* ITU-T Rec. H.265 Annex A: general_profile_idc */
+#define V4L2_HEVC_PROFILE_IDC_MAIN		1
+#define V4L2_HEVC_PROFILE_IDC_MAIN_10		2
+#define V4L2_HEVC_PROFILE_IDC_MAIN_STILL_PICTURE	3
+#define V4L2_HEVC_PROFILE_IDC_RANGE_EXTENSIONS	4
+
+/* ITU-T Rec. H.265 Table 7-2: pic_type in access unit delimiters */
+#define V4L2_HEVC_AUD_PIC_TYPE_I	0
+#define V4L2_HEVC_AUD_PIC_TYPE_PI	1
+#define V4L2_HEVC_AUD_PIC_TYPE_BPI	2
+
+/* ITU-T Rec. H.265 Table E.1: aspect_ratio_idc */
+#define V4L2_HEVC_VUI_ASPECT_RATIO_IDC_EXTENDED	255
+
+/* ITU-T Rec. H.265 Table E.2: video_format */
+#define V4L2_HEVC_VUI_VIDEO_FORMAT_UNSPECIFIED	5
+
+/* ITU-T Rec. H.265 Tables E.3 to E.5 (ITU-T H.273 code points) */
+#define V4L2_HEVC_VUI_COLOUR_BT709		1
+#define V4L2_HEVC_VUI_COLOUR_UNSPECIFIED	2
+#define V4L2_HEVC_VUI_COLOUR_BT470_SYSTEM_M	4
+#define V4L2_HEVC_VUI_COLOUR_BT470_SYSTEM_BG	5
+#define V4L2_HEVC_VUI_COLOUR_SMPTE170M		6
+#define V4L2_HEVC_VUI_COLOUR_SMPTE240M		7
+#define V4L2_HEVC_VUI_COLOUR_BT2020		9
+#define V4L2_HEVC_VUI_COLOUR_SMPTE431		11
+
+#define V4L2_HEVC_VUI_TRANSFER_BT709		1
+#define V4L2_HEVC_VUI_TRANSFER_UNSPECIFIED	2
+#define V4L2_HEVC_VUI_TRANSFER_SMPTE170M	6
+#define V4L2_HEVC_VUI_TRANSFER_SMPTE240M	7
+#define V4L2_HEVC_VUI_TRANSFER_LINEAR		8
+#define V4L2_HEVC_VUI_TRANSFER_SRGB		13
+#define V4L2_HEVC_VUI_TRANSFER_SMPTE2084	16
+
+#define V4L2_HEVC_VUI_MATRIX_BT709		1
+#define V4L2_HEVC_VUI_MATRIX_UNSPECIFIED	2
+#define V4L2_HEVC_VUI_MATRIX_BT470_SYSTEM_BG	5
+#define V4L2_HEVC_VUI_MATRIX_SMPTE170M		6
+#define V4L2_HEVC_VUI_MATRIX_SMPTE240M		7
+#define V4L2_HEVC_VUI_MATRIX_BT2020_NCL		9
+#define V4L2_HEVC_VUI_MATRIX_BT2020_CL		10
+
+/* Maximum number of temporal sub-layers (sps_max_sub_layers_minus1 + 1) */
+#define V4L2_HEVC_MAX_SUB_LAYERS	7
+
+/* Maximum number of pictures in a short-term RPS (7.4.8) */
+#define V4L2_HEVC_MAX_ST_RPS_PICS	16
+
+/* Maximum number of long-term pictures signalled in a slice header */
+#define V4L2_HEVC_MAX_LT_PICS		16
+
+static inline bool v4l2_hevc_nal_is_irap(unsigned int nal_unit_type)
+{
+	return nal_unit_type >= V4L2_HEVC_NAL_BLA_W_LP &&
+	       nal_unit_type <= V4L2_HEVC_NAL_RSV_IRAP_VCL23;
+}
+
+static inline bool v4l2_hevc_nal_is_idr(unsigned int nal_unit_type)
+{
+	return nal_unit_type == V4L2_HEVC_NAL_IDR_W_RADL ||
+	       nal_unit_type == V4L2_HEVC_NAL_IDR_N_LP;
+}
+
+static inline bool v4l2_hevc_nal_is_bla(unsigned int nal_unit_type)
+{
+	return nal_unit_type >= V4L2_HEVC_NAL_BLA_W_LP &&
+	       nal_unit_type <= V4L2_HEVC_NAL_BLA_N_LP;
+}
+
+/*
+ * Sub-layer non-reference picture (3.132): even nal_unit_type values in the
+ * VCL range below RSV_VCL_N14.
+ */
+static inline bool v4l2_hevc_nal_is_sub_layer_non_ref(unsigned int nal_unit_type)
+{
+	return nal_unit_type <= V4L2_HEVC_NAL_RSV_VCL_N14 &&
+	       !(nal_unit_type & 1);
+}
+
+static inline bool v4l2_hevc_nal_is_leading(unsigned int nal_unit_type)
+{
+	return nal_unit_type >= V4L2_HEVC_NAL_RADL_N &&
+	       nal_unit_type <= V4L2_HEVC_NAL_RASL_R;
+}
+
+static inline char v4l2_hevc_slice_type_char(unsigned char slice_type)
+{
+	switch (slice_type) {
+	case V4L2_HEVC_SLICE_TYPE_B:
+		return 'B';
+	case V4L2_HEVC_SLICE_TYPE_P:
+		return 'P';
+	case V4L2_HEVC_SLICE_TYPE_I:
+		return 'I';
+	default:
+		return 'X';
+	}
+}
+
 #endif /* _MEDIA_V4L2_HEVC_H */

-- 
2.55.0



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

* [PATCH RFC 02/10] media: uapi: Add HEVC stateless encode support
  2026-11-10 10:07 [PATCH RFC 00/10] media: Add a V4L2 HEVC stateless encode uAPI and helpers Arun Iyengar via B4 Relay
  2026-11-10 10:07 ` [PATCH RFC 01/10] media: hevc: Add H.265 syntax definitions Arun Iyengar via B4 Relay
@ 2026-11-10 10:07 ` Arun Iyengar via B4 Relay
  2026-11-10 10:07 ` [PATCH RFC 03/10] media: hevc: Add stateless encode RBSP writer Arun Iyengar via B4 Relay
                   ` (7 subsequent siblings)
  9 siblings, 0 replies; 11+ messages in thread
From: Arun Iyengar via B4 Relay @ 2026-11-10 10:07 UTC (permalink / raw)
  To: Mauro Carvalho Chehab
  Cc: linux-media, linux-kernel, Paul Kocialkowski, Nicolas Dufresne,
	Hans Verkuil, Arun Iyengar

From: Arun Iyengar <aiyengar1990@gmail.com>

Introduce the per-picture controls of a stateless HEVC encoder, modelled
on the H.264 stateless encode uAPI: the sequence and picture parameter
sets keep using the existing V4L2_CID_STATELESS_HEVC_SPS and _PPS
controls (and struct v4l2_ctrl_hevc_sps/pps), and a new
V4L2_CID_STATELESS_HEVC_ENCODE_PARAMS control carries what the slice
segment headers of one picture need.

Unlike H.264, HEVC signals reference picture marking explicitly in every
slice header through the reference picture set (RPS). The encode params
therefore carry the RPS as a list of reference pictures identified by
the timestamp of the OUTPUT buffer they were encoded from, each marked
as used by the current picture or kept for later pictures, and short or
long term. The kernel knows the picture order count of each
reconstructed picture it keeps, so it turns that list into
st_ref_pic_set() and the long-term part of the slice header, builds the
reference picture lists, and drops every picture that is not listed,
exactly like a decoder does (8.3.2). Hardware with a single reference
read port selects its reference simply by having one entry marked as
used.

The flags that carry slice header syntax elements use the same bit
values as their V4L2_HEVC_SLICE_PARAMS_FLAG_* counterparts.

Add a read-only V4L2_CID_STATELESS_HEVC_ENCODE_FEEDBACK control that
describes how the picture was actually encoded: the values the kernel
settled on (slice QP, the resolved reference picture lists) and
statistics measured by the hardware. Since applications never set it,
v4l2_ctrl_request_complete() copies the value the driver set at the end
of the encode run into the request, so the feedback can be read back per
request without read-only request support. The statistics are kept in a
codec-agnostic struct v4l2_stateless_encode_stats, so that the H.264
encoder can report the same data.

Validate the new compound controls: reject NAL unit types that are not
VCL picture types, TemporalId values that H.265 forbids for the given
type, IRAP pictures with P/B slices or used references, P/B pictures
without a used reference and duplicate references; zero the fields that
are not coded for the given slice type.

Signed-off-by: Arun Iyengar <aiyengar1990@gmail.com>
---
 drivers/media/v4l2-core/v4l2-ctrls-core.c | 194 ++++++++++++++++++++++++++++++
 drivers/media/v4l2-core/v4l2-ctrls-defs.c |   9 ++
 include/media/v4l2-ctrls.h                |   4 +
 include/uapi/linux/v4l2-controls.h        | 188 +++++++++++++++++++++++++++++
 include/uapi/linux/videodev2.h            |   2 +
 5 files changed, 397 insertions(+)

diff --git a/drivers/media/v4l2-core/v4l2-ctrls-core.c b/drivers/media/v4l2-core/v4l2-ctrls-core.c
index cb2b4b72914f..bdeabeb4c751 100644
--- a/drivers/media/v4l2-core/v4l2-ctrls-core.c
+++ b/drivers/media/v4l2-core/v4l2-ctrls-core.c
@@ -11,6 +11,7 @@
 #include <media/v4l2-ctrls.h>
 #include <media/v4l2-event.h>
 #include <media/v4l2-fwnode.h>
+#include <media/v4l2-hevc.h>
 
 #include "v4l2-ctrls-priv.h"
 
@@ -19,6 +20,21 @@ static const union v4l2_ctrl_ptr ptr_null;
 #define V4L2_HEVC_MAX_SHORT_TERM_REF_PIC_SETS	64
 #define V4L2_HEVC_MAX_LONG_TERM_REF_PICS_SPS	32
 
+#define V4L2_HEVC_ENCODE_FLAGS_ALL \
+	(V4L2_HEVC_ENCODE_FLAG_SLICE_SAO_LUMA | \
+	 V4L2_HEVC_ENCODE_FLAG_SLICE_SAO_CHROMA | \
+	 V4L2_HEVC_ENCODE_FLAG_SLICE_TEMPORAL_MVP_ENABLED | \
+	 V4L2_HEVC_ENCODE_FLAG_MVD_L1_ZERO | \
+	 V4L2_HEVC_ENCODE_FLAG_CABAC_INIT | \
+	 V4L2_HEVC_ENCODE_FLAG_COLLOCATED_FROM_L0 | \
+	 V4L2_HEVC_ENCODE_FLAG_SLICE_DEBLOCKING_FILTER_DISABLED | \
+	 V4L2_HEVC_ENCODE_FLAG_SLICE_LOOP_FILTER_ACROSS_SLICES_ENABLED | \
+	 V4L2_HEVC_ENCODE_FLAG_REFERENCE | \
+	 V4L2_HEVC_ENCODE_FLAG_NO_OUTPUT_OF_PRIOR_PICS | \
+	 V4L2_HEVC_ENCODE_FLAG_NO_PIC_OUTPUT | \
+	 V4L2_HEVC_ENCODE_FLAG_NUM_REF_IDX_ACTIVE_OVERRIDE | \
+	 V4L2_HEVC_ENCODE_FLAG_DEBLOCKING_FILTER_OVERRIDE)
+
 static void fill_event(struct v4l2_event *ev, struct v4l2_ctrl *ctrl,
 		       u32 changes)
 {
@@ -116,6 +132,8 @@ static void std_init_compound(const struct v4l2_ctrl *ctrl, u32 idx,
 	struct v4l2_ctrl_h264_scaling_matrix *p_h264_scaling_matrix;
 	struct v4l2_ctrl_av1_sequence *p_av1_sequence;
 	struct v4l2_ctrl_hevc_sps *p_hevc_sps;
+	struct v4l2_ctrl_hevc_encode_params *p_hevc_enc_params;
+	struct v4l2_ctrl_hevc_encode_feedback *p_hevc_enc_feedback;
 	void *p = ptr.p + idx * ctrl->elem_size;
 
 	if (ctrl->p_def.p_const)
@@ -195,6 +213,20 @@ static void std_init_compound(const struct v4l2_ctrl *ctrl, u32 idx,
 		/* 4:2:0 */
 		p_hevc_sps->chroma_format_idc = 1;
 		break;
+	case V4L2_CTRL_TYPE_HEVC_ENCODE_PARAMS:
+		p_hevc_enc_params = p;
+		/* A valid default: an IDR picture. */
+		p_hevc_enc_params->nal_unit_type = V4L2_HEVC_NAL_IDR_W_RADL;
+		p_hevc_enc_params->nuh_temporal_id_plus1 = 1;
+		p_hevc_enc_params->slice_type = V4L2_HEVC_SLICE_TYPE_I;
+		p_hevc_enc_params->flags = V4L2_HEVC_ENCODE_FLAG_REFERENCE;
+		break;
+	case V4L2_CTRL_TYPE_HEVC_ENCODE_FEEDBACK:
+		p_hevc_enc_feedback = p;
+		p_hevc_enc_feedback->nal_unit_type = V4L2_HEVC_NAL_IDR_W_RADL;
+		p_hevc_enc_feedback->nuh_temporal_id_plus1 = 1;
+		p_hevc_enc_feedback->slice_type = V4L2_HEVC_SLICE_TYPE_I;
+		break;
 	}
 }
 
@@ -449,6 +481,12 @@ void v4l2_ctrl_type_op_log(const struct v4l2_ctrl *ctrl)
 	case V4L2_CTRL_TYPE_HEVC_DECODE_PARAMS:
 		pr_cont("HEVC_DECODE_PARAMS");
 		break;
+	case V4L2_CTRL_TYPE_HEVC_ENCODE_PARAMS:
+		pr_cont("HEVC_ENCODE_PARAMS");
+		break;
+	case V4L2_CTRL_TYPE_HEVC_ENCODE_FEEDBACK:
+		pr_cont("HEVC_ENCODE_FEEDBACK");
+		break;
 	case V4L2_CTRL_TYPE_AV1_SEQUENCE:
 		pr_cont("AV1_SEQUENCE");
 		break;
@@ -988,6 +1026,140 @@ static int validate_av1_sequence(struct v4l2_ctrl_av1_sequence *s)
  * in order to properly detect unchanged controls with v4l2_ctrl_type_op_equal's
  * memcmp.
  */
+static int
+validate_hevc_encode_params(struct v4l2_ctrl_hevc_encode_params *p)
+{
+	u8 nal_unit_type = p->nal_unit_type;
+	bool used = false;
+	unsigned int i, j;
+
+	if (p->flags & ~V4L2_HEVC_ENCODE_FLAGS_ALL)
+		return -EINVAL;
+
+	/* Only the VCL NAL unit types of Table 7-1 that can be produced. */
+	if (nal_unit_type > V4L2_HEVC_NAL_RASL_R &&
+	    (nal_unit_type < V4L2_HEVC_NAL_BLA_W_LP ||
+	     nal_unit_type > V4L2_HEVC_NAL_CRA_NUT))
+		return -EINVAL;
+
+	if (p->slice_type > V4L2_HEVC_SLICE_TYPE_I)
+		return -EINVAL;
+
+	if (!p->nuh_temporal_id_plus1 ||
+	    p->nuh_temporal_id_plus1 > V4L2_HEVC_MAX_SUB_LAYERS)
+		return -EINVAL;
+
+	/* IRAP pictures only contain I slices and have TemporalId 0. */
+	if (v4l2_hevc_nal_is_irap(nal_unit_type) &&
+	    (p->slice_type != V4L2_HEVC_SLICE_TYPE_I ||
+	     p->nuh_temporal_id_plus1 != 1))
+		return -EINVAL;
+
+	/* TSA and STSA pictures cannot have TemporalId 0 (7.4.2.2). */
+	if (nal_unit_type >= V4L2_HEVC_NAL_TSA_N &&
+	    nal_unit_type <= V4L2_HEVC_NAL_STSA_R &&
+	    p->nuh_temporal_id_plus1 == 1)
+		return -EINVAL;
+
+	if (p->num_refs > V4L2_HEVC_DPB_ENTRIES_NUM_MAX)
+		return -EINVAL;
+
+	if (p->five_minus_max_num_merge_cand > 4)
+		return -EINVAL;
+
+	if (p->slice_cb_qp_offset < -12 || p->slice_cb_qp_offset > 12 ||
+	    p->slice_cr_qp_offset < -12 || p->slice_cr_qp_offset > 12)
+		return -EINVAL;
+
+	if (p->num_ref_idx_l0_active_minus1 >= V4L2_HEVC_DPB_ENTRIES_NUM_MAX - 1 ||
+	    p->num_ref_idx_l1_active_minus1 >= V4L2_HEVC_DPB_ENTRIES_NUM_MAX - 1 ||
+	    p->collocated_ref_idx >= V4L2_HEVC_DPB_ENTRIES_NUM_MAX - 1)
+		return -EINVAL;
+
+	if (p->flags & V4L2_HEVC_ENCODE_FLAG_DEBLOCKING_FILTER_OVERRIDE &&
+	    !(p->flags & V4L2_HEVC_ENCODE_FLAG_SLICE_DEBLOCKING_FILTER_DISABLED)) {
+		if (p->slice_beta_offset_div2 < -6 ||
+		    p->slice_beta_offset_div2 > 6 ||
+		    p->slice_tc_offset_div2 < -6 ||
+		    p->slice_tc_offset_div2 > 6)
+			return -EINVAL;
+	} else {
+		p->slice_beta_offset_div2 = 0;
+		p->slice_tc_offset_div2 = 0;
+	}
+
+	if (!(p->flags & V4L2_HEVC_ENCODE_FLAG_DEBLOCKING_FILTER_OVERRIDE))
+		p->flags &= ~V4L2_HEVC_ENCODE_FLAG_SLICE_DEBLOCKING_FILTER_DISABLED;
+
+	/* An IDR picture empties the DPB: it has no reference picture set. */
+	if (v4l2_hevc_nal_is_idr(nal_unit_type)) {
+		p->pic_order_cnt_val = 0;
+		p->num_refs = 0;
+	}
+
+	for (i = 0; i < V4L2_HEVC_DPB_ENTRIES_NUM_MAX; i++) {
+		struct v4l2_hevc_encode_ref *ref = &p->refs[i];
+
+		if (i >= p->num_refs) {
+			memset(ref, 0, sizeof(*ref));
+			continue;
+		}
+
+		if (ref->flags & ~(V4L2_HEVC_ENCODE_REF_FLAG_USED_BY_CURR_PIC |
+				   V4L2_HEVC_ENCODE_REF_FLAG_LONG_TERM))
+			return -EINVAL;
+
+		zero_reserved(*ref);
+
+		for (j = 0; j < i; j++)
+			if (p->refs[j].timestamp == ref->timestamp)
+				return -EINVAL;
+
+		if (ref->flags & V4L2_HEVC_ENCODE_REF_FLAG_USED_BY_CURR_PIC)
+			used = true;
+	}
+
+	/* NumPicTotalCurr is 0 for BLA and CRA pictures (7.4.7.2). */
+	if (v4l2_hevc_nal_is_irap(nal_unit_type) && used)
+		return -EINVAL;
+
+	/* P and B slices need at least one reference (7.4.7.2). */
+	if (p->slice_type != V4L2_HEVC_SLICE_TYPE_I && !used)
+		return -EINVAL;
+
+	if (p->slice_type == V4L2_HEVC_SLICE_TYPE_I) {
+		p->num_ref_idx_l0_active_minus1 = 0;
+		p->num_ref_idx_l1_active_minus1 = 0;
+		p->collocated_ref_idx = 0;
+		p->five_minus_max_num_merge_cand = 0;
+		p->flags &= ~(V4L2_HEVC_ENCODE_FLAG_NUM_REF_IDX_ACTIVE_OVERRIDE |
+			      V4L2_HEVC_ENCODE_FLAG_SLICE_TEMPORAL_MVP_ENABLED |
+			      V4L2_HEVC_ENCODE_FLAG_COLLOCATED_FROM_L0 |
+			      V4L2_HEVC_ENCODE_FLAG_CABAC_INIT);
+	}
+
+	if (p->slice_type != V4L2_HEVC_SLICE_TYPE_B) {
+		p->num_ref_idx_l1_active_minus1 = 0;
+		p->flags &= ~V4L2_HEVC_ENCODE_FLAG_MVD_L1_ZERO;
+
+		/* collocated_from_l0_flag is inferred to be 1 (7.4.7.1). */
+		if (p->slice_type == V4L2_HEVC_SLICE_TYPE_P)
+			p->flags |= V4L2_HEVC_ENCODE_FLAG_COLLOCATED_FROM_L0;
+	}
+
+	if (!(p->flags & V4L2_HEVC_ENCODE_FLAG_NUM_REF_IDX_ACTIVE_OVERRIDE)) {
+		p->num_ref_idx_l0_active_minus1 = 0;
+		p->num_ref_idx_l1_active_minus1 = 0;
+	}
+
+	if (!(p->flags & V4L2_HEVC_ENCODE_FLAG_SLICE_TEMPORAL_MVP_ENABLED))
+		p->collocated_ref_idx = 0;
+
+	zero_reserved(*p);
+
+	return 0;
+}
+
 static int std_validate_compound(const struct v4l2_ctrl *ctrl, u32 idx,
 				 union v4l2_ctrl_ptr ptr)
 {
@@ -1008,6 +1180,7 @@ static int std_validate_compound(const struct v4l2_ctrl *ctrl, u32 idx,
 	struct v4l2_ctrl_hevc_slice_params *p_hevc_slice_params;
 	struct v4l2_ctrl_hdr10_mastering_display *p_hdr10_mastering;
 	struct v4l2_ctrl_hevc_decode_params *p_hevc_decode_params;
+	struct v4l2_ctrl_hevc_encode_feedback *p_hevc_enc_feedback;
 	struct v4l2_area *area;
 	struct v4l2_rect *rect;
 	void *p = ptr.p + idx * ctrl->elem_size;
@@ -1402,6 +1575,21 @@ static int std_validate_compound(const struct v4l2_ctrl *ctrl, u32 idx,
 			return -EINVAL;
 		break;
 
+	case V4L2_CTRL_TYPE_HEVC_ENCODE_PARAMS:
+		return validate_hevc_encode_params(p);
+
+	case V4L2_CTRL_TYPE_HEVC_ENCODE_FEEDBACK:
+		p_hevc_enc_feedback = p;
+
+		memset(&p_hevc_enc_feedback->reserved0, 0,
+		       sizeof(p_hevc_enc_feedback->reserved0));
+		memset(&p_hevc_enc_feedback->reserved1, 0,
+		       sizeof(p_hevc_enc_feedback->reserved1));
+		memset(&p_hevc_enc_feedback->stats.reserved0, 0,
+		       sizeof(p_hevc_enc_feedback->stats.reserved0));
+		p_hevc_enc_feedback->stats.reserved1 = 0;
+		break;
+
 	case V4L2_CTRL_TYPE_HDR10_CLL_INFO:
 		break;
 
@@ -2185,6 +2373,12 @@ static struct v4l2_ctrl *v4l2_ctrl_new(struct v4l2_ctrl_handler *hdl,
 	case V4L2_CTRL_TYPE_HEVC_DECODE_PARAMS:
 		elem_size = sizeof(struct v4l2_ctrl_hevc_decode_params);
 		break;
+	case V4L2_CTRL_TYPE_HEVC_ENCODE_PARAMS:
+		elem_size = sizeof(struct v4l2_ctrl_hevc_encode_params);
+		break;
+	case V4L2_CTRL_TYPE_HEVC_ENCODE_FEEDBACK:
+		elem_size = sizeof(struct v4l2_ctrl_hevc_encode_feedback);
+		break;
 	case V4L2_CTRL_TYPE_HDR10_CLL_INFO:
 		elem_size = sizeof(struct v4l2_ctrl_hdr10_cll_info);
 		break;
diff --git a/drivers/media/v4l2-core/v4l2-ctrls-defs.c b/drivers/media/v4l2-core/v4l2-ctrls-defs.c
index a0ac858968b6..123b56a32ed5 100644
--- a/drivers/media/v4l2-core/v4l2-ctrls-defs.c
+++ b/drivers/media/v4l2-core/v4l2-ctrls-defs.c
@@ -1239,6 +1239,8 @@ const char *v4l2_ctrl_get_name(u32 id)
 	case V4L2_CID_STATELESS_HEVC_ENTRY_POINT_OFFSETS:	return "HEVC Entry Point Offsets";
 	case V4L2_CID_STATELESS_HEVC_EXT_SPS_ST_RPS:		return "HEVC Short Term Ref Sets";
 	case V4L2_CID_STATELESS_HEVC_EXT_SPS_LT_RPS:		return "HEVC Long Term Ref Sets";
+	case V4L2_CID_STATELESS_HEVC_ENCODE_PARAMS:		return "HEVC Encode Parameters";
+	case V4L2_CID_STATELESS_HEVC_ENCODE_FEEDBACK:		return "HEVC Encode Feedback";
 	case V4L2_CID_STATELESS_AV1_SEQUENCE:			return "AV1 Sequence Parameters";
 	case V4L2_CID_STATELESS_AV1_TILE_GROUP_ENTRY:		return "AV1 Tile Group Entry";
 	case V4L2_CID_STATELESS_AV1_FRAME:			return "AV1 Frame Parameters";
@@ -1597,6 +1599,13 @@ void v4l2_ctrl_fill(u32 id, const char **name, enum v4l2_ctrl_type *type,
 		*type = V4L2_CTRL_TYPE_HEVC_EXT_SPS_LT_RPS;
 		*flags |= V4L2_CTRL_FLAG_DYNAMIC_ARRAY;
 		break;
+	case V4L2_CID_STATELESS_HEVC_ENCODE_PARAMS:
+		*type = V4L2_CTRL_TYPE_HEVC_ENCODE_PARAMS;
+		break;
+	case V4L2_CID_STATELESS_HEVC_ENCODE_FEEDBACK:
+		*type = V4L2_CTRL_TYPE_HEVC_ENCODE_FEEDBACK;
+		*flags |= V4L2_CTRL_FLAG_READ_ONLY;
+		break;
 	case V4L2_CID_STATELESS_VP9_COMPRESSED_HDR:
 		*type = V4L2_CTRL_TYPE_VP9_COMPRESSED_HDR;
 		break;
diff --git a/include/media/v4l2-ctrls.h b/include/media/v4l2-ctrls.h
index 63f1aa373735..042d30b061da 100644
--- a/include/media/v4l2-ctrls.h
+++ b/include/media/v4l2-ctrls.h
@@ -50,6 +50,8 @@ struct video_device;
  * @p_hevc_sps:			Pointer to an HEVC sequence parameter set structure.
  * @p_hevc_pps:			Pointer to an HEVC picture parameter set structure.
  * @p_hevc_slice_params:	Pointer to an HEVC slice parameters structure.
+ * @p_hevc_encode_params:	Pointer to an HEVC encode parameters structure.
+ * @p_hevc_encode_feedback:	Pointer to an HEVC encode feedback structure.
  * @p_hdr10_cll:		Pointer to an HDR10 Content Light Level structure.
  * @p_hdr10_mastering:		Pointer to an HDR10 Mastering Display structure.
  * @p_area:			Pointer to an area.
@@ -83,6 +85,8 @@ union v4l2_ctrl_ptr {
 	struct v4l2_ctrl_hevc_sps *p_hevc_sps;
 	struct v4l2_ctrl_hevc_pps *p_hevc_pps;
 	struct v4l2_ctrl_hevc_slice_params *p_hevc_slice_params;
+	struct v4l2_ctrl_hevc_encode_params *p_hevc_encode_params;
+	struct v4l2_ctrl_hevc_encode_feedback *p_hevc_encode_feedback;
 	struct v4l2_ctrl_vp9_compressed_hdr *p_vp9_compressed_hdr_probs;
 	struct v4l2_ctrl_vp9_frame *p_vp9_frame;
 	struct v4l2_ctrl_hdr10_cll_info *p_hdr10_cll;
diff --git a/include/uapi/linux/v4l2-controls.h b/include/uapi/linux/v4l2-controls.h
index 3aca0f8c5248..cb88a560bdfa 100644
--- a/include/uapi/linux/v4l2-controls.h
+++ b/include/uapi/linux/v4l2-controls.h
@@ -2159,6 +2159,8 @@ struct v4l2_ctrl_mpeg2_quantisation {
 #define V4L2_CID_STATELESS_HEVC_ENTRY_POINT_OFFSETS (V4L2_CID_CODEC_STATELESS_BASE + 407)
 #define V4L2_CID_STATELESS_HEVC_EXT_SPS_ST_RPS  (V4L2_CID_CODEC_STATELESS_BASE + 408)
 #define V4L2_CID_STATELESS_HEVC_EXT_SPS_LT_RPS  (V4L2_CID_CODEC_STATELESS_BASE + 409)
+#define V4L2_CID_STATELESS_HEVC_ENCODE_PARAMS	(V4L2_CID_CODEC_STATELESS_BASE + 410)
+#define V4L2_CID_STATELESS_HEVC_ENCODE_FEEDBACK	(V4L2_CID_CODEC_STATELESS_BASE + 411)
 
 enum v4l2_stateless_hevc_decode_mode {
 	V4L2_STATELESS_HEVC_DECODE_MODE_SLICE_BASED,
@@ -2673,6 +2675,192 @@ struct v4l2_ctrl_hevc_ext_sps_lt_rps {
 	__u16	flags;
 };
 
+#define V4L2_STATELESS_ENCODE_STATS_FLAG_QP		0x00000001
+#define V4L2_STATELESS_ENCODE_STATS_FLAG_SSE_LUMA	0x00000002
+#define V4L2_STATELESS_ENCODE_STATS_FLAG_BLOCKS		0x00000004
+#define V4L2_STATELESS_ENCODE_STATS_FLAG_SKIP_BLOCKS	0x00000008
+
+/**
+ * struct v4l2_stateless_encode_stats - stateless encoder per-frame statistics
+ *
+ * Statistics measured by the hardware while encoding one frame. Only the
+ * fields whose V4L2_STATELESS_ENCODE_STATS_FLAG_{} bit is set in @flags are
+ * valid, the others are zero. This structure does not depend on the codec.
+ *
+ * @flags: see V4L2_STATELESS_ENCODE_STATS_FLAG_{}
+ * @header_size: number of bytes the kernel wrote at the start of the
+ *		 CAPTURE buffer (parameter sets, access unit delimiter, slice
+ *		 header) before the hardware-produced data
+ * @qp_sum: sum of the luma quantisation parameters of all blocks counted in
+ *	    @qp_count. Valid with V4L2_STATELESS_ENCODE_STATS_FLAG_QP.
+ * @qp_count: number of blocks summed in @qp_sum. @qp_sum / @qp_count is the
+ *	      average QP of the frame.
+ * @qp_min: smallest luma quantisation parameter used in the frame
+ * @qp_max: largest luma quantisation parameter used in the frame
+ * @reserved0: padding field. Should be zeroed by applications.
+ * @sse_luma: sum of squared errors between the source and the reconstructed
+ *	      luma samples of the frame. Valid with
+ *	      V4L2_STATELESS_ENCODE_STATS_FLAG_SSE_LUMA.
+ * @num_intra_blocks: number of 8x8 luma blocks coded with intra prediction.
+ *		      Valid with V4L2_STATELESS_ENCODE_STATS_FLAG_BLOCKS, like
+ *		      @num_inter_blocks.
+ * @num_inter_blocks: number of 8x8 luma blocks coded with inter prediction,
+ *		      including skipped blocks
+ * @num_skip_blocks: number of 8x8 luma blocks coded in skip mode, part of
+ *		     @num_inter_blocks. Valid with
+ *		     V4L2_STATELESS_ENCODE_STATS_FLAG_SKIP_BLOCKS.
+ * @reserved1: padding field. Should be zeroed by applications.
+ */
+struct v4l2_stateless_encode_stats {
+	__u32	flags;
+	__u32	header_size;
+	__u32	qp_sum;
+	__u32	qp_count;
+	__s8	qp_min;
+	__s8	qp_max;
+	__u8	reserved0[6];
+	__u64	sse_luma;
+	__u32	num_intra_blocks;
+	__u32	num_inter_blocks;
+	__u32	num_skip_blocks;
+	__u32	reserved1;
+};
+
+#define V4L2_HEVC_ENCODE_REF_FLAG_USED_BY_CURR_PIC	0x01
+#define V4L2_HEVC_ENCODE_REF_FLAG_LONG_TERM		0x02
+
+/**
+ * struct v4l2_hevc_encode_ref - HEVC encoder reference picture set entry
+ *
+ * @timestamp: timestamp of the OUTPUT (source) buffer from which the
+ *	       reference picture was encoded. Use v4l2_timeval_to_ns() to
+ *	       convert the struct timeval to a __u64.
+ * @flags: see V4L2_HEVC_ENCODE_REF_FLAG_{}
+ * @reserved: padding field. Should be zeroed by applications.
+ */
+struct v4l2_hevc_encode_ref {
+	__u64	timestamp;
+	__u8	flags;
+	__u8	reserved[7];
+};
+
+/*
+ * The first flags have the same values as the V4L2_HEVC_SLICE_PARAMS_FLAG_{}
+ * flags that carry the same slice header syntax element.
+ */
+#define V4L2_HEVC_ENCODE_FLAG_SLICE_SAO_LUMA			(1ULL << 0)
+#define V4L2_HEVC_ENCODE_FLAG_SLICE_SAO_CHROMA			(1ULL << 1)
+#define V4L2_HEVC_ENCODE_FLAG_SLICE_TEMPORAL_MVP_ENABLED	(1ULL << 2)
+#define V4L2_HEVC_ENCODE_FLAG_MVD_L1_ZERO			(1ULL << 3)
+#define V4L2_HEVC_ENCODE_FLAG_CABAC_INIT			(1ULL << 4)
+#define V4L2_HEVC_ENCODE_FLAG_COLLOCATED_FROM_L0		(1ULL << 5)
+#define V4L2_HEVC_ENCODE_FLAG_SLICE_DEBLOCKING_FILTER_DISABLED	(1ULL << 7)
+#define V4L2_HEVC_ENCODE_FLAG_SLICE_LOOP_FILTER_ACROSS_SLICES_ENABLED (1ULL << 8)
+#define V4L2_HEVC_ENCODE_FLAG_REFERENCE				(1ULL << 16)
+#define V4L2_HEVC_ENCODE_FLAG_NO_OUTPUT_OF_PRIOR_PICS		(1ULL << 17)
+#define V4L2_HEVC_ENCODE_FLAG_NO_PIC_OUTPUT			(1ULL << 18)
+#define V4L2_HEVC_ENCODE_FLAG_NUM_REF_IDX_ACTIVE_OVERRIDE	(1ULL << 19)
+#define V4L2_HEVC_ENCODE_FLAG_DEBLOCKING_FILTER_OVERRIDE	(1ULL << 20)
+
+/**
+ * struct v4l2_ctrl_hevc_encode_params - HEVC stateless encode parameters
+ *
+ * Per-picture parameters of a stateless HEVC encode run. The kernel writes
+ * the NAL unit header and the slice segment header(s) from these values,
+ * or has the hardware write them.
+ *
+ * @nal_unit_type: NAL unit type of the coded slice segments (ITU-T Rec.
+ *		   H.265 Table 7-1, VCL types 0 to 9 and 16 to 21)
+ * @nuh_temporal_id_plus1: TemporalId of the picture plus 1
+ * @slice_type: slice type of the picture, see V4L2_HEVC_SLICE_TYPE_{}
+ * @pic_parameter_set_id: identifies the PPS in use
+ * @pic_order_cnt_val: picture order count of the picture (PicOrderCntVal).
+ *		       The kernel derives slice_pic_order_cnt_lsb from it.
+ * @num_ref_idx_l0_active_minus1: maximum reference index for reference
+ *				  picture list 0, used with
+ *				  V4L2_HEVC_ENCODE_FLAG_NUM_REF_IDX_ACTIVE_OVERRIDE
+ * @num_ref_idx_l1_active_minus1: maximum reference index for reference
+ *				  picture list 1, used with
+ *				  V4L2_HEVC_ENCODE_FLAG_NUM_REF_IDX_ACTIVE_OVERRIDE
+ * @collocated_ref_idx: reference index of the collocated picture used for
+ *			temporal motion vector prediction
+ * @five_minus_max_num_merge_cand: maximum number of merging motion vector
+ *				   prediction candidates subtracted from 5
+ * @slice_cb_qp_offset: difference to be added to pps_cb_qp_offset
+ * @slice_cr_qp_offset: difference to be added to pps_cr_qp_offset
+ * @slice_beta_offset_div2: deblocking parameter offset for beta divided by 2
+ * @slice_tc_offset_div2: deblocking parameter offset for tC divided by 2
+ * @num_refs: number of valid entries in @refs
+ * @reserved: padding field. Should be zeroed by applications.
+ * @refs: reference picture set of the picture: every previously encoded
+ *	  picture that the current picture or a following picture uses for
+ *	  inter prediction. Pictures that are not listed are removed from the
+ *	  decoded picture buffer.
+ * @flags: see V4L2_HEVC_ENCODE_FLAG_{}
+ */
+struct v4l2_ctrl_hevc_encode_params {
+	__u8	nal_unit_type;
+	__u8	nuh_temporal_id_plus1;
+	__u8	slice_type;
+	__u8	pic_parameter_set_id;
+	__s32	pic_order_cnt_val;
+	__u8	num_ref_idx_l0_active_minus1;
+	__u8	num_ref_idx_l1_active_minus1;
+	__u8	collocated_ref_idx;
+	__u8	five_minus_max_num_merge_cand;
+	__s8	slice_cb_qp_offset;
+	__s8	slice_cr_qp_offset;
+	__s8	slice_beta_offset_div2;
+	__s8	slice_tc_offset_div2;
+	__u8	num_refs;
+	__u8	reserved[7];
+	struct v4l2_hevc_encode_ref refs[V4L2_HEVC_DPB_ENTRIES_NUM_MAX];
+	__u64	flags;
+};
+
+#define V4L2_HEVC_ENCODE_FEEDBACK_FLAG_REFERENCE		0x01
+
+/**
+ * struct v4l2_ctrl_hevc_encode_feedback - HEVC stateless encode feedback
+ *
+ * Read-only description of how a picture was actually encoded. The kernel
+ * sets it when the encode run completes, so applications read it from the
+ * completed request.
+ *
+ * @timestamp: timestamp of the OUTPUT (source) buffer that was encoded
+ * @pic_order_cnt_val: picture order count of the picture
+ * @nal_unit_type: NAL unit type that was used
+ * @nuh_temporal_id_plus1: TemporalId that was used, plus 1
+ * @slice_type: slice type that was used, see V4L2_HEVC_SLICE_TYPE_{}
+ * @slice_qp: luma quantisation parameter of the first slice (SliceQpY)
+ * @num_ref_idx_l0_active: number of entries in reference picture list 0
+ * @num_ref_idx_l1_active: number of entries in reference picture list 1
+ * @reserved0: padding field. Should be zeroed by applications.
+ * @ref_idx_l0: index in &v4l2_ctrl_hevc_encode_params.refs of each entry of
+ *		reference picture list 0
+ * @ref_idx_l1: index in &v4l2_ctrl_hevc_encode_params.refs of each entry of
+ *		reference picture list 1
+ * @reserved1: padding field. Should be zeroed by applications.
+ * @stats: statistics measured while encoding the picture
+ * @flags: see V4L2_HEVC_ENCODE_FEEDBACK_FLAG_{}
+ */
+struct v4l2_ctrl_hevc_encode_feedback {
+	__u64	timestamp;
+	__s32	pic_order_cnt_val;
+	__u8	nal_unit_type;
+	__u8	nuh_temporal_id_plus1;
+	__u8	slice_type;
+	__s8	slice_qp;
+	__u8	num_ref_idx_l0_active;
+	__u8	num_ref_idx_l1_active;
+	__u8	reserved0[2];
+	__u8	ref_idx_l0[V4L2_HEVC_DPB_ENTRIES_NUM_MAX];
+	__u8	ref_idx_l1[V4L2_HEVC_DPB_ENTRIES_NUM_MAX];
+	__u8	reserved1[4];
+	struct v4l2_stateless_encode_stats stats;
+	__u64	flags;
+};
+
 /* Stateless VP9 controls */
 
 #define V4L2_VP9_LOOP_FILTER_FLAG_DELTA_ENABLED	0x1
diff --git a/include/uapi/linux/videodev2.h b/include/uapi/linux/videodev2.h
index e19628c520e6..a5673254e315 100644
--- a/include/uapi/linux/videodev2.h
+++ b/include/uapi/linux/videodev2.h
@@ -1993,6 +1993,8 @@ enum v4l2_ctrl_type {
 	V4L2_CTRL_TYPE_HEVC_DECODE_PARAMS	= 0x0274,
 	V4L2_CTRL_TYPE_HEVC_EXT_SPS_ST_RPS	= 0x0275,
 	V4L2_CTRL_TYPE_HEVC_EXT_SPS_LT_RPS	= 0x0276,
+	V4L2_CTRL_TYPE_HEVC_ENCODE_PARAMS	= 0x0277,
+	V4L2_CTRL_TYPE_HEVC_ENCODE_FEEDBACK	= 0x0278,
 
 	V4L2_CTRL_TYPE_AV1_SEQUENCE	    = 0x280,
 	V4L2_CTRL_TYPE_AV1_TILE_GROUP_ENTRY = 0x281,

-- 
2.55.0



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

* [PATCH RFC 03/10] media: hevc: Add stateless encode RBSP writer
  2026-11-10 10:07 [PATCH RFC 00/10] media: Add a V4L2 HEVC stateless encode uAPI and helpers Arun Iyengar via B4 Relay
  2026-11-10 10:07 ` [PATCH RFC 01/10] media: hevc: Add H.265 syntax definitions Arun Iyengar via B4 Relay
  2026-11-10 10:07 ` [PATCH RFC 02/10] media: uapi: Add HEVC stateless encode support Arun Iyengar via B4 Relay
@ 2026-11-10 10:07 ` Arun Iyengar via B4 Relay
  2026-11-10 10:07 ` [PATCH RFC 04/10] media: hevc: Add KUnit tests for the " Arun Iyengar via B4 Relay
                   ` (6 subsequent siblings)
  9 siblings, 0 replies; 11+ messages in thread
From: Arun Iyengar via B4 Relay @ 2026-11-10 10:07 UTC (permalink / raw)
  To: Mauro Carvalho Chehab
  Cc: linux-media, linux-kernel, Paul Kocialkowski, Nicolas Dufresne,
	Hans Verkuil, Arun Iyengar

From: Arun Iyengar <aiyengar1990@gmail.com>

Add a writer for the HEVC NAL units that a stateless encoder needs the
kernel to produce: video, sequence and picture parameter sets, access
unit delimiters and slice segment headers, with optional Annex B start
codes. It is the HEVC counterpart of the H.264 stateless encode RBSP
helper.

The SPS and PPS are written from the existing struct v4l2_ctrl_hevc_sps
and struct v4l2_ctrl_hevc_pps. What those structures do not carry
because a decoder does not need it (profile, tier and level, the
conformance window, VUI, temporal nesting) comes from a kernel-internal
struct v4l2_hevc_sps_video, like struct v4l2_h264_sps_video does for
H.264. The VPS is derived from both. Slice segment headers are written
from a struct v4l2_hevc_slice_header holding the resolved syntax element
values, so that drivers for hardware that writes its own slice headers
can program their registers from the same structure.

The scope is what an encoder chooses to use: reference picture sets are
always coded in the slice header, the SPS carries no RPS and no
long-term reference pictures, scaling lists are the default ones,
reference picture lists are not modified and there are no weighted
prediction tables, HRD parameters, SEI or extensions. Writing those
returns -EINVAL rather than an incorrect bitstream.

Emulation prevention is applied byte by byte as the payload is written,
and errors (a full buffer, values that cannot be coded) are sticky so
that the syntax writers read like the syntax tables of the
specification. The H.264 writer keeps its own bit writer for now; both
could share one later.

Signed-off-by: Arun Iyengar <aiyengar1990@gmail.com>
---
 drivers/media/v4l2-core/Kconfig              |   4 +
 drivers/media/v4l2-core/Makefile             |   1 +
 drivers/media/v4l2-core/v4l2-hevc-enc-rbsp.c | 977 +++++++++++++++++++++++++++
 include/media/v4l2-hevc-enc-rbsp.h           | 279 ++++++++
 4 files changed, 1261 insertions(+)

diff --git a/drivers/media/v4l2-core/Kconfig b/drivers/media/v4l2-core/Kconfig
index b8b849a44abd..9f9eda5d6eac 100644
--- a/drivers/media/v4l2-core/Kconfig
+++ b/drivers/media/v4l2-core/Kconfig
@@ -53,6 +53,10 @@ config V4L2_H264_ENC
 	tristate
 	select V4L2_ENC_CBR
 
+# Used by drivers that need v4l2-hevc-enc-rbsp.ko
+config V4L2_HEVC_ENC_RBSP
+	tristate
+
 # Used by drivers that need v4l2-vp9.ko
 config V4L2_VP9
 	tristate
diff --git a/drivers/media/v4l2-core/Makefile b/drivers/media/v4l2-core/Makefile
index 3aa0855d7be7..3dda145d94d1 100644
--- a/drivers/media/v4l2-core/Makefile
+++ b/drivers/media/v4l2-core/Makefile
@@ -32,6 +32,7 @@ obj-$(CONFIG_V4L2_FWNODE) += v4l2-fwnode.o
 obj-$(CONFIG_V4L2_H264) += v4l2-h264.o
 obj-$(CONFIG_V4L2_H264_ENC) += v4l2-h264-enc.o v4l2-h264-enc-rbsp.o \
 			       v4l2-h264-enc-rc.o
+obj-$(CONFIG_V4L2_HEVC_ENC_RBSP) += v4l2-hevc-enc-rbsp.o
 obj-$(CONFIG_V4L2_ISP) += v4l2-isp.o
 obj-$(CONFIG_V4L2_JPEG_HELPER) += v4l2-jpeg.o
 obj-$(CONFIG_V4L2_MEM2MEM_DEV) += v4l2-mem2mem.o
diff --git a/drivers/media/v4l2-core/v4l2-hevc-enc-rbsp.c b/drivers/media/v4l2-core/v4l2-hevc-enc-rbsp.c
new file mode 100644
index 000000000000..ee9242f92950
--- /dev/null
+++ b/drivers/media/v4l2-core/v4l2-hevc-enc-rbsp.c
@@ -0,0 +1,977 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * V4L2 HEVC Encode RBSP
+ *
+ * Section and table numbers refer to ITU-T Rec. H.265 (V10, 07/2024).
+ */
+
+#include <linux/bitops.h>
+#include <linux/bits.h>
+#include <linux/bug.h>
+#include <linux/errno.h>
+#include <linux/export.h>
+#include <linux/limits.h>
+#include <linux/log2.h>
+#include <linux/math.h>
+#include <linux/minmax.h>
+#include <linux/module.h>
+#include <linux/string.h>
+#include <linux/types.h>
+#include <linux/v4l2-controls.h>
+#include <media/v4l2-hevc.h>
+#include <media/v4l2-hevc-enc-rbsp.h>
+
+#define EMULATION_PREVENTION_THREE_BYTE	0x03
+
+/**
+ * v4l2_hevc_enc_rbsp_init() - start writing NAL units to a buffer
+ * @rbsp: the writer
+ * @data: the destination buffer
+ * @size: the size of @data in bytes
+ */
+void v4l2_hevc_enc_rbsp_init(struct v4l2_hevc_enc_rbsp *rbsp, u8 *data,
+			     unsigned int size)
+{
+	memset(rbsp, 0, sizeof(*rbsp));
+	rbsp->data = data;
+	rbsp->size = size;
+}
+EXPORT_SYMBOL_GPL(v4l2_hevc_enc_rbsp_init);
+
+/**
+ * v4l2_hevc_enc_rbsp_bytes() - number of bytes written so far
+ * @rbsp: the writer
+ *
+ * Bits that do not fill a byte yet are not counted. All the NAL unit and
+ * slice header writers end byte-aligned.
+ *
+ * Return: the number of bytes written, emulation prevention bytes included.
+ */
+unsigned int v4l2_hevc_enc_rbsp_bytes(const struct v4l2_hevc_enc_rbsp *rbsp)
+{
+	return rbsp->offset;
+}
+EXPORT_SYMBOL_GPL(v4l2_hevc_enc_rbsp_bytes);
+
+/**
+ * v4l2_hevc_enc_rbsp_error() - first error of the writer
+ * @rbsp: the writer
+ *
+ * Return: 0, -ENOSPC when the buffer is too small, or -EINVAL when a value
+ * cannot be represented or the parameters are not supported.
+ */
+int v4l2_hevc_enc_rbsp_error(const struct v4l2_hevc_enc_rbsp *rbsp)
+{
+	return rbsp->error;
+}
+EXPORT_SYMBOL_GPL(v4l2_hevc_enc_rbsp_error);
+
+static void rbsp_fail(struct v4l2_hevc_enc_rbsp *rbsp, int error)
+{
+	if (!rbsp->error)
+		rbsp->error = error;
+}
+
+static void rbsp_emit(struct v4l2_hevc_enc_rbsp *rbsp, u8 byte)
+{
+	if (rbsp->offset >= rbsp->size) {
+		rbsp_fail(rbsp, -ENOSPC);
+		return;
+	}
+
+	rbsp->data[rbsp->offset++] = byte;
+}
+
+/*
+ * Write one byte of a NAL unit. Inside the payload, a byte in the range
+ * 0x00 to 0x03 that follows two zero bytes gets an
+ * emulation_prevention_three_byte in front of it (7.4.2).
+ */
+static void rbsp_put_byte(struct v4l2_hevc_enc_rbsp *rbsp, u8 byte)
+{
+	if (rbsp->error)
+		return;
+
+	if (rbsp->epb && rbsp->zero_bytes >= 2 &&
+	    byte <= EMULATION_PREVENTION_THREE_BYTE) {
+		rbsp_emit(rbsp, EMULATION_PREVENTION_THREE_BYTE);
+		rbsp->zero_bytes = 0;
+	}
+
+	rbsp_emit(rbsp, byte);
+
+	if (byte)
+		rbsp->zero_bytes = 0;
+	else
+		rbsp->zero_bytes++;
+}
+
+/**
+ * v4l2_hevc_enc_rbsp_bits() - write a fixed-length value, u(n)
+ * @rbsp: the writer
+ * @value: the value, only the @count low bits are written
+ * @count: the number of bits, up to 32
+ */
+void v4l2_hevc_enc_rbsp_bits(struct v4l2_hevc_enc_rbsp *rbsp, u32 value,
+			     unsigned int count)
+{
+	if (rbsp->error || !count)
+		return;
+
+	if (WARN_ON(count > 32)) {
+		rbsp_fail(rbsp, -EINVAL);
+		return;
+	}
+
+	rbsp->cache = (rbsp->cache << count) | (value & GENMASK(count - 1, 0));
+	rbsp->cache_bits += count;
+
+	while (rbsp->cache_bits >= 8) {
+		rbsp->cache_bits -= 8;
+		rbsp_put_byte(rbsp, rbsp->cache >> rbsp->cache_bits);
+	}
+
+	rbsp->cache &= GENMASK_ULL(7, 0) >> (8 - rbsp->cache_bits);
+}
+EXPORT_SYMBOL_GPL(v4l2_hevc_enc_rbsp_bits);
+
+static void rbsp_flag(struct v4l2_hevc_enc_rbsp *rbsp, bool value)
+{
+	v4l2_hevc_enc_rbsp_bits(rbsp, value, 1);
+}
+
+static void rbsp_flags(struct v4l2_hevc_enc_rbsp *rbsp, u64 flags, u64 flag)
+{
+	rbsp_flag(rbsp, !!(flags & flag));
+}
+
+/**
+ * v4l2_hevc_enc_rbsp_ue() - write an unsigned Exp-Golomb value, ue(v) (9.2)
+ * @rbsp: the writer
+ * @value: the value, up to 2^32 - 2
+ */
+void v4l2_hevc_enc_rbsp_ue(struct v4l2_hevc_enc_rbsp *rbsp, u32 value)
+{
+	u64 code = (u64)value + 1;
+	unsigned int len = fls64(code);
+
+	if (value == U32_MAX) {
+		rbsp_fail(rbsp, -EINVAL);
+		return;
+	}
+
+	/* len - 1 leading zero bits, then code on len bits. */
+	v4l2_hevc_enc_rbsp_bits(rbsp, 0, len - 1);
+	v4l2_hevc_enc_rbsp_bits(rbsp, code, len);
+}
+EXPORT_SYMBOL_GPL(v4l2_hevc_enc_rbsp_ue);
+
+/**
+ * v4l2_hevc_enc_rbsp_se() - write a signed Exp-Golomb value, se(v) (9.2.2)
+ * @rbsp: the writer
+ * @value: the value, from -(2^31 - 1) to 2^31 - 1
+ */
+void v4l2_hevc_enc_rbsp_se(struct v4l2_hevc_enc_rbsp *rbsp, s32 value)
+{
+	if (value == S32_MIN) {
+		rbsp_fail(rbsp, -EINVAL);
+		return;
+	}
+
+	if (value > 0)
+		v4l2_hevc_enc_rbsp_ue(rbsp, 2 * (u32)value - 1);
+	else
+		v4l2_hevc_enc_rbsp_ue(rbsp, 2 * (u32)-value);
+}
+EXPORT_SYMBOL_GPL(v4l2_hevc_enc_rbsp_se);
+
+/**
+ * v4l2_hevc_enc_rbsp_trailing_bits() - write rbsp_trailing_bits() (7.3.2.11)
+ * @rbsp: the writer
+ *
+ * A one bit followed by zero bits up to the next byte boundary. This is
+ * also the bit pattern of byte_alignment() (7.3.2.12), which ends slice
+ * segment headers.
+ */
+void v4l2_hevc_enc_rbsp_trailing_bits(struct v4l2_hevc_enc_rbsp *rbsp)
+{
+	rbsp_flag(rbsp, 1);
+
+	if (rbsp->cache_bits)
+		v4l2_hevc_enc_rbsp_bits(rbsp, 0, 8 - rbsp->cache_bits);
+}
+EXPORT_SYMBOL_GPL(v4l2_hevc_enc_rbsp_trailing_bits);
+
+/**
+ * v4l2_hevc_enc_rbsp_nal_begin() - start a NAL unit
+ * @rbsp: the writer
+ * @start_code: whether to write an Annex B start code (with zero_byte)
+ * @nal_unit_type: nal_unit_type
+ * @nuh_temporal_id_plus1: nuh_temporal_id_plus1
+ *
+ * Writes the optional start code and the NAL unit header (7.3.1.2), with
+ * nuh_layer_id equal to 0, then enables emulation prevention for the
+ * payload.
+ */
+void v4l2_hevc_enc_rbsp_nal_begin(struct v4l2_hevc_enc_rbsp *rbsp,
+				  bool start_code, u8 nal_unit_type,
+				  u8 nuh_temporal_id_plus1)
+{
+	if (WARN_ON(rbsp->cache_bits) || nal_unit_type > 63 ||
+	    !nuh_temporal_id_plus1 || nuh_temporal_id_plus1 > 7) {
+		rbsp_fail(rbsp, -EINVAL);
+		return;
+	}
+
+	rbsp->epb = false;
+
+	if (start_code)
+		v4l2_hevc_enc_rbsp_bits(rbsp, 0x00000001, 32);
+
+	/* forbidden_zero_bit, nal_unit_type, nuh_layer_id */
+	v4l2_hevc_enc_rbsp_bits(rbsp, nal_unit_type << 1, 8);
+	v4l2_hevc_enc_rbsp_bits(rbsp, nuh_temporal_id_plus1, 8);
+
+	rbsp->epb = true;
+	rbsp->zero_bytes = 0;
+}
+EXPORT_SYMBOL_GPL(v4l2_hevc_enc_rbsp_nal_begin);
+
+/**
+ * v4l2_hevc_enc_rbsp_nal_end() - end a NAL unit with rbsp_trailing_bits()
+ * @rbsp: the writer
+ */
+void v4l2_hevc_enc_rbsp_nal_end(struct v4l2_hevc_enc_rbsp *rbsp)
+{
+	v4l2_hevc_enc_rbsp_trailing_bits(rbsp);
+	rbsp->epb = false;
+}
+EXPORT_SYMBOL_GPL(v4l2_hevc_enc_rbsp_nal_end);
+
+/**
+ * v4l2_hevc_enc_rbsp_aud() - write an access unit delimiter (7.3.2.5)
+ * @rbsp: the writer
+ * @start_code: whether to write an Annex B start code
+ * @pic_type: pic_type, see V4L2_HEVC_AUD_PIC_TYPE_{}
+ * @nuh_temporal_id_plus1: TemporalId of the access unit, plus 1
+ *
+ * Return: 0 or the first error of the writer.
+ */
+int v4l2_hevc_enc_rbsp_aud(struct v4l2_hevc_enc_rbsp *rbsp, bool start_code,
+			   u8 pic_type, u8 nuh_temporal_id_plus1)
+{
+	if (pic_type > V4L2_HEVC_AUD_PIC_TYPE_BPI)
+		rbsp_fail(rbsp, -EINVAL);
+
+	v4l2_hevc_enc_rbsp_nal_begin(rbsp, start_code, V4L2_HEVC_NAL_AUD,
+				     nuh_temporal_id_plus1);
+	v4l2_hevc_enc_rbsp_bits(rbsp, pic_type, 3);
+	v4l2_hevc_enc_rbsp_nal_end(rbsp);
+
+	return rbsp->error;
+}
+EXPORT_SYMBOL_GPL(v4l2_hevc_enc_rbsp_aud);
+
+/* profile_tier_level(1, max_sub_layers_minus1) (7.3.3) */
+static void rbsp_ptl(struct v4l2_hevc_enc_rbsp *rbsp,
+		     const struct v4l2_hevc_ptl *ptl,
+		     unsigned int max_sub_layers_minus1)
+{
+	unsigned int i;
+
+	v4l2_hevc_enc_rbsp_bits(rbsp, ptl->general_profile_space, 2);
+	rbsp_flag(rbsp, ptl->general_tier_flag);
+	v4l2_hevc_enc_rbsp_bits(rbsp, ptl->general_profile_idc, 5);
+	v4l2_hevc_enc_rbsp_bits(rbsp, ptl->general_profile_compatibility_flags,
+				32);
+	rbsp_flags(rbsp, ptl->flags, V4L2_HEVC_PTL_FLAG_PROGRESSIVE_SOURCE);
+	rbsp_flags(rbsp, ptl->flags, V4L2_HEVC_PTL_FLAG_INTERLACED_SOURCE);
+	rbsp_flags(rbsp, ptl->flags, V4L2_HEVC_PTL_FLAG_NON_PACKED_CONSTRAINT);
+	rbsp_flags(rbsp, ptl->flags, V4L2_HEVC_PTL_FLAG_FRAME_ONLY_CONSTRAINT);
+
+	/*
+	 * The 43 bits of general_reserved_zero_43bits, or of the format range
+	 * extensions constraint flags which are all 0 for the profiles that
+	 * can be signalled, then general_inbld_flag or
+	 * general_reserved_zero_bit.
+	 */
+	v4l2_hevc_enc_rbsp_bits(rbsp, 0, 32);
+	v4l2_hevc_enc_rbsp_bits(rbsp, 0, 11);
+	v4l2_hevc_enc_rbsp_bits(rbsp, 0, 1);
+
+	v4l2_hevc_enc_rbsp_bits(rbsp, ptl->general_level_idc, 8);
+
+	/* sub_layer_profile_present_flag and sub_layer_level_present_flag */
+	for (i = 0; i < max_sub_layers_minus1; i++)
+		v4l2_hevc_enc_rbsp_bits(rbsp, 0, 2);
+
+	/* reserved_zero_2bits */
+	if (max_sub_layers_minus1)
+		for (i = max_sub_layers_minus1; i < 8; i++)
+			v4l2_hevc_enc_rbsp_bits(rbsp, 0, 2);
+}
+
+/*
+ * The sub-layer ordering info is written once, for the highest sub-layer,
+ * when there are several sub-layers: the lower ones infer the same values.
+ * With a single sub-layer, sub_layer_ordering_info_present_flag is set like
+ * common encoders do; the bits are the same apart from the flag.
+ */
+static void rbsp_sub_layer_ordering(struct v4l2_hevc_enc_rbsp *rbsp,
+				    const struct v4l2_ctrl_hevc_sps *sps)
+{
+	rbsp_flag(rbsp, !sps->sps_max_sub_layers_minus1);
+	v4l2_hevc_enc_rbsp_ue(rbsp, sps->sps_max_dec_pic_buffering_minus1);
+	v4l2_hevc_enc_rbsp_ue(rbsp, sps->sps_max_num_reorder_pics);
+	v4l2_hevc_enc_rbsp_ue(rbsp, sps->sps_max_latency_increase_plus1);
+}
+
+static void rbsp_timing_info(struct v4l2_hevc_enc_rbsp *rbsp,
+			     const struct v4l2_hevc_sps_video *video)
+{
+	bool poc_proportional = video->flags &
+		V4L2_HEVC_SPS_VIDEO_FLAG_VUI_POC_PROPORTIONAL_TO_TIMING;
+
+	v4l2_hevc_enc_rbsp_bits(rbsp, video->num_units_in_tick, 32);
+	v4l2_hevc_enc_rbsp_bits(rbsp, video->time_scale, 32);
+	rbsp_flag(rbsp, poc_proportional);
+	if (poc_proportional)
+		v4l2_hevc_enc_rbsp_ue(rbsp,
+				      video->num_ticks_poc_diff_one_minus1);
+}
+
+/**
+ * v4l2_hevc_enc_rbsp_vps() - write a video parameter set (7.3.2.1)
+ * @rbsp: the writer
+ * @start_code: whether to write an Annex B start code
+ * @sps: the SPS the VPS describes
+ * @sps_video: the VPS/SPS fields not in @sps
+ *
+ * The VPS describes a single layer with the sub-layers of @sps and no HRD
+ * parameters.
+ *
+ * Return: 0 or the first error of the writer.
+ */
+int v4l2_hevc_enc_rbsp_vps(struct v4l2_hevc_enc_rbsp *rbsp, bool start_code,
+			   const struct v4l2_ctrl_hevc_sps *sps,
+			   const struct v4l2_hevc_sps_video *sps_video)
+{
+	u32 flags = sps_video->flags;
+	bool timing = flags & V4L2_HEVC_SPS_VIDEO_FLAG_VPS_TIMING_INFO_PRESENT;
+
+	if (sps->video_parameter_set_id > 15 ||
+	    sps->sps_max_sub_layers_minus1 >= V4L2_HEVC_MAX_SUB_LAYERS)
+		rbsp_fail(rbsp, -EINVAL);
+
+	v4l2_hevc_enc_rbsp_nal_begin(rbsp, start_code, V4L2_HEVC_NAL_VPS, 1);
+
+	v4l2_hevc_enc_rbsp_bits(rbsp, sps->video_parameter_set_id, 4);
+	rbsp_flag(rbsp, 1);			/* vps_base_layer_internal_flag */
+	rbsp_flag(rbsp, 1);			/* vps_base_layer_available_flag */
+	v4l2_hevc_enc_rbsp_bits(rbsp, 0, 6);	/* vps_max_layers_minus1 */
+	v4l2_hevc_enc_rbsp_bits(rbsp, sps->sps_max_sub_layers_minus1, 3);
+	rbsp_flags(rbsp, flags, V4L2_HEVC_SPS_VIDEO_FLAG_TEMPORAL_ID_NESTING);
+	v4l2_hevc_enc_rbsp_bits(rbsp, 0xffff, 16); /* vps_reserved_0xffff_16bits */
+
+	rbsp_ptl(rbsp, &sps_video->ptl, sps->sps_max_sub_layers_minus1);
+	rbsp_sub_layer_ordering(rbsp, sps);
+
+	v4l2_hevc_enc_rbsp_bits(rbsp, 0, 6);	/* vps_max_layer_id */
+	v4l2_hevc_enc_rbsp_ue(rbsp, 0);		/* vps_num_layer_sets_minus1 */
+
+	rbsp_flag(rbsp, timing);
+	if (timing) {
+		rbsp_timing_info(rbsp, sps_video);
+		v4l2_hevc_enc_rbsp_ue(rbsp, 0);	/* vps_num_hrd_parameters */
+	}
+
+	rbsp_flag(rbsp, 0);			/* vps_extension_flag */
+
+	v4l2_hevc_enc_rbsp_nal_end(rbsp);
+
+	return rbsp->error;
+}
+EXPORT_SYMBOL_GPL(v4l2_hevc_enc_rbsp_vps);
+
+/* vui_parameters() (E.2.1), without HRD parameters */
+static void rbsp_vui(struct v4l2_hevc_enc_rbsp *rbsp,
+		     const struct v4l2_hevc_sps_video *video)
+{
+	u32 flags = video->flags;
+
+	rbsp_flags(rbsp, flags,
+		   V4L2_HEVC_SPS_VIDEO_FLAG_VUI_ASPECT_RATIO_INFO_PRESENT);
+	if (flags & V4L2_HEVC_SPS_VIDEO_FLAG_VUI_ASPECT_RATIO_INFO_PRESENT) {
+		v4l2_hevc_enc_rbsp_bits(rbsp, video->aspect_ratio_idc, 8);
+		if (video->aspect_ratio_idc ==
+		    V4L2_HEVC_VUI_ASPECT_RATIO_IDC_EXTENDED) {
+			v4l2_hevc_enc_rbsp_bits(rbsp, video->sar_width, 16);
+			v4l2_hevc_enc_rbsp_bits(rbsp, video->sar_height, 16);
+		}
+	}
+
+	rbsp_flags(rbsp, flags,
+		   V4L2_HEVC_SPS_VIDEO_FLAG_VUI_OVERSCAN_INFO_PRESENT);
+	if (flags & V4L2_HEVC_SPS_VIDEO_FLAG_VUI_OVERSCAN_INFO_PRESENT)
+		rbsp_flags(rbsp, flags,
+			   V4L2_HEVC_SPS_VIDEO_FLAG_VUI_OVERSCAN_APPROPRIATE);
+
+	rbsp_flags(rbsp, flags,
+		   V4L2_HEVC_SPS_VIDEO_FLAG_VUI_VIDEO_SIGNAL_TYPE_PRESENT);
+	if (flags & V4L2_HEVC_SPS_VIDEO_FLAG_VUI_VIDEO_SIGNAL_TYPE_PRESENT) {
+		v4l2_hevc_enc_rbsp_bits(rbsp, video->video_format, 3);
+		rbsp_flags(rbsp, flags,
+			   V4L2_HEVC_SPS_VIDEO_FLAG_VUI_VIDEO_FULL_RANGE);
+		rbsp_flags(rbsp, flags,
+			   V4L2_HEVC_SPS_VIDEO_FLAG_VUI_COLOUR_DESCRIPTION_PRESENT);
+		if (flags &
+		    V4L2_HEVC_SPS_VIDEO_FLAG_VUI_COLOUR_DESCRIPTION_PRESENT) {
+			v4l2_hevc_enc_rbsp_bits(rbsp, video->colour_primaries,
+						8);
+			v4l2_hevc_enc_rbsp_bits(rbsp,
+						video->transfer_characteristics,
+						8);
+			v4l2_hevc_enc_rbsp_bits(rbsp, video->matrix_coeffs, 8);
+		}
+	}
+
+	rbsp_flags(rbsp, flags,
+		   V4L2_HEVC_SPS_VIDEO_FLAG_VUI_CHROMA_LOC_INFO_PRESENT);
+	if (flags & V4L2_HEVC_SPS_VIDEO_FLAG_VUI_CHROMA_LOC_INFO_PRESENT) {
+		v4l2_hevc_enc_rbsp_ue(rbsp,
+				      video->chroma_sample_loc_type_top_field);
+		v4l2_hevc_enc_rbsp_ue(rbsp,
+				      video->chroma_sample_loc_type_bottom_field);
+	}
+
+	rbsp_flag(rbsp, 0);	/* neutral_chroma_indication_flag */
+	rbsp_flag(rbsp, 0);	/* field_seq_flag */
+	rbsp_flag(rbsp, 0);	/* frame_field_info_present_flag */
+	rbsp_flag(rbsp, 0);	/* default_display_window_flag */
+
+	rbsp_flags(rbsp, flags, V4L2_HEVC_SPS_VIDEO_FLAG_VUI_TIMING_INFO_PRESENT);
+	if (flags & V4L2_HEVC_SPS_VIDEO_FLAG_VUI_TIMING_INFO_PRESENT) {
+		rbsp_timing_info(rbsp, video);
+		rbsp_flag(rbsp, 0);	/* vui_hrd_parameters_present_flag */
+	}
+
+	rbsp_flags(rbsp, flags, V4L2_HEVC_SPS_VIDEO_FLAG_VUI_BITSTREAM_RESTRICTION);
+	if (flags & V4L2_HEVC_SPS_VIDEO_FLAG_VUI_BITSTREAM_RESTRICTION) {
+		rbsp_flags(rbsp, flags,
+			   V4L2_HEVC_SPS_VIDEO_FLAG_VUI_TILES_FIXED_STRUCTURE);
+		rbsp_flags(rbsp, flags,
+			   V4L2_HEVC_SPS_VIDEO_FLAG_VUI_MOTION_VECTORS_OVER_PIC_BOUNDARIES);
+		rbsp_flags(rbsp, flags,
+			   V4L2_HEVC_SPS_VIDEO_FLAG_VUI_RESTRICTED_REF_PIC_LISTS);
+		v4l2_hevc_enc_rbsp_ue(rbsp, video->min_spatial_segmentation_idc);
+		v4l2_hevc_enc_rbsp_ue(rbsp, video->max_bytes_per_pic_denom);
+		v4l2_hevc_enc_rbsp_ue(rbsp, video->max_bits_per_min_cu_denom);
+		v4l2_hevc_enc_rbsp_ue(rbsp,
+				      video->log2_max_mv_length_horizontal);
+		v4l2_hevc_enc_rbsp_ue(rbsp, video->log2_max_mv_length_vertical);
+	}
+}
+
+/**
+ * v4l2_hevc_enc_rbsp_sps() - write a sequence parameter set (7.3.2.2)
+ * @rbsp: the writer
+ * @start_code: whether to write an Annex B start code
+ * @sps: the SPS
+ * @sps_video: the VPS/SPS fields not in @sps
+ *
+ * Short-term reference picture sets and long-term reference pictures
+ * cannot be signalled in the SPS (num_short_term_ref_pic_sets and
+ * num_long_term_ref_pics_sps must be 0), and scaling lists, when enabled,
+ * are the default ones.
+ *
+ * Return: 0 or the first error of the writer.
+ */
+int v4l2_hevc_enc_rbsp_sps(struct v4l2_hevc_enc_rbsp *rbsp, bool start_code,
+			   const struct v4l2_ctrl_hevc_sps *sps,
+			   const struct v4l2_hevc_sps_video *sps_video)
+{
+	u64 flags = sps->flags;
+	bool conformance_window = sps_video->flags &
+		V4L2_HEVC_SPS_VIDEO_FLAG_CONFORMANCE_WINDOW;
+	bool vui = sps_video->flags &
+		V4L2_HEVC_SPS_VIDEO_FLAG_VUI_PARAMETERS_PRESENT;
+
+	if (sps->video_parameter_set_id > 15 ||
+	    sps->seq_parameter_set_id > 15 ||
+	    sps->sps_max_sub_layers_minus1 >= V4L2_HEVC_MAX_SUB_LAYERS ||
+	    sps->chroma_format_idc > 3 ||
+	    sps->num_short_term_ref_pic_sets ||
+	    sps->num_long_term_ref_pics_sps)
+		rbsp_fail(rbsp, -EINVAL);
+
+	v4l2_hevc_enc_rbsp_nal_begin(rbsp, start_code, V4L2_HEVC_NAL_SPS, 1);
+
+	v4l2_hevc_enc_rbsp_bits(rbsp, sps->video_parameter_set_id, 4);
+	v4l2_hevc_enc_rbsp_bits(rbsp, sps->sps_max_sub_layers_minus1, 3);
+	rbsp_flags(rbsp, sps_video->flags,
+		   V4L2_HEVC_SPS_VIDEO_FLAG_TEMPORAL_ID_NESTING);
+	rbsp_ptl(rbsp, &sps_video->ptl, sps->sps_max_sub_layers_minus1);
+
+	v4l2_hevc_enc_rbsp_ue(rbsp, sps->seq_parameter_set_id);
+	v4l2_hevc_enc_rbsp_ue(rbsp, sps->chroma_format_idc);
+	if (sps->chroma_format_idc == 3)
+		rbsp_flags(rbsp, flags, V4L2_HEVC_SPS_FLAG_SEPARATE_COLOUR_PLANE);
+	v4l2_hevc_enc_rbsp_ue(rbsp, sps->pic_width_in_luma_samples);
+	v4l2_hevc_enc_rbsp_ue(rbsp, sps->pic_height_in_luma_samples);
+
+	rbsp_flag(rbsp, conformance_window);
+	if (conformance_window) {
+		v4l2_hevc_enc_rbsp_ue(rbsp, sps_video->conf_win_left_offset);
+		v4l2_hevc_enc_rbsp_ue(rbsp, sps_video->conf_win_right_offset);
+		v4l2_hevc_enc_rbsp_ue(rbsp, sps_video->conf_win_top_offset);
+		v4l2_hevc_enc_rbsp_ue(rbsp, sps_video->conf_win_bottom_offset);
+	}
+
+	v4l2_hevc_enc_rbsp_ue(rbsp, sps->bit_depth_luma_minus8);
+	v4l2_hevc_enc_rbsp_ue(rbsp, sps->bit_depth_chroma_minus8);
+	v4l2_hevc_enc_rbsp_ue(rbsp, sps->log2_max_pic_order_cnt_lsb_minus4);
+
+	rbsp_sub_layer_ordering(rbsp, sps);
+
+	v4l2_hevc_enc_rbsp_ue(rbsp, sps->log2_min_luma_coding_block_size_minus3);
+	v4l2_hevc_enc_rbsp_ue(rbsp, sps->log2_diff_max_min_luma_coding_block_size);
+	v4l2_hevc_enc_rbsp_ue(rbsp,
+			      sps->log2_min_luma_transform_block_size_minus2);
+	v4l2_hevc_enc_rbsp_ue(rbsp,
+			      sps->log2_diff_max_min_luma_transform_block_size);
+	v4l2_hevc_enc_rbsp_ue(rbsp, sps->max_transform_hierarchy_depth_inter);
+	v4l2_hevc_enc_rbsp_ue(rbsp, sps->max_transform_hierarchy_depth_intra);
+
+	rbsp_flags(rbsp, flags, V4L2_HEVC_SPS_FLAG_SCALING_LIST_ENABLED);
+	if (flags & V4L2_HEVC_SPS_FLAG_SCALING_LIST_ENABLED)
+		rbsp_flag(rbsp, 0);	/* sps_scaling_list_data_present_flag */
+
+	rbsp_flags(rbsp, flags, V4L2_HEVC_SPS_FLAG_AMP_ENABLED);
+	rbsp_flags(rbsp, flags, V4L2_HEVC_SPS_FLAG_SAMPLE_ADAPTIVE_OFFSET);
+
+	rbsp_flags(rbsp, flags, V4L2_HEVC_SPS_FLAG_PCM_ENABLED);
+	if (flags & V4L2_HEVC_SPS_FLAG_PCM_ENABLED) {
+		v4l2_hevc_enc_rbsp_bits(rbsp,
+					sps->pcm_sample_bit_depth_luma_minus1,
+					4);
+		v4l2_hevc_enc_rbsp_bits(rbsp,
+					sps->pcm_sample_bit_depth_chroma_minus1,
+					4);
+		v4l2_hevc_enc_rbsp_ue(rbsp,
+				      sps->log2_min_pcm_luma_coding_block_size_minus3);
+		v4l2_hevc_enc_rbsp_ue(rbsp,
+				      sps->log2_diff_max_min_pcm_luma_coding_block_size);
+		rbsp_flags(rbsp, flags,
+			   V4L2_HEVC_SPS_FLAG_PCM_LOOP_FILTER_DISABLED);
+	}
+
+	v4l2_hevc_enc_rbsp_ue(rbsp, 0);		/* num_short_term_ref_pic_sets */
+
+	rbsp_flags(rbsp, flags, V4L2_HEVC_SPS_FLAG_LONG_TERM_REF_PICS_PRESENT);
+	if (flags & V4L2_HEVC_SPS_FLAG_LONG_TERM_REF_PICS_PRESENT)
+		v4l2_hevc_enc_rbsp_ue(rbsp, 0);	/* num_long_term_ref_pics_sps */
+
+	rbsp_flags(rbsp, flags, V4L2_HEVC_SPS_FLAG_SPS_TEMPORAL_MVP_ENABLED);
+	rbsp_flags(rbsp, flags, V4L2_HEVC_SPS_FLAG_STRONG_INTRA_SMOOTHING_ENABLED);
+
+	rbsp_flag(rbsp, vui);
+	if (vui)
+		rbsp_vui(rbsp, sps_video);
+
+	rbsp_flag(rbsp, 0);			/* sps_extension_present_flag */
+
+	v4l2_hevc_enc_rbsp_nal_end(rbsp);
+
+	return rbsp->error;
+}
+EXPORT_SYMBOL_GPL(v4l2_hevc_enc_rbsp_sps);
+
+/**
+ * v4l2_hevc_enc_rbsp_pps() - write a picture parameter set (7.3.2.3)
+ * @rbsp: the writer
+ * @start_code: whether to write an Annex B start code
+ * @sps: the SPS the PPS refers to
+ * @pps: the PPS
+ *
+ * Scaling lists are never signalled in the PPS and no PPS extension is
+ * written.
+ *
+ * Return: 0 or the first error of the writer.
+ */
+int v4l2_hevc_enc_rbsp_pps(struct v4l2_hevc_enc_rbsp *rbsp, bool start_code,
+			   const struct v4l2_ctrl_hevc_sps *sps,
+			   const struct v4l2_ctrl_hevc_pps *pps)
+{
+	u64 flags = pps->flags;
+	unsigned int i;
+
+	if (pps->pic_parameter_set_id > 63 ||
+	    pps->num_extra_slice_header_bits > 7 ||
+	    (flags & V4L2_HEVC_PPS_FLAG_TILES_ENABLED &&
+	     (pps->num_tile_columns_minus1 >=
+	      ARRAY_SIZE(pps->column_width_minus1) ||
+	      pps->num_tile_rows_minus1 >= ARRAY_SIZE(pps->row_height_minus1))))
+		rbsp_fail(rbsp, -EINVAL);
+
+	v4l2_hevc_enc_rbsp_nal_begin(rbsp, start_code, V4L2_HEVC_NAL_PPS, 1);
+
+	v4l2_hevc_enc_rbsp_ue(rbsp, pps->pic_parameter_set_id);
+	v4l2_hevc_enc_rbsp_ue(rbsp, sps->seq_parameter_set_id);
+	rbsp_flags(rbsp, flags,
+		   V4L2_HEVC_PPS_FLAG_DEPENDENT_SLICE_SEGMENT_ENABLED);
+	rbsp_flags(rbsp, flags, V4L2_HEVC_PPS_FLAG_OUTPUT_FLAG_PRESENT);
+	v4l2_hevc_enc_rbsp_bits(rbsp, pps->num_extra_slice_header_bits, 3);
+	rbsp_flags(rbsp, flags, V4L2_HEVC_PPS_FLAG_SIGN_DATA_HIDING_ENABLED);
+	rbsp_flags(rbsp, flags, V4L2_HEVC_PPS_FLAG_CABAC_INIT_PRESENT);
+	v4l2_hevc_enc_rbsp_ue(rbsp, pps->num_ref_idx_l0_default_active_minus1);
+	v4l2_hevc_enc_rbsp_ue(rbsp, pps->num_ref_idx_l1_default_active_minus1);
+	v4l2_hevc_enc_rbsp_se(rbsp, pps->init_qp_minus26);
+	rbsp_flags(rbsp, flags, V4L2_HEVC_PPS_FLAG_CONSTRAINED_INTRA_PRED);
+	rbsp_flags(rbsp, flags, V4L2_HEVC_PPS_FLAG_TRANSFORM_SKIP_ENABLED);
+
+	rbsp_flags(rbsp, flags, V4L2_HEVC_PPS_FLAG_CU_QP_DELTA_ENABLED);
+	if (flags & V4L2_HEVC_PPS_FLAG_CU_QP_DELTA_ENABLED)
+		v4l2_hevc_enc_rbsp_ue(rbsp, pps->diff_cu_qp_delta_depth);
+
+	v4l2_hevc_enc_rbsp_se(rbsp, pps->pps_cb_qp_offset);
+	v4l2_hevc_enc_rbsp_se(rbsp, pps->pps_cr_qp_offset);
+	rbsp_flags(rbsp, flags,
+		   V4L2_HEVC_PPS_FLAG_PPS_SLICE_CHROMA_QP_OFFSETS_PRESENT);
+	rbsp_flags(rbsp, flags, V4L2_HEVC_PPS_FLAG_WEIGHTED_PRED);
+	rbsp_flags(rbsp, flags, V4L2_HEVC_PPS_FLAG_WEIGHTED_BIPRED);
+	rbsp_flags(rbsp, flags, V4L2_HEVC_PPS_FLAG_TRANSQUANT_BYPASS_ENABLED);
+	rbsp_flags(rbsp, flags, V4L2_HEVC_PPS_FLAG_TILES_ENABLED);
+	rbsp_flags(rbsp, flags, V4L2_HEVC_PPS_FLAG_ENTROPY_CODING_SYNC_ENABLED);
+
+	if (flags & V4L2_HEVC_PPS_FLAG_TILES_ENABLED && !rbsp->error) {
+		bool uniform = flags & V4L2_HEVC_PPS_FLAG_UNIFORM_SPACING;
+
+		v4l2_hevc_enc_rbsp_ue(rbsp, pps->num_tile_columns_minus1);
+		v4l2_hevc_enc_rbsp_ue(rbsp, pps->num_tile_rows_minus1);
+		rbsp_flag(rbsp, uniform);
+		if (!uniform) {
+			for (i = 0; i < pps->num_tile_columns_minus1; i++)
+				v4l2_hevc_enc_rbsp_ue(rbsp,
+						      pps->column_width_minus1[i]);
+			for (i = 0; i < pps->num_tile_rows_minus1; i++)
+				v4l2_hevc_enc_rbsp_ue(rbsp,
+						      pps->row_height_minus1[i]);
+		}
+		rbsp_flags(rbsp, flags,
+			   V4L2_HEVC_PPS_FLAG_LOOP_FILTER_ACROSS_TILES_ENABLED);
+	}
+
+	rbsp_flags(rbsp, flags,
+		   V4L2_HEVC_PPS_FLAG_PPS_LOOP_FILTER_ACROSS_SLICES_ENABLED);
+
+	rbsp_flags(rbsp, flags,
+		   V4L2_HEVC_PPS_FLAG_DEBLOCKING_FILTER_CONTROL_PRESENT);
+	if (flags & V4L2_HEVC_PPS_FLAG_DEBLOCKING_FILTER_CONTROL_PRESENT) {
+		rbsp_flags(rbsp, flags,
+			   V4L2_HEVC_PPS_FLAG_DEBLOCKING_FILTER_OVERRIDE_ENABLED);
+		rbsp_flags(rbsp, flags,
+			   V4L2_HEVC_PPS_FLAG_PPS_DISABLE_DEBLOCKING_FILTER);
+		if (!(flags & V4L2_HEVC_PPS_FLAG_PPS_DISABLE_DEBLOCKING_FILTER)) {
+			v4l2_hevc_enc_rbsp_se(rbsp, pps->pps_beta_offset_div2);
+			v4l2_hevc_enc_rbsp_se(rbsp, pps->pps_tc_offset_div2);
+		}
+	}
+
+	rbsp_flag(rbsp, 0);			/* pps_scaling_list_data_present_flag */
+	rbsp_flags(rbsp, flags, V4L2_HEVC_PPS_FLAG_LISTS_MODIFICATION_PRESENT);
+	v4l2_hevc_enc_rbsp_ue(rbsp, pps->log2_parallel_merge_level_minus2);
+	rbsp_flags(rbsp, flags,
+		   V4L2_HEVC_PPS_FLAG_SLICE_SEGMENT_HEADER_EXTENSION_PRESENT);
+	rbsp_flag(rbsp, 0);			/* pps_extension_present_flag */
+
+	v4l2_hevc_enc_rbsp_nal_end(rbsp);
+
+	return rbsp->error;
+}
+EXPORT_SYMBOL_GPL(v4l2_hevc_enc_rbsp_pps);
+
+/* st_ref_pic_set(num_short_term_ref_pic_sets) (7.3.7), explicit form */
+static void rbsp_st_rps(struct v4l2_hevc_enc_rbsp *rbsp,
+			const struct v4l2_hevc_st_rps *rps,
+			unsigned int st_rps_idx)
+{
+	unsigned int i;
+
+	if (rps->num_negative_pics + rps->num_positive_pics >
+	    V4L2_HEVC_MAX_ST_RPS_PICS) {
+		rbsp_fail(rbsp, -EINVAL);
+		return;
+	}
+
+	if (st_rps_idx)
+		rbsp_flag(rbsp, 0);	/* inter_ref_pic_set_prediction_flag */
+
+	v4l2_hevc_enc_rbsp_ue(rbsp, rps->num_negative_pics);
+	v4l2_hevc_enc_rbsp_ue(rbsp, rps->num_positive_pics);
+
+	for (i = 0; i < rps->num_negative_pics; i++) {
+		v4l2_hevc_enc_rbsp_ue(rbsp, rps->delta_poc_s0_minus1[i]);
+		rbsp_flag(rbsp, rps->used_by_curr_pic_s0 & BIT(i));
+	}
+
+	for (i = 0; i < rps->num_positive_pics; i++) {
+		v4l2_hevc_enc_rbsp_ue(rbsp, rps->delta_poc_s1_minus1[i]);
+		rbsp_flag(rbsp, rps->used_by_curr_pic_s1 & BIT(i));
+	}
+}
+
+static unsigned int num_pic_total_curr(const struct v4l2_hevc_slice_header *sh)
+{
+	unsigned int count;
+	unsigned int i;
+
+	count = hweight32(sh->st_rps.used_by_curr_pic_s0 &
+			  (BIT(sh->st_rps.num_negative_pics) - 1));
+	count += hweight32(sh->st_rps.used_by_curr_pic_s1 &
+			   (BIT(sh->st_rps.num_positive_pics) - 1));
+
+	for (i = 0; i < sh->num_long_term_pics; i++)
+		count += sh->lt[i].used_by_curr_pic_lt;
+
+	return count;
+}
+
+/**
+ * v4l2_hevc_enc_rbsp_slice_header() - write a slice segment header (7.3.6)
+ * @rbsp: the writer
+ * @start_code: whether to write an Annex B start code
+ * @sps: the active SPS
+ * @pps: the active PPS
+ * @sh: the slice segment header syntax element values
+ *
+ * Writes the NAL unit header, then slice_segment_header() and
+ * byte_alignment(): the caller (usually the hardware) appends
+ * slice_segment_data() and the trailing bits. The short-term reference
+ * picture set is always coded in the slice header. Reference picture list
+ * modification is written as "no modification" and weighted prediction
+ * tables are not supported.
+ *
+ * Return: 0 or the first error of the writer.
+ */
+int v4l2_hevc_enc_rbsp_slice_header(struct v4l2_hevc_enc_rbsp *rbsp,
+				    bool start_code,
+				    const struct v4l2_ctrl_hevc_sps *sps,
+				    const struct v4l2_ctrl_hevc_pps *pps,
+				    const struct v4l2_hevc_slice_header *sh)
+{
+	unsigned int ctb_log2_size = sps->log2_min_luma_coding_block_size_minus3 +
+				     3 + sps->log2_diff_max_min_luma_coding_block_size;
+	unsigned int width_ctbs = DIV_ROUND_UP(sps->pic_width_in_luma_samples,
+					       1 << ctb_log2_size);
+	unsigned int height_ctbs = DIV_ROUND_UP(sps->pic_height_in_luma_samples,
+						1 << ctb_log2_size);
+	unsigned int poc_lsb_bits = sps->log2_max_pic_order_cnt_lsb_minus4 + 4;
+	bool first = !sh->slice_segment_address;
+	bool dependent = sh->flags &
+			 V4L2_HEVC_SLICE_HEADER_FLAG_DEPENDENT_SLICE_SEGMENT;
+	bool inter = sh->slice_type != V4L2_HEVC_SLICE_TYPE_I;
+	bool bipred = sh->slice_type == V4L2_HEVC_SLICE_TYPE_B;
+	bool temporal_mvp = false;
+	bool sao_luma = false;
+	bool sao_chroma = false;
+	bool deblocking_disabled;
+	u64 flags = sh->flags;
+	u64 pps_flags = pps->flags;
+	unsigned int i;
+
+	if (sh->slice_type > V4L2_HEVC_SLICE_TYPE_I ||
+	    (first && dependent) ||
+	    sh->num_long_term_pics > V4L2_HEVC_MAX_LT_PICS ||
+	    (sh->num_long_term_pics &&
+	     !(sps->flags & V4L2_HEVC_SPS_FLAG_LONG_TERM_REF_PICS_PRESENT)) ||
+	    sps->flags & V4L2_HEVC_SPS_FLAG_SEPARATE_COLOUR_PLANE ||
+	    (sh->slice_type == V4L2_HEVC_SLICE_TYPE_P &&
+	     pps_flags & V4L2_HEVC_PPS_FLAG_WEIGHTED_PRED) ||
+	    (bipred && pps_flags & V4L2_HEVC_PPS_FLAG_WEIGHTED_BIPRED) ||
+	    sh->slice_segment_address >= width_ctbs * height_ctbs ||
+	    (sh->num_entry_point_offsets && !sh->entry_point_offset_minus1))
+		rbsp_fail(rbsp, -EINVAL);
+
+	if (rbsp->error)
+		return rbsp->error;
+
+	v4l2_hevc_enc_rbsp_nal_begin(rbsp, start_code, sh->nal_unit_type,
+				     sh->nuh_temporal_id_plus1);
+
+	rbsp_flag(rbsp, first);
+	if (v4l2_hevc_nal_is_irap(sh->nal_unit_type))
+		rbsp_flags(rbsp, flags,
+			   V4L2_HEVC_ENCODE_FLAG_NO_OUTPUT_OF_PRIOR_PICS);
+	v4l2_hevc_enc_rbsp_ue(rbsp, pps->pic_parameter_set_id);
+
+	if (!first) {
+		if (pps_flags & V4L2_HEVC_PPS_FLAG_DEPENDENT_SLICE_SEGMENT_ENABLED)
+			rbsp_flag(rbsp, dependent);
+		v4l2_hevc_enc_rbsp_bits(rbsp, sh->slice_segment_address,
+					order_base_2(width_ctbs * height_ctbs));
+	}
+
+	deblocking_disabled =
+		pps_flags & V4L2_HEVC_PPS_FLAG_PPS_DISABLE_DEBLOCKING_FILTER;
+
+	if (dependent)
+		goto entry_points;
+
+	/* slice_reserved_flag[] */
+	v4l2_hevc_enc_rbsp_bits(rbsp, 0, pps->num_extra_slice_header_bits);
+	v4l2_hevc_enc_rbsp_ue(rbsp, sh->slice_type);
+
+	if (pps_flags & V4L2_HEVC_PPS_FLAG_OUTPUT_FLAG_PRESENT)
+		rbsp_flag(rbsp, !(flags & V4L2_HEVC_ENCODE_FLAG_NO_PIC_OUTPUT));
+
+	if (!v4l2_hevc_nal_is_idr(sh->nal_unit_type)) {
+		v4l2_hevc_enc_rbsp_bits(rbsp, sh->slice_pic_order_cnt_lsb,
+					poc_lsb_bits);
+
+		/* short_term_ref_pic_set_sps_flag */
+		rbsp_flag(rbsp, 0);
+		rbsp_st_rps(rbsp, &sh->st_rps, sps->num_short_term_ref_pic_sets);
+
+		if (sps->flags & V4L2_HEVC_SPS_FLAG_LONG_TERM_REF_PICS_PRESENT) {
+			v4l2_hevc_enc_rbsp_ue(rbsp, sh->num_long_term_pics);
+			for (i = 0; i < sh->num_long_term_pics; i++) {
+				const struct v4l2_hevc_lt_pic *lt = &sh->lt[i];
+
+				v4l2_hevc_enc_rbsp_bits(rbsp, lt->poc_lsb_lt,
+							poc_lsb_bits);
+				rbsp_flag(rbsp, lt->used_by_curr_pic_lt);
+				rbsp_flag(rbsp, lt->delta_poc_msb_present);
+				if (lt->delta_poc_msb_present)
+					v4l2_hevc_enc_rbsp_ue(rbsp,
+							      lt->delta_poc_msb_cycle_lt);
+			}
+		}
+
+		if (sps->flags & V4L2_HEVC_SPS_FLAG_SPS_TEMPORAL_MVP_ENABLED) {
+			temporal_mvp = flags &
+				V4L2_HEVC_ENCODE_FLAG_SLICE_TEMPORAL_MVP_ENABLED;
+			rbsp_flag(rbsp, temporal_mvp);
+		}
+	}
+
+	if (sps->flags & V4L2_HEVC_SPS_FLAG_SAMPLE_ADAPTIVE_OFFSET) {
+		sao_luma = flags & V4L2_HEVC_ENCODE_FLAG_SLICE_SAO_LUMA;
+		rbsp_flag(rbsp, sao_luma);
+		if (sps->chroma_format_idc) {
+			sao_chroma = flags &
+				     V4L2_HEVC_ENCODE_FLAG_SLICE_SAO_CHROMA;
+			rbsp_flag(rbsp, sao_chroma);
+		}
+	}
+
+	if (inter) {
+		bool override = flags &
+			V4L2_HEVC_ENCODE_FLAG_NUM_REF_IDX_ACTIVE_OVERRIDE;
+		unsigned int l0_active = pps->num_ref_idx_l0_default_active_minus1;
+		unsigned int l1_active = pps->num_ref_idx_l1_default_active_minus1;
+
+		rbsp_flag(rbsp, override);
+		if (override) {
+			l0_active = sh->num_ref_idx_l0_active_minus1;
+			v4l2_hevc_enc_rbsp_ue(rbsp, l0_active);
+			if (bipred) {
+				l1_active = sh->num_ref_idx_l1_active_minus1;
+				v4l2_hevc_enc_rbsp_ue(rbsp, l1_active);
+			}
+		}
+
+		if (pps_flags & V4L2_HEVC_PPS_FLAG_LISTS_MODIFICATION_PRESENT &&
+		    num_pic_total_curr(sh) > 1) {
+			/* ref_pic_list_modification_flag_l0 and _l1 */
+			rbsp_flag(rbsp, 0);
+			if (bipred)
+				rbsp_flag(rbsp, 0);
+		}
+
+		if (bipred)
+			rbsp_flags(rbsp, flags, V4L2_HEVC_ENCODE_FLAG_MVD_L1_ZERO);
+
+		if (pps_flags & V4L2_HEVC_PPS_FLAG_CABAC_INIT_PRESENT)
+			rbsp_flags(rbsp, flags, V4L2_HEVC_ENCODE_FLAG_CABAC_INIT);
+
+		if (temporal_mvp) {
+			bool from_l0 = !bipred ||
+				       flags & V4L2_HEVC_ENCODE_FLAG_COLLOCATED_FROM_L0;
+
+			if (bipred)
+				rbsp_flag(rbsp, from_l0);
+
+			if ((from_l0 && l0_active) || (!from_l0 && l1_active))
+				v4l2_hevc_enc_rbsp_ue(rbsp,
+						      sh->collocated_ref_idx);
+		}
+
+		v4l2_hevc_enc_rbsp_ue(rbsp, sh->five_minus_max_num_merge_cand);
+	}
+
+	v4l2_hevc_enc_rbsp_se(rbsp, sh->slice_qp_delta);
+
+	if (pps_flags & V4L2_HEVC_PPS_FLAG_PPS_SLICE_CHROMA_QP_OFFSETS_PRESENT) {
+		v4l2_hevc_enc_rbsp_se(rbsp, sh->slice_cb_qp_offset);
+		v4l2_hevc_enc_rbsp_se(rbsp, sh->slice_cr_qp_offset);
+	}
+
+	if (pps_flags & V4L2_HEVC_PPS_FLAG_DEBLOCKING_FILTER_OVERRIDE_ENABLED) {
+		bool override = flags &
+			V4L2_HEVC_ENCODE_FLAG_DEBLOCKING_FILTER_OVERRIDE;
+
+		rbsp_flag(rbsp, override);
+		if (override) {
+			deblocking_disabled = flags &
+				V4L2_HEVC_ENCODE_FLAG_SLICE_DEBLOCKING_FILTER_DISABLED;
+			rbsp_flag(rbsp, deblocking_disabled);
+			if (!deblocking_disabled) {
+				v4l2_hevc_enc_rbsp_se(rbsp,
+						      sh->slice_beta_offset_div2);
+				v4l2_hevc_enc_rbsp_se(rbsp,
+						      sh->slice_tc_offset_div2);
+			}
+		}
+	}
+
+	if (pps_flags & V4L2_HEVC_PPS_FLAG_PPS_LOOP_FILTER_ACROSS_SLICES_ENABLED &&
+	    (sao_luma || sao_chroma || !deblocking_disabled))
+		rbsp_flags(rbsp, flags,
+			   V4L2_HEVC_ENCODE_FLAG_SLICE_LOOP_FILTER_ACROSS_SLICES_ENABLED);
+
+entry_points:
+	if (pps_flags & (V4L2_HEVC_PPS_FLAG_TILES_ENABLED |
+			 V4L2_HEVC_PPS_FLAG_ENTROPY_CODING_SYNC_ENABLED)) {
+		v4l2_hevc_enc_rbsp_ue(rbsp, sh->num_entry_point_offsets);
+		if (sh->num_entry_point_offsets) {
+			unsigned int offset_len;
+			u32 offset_max = 0;
+
+			for (i = 0; i < sh->num_entry_point_offsets; i++)
+				offset_max = max(offset_max,
+						 sh->entry_point_offset_minus1[i]);
+
+			offset_len = offset_max ? fls(offset_max) : 1;
+
+			/* offset_len_minus1 */
+			v4l2_hevc_enc_rbsp_ue(rbsp, offset_len - 1);
+			for (i = 0; i < sh->num_entry_point_offsets; i++)
+				v4l2_hevc_enc_rbsp_bits(rbsp,
+							sh->entry_point_offset_minus1[i],
+							offset_len);
+		}
+	}
+
+	/* slice_segment_header_extension_length */
+	if (pps_flags & V4L2_HEVC_PPS_FLAG_SLICE_SEGMENT_HEADER_EXTENSION_PRESENT)
+		v4l2_hevc_enc_rbsp_ue(rbsp, 0);
+
+	/* byte_alignment() */
+	v4l2_hevc_enc_rbsp_trailing_bits(rbsp);
+
+	return rbsp->error;
+}
+EXPORT_SYMBOL_GPL(v4l2_hevc_enc_rbsp_slice_header);
+
+MODULE_DESCRIPTION("V4L2 HEVC Encode RBSP");
+MODULE_LICENSE("GPL");
diff --git a/include/media/v4l2-hevc-enc-rbsp.h b/include/media/v4l2-hevc-enc-rbsp.h
new file mode 100644
index 000000000000..b593fd6c0d96
--- /dev/null
+++ b/include/media/v4l2-hevc-enc-rbsp.h
@@ -0,0 +1,279 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * V4L2 HEVC Encode RBSP
+ *
+ * Writes HEVC NAL units (VPS, SPS, PPS, access unit delimiter and slice
+ * segment headers) into a buffer, with Annex B start codes and emulation
+ * prevention.
+ */
+
+#ifndef _MEDIA_V4L2_HEVC_ENC_RBSP_H
+#define _MEDIA_V4L2_HEVC_ENC_RBSP_H
+
+#include <linux/bits.h>
+#include <linux/types.h>
+#include <linux/v4l2-controls.h>
+
+/**
+ * struct v4l2_hevc_enc_rbsp - HEVC NAL unit writer
+ *
+ * @data: start of the destination buffer
+ * @size: size of the destination buffer in bytes
+ * @offset: number of bytes written to @data
+ * @cache: bits not yet written to @data
+ * @cache_bits: number of valid bits in @cache, always less than 8 between
+ *		calls
+ * @zero_bytes: number of consecutive zero bytes written last, used for
+ *		emulation prevention
+ * @epb: whether emulation prevention is active (inside a NAL unit payload)
+ * @error: first error that happened, sticky until the next init. Writes
+ *	   after an error are ignored.
+ */
+struct v4l2_hevc_enc_rbsp {
+	u8 *data;
+	unsigned int size;
+	unsigned int offset;
+	u64 cache;
+	unsigned int cache_bits;
+	unsigned int zero_bytes;
+	bool epb;
+	int error;
+};
+
+#define V4L2_HEVC_PTL_FLAG_PROGRESSIVE_SOURCE		0x1
+#define V4L2_HEVC_PTL_FLAG_INTERLACED_SOURCE		0x2
+#define V4L2_HEVC_PTL_FLAG_NON_PACKED_CONSTRAINT	0x4
+#define V4L2_HEVC_PTL_FLAG_FRAME_ONLY_CONSTRAINT	0x8
+
+/**
+ * struct v4l2_hevc_ptl - HEVC general profile, tier and level (7.3.3)
+ *
+ * Sub-layer profile and level information is never written.
+ *
+ * @general_profile_space: general_profile_space
+ * @general_tier_flag: general_tier_flag
+ * @general_profile_idc: general_profile_idc
+ * @general_level_idc: general_level_idc (30 times the level number)
+ * @general_profile_compatibility_flags: general_profile_compatibility_flag[j]
+ *					 is bit (31 - j)
+ * @flags: see V4L2_HEVC_PTL_FLAG_{}
+ */
+struct v4l2_hevc_ptl {
+	u8 general_profile_space;
+	u8 general_tier_flag;
+	u8 general_profile_idc;
+	u8 general_level_idc;
+	u32 general_profile_compatibility_flags;
+	u32 flags;
+};
+
+#define V4L2_HEVC_SPS_VIDEO_FLAG_TEMPORAL_ID_NESTING			BIT(0)
+#define V4L2_HEVC_SPS_VIDEO_FLAG_CONFORMANCE_WINDOW			BIT(1)
+#define V4L2_HEVC_SPS_VIDEO_FLAG_VPS_TIMING_INFO_PRESENT		BIT(2)
+#define V4L2_HEVC_SPS_VIDEO_FLAG_VUI_PARAMETERS_PRESENT			BIT(3)
+#define V4L2_HEVC_SPS_VIDEO_FLAG_VUI_ASPECT_RATIO_INFO_PRESENT		BIT(4)
+#define V4L2_HEVC_SPS_VIDEO_FLAG_VUI_OVERSCAN_INFO_PRESENT		BIT(5)
+#define V4L2_HEVC_SPS_VIDEO_FLAG_VUI_OVERSCAN_APPROPRIATE		BIT(6)
+#define V4L2_HEVC_SPS_VIDEO_FLAG_VUI_VIDEO_SIGNAL_TYPE_PRESENT		BIT(7)
+#define V4L2_HEVC_SPS_VIDEO_FLAG_VUI_VIDEO_FULL_RANGE			BIT(8)
+#define V4L2_HEVC_SPS_VIDEO_FLAG_VUI_COLOUR_DESCRIPTION_PRESENT		BIT(9)
+#define V4L2_HEVC_SPS_VIDEO_FLAG_VUI_CHROMA_LOC_INFO_PRESENT		BIT(10)
+#define V4L2_HEVC_SPS_VIDEO_FLAG_VUI_TIMING_INFO_PRESENT		BIT(11)
+#define V4L2_HEVC_SPS_VIDEO_FLAG_VUI_POC_PROPORTIONAL_TO_TIMING		BIT(12)
+#define V4L2_HEVC_SPS_VIDEO_FLAG_VUI_BITSTREAM_RESTRICTION		BIT(13)
+#define V4L2_HEVC_SPS_VIDEO_FLAG_VUI_TILES_FIXED_STRUCTURE		BIT(14)
+#define V4L2_HEVC_SPS_VIDEO_FLAG_VUI_MOTION_VECTORS_OVER_PIC_BOUNDARIES	BIT(15)
+#define V4L2_HEVC_SPS_VIDEO_FLAG_VUI_RESTRICTED_REF_PIC_LISTS		BIT(16)
+
+/**
+ * struct v4l2_hevc_sps_video - HEVC VPS/SPS fields not in v4l2_ctrl_hevc_sps
+ *
+ * The parts of the parameter sets that describe the video rather than the
+ * coding tools. The stateless encode core derives them from the format,
+ * the frame interval and the stateful codec controls.
+ *
+ * @ptl: general profile, tier and level
+ * @flags: see V4L2_HEVC_SPS_VIDEO_FLAG_{}
+ * @conf_win_left_offset: conformance window left offset, in chroma samples
+ * @conf_win_right_offset: conformance window right offset, in chroma samples
+ * @conf_win_top_offset: conformance window top offset, in chroma samples
+ * @conf_win_bottom_offset: conformance window bottom offset, in chroma
+ *			    samples
+ * @aspect_ratio_idc: aspect_ratio_idc (Table E.1)
+ * @sar_width: sar_width, with V4L2_HEVC_VUI_ASPECT_RATIO_IDC_EXTENDED
+ * @sar_height: sar_height, with V4L2_HEVC_VUI_ASPECT_RATIO_IDC_EXTENDED
+ * @video_format: video_format (Table E.2)
+ * @colour_primaries: colour_primaries (Table E.3)
+ * @transfer_characteristics: transfer_characteristics (Table E.4)
+ * @matrix_coeffs: matrix_coeffs (Table E.5)
+ * @chroma_sample_loc_type_top_field: chroma_sample_loc_type_top_field
+ * @chroma_sample_loc_type_bottom_field: chroma_sample_loc_type_bottom_field
+ * @num_units_in_tick: num_units_in_tick of the VUI and VPS timing info
+ * @time_scale: time_scale of the VUI and VPS timing info
+ * @num_ticks_poc_diff_one_minus1: num_ticks_poc_diff_one_minus1
+ * @min_spatial_segmentation_idc: min_spatial_segmentation_idc
+ * @max_bytes_per_pic_denom: max_bytes_per_pic_denom
+ * @max_bits_per_min_cu_denom: max_bits_per_min_cu_denom
+ * @log2_max_mv_length_horizontal: log2_max_mv_length_horizontal
+ * @log2_max_mv_length_vertical: log2_max_mv_length_vertical
+ */
+struct v4l2_hevc_sps_video {
+	struct v4l2_hevc_ptl ptl;
+	u32 flags;
+
+	u16 conf_win_left_offset;
+	u16 conf_win_right_offset;
+	u16 conf_win_top_offset;
+	u16 conf_win_bottom_offset;
+
+	u8 aspect_ratio_idc;
+	u16 sar_width;
+	u16 sar_height;
+
+	u8 video_format;
+	u8 colour_primaries;
+	u8 transfer_characteristics;
+	u8 matrix_coeffs;
+
+	u8 chroma_sample_loc_type_top_field;
+	u8 chroma_sample_loc_type_bottom_field;
+
+	u32 num_units_in_tick;
+	u32 time_scale;
+	u32 num_ticks_poc_diff_one_minus1;
+
+	u16 min_spatial_segmentation_idc;
+	u8 max_bytes_per_pic_denom;
+	u8 max_bits_per_min_cu_denom;
+	u8 log2_max_mv_length_horizontal;
+	u8 log2_max_mv_length_vertical;
+};
+
+/**
+ * struct v4l2_hevc_st_rps - HEVC short-term reference picture set (7.3.7)
+ *
+ * Always written explicitly (inter_ref_pic_set_prediction_flag = 0).
+ *
+ * @num_negative_pics: num_negative_pics
+ * @num_positive_pics: num_positive_pics
+ * @used_by_curr_pic_s0: bit i is used_by_curr_pic_s0_flag[i]
+ * @used_by_curr_pic_s1: bit i is used_by_curr_pic_s1_flag[i]
+ * @delta_poc_s0_minus1: delta_poc_s0_minus1[]
+ * @delta_poc_s1_minus1: delta_poc_s1_minus1[]
+ */
+struct v4l2_hevc_st_rps {
+	u8 num_negative_pics;
+	u8 num_positive_pics;
+	u16 used_by_curr_pic_s0;
+	u16 used_by_curr_pic_s1;
+	u16 delta_poc_s0_minus1[16];
+	u16 delta_poc_s1_minus1[16];
+};
+
+/**
+ * struct v4l2_hevc_lt_pic - HEVC long-term picture of a slice header
+ *
+ * @poc_lsb_lt: poc_lsb_lt[i]
+ * @used_by_curr_pic_lt: used_by_curr_pic_lt_flag[i]
+ * @delta_poc_msb_present: delta_poc_msb_present_flag[i]
+ * @delta_poc_msb_cycle_lt: delta_poc_msb_cycle_lt[i]
+ */
+struct v4l2_hevc_lt_pic {
+	u16 poc_lsb_lt;
+	bool used_by_curr_pic_lt;
+	bool delta_poc_msb_present;
+	u32 delta_poc_msb_cycle_lt;
+};
+
+/* Slice header flags beyond the V4L2_HEVC_ENCODE_FLAG_{} uAPI flags. */
+#define V4L2_HEVC_SLICE_HEADER_FLAG_DEPENDENT_SLICE_SEGMENT	BIT_ULL(32)
+
+/**
+ * struct v4l2_hevc_slice_header - HEVC slice segment header (7.3.6)
+ *
+ * The values of the syntax elements, resolved. Elements that the active
+ * SPS/PPS make absent are ignored.
+ *
+ * @nal_unit_type: nal_unit_type
+ * @nuh_temporal_id_plus1: nuh_temporal_id_plus1
+ * @slice_type: slice_type
+ * @slice_segment_address: slice_segment_address, 0 for the first slice
+ *			   segment of the picture
+ * @slice_pic_order_cnt_lsb: slice_pic_order_cnt_lsb
+ * @st_rps: short-term reference picture set, coded in the slice header
+ * @num_long_term_pics: num_long_term_pics
+ * @lt: long-term pictures
+ * @num_ref_idx_l0_active_minus1: num_ref_idx_l0_active_minus1
+ * @num_ref_idx_l1_active_minus1: num_ref_idx_l1_active_minus1
+ * @collocated_ref_idx: collocated_ref_idx
+ * @five_minus_max_num_merge_cand: five_minus_max_num_merge_cand
+ * @slice_qp_delta: slice_qp_delta
+ * @slice_cb_qp_offset: slice_cb_qp_offset
+ * @slice_cr_qp_offset: slice_cr_qp_offset
+ * @slice_beta_offset_div2: slice_beta_offset_div2
+ * @slice_tc_offset_div2: slice_tc_offset_div2
+ * @num_entry_point_offsets: num_entry_point_offsets
+ * @entry_point_offset_minus1: entry_point_offset_minus1[], must point to
+ *			       @num_entry_point_offsets values when non-zero
+ * @flags: V4L2_HEVC_ENCODE_FLAG_{} and V4L2_HEVC_SLICE_HEADER_FLAG_{}
+ */
+struct v4l2_hevc_slice_header {
+	u8 nal_unit_type;
+	u8 nuh_temporal_id_plus1;
+	u8 slice_type;
+	u32 slice_segment_address;
+	u16 slice_pic_order_cnt_lsb;
+
+	struct v4l2_hevc_st_rps st_rps;
+	u8 num_long_term_pics;
+	struct v4l2_hevc_lt_pic lt[16];
+
+	u8 num_ref_idx_l0_active_minus1;
+	u8 num_ref_idx_l1_active_minus1;
+	u8 collocated_ref_idx;
+	u8 five_minus_max_num_merge_cand;
+	s8 slice_qp_delta;
+	s8 slice_cb_qp_offset;
+	s8 slice_cr_qp_offset;
+	s8 slice_beta_offset_div2;
+	s8 slice_tc_offset_div2;
+
+	u32 num_entry_point_offsets;
+	const u32 *entry_point_offset_minus1;
+
+	u64 flags;
+};
+
+void v4l2_hevc_enc_rbsp_init(struct v4l2_hevc_enc_rbsp *rbsp, u8 *data,
+			     unsigned int size);
+unsigned int v4l2_hevc_enc_rbsp_bytes(const struct v4l2_hevc_enc_rbsp *rbsp);
+int v4l2_hevc_enc_rbsp_error(const struct v4l2_hevc_enc_rbsp *rbsp);
+
+void v4l2_hevc_enc_rbsp_bits(struct v4l2_hevc_enc_rbsp *rbsp, u32 value,
+			     unsigned int count);
+void v4l2_hevc_enc_rbsp_ue(struct v4l2_hevc_enc_rbsp *rbsp, u32 value);
+void v4l2_hevc_enc_rbsp_se(struct v4l2_hevc_enc_rbsp *rbsp, s32 value);
+void v4l2_hevc_enc_rbsp_trailing_bits(struct v4l2_hevc_enc_rbsp *rbsp);
+void v4l2_hevc_enc_rbsp_nal_begin(struct v4l2_hevc_enc_rbsp *rbsp,
+				  bool start_code, u8 nal_unit_type,
+				  u8 nuh_temporal_id_plus1);
+void v4l2_hevc_enc_rbsp_nal_end(struct v4l2_hevc_enc_rbsp *rbsp);
+
+int v4l2_hevc_enc_rbsp_aud(struct v4l2_hevc_enc_rbsp *rbsp, bool start_code,
+			   u8 pic_type, u8 nuh_temporal_id_plus1);
+int v4l2_hevc_enc_rbsp_vps(struct v4l2_hevc_enc_rbsp *rbsp, bool start_code,
+			   const struct v4l2_ctrl_hevc_sps *sps,
+			   const struct v4l2_hevc_sps_video *sps_video);
+int v4l2_hevc_enc_rbsp_sps(struct v4l2_hevc_enc_rbsp *rbsp, bool start_code,
+			   const struct v4l2_ctrl_hevc_sps *sps,
+			   const struct v4l2_hevc_sps_video *sps_video);
+int v4l2_hevc_enc_rbsp_pps(struct v4l2_hevc_enc_rbsp *rbsp, bool start_code,
+			   const struct v4l2_ctrl_hevc_sps *sps,
+			   const struct v4l2_ctrl_hevc_pps *pps);
+int v4l2_hevc_enc_rbsp_slice_header(struct v4l2_hevc_enc_rbsp *rbsp,
+				    bool start_code,
+				    const struct v4l2_ctrl_hevc_sps *sps,
+				    const struct v4l2_ctrl_hevc_pps *pps,
+				    const struct v4l2_hevc_slice_header *sh);
+
+#endif

-- 
2.55.0



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

* [PATCH RFC 04/10] media: hevc: Add KUnit tests for the stateless encode RBSP writer
  2026-11-10 10:07 [PATCH RFC 00/10] media: Add a V4L2 HEVC stateless encode uAPI and helpers Arun Iyengar via B4 Relay
                   ` (2 preceding siblings ...)
  2026-11-10 10:07 ` [PATCH RFC 03/10] media: hevc: Add stateless encode RBSP writer Arun Iyengar via B4 Relay
@ 2026-11-10 10:07 ` Arun Iyengar via B4 Relay
  2026-11-10 10:07 ` [PATCH RFC 05/10] media: hevc: Add stateless encode core Arun Iyengar via B4 Relay
                   ` (5 subsequent siblings)
  9 siblings, 0 replies; 11+ messages in thread
From: Arun Iyengar via B4 Relay @ 2026-11-10 10:07 UTC (permalink / raw)
  To: Mauro Carvalho Chehab
  Cc: linux-media, linux-kernel, Paul Kocialkowski, Nicolas Dufresne,
	Hans Verkuil, Arun Iyengar

From: Arun Iyengar <aiyengar1990@gmail.com>

Check the HEVC RBSP writer byte for byte: Exp-Golomb coding, emulation
prevention (including zero runs that do not start on byte boundaries
and the NAL unit header, which is not subject to it), error handling,
and the VPS, SPS, PPS, access unit delimiter and slice segment headers
of three configurations that between them use every syntax element the
writer can produce:

- 720p Main level 3.1 with SAO, AMP, TMVP and a VUI with colour
  description and timing;
- 640x360 coded as 640x368 with a conformance window, PCM, long-term
  references and a PPS with dependent slices, output flag, extra slice
  header bits, CABAC init, chroma QP offsets, 2x2 uniform tiles,
  deblocking override, list modification and slice header extension;
- 2160p60 Main 10 high tier level 5.1 with three temporal sub-layers,
  default scaling lists, a full VUI, VPS timing, non-uniform tiles with
  wavefront parallel processing and deblocking disabled.

No HEVC encoder was available to produce the expected bitstreams, so
they come from an independent implementation that builds bit lists from
the H.265 syntax tables and applies emulation prevention as a separate
pass, and that parses them back to check every syntax element. The VPS
of the first configuration also matches the VPS commonly found in x265
output for such a stream (from memory: it could not be regenerated
here).

Add a .kunitconfig so the tests can be run with:

  ./tools/testing/kunit/kunit.py run --kunitconfig=drivers/media/v4l2-core

Signed-off-by: Arun Iyengar <aiyengar1990@gmail.com>
---
 drivers/media/v4l2-core/.kunitconfig               |   5 +
 drivers/media/v4l2-core/Kconfig                    |  13 +
 drivers/media/v4l2-core/Makefile                   |   1 +
 drivers/media/v4l2-core/v4l2-hevc-enc-rbsp-kunit.c | 696 +++++++++++++++++++++
 4 files changed, 715 insertions(+)

diff --git a/drivers/media/v4l2-core/.kunitconfig b/drivers/media/v4l2-core/.kunitconfig
new file mode 100644
index 000000000000..4ef422b5e0a8
--- /dev/null
+++ b/drivers/media/v4l2-core/.kunitconfig
@@ -0,0 +1,5 @@
+CONFIG_KUNIT=y
+CONFIG_MEDIA_SUPPORT=y
+CONFIG_MEDIA_SUPPORT_FILTER=n
+CONFIG_VIDEO_DEV=y
+CONFIG_V4L2_HEVC_ENC_RBSP_KUNIT_TEST=y
diff --git a/drivers/media/v4l2-core/Kconfig b/drivers/media/v4l2-core/Kconfig
index 9f9eda5d6eac..bc6bf06aa681 100644
--- a/drivers/media/v4l2-core/Kconfig
+++ b/drivers/media/v4l2-core/Kconfig
@@ -57,6 +57,19 @@ config V4L2_H264_ENC
 config V4L2_HEVC_ENC_RBSP
 	tristate
 
+config V4L2_HEVC_ENC_RBSP_KUNIT_TEST
+	tristate "KUnit tests for the V4L2 HEVC encode RBSP writer" if !KUNIT_ALL_TESTS
+	depends on KUNIT
+	select V4L2_HEVC_ENC_RBSP
+	default KUNIT_ALL_TESTS
+	help
+	  Build the KUnit tests of the HEVC NAL unit writer used by stateless
+	  HEVC encoders. They check the VPS, SPS, PPS, access unit delimiter
+	  and slice segment headers it writes, byte for byte, against known
+	  bitstreams.
+
+	  If unsure, say N.
+
 # Used by drivers that need v4l2-vp9.ko
 config V4L2_VP9
 	tristate
diff --git a/drivers/media/v4l2-core/Makefile b/drivers/media/v4l2-core/Makefile
index 3dda145d94d1..6bf89b90165f 100644
--- a/drivers/media/v4l2-core/Makefile
+++ b/drivers/media/v4l2-core/Makefile
@@ -33,6 +33,7 @@ obj-$(CONFIG_V4L2_H264) += v4l2-h264.o
 obj-$(CONFIG_V4L2_H264_ENC) += v4l2-h264-enc.o v4l2-h264-enc-rbsp.o \
 			       v4l2-h264-enc-rc.o
 obj-$(CONFIG_V4L2_HEVC_ENC_RBSP) += v4l2-hevc-enc-rbsp.o
+obj-$(CONFIG_V4L2_HEVC_ENC_RBSP_KUNIT_TEST) += v4l2-hevc-enc-rbsp-kunit.o
 obj-$(CONFIG_V4L2_ISP) += v4l2-isp.o
 obj-$(CONFIG_V4L2_JPEG_HELPER) += v4l2-jpeg.o
 obj-$(CONFIG_V4L2_MEM2MEM_DEV) += v4l2-mem2mem.o
diff --git a/drivers/media/v4l2-core/v4l2-hevc-enc-rbsp-kunit.c b/drivers/media/v4l2-core/v4l2-hevc-enc-rbsp-kunit.c
new file mode 100644
index 000000000000..53f89dd02904
--- /dev/null
+++ b/drivers/media/v4l2-core/v4l2-hevc-enc-rbsp-kunit.c
@@ -0,0 +1,696 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * KUnit tests for the V4L2 HEVC encode RBSP writer
+ *
+ * The expected bitstreams come from an independent reference writer
+ * (bit lists per syntax element, then a separate emulation prevention
+ * pass), and were parsed back with an independent syntax parser. The VPS
+ * of configuration A also matches the VPS commonly found in x265 output for
+ * a single-layer Main level 3.1 stream with 5 DPB pictures and 2 reorder
+ * pictures.
+ */
+
+#include <kunit/test.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/v4l2-controls.h>
+#include <media/v4l2-hevc.h>
+#include <media/v4l2-hevc-enc-rbsp.h>
+
+#define BUF_SIZE	256
+
+/* Expected bitstreams */
+
+static const u8 vps_a[] = {
+	0x00, 0x00, 0x00, 0x01, 0x40, 0x01, 0x0c, 0x01, 0xff, 0xff, 0x01, 0x60,
+	0x00, 0x00, 0x03, 0x00, 0x90, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00,
+	0x5d, 0x95, 0x98, 0x09,
+};
+
+static const u8 sps_a[] = {
+	0x00, 0x00, 0x00, 0x01, 0x42, 0x01, 0x01, 0x01, 0x60, 0x00, 0x00, 0x03,
+	0x00, 0x90, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x5d, 0xa0, 0x02,
+	0x80, 0x80, 0x2d, 0x16, 0x59, 0x59, 0xa4, 0x93, 0x6b, 0x9a, 0x80, 0x80,
+	0x80, 0x82, 0x00, 0x00, 0x03, 0x00, 0x02, 0x00, 0x00, 0x03, 0x00, 0x32,
+	0x10,
+};
+
+static const u8 pps_a[] = {
+	0x00, 0x00, 0x00, 0x01, 0x44, 0x01, 0xc1, 0x71, 0x81, 0x12,
+};
+
+static const u8 vps_b[] = {
+	0x00, 0x00, 0x00, 0x01, 0x40, 0x01, 0x1c, 0x01, 0xff, 0xff, 0x01, 0x60,
+	0x00, 0x00, 0x03, 0x00, 0x90, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00,
+	0x5a, 0xbc, 0x09,
+};
+
+static const u8 sps_b[] = {
+	0x00, 0x00, 0x00, 0x01, 0x42, 0x01, 0x11, 0x01, 0x60, 0x00, 0x00, 0x03,
+	0x00, 0x90, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x5a, 0x48, 0x01,
+	0x40, 0x80, 0x5c, 0x7c, 0xbe, 0xfb, 0x91, 0x21, 0x77, 0xaf, 0x88,
+};
+
+static const u8 pps_b[] = {
+	0x00, 0x00, 0x00, 0x01, 0x44, 0x01, 0x4b, 0x5a, 0x89, 0xe8, 0xa6, 0x89,
+	0x2f, 0x8a, 0x97, 0x40,
+};
+
+static const u8 vps_c[] = {
+	0x00, 0x00, 0x00, 0x01, 0x40, 0x01, 0x2c, 0x05, 0xff, 0xff, 0x22, 0x20,
+	0x00, 0x00, 0x03, 0x00, 0x90, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00,
+	0x99, 0x00, 0x00, 0x1b, 0x03, 0x00, 0x00, 0x03, 0x03, 0xe9, 0x00, 0x00,
+	0xea, 0x60, 0xe8,
+};
+
+static const u8 sps_c[] = {
+	0x00, 0x00, 0x00, 0x01, 0x42, 0x01, 0x25, 0x22, 0x20, 0x00, 0x00, 0x03,
+	0x00, 0x90, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03, 0x00, 0x99, 0x00, 0x00,
+	0x68, 0x00, 0x78, 0x08, 0x00, 0x87, 0x13, 0x63, 0x46, 0xe4, 0x91, 0xba,
+	0x5f, 0xfe, 0x00, 0x08, 0x00, 0x07, 0x6e, 0x12, 0x20, 0x13, 0x6c, 0x20,
+	0x00, 0x00, 0x7d, 0x20, 0x00, 0x1d, 0x4c, 0x1b, 0xed, 0x04, 0x02, 0x08,
+};
+
+static const u8 pps_c[] = {
+	0x00, 0x00, 0x00, 0x01, 0x44, 0x01, 0x6c, 0x06, 0x10, 0x2f, 0x0d, 0xa0,
+	0x50, 0x28, 0x11, 0x29, 0x20,
+};
+
+static const u8 slice_a_idr[] = {
+	0x00, 0x00, 0x00, 0x01, 0x26, 0x01, 0xaf, 0x22,
+};
+
+static const u8 slice_a_p[] = {
+	0x00, 0x00, 0x00, 0x01, 0x02, 0x01, 0xd0, 0x41, 0xf4, 0xc6, 0x7c,
+};
+
+static const u8 slice_b_b[] = {
+	0x00, 0x00, 0x00, 0x01, 0x02, 0x01, 0x20, 0x29, 0x19, 0x2d, 0x50, 0x2b,
+	0x48, 0xcb, 0x4e, 0x21, 0x64, 0x16, 0x18, 0xfe, 0x78, 0x06, 0xc0,
+};
+
+static const u8 slice_b_dep[] = {
+	0x00, 0x00, 0x00, 0x01, 0x02, 0x01, 0x28, 0x57,
+};
+
+static const u8 slice_b_cra[] = {
+	0x00, 0x00, 0x00, 0x01, 0x2a, 0x01, 0xd0, 0xe0, 0xe7, 0xfc,
+};
+
+static const u8 slice_c_tsa[] = {
+	0x00, 0x00, 0x00, 0x01, 0x04, 0x03, 0xb4, 0x1f, 0xfe, 0x55, 0x10, 0xa6,
+	0x6c, 0x7b, 0x80,
+};
+
+static const u8 aud[] = {
+	0x00, 0x00, 0x00, 0x01, 0x46, 0x02, 0x30,
+};
+
+static const u8 exp_golomb[] = {
+	0xa6, 0x42, 0x98, 0xe2, 0x04, 0xa6, 0x42, 0xc7, 0x0f, 0xde, 0xad, 0xbe,
+	0xef, 0x00, 0x00, 0x00, 0x01, 0xff, 0xff, 0xff, 0xff,
+};
+
+/* Configurations */
+
+#define MAIN_PTL(level)							\
+	{								\
+		.general_profile_idc = V4L2_HEVC_PROFILE_IDC_MAIN,	\
+		.general_profile_compatibility_flags = 0x60000000,	\
+		.general_level_idc = (level),				\
+		.flags = V4L2_HEVC_PTL_FLAG_PROGRESSIVE_SOURCE |	\
+			 V4L2_HEVC_PTL_FLAG_FRAME_ONLY_CONSTRAINT,	\
+	}
+
+/* A: 720p Main, CTB 64, SAO, AMP, TMVP, VUI with colour and timing */
+static const struct v4l2_ctrl_hevc_sps sps_a_cfg = {
+	.pic_width_in_luma_samples = 1280,
+	.pic_height_in_luma_samples = 720,
+	.log2_max_pic_order_cnt_lsb_minus4 = 4,
+	.sps_max_dec_pic_buffering_minus1 = 4,
+	.sps_max_num_reorder_pics = 2,
+	.sps_max_latency_increase_plus1 = 5,
+	.log2_diff_max_min_luma_coding_block_size = 3,
+	.log2_diff_max_min_luma_transform_block_size = 3,
+	.chroma_format_idc = 1,
+	.flags = V4L2_HEVC_SPS_FLAG_AMP_ENABLED |
+		 V4L2_HEVC_SPS_FLAG_SAMPLE_ADAPTIVE_OFFSET |
+		 V4L2_HEVC_SPS_FLAG_SPS_TEMPORAL_MVP_ENABLED |
+		 V4L2_HEVC_SPS_FLAG_STRONG_INTRA_SMOOTHING_ENABLED,
+};
+
+static const struct v4l2_hevc_sps_video video_a_cfg = {
+	.ptl = MAIN_PTL(93),
+	.flags = V4L2_HEVC_SPS_VIDEO_FLAG_TEMPORAL_ID_NESTING |
+		 V4L2_HEVC_SPS_VIDEO_FLAG_VUI_PARAMETERS_PRESENT |
+		 V4L2_HEVC_SPS_VIDEO_FLAG_VUI_VIDEO_SIGNAL_TYPE_PRESENT |
+		 V4L2_HEVC_SPS_VIDEO_FLAG_VUI_COLOUR_DESCRIPTION_PRESENT |
+		 V4L2_HEVC_SPS_VIDEO_FLAG_VUI_TIMING_INFO_PRESENT,
+	.video_format = V4L2_HEVC_VUI_VIDEO_FORMAT_UNSPECIFIED,
+	.colour_primaries = V4L2_HEVC_VUI_COLOUR_BT709,
+	.transfer_characteristics = V4L2_HEVC_VUI_TRANSFER_BT709,
+	.matrix_coeffs = V4L2_HEVC_VUI_MATRIX_BT709,
+	.num_units_in_tick = 1,
+	.time_scale = 25,
+};
+
+static const struct v4l2_ctrl_hevc_pps pps_a_cfg = {
+	.flags = V4L2_HEVC_PPS_FLAG_SIGN_DATA_HIDING_ENABLED |
+		 V4L2_HEVC_PPS_FLAG_PPS_LOOP_FILTER_ACROSS_SLICES_ENABLED,
+};
+
+/*
+ * B: 640x360 coded as 640x368 with a conformance window, CTB 32, PCM,
+ * long-term references, and a PPS with most optional slice header syntax
+ * enabled: dependent slices, output flag, extra bits, CABAC init, chroma QP
+ * offsets, 2x2 uniform tiles, deblocking override, list modification and
+ * header extension.
+ */
+static const struct v4l2_ctrl_hevc_sps sps_b_cfg = {
+	.video_parameter_set_id = 1,
+	.seq_parameter_set_id = 1,
+	.pic_width_in_luma_samples = 640,
+	.pic_height_in_luma_samples = 368,
+	.sps_max_dec_pic_buffering_minus1 = 2,
+	.log2_diff_max_min_luma_coding_block_size = 2,
+	.log2_diff_max_min_luma_transform_block_size = 3,
+	.max_transform_hierarchy_depth_inter = 1,
+	.max_transform_hierarchy_depth_intra = 1,
+	.pcm_sample_bit_depth_luma_minus1 = 7,
+	.pcm_sample_bit_depth_chroma_minus1 = 7,
+	.log2_diff_max_min_pcm_luma_coding_block_size = 1,
+	.chroma_format_idc = 1,
+	.flags = V4L2_HEVC_SPS_FLAG_PCM_ENABLED |
+		 V4L2_HEVC_SPS_FLAG_PCM_LOOP_FILTER_DISABLED |
+		 V4L2_HEVC_SPS_FLAG_LONG_TERM_REF_PICS_PRESENT |
+		 V4L2_HEVC_SPS_FLAG_SPS_TEMPORAL_MVP_ENABLED,
+};
+
+static const struct v4l2_hevc_sps_video video_b_cfg = {
+	.ptl = MAIN_PTL(90),
+	.flags = V4L2_HEVC_SPS_VIDEO_FLAG_TEMPORAL_ID_NESTING |
+		 V4L2_HEVC_SPS_VIDEO_FLAG_CONFORMANCE_WINDOW,
+	.conf_win_bottom_offset = 4,
+};
+
+static const struct v4l2_ctrl_hevc_pps pps_b_cfg = {
+	.pic_parameter_set_id = 1,
+	.num_extra_slice_header_bits = 2,
+	.num_ref_idx_l0_default_active_minus1 = 1,
+	.init_qp_minus26 = -4,
+	.diff_cu_qp_delta_depth = 1,
+	.pps_cb_qp_offset = -2,
+	.pps_cr_qp_offset = 3,
+	.num_tile_columns_minus1 = 1,
+	.num_tile_rows_minus1 = 1,
+	.pps_beta_offset_div2 = -2,
+	.pps_tc_offset_div2 = 1,
+	.log2_parallel_merge_level_minus2 = 2,
+	.flags = V4L2_HEVC_PPS_FLAG_DEPENDENT_SLICE_SEGMENT_ENABLED |
+		 V4L2_HEVC_PPS_FLAG_OUTPUT_FLAG_PRESENT |
+		 V4L2_HEVC_PPS_FLAG_SIGN_DATA_HIDING_ENABLED |
+		 V4L2_HEVC_PPS_FLAG_CABAC_INIT_PRESENT |
+		 V4L2_HEVC_PPS_FLAG_CONSTRAINED_INTRA_PRED |
+		 V4L2_HEVC_PPS_FLAG_TRANSFORM_SKIP_ENABLED |
+		 V4L2_HEVC_PPS_FLAG_CU_QP_DELTA_ENABLED |
+		 V4L2_HEVC_PPS_FLAG_PPS_SLICE_CHROMA_QP_OFFSETS_PRESENT |
+		 V4L2_HEVC_PPS_FLAG_TILES_ENABLED |
+		 V4L2_HEVC_PPS_FLAG_UNIFORM_SPACING |
+		 V4L2_HEVC_PPS_FLAG_LOOP_FILTER_ACROSS_TILES_ENABLED |
+		 V4L2_HEVC_PPS_FLAG_PPS_LOOP_FILTER_ACROSS_SLICES_ENABLED |
+		 V4L2_HEVC_PPS_FLAG_DEBLOCKING_FILTER_CONTROL_PRESENT |
+		 V4L2_HEVC_PPS_FLAG_DEBLOCKING_FILTER_OVERRIDE_ENABLED |
+		 V4L2_HEVC_PPS_FLAG_LISTS_MODIFICATION_PRESENT |
+		 V4L2_HEVC_PPS_FLAG_SLICE_SEGMENT_HEADER_EXTENSION_PRESENT,
+};
+
+/*
+ * C: 2160p60 Main 10 high tier level 5.1, 3 temporal sub-layers, default
+ * scaling lists, full VUI (extended SAR, BT.2020 PQ full range, chroma
+ * location, timing, bitstream restriction), VPS timing, 3x2 non-uniform
+ * tiles with wavefront parallel processing and deblocking disabled.
+ */
+static const struct v4l2_ctrl_hevc_sps sps_c_cfg = {
+	.video_parameter_set_id = 2,
+	.seq_parameter_set_id = 2,
+	.pic_width_in_luma_samples = 3840,
+	.pic_height_in_luma_samples = 2160,
+	.bit_depth_luma_minus8 = 2,
+	.bit_depth_chroma_minus8 = 2,
+	.log2_max_pic_order_cnt_lsb_minus4 = 12,
+	.sps_max_dec_pic_buffering_minus1 = 5,
+	.log2_diff_max_min_luma_coding_block_size = 3,
+	.log2_diff_max_min_luma_transform_block_size = 3,
+	.max_transform_hierarchy_depth_inter = 2,
+	.max_transform_hierarchy_depth_intra = 2,
+	.chroma_format_idc = 1,
+	.sps_max_sub_layers_minus1 = 2,
+	.flags = V4L2_HEVC_SPS_FLAG_SCALING_LIST_ENABLED |
+		 V4L2_HEVC_SPS_FLAG_AMP_ENABLED |
+		 V4L2_HEVC_SPS_FLAG_SPS_TEMPORAL_MVP_ENABLED |
+		 V4L2_HEVC_SPS_FLAG_STRONG_INTRA_SMOOTHING_ENABLED,
+};
+
+static const struct v4l2_hevc_sps_video video_c_cfg = {
+	.ptl = {
+		.general_tier_flag = 1,
+		.general_profile_idc = V4L2_HEVC_PROFILE_IDC_MAIN_10,
+		.general_profile_compatibility_flags = 0x20000000,
+		.general_level_idc = 153,
+		.flags = V4L2_HEVC_PTL_FLAG_PROGRESSIVE_SOURCE |
+			 V4L2_HEVC_PTL_FLAG_FRAME_ONLY_CONSTRAINT,
+	},
+	.flags = V4L2_HEVC_SPS_VIDEO_FLAG_TEMPORAL_ID_NESTING |
+		 V4L2_HEVC_SPS_VIDEO_FLAG_VPS_TIMING_INFO_PRESENT |
+		 V4L2_HEVC_SPS_VIDEO_FLAG_VUI_PARAMETERS_PRESENT |
+		 V4L2_HEVC_SPS_VIDEO_FLAG_VUI_ASPECT_RATIO_INFO_PRESENT |
+		 V4L2_HEVC_SPS_VIDEO_FLAG_VUI_OVERSCAN_INFO_PRESENT |
+		 V4L2_HEVC_SPS_VIDEO_FLAG_VUI_VIDEO_SIGNAL_TYPE_PRESENT |
+		 V4L2_HEVC_SPS_VIDEO_FLAG_VUI_VIDEO_FULL_RANGE |
+		 V4L2_HEVC_SPS_VIDEO_FLAG_VUI_COLOUR_DESCRIPTION_PRESENT |
+		 V4L2_HEVC_SPS_VIDEO_FLAG_VUI_CHROMA_LOC_INFO_PRESENT |
+		 V4L2_HEVC_SPS_VIDEO_FLAG_VUI_TIMING_INFO_PRESENT |
+		 V4L2_HEVC_SPS_VIDEO_FLAG_VUI_POC_PROPORTIONAL_TO_TIMING |
+		 V4L2_HEVC_SPS_VIDEO_FLAG_VUI_BITSTREAM_RESTRICTION |
+		 V4L2_HEVC_SPS_VIDEO_FLAG_VUI_TILES_FIXED_STRUCTURE |
+		 V4L2_HEVC_SPS_VIDEO_FLAG_VUI_MOTION_VECTORS_OVER_PIC_BOUNDARIES |
+		 V4L2_HEVC_SPS_VIDEO_FLAG_VUI_RESTRICTED_REF_PIC_LISTS,
+	.aspect_ratio_idc = V4L2_HEVC_VUI_ASPECT_RATIO_IDC_EXTENDED,
+	.sar_width = 4,
+	.sar_height = 3,
+	.video_format = V4L2_HEVC_VUI_VIDEO_FORMAT_UNSPECIFIED,
+	.colour_primaries = V4L2_HEVC_VUI_COLOUR_BT2020,
+	.transfer_characteristics = V4L2_HEVC_VUI_TRANSFER_SMPTE2084,
+	.matrix_coeffs = V4L2_HEVC_VUI_MATRIX_BT2020_NCL,
+	.chroma_sample_loc_type_top_field = 2,
+	.chroma_sample_loc_type_bottom_field = 2,
+	.num_units_in_tick = 1001,
+	.time_scale = 60000,
+	.max_bytes_per_pic_denom = 2,
+	.max_bits_per_min_cu_denom = 1,
+	.log2_max_mv_length_horizontal = 15,
+	.log2_max_mv_length_vertical = 15,
+};
+
+static const struct v4l2_ctrl_hevc_pps pps_c_cfg = {
+	.pic_parameter_set_id = 2,
+	.init_qp_minus26 = 8,
+	.diff_cu_qp_delta_depth = 2,
+	.num_tile_columns_minus1 = 2,
+	.num_tile_rows_minus1 = 1,
+	.column_width_minus1 = { 19, 19 },
+	.row_height_minus1 = { 16 },
+	.flags = V4L2_HEVC_PPS_FLAG_CU_QP_DELTA_ENABLED |
+		 V4L2_HEVC_PPS_FLAG_TILES_ENABLED |
+		 V4L2_HEVC_PPS_FLAG_ENTROPY_CODING_SYNC_ENABLED |
+		 V4L2_HEVC_PPS_FLAG_DEBLOCKING_FILTER_CONTROL_PRESENT |
+		 V4L2_HEVC_PPS_FLAG_PPS_DISABLE_DEBLOCKING_FILTER,
+};
+
+static const u32 entry_points_b[] = { 99, 1999, 6 };
+static const u32 entry_points_c[] = { 49, 59 };
+
+struct hevc_rbsp_slice_case {
+	const char *name;
+	const struct v4l2_ctrl_hevc_sps *sps;
+	const struct v4l2_ctrl_hevc_pps *pps;
+	struct v4l2_hevc_slice_header sh;
+	const u8 *expected;
+	unsigned int expected_size;
+};
+
+#define SLICE_CASE(n, s, p, e, ...)					\
+	{								\
+		.name = n, .sps = s, .pps = p,				\
+		.sh = { __VA_ARGS__ },					\
+		.expected = e, .expected_size = sizeof(e),		\
+	}
+
+static const struct hevc_rbsp_slice_case slice_cases[] = {
+	SLICE_CASE("A IDR", &sps_a_cfg, &pps_a_cfg, slice_a_idr,
+		   .nal_unit_type = V4L2_HEVC_NAL_IDR_W_RADL,
+		   .nuh_temporal_id_plus1 = 1,
+		   .slice_type = V4L2_HEVC_SLICE_TYPE_I,
+		   .slice_qp_delta = 2,
+		   .flags = V4L2_HEVC_ENCODE_FLAG_SLICE_SAO_LUMA |
+			    V4L2_HEVC_ENCODE_FLAG_SLICE_SAO_CHROMA),
+	SLICE_CASE("A P", &sps_a_cfg, &pps_a_cfg, slice_a_p,
+		   .nal_unit_type = V4L2_HEVC_NAL_TRAIL_R,
+		   .nuh_temporal_id_plus1 = 1,
+		   .slice_type = V4L2_HEVC_SLICE_TYPE_P,
+		   .slice_pic_order_cnt_lsb = 8,
+		   .st_rps = {
+			.num_negative_pics = 2,
+			.used_by_curr_pic_s0 = 0x1,
+			.delta_poc_s0_minus1 = { 0, 1 },
+		   },
+		   .five_minus_max_num_merge_cand = 2,
+		   .slice_qp_delta = -3,
+		   .flags = V4L2_HEVC_ENCODE_FLAG_SLICE_TEMPORAL_MVP_ENABLED |
+			    V4L2_HEVC_ENCODE_FLAG_SLICE_SAO_LUMA |
+			    V4L2_HEVC_ENCODE_FLAG_SLICE_LOOP_FILTER_ACROSS_SLICES_ENABLED),
+	SLICE_CASE("B B", &sps_b_cfg, &pps_b_cfg, slice_b_b,
+		   .nal_unit_type = V4L2_HEVC_NAL_TRAIL_R,
+		   .nuh_temporal_id_plus1 = 1,
+		   .slice_type = V4L2_HEVC_SLICE_TYPE_B,
+		   .slice_segment_address = 5,
+		   .slice_pic_order_cnt_lsb = 3,
+		   .st_rps = {
+			.num_negative_pics = 1,
+			.num_positive_pics = 1,
+			.used_by_curr_pic_s0 = 0x1,
+			.used_by_curr_pic_s1 = 0x1,
+			.delta_poc_s0_minus1 = { 0 },
+			.delta_poc_s1_minus1 = { 1 },
+		   },
+		   .num_long_term_pics = 1,
+		   .lt = { { .poc_lsb_lt = 0, .delta_poc_msb_present = true,
+			     .delta_poc_msb_cycle_lt = 1 } },
+		   .num_ref_idx_l0_active_minus1 = 1,
+		   .num_ref_idx_l1_active_minus1 = 1,
+		   .collocated_ref_idx = 1,
+		   .slice_cb_qp_offset = 1,
+		   .slice_cr_qp_offset = -1,
+		   .slice_beta_offset_div2 = 2,
+		   .slice_tc_offset_div2 = -2,
+		   .num_entry_point_offsets = ARRAY_SIZE(entry_points_b),
+		   .entry_point_offset_minus1 = entry_points_b,
+		   .flags = V4L2_HEVC_ENCODE_FLAG_NO_PIC_OUTPUT |
+			    V4L2_HEVC_ENCODE_FLAG_SLICE_TEMPORAL_MVP_ENABLED |
+			    V4L2_HEVC_ENCODE_FLAG_NUM_REF_IDX_ACTIVE_OVERRIDE |
+			    V4L2_HEVC_ENCODE_FLAG_MVD_L1_ZERO |
+			    V4L2_HEVC_ENCODE_FLAG_CABAC_INIT |
+			    V4L2_HEVC_ENCODE_FLAG_DEBLOCKING_FILTER_OVERRIDE |
+			    V4L2_HEVC_ENCODE_FLAG_SLICE_LOOP_FILTER_ACROSS_SLICES_ENABLED),
+	SLICE_CASE("B dependent", &sps_b_cfg, &pps_b_cfg, slice_b_dep,
+		   .nal_unit_type = V4L2_HEVC_NAL_TRAIL_R,
+		   .nuh_temporal_id_plus1 = 1,
+		   .slice_type = V4L2_HEVC_SLICE_TYPE_B,
+		   .slice_segment_address = 10,
+		   .flags = V4L2_HEVC_SLICE_HEADER_FLAG_DEPENDENT_SLICE_SEGMENT),
+	SLICE_CASE("B CRA", &sps_b_cfg, &pps_b_cfg, slice_b_cra,
+		   .nal_unit_type = V4L2_HEVC_NAL_CRA_NUT,
+		   .nuh_temporal_id_plus1 = 1,
+		   .slice_type = V4L2_HEVC_SLICE_TYPE_I,
+		   .slice_qp_delta = -1,
+		   .flags = V4L2_HEVC_ENCODE_FLAG_NO_OUTPUT_OF_PRIOR_PICS |
+			    V4L2_HEVC_ENCODE_FLAG_DEBLOCKING_FILTER_OVERRIDE |
+			    V4L2_HEVC_ENCODE_FLAG_SLICE_DEBLOCKING_FILTER_DISABLED),
+	SLICE_CASE("C TSA", &sps_c_cfg, &pps_c_cfg, slice_c_tsa,
+		   .nal_unit_type = V4L2_HEVC_NAL_TSA_N,
+		   .nuh_temporal_id_plus1 = 3,
+		   .slice_type = V4L2_HEVC_SLICE_TYPE_P,
+		   .slice_pic_order_cnt_lsb = 4095,
+		   .st_rps = {
+			.num_negative_pics = 1,
+			.used_by_curr_pic_s0 = 0x1,
+			.delta_poc_s0_minus1 = { 1 },
+		   },
+		   .five_minus_max_num_merge_cand = 1,
+		   .slice_qp_delta = 5,
+		   .num_entry_point_offsets = ARRAY_SIZE(entry_points_c),
+		   .entry_point_offset_minus1 = entry_points_c),
+};
+
+static void hevc_rbsp_expect(struct kunit *test, struct v4l2_hevc_enc_rbsp *rbsp,
+			     int ret, const u8 *expected, unsigned int size)
+{
+	KUNIT_ASSERT_EQ(test, ret, 0);
+	KUNIT_ASSERT_EQ(test, v4l2_hevc_enc_rbsp_bytes(rbsp), size);
+	KUNIT_EXPECT_MEMEQ(test, rbsp->data, expected, size);
+}
+
+static u8 *hevc_rbsp_buffer(struct kunit *test, struct v4l2_hevc_enc_rbsp *rbsp)
+{
+	u8 *buf = kunit_kzalloc(test, BUF_SIZE, GFP_KERNEL);
+
+	KUNIT_ASSERT_NOT_ERR_OR_NULL(test, buf);
+	v4l2_hevc_enc_rbsp_init(rbsp, buf, BUF_SIZE);
+
+	return buf;
+}
+
+/* Exp-Golomb codes, and no emulation prevention outside NAL units. */
+static void hevc_rbsp_exp_golomb(struct kunit *test)
+{
+	struct v4l2_hevc_enc_rbsp rbsp;
+	unsigned int i;
+	static const s32 se[] = { 1, -1, 2, -2, 0, 7, -7 };
+
+	hevc_rbsp_buffer(test, &rbsp);
+
+	for (i = 0; i < 9; i++)
+		v4l2_hevc_enc_rbsp_ue(&rbsp, i);
+	for (i = 0; i < ARRAY_SIZE(se); i++)
+		v4l2_hevc_enc_rbsp_se(&rbsp, se[i]);
+	v4l2_hevc_enc_rbsp_bits(&rbsp, 0xdeadbeef, 32);
+	v4l2_hevc_enc_rbsp_ue(&rbsp, 0xfffffffe);
+	v4l2_hevc_enc_rbsp_trailing_bits(&rbsp);
+
+	hevc_rbsp_expect(test, &rbsp, v4l2_hevc_enc_rbsp_error(&rbsp),
+			 exp_golomb, sizeof(exp_golomb));
+}
+
+struct hevc_rbsp_epb_case {
+	const char *name;
+	u8 in[8];
+	unsigned int in_size;
+	u8 out[12];
+	unsigned int out_size;
+};
+
+static const struct hevc_rbsp_epb_case epb_cases[] = {
+	{ "00 00 00", { 0, 0, 0 }, 3, { 0, 0, 3, 0 }, 4 },
+	{ "00 00 01", { 0, 0, 1 }, 3, { 0, 0, 3, 1 }, 4 },
+	{ "00 00 02", { 0, 0, 2 }, 3, { 0, 0, 3, 2 }, 4 },
+	{ "00 00 03", { 0, 0, 3 }, 3, { 0, 0, 3, 3 }, 4 },
+	{ "00 00 04", { 0, 0, 4 }, 3, { 0, 0, 4 }, 3 },
+	{ "5 zeros", { 0, 0, 0, 0, 0 }, 5, { 0, 0, 3, 0, 0, 3, 0 }, 7 },
+	{ "00 00 03 00", { 0, 0, 3, 0 }, 4, { 0, 0, 3, 3, 0 }, 5 },
+	{ "00 01 00 00 00 01", { 0, 1, 0, 0, 0, 1 }, 6,
+	  { 0, 1, 0, 0, 3, 0, 1 }, 7 },
+	{ "00 00", { 0, 0 }, 2, { 0, 0 }, 2 },
+};
+
+/*
+ * Emulation prevention inside a NAL unit payload, with the bytes written
+ * as fields that do not line up with byte boundaries (3 + 5 + 16 bits).
+ */
+static void hevc_rbsp_epb(struct kunit *test)
+{
+	struct v4l2_hevc_enc_rbsp rbsp;
+	unsigned int i, j;
+
+	for (i = 0; i < ARRAY_SIZE(epb_cases); i++) {
+		const struct hevc_rbsp_epb_case *c = &epb_cases[i];
+		u8 out[2 + ARRAY_SIZE(c->out)];
+
+		hevc_rbsp_buffer(test, &rbsp);
+
+		/* TRAIL_N header: 0x00 0x01, not subject to prevention. */
+		v4l2_hevc_enc_rbsp_nal_begin(&rbsp, false, V4L2_HEVC_NAL_TRAIL_N,
+					     1);
+		for (j = 0; j + 3 <= c->in_size; j += 3) {
+			u32 v = c->in[j] << 16 | c->in[j + 1] << 8 | c->in[j + 2];
+
+			v4l2_hevc_enc_rbsp_bits(&rbsp, v >> 21, 3);
+			v4l2_hevc_enc_rbsp_bits(&rbsp, v >> 16, 5);
+			v4l2_hevc_enc_rbsp_bits(&rbsp, v, 16);
+		}
+		for (; j < c->in_size; j++)
+			v4l2_hevc_enc_rbsp_bits(&rbsp, c->in[j], 8);
+
+		out[0] = 0x00;
+		out[1] = 0x01;
+		memcpy(&out[2], c->out, c->out_size);
+
+		KUNIT_EXPECT_EQ_MSG(test, v4l2_hevc_enc_rbsp_error(&rbsp), 0,
+				    "%s", c->name);
+		KUNIT_EXPECT_EQ_MSG(test, v4l2_hevc_enc_rbsp_bytes(&rbsp),
+				    c->out_size + 2, "%s", c->name);
+		KUNIT_EXPECT_MEMEQ_MSG(test, rbsp.data, out, c->out_size + 2,
+				       "%s", c->name);
+	}
+}
+
+static void hevc_rbsp_errors(struct kunit *test)
+{
+	struct v4l2_hevc_enc_rbsp rbsp;
+	struct v4l2_ctrl_hevc_sps sps = sps_a_cfg;
+	u8 buf[8];
+
+	/* A buffer that is too small. */
+	v4l2_hevc_enc_rbsp_init(&rbsp, buf, sizeof(buf));
+	KUNIT_EXPECT_EQ(test, v4l2_hevc_enc_rbsp_vps(&rbsp, true, &sps,
+						     &video_a_cfg),
+			-ENOSPC);
+	KUNIT_EXPECT_EQ(test, v4l2_hevc_enc_rbsp_bytes(&rbsp), sizeof(buf));
+
+	/* The error is sticky. */
+	v4l2_hevc_enc_rbsp_bits(&rbsp, 1, 1);
+	KUNIT_EXPECT_EQ(test, v4l2_hevc_enc_rbsp_error(&rbsp), -ENOSPC);
+
+	/* Values that cannot be represented. */
+	hevc_rbsp_buffer(test, &rbsp);
+	v4l2_hevc_enc_rbsp_ue(&rbsp, U32_MAX);
+	KUNIT_EXPECT_EQ(test, v4l2_hevc_enc_rbsp_error(&rbsp), -EINVAL);
+
+	hevc_rbsp_buffer(test, &rbsp);
+	v4l2_hevc_enc_rbsp_se(&rbsp, S32_MIN);
+	KUNIT_EXPECT_EQ(test, v4l2_hevc_enc_rbsp_error(&rbsp), -EINVAL);
+
+	/* Parameter sets the writer cannot express. */
+	sps.num_short_term_ref_pic_sets = 1;
+	hevc_rbsp_buffer(test, &rbsp);
+	KUNIT_EXPECT_EQ(test, v4l2_hevc_enc_rbsp_sps(&rbsp, true, &sps,
+						     &video_a_cfg),
+			-EINVAL);
+	KUNIT_EXPECT_EQ(test, v4l2_hevc_enc_rbsp_bytes(&rbsp), 0);
+}
+
+struct hevc_rbsp_ps_case {
+	const char *name;
+	const struct v4l2_ctrl_hevc_sps *sps;
+	const struct v4l2_hevc_sps_video *video;
+	const struct v4l2_ctrl_hevc_pps *pps;
+	const u8 *vps, *sps_exp, *pps_exp;
+	unsigned int vps_size, sps_size, pps_size;
+};
+
+#define PS_CASE(n, x)							\
+	{								\
+		.name = n, .sps = &sps_##x##_cfg,			\
+		.video = &video_##x##_cfg, .pps = &pps_##x##_cfg,	\
+		.vps = vps_##x, .vps_size = sizeof(vps_##x),		\
+		.sps_exp = sps_##x, .sps_size = sizeof(sps_##x),	\
+		.pps_exp = pps_##x, .pps_size = sizeof(pps_##x),	\
+	}
+
+static const struct hevc_rbsp_ps_case ps_cases[] = {
+	PS_CASE("A", a),
+	PS_CASE("B", b),
+	PS_CASE("C", c),
+};
+
+static void hevc_rbsp_ps_desc(const struct hevc_rbsp_ps_case *c, char *desc)
+{
+	strscpy(desc, c->name, KUNIT_PARAM_DESC_SIZE);
+}
+
+KUNIT_ARRAY_PARAM(hevc_rbsp_ps, ps_cases, hevc_rbsp_ps_desc);
+
+static void hevc_rbsp_vps_test(struct kunit *test)
+{
+	const struct hevc_rbsp_ps_case *c = test->param_value;
+	struct v4l2_hevc_enc_rbsp rbsp;
+	int ret;
+
+	hevc_rbsp_buffer(test, &rbsp);
+	ret = v4l2_hevc_enc_rbsp_vps(&rbsp, true, c->sps, c->video);
+	hevc_rbsp_expect(test, &rbsp, ret, c->vps, c->vps_size);
+}
+
+static void hevc_rbsp_sps_test(struct kunit *test)
+{
+	const struct hevc_rbsp_ps_case *c = test->param_value;
+	struct v4l2_hevc_enc_rbsp rbsp;
+	int ret;
+
+	hevc_rbsp_buffer(test, &rbsp);
+	ret = v4l2_hevc_enc_rbsp_sps(&rbsp, true, c->sps, c->video);
+	hevc_rbsp_expect(test, &rbsp, ret, c->sps_exp, c->sps_size);
+}
+
+static void hevc_rbsp_pps_test(struct kunit *test)
+{
+	const struct hevc_rbsp_ps_case *c = test->param_value;
+	struct v4l2_hevc_enc_rbsp rbsp;
+	int ret;
+
+	hevc_rbsp_buffer(test, &rbsp);
+	ret = v4l2_hevc_enc_rbsp_pps(&rbsp, true, c->sps, c->pps);
+	hevc_rbsp_expect(test, &rbsp, ret, c->pps_exp, c->pps_size);
+}
+
+static void hevc_rbsp_slice_desc(const struct hevc_rbsp_slice_case *c,
+				 char *desc)
+{
+	strscpy(desc, c->name, KUNIT_PARAM_DESC_SIZE);
+}
+
+KUNIT_ARRAY_PARAM(hevc_rbsp_slice, slice_cases, hevc_rbsp_slice_desc);
+
+static void hevc_rbsp_slice_test(struct kunit *test)
+{
+	const struct hevc_rbsp_slice_case *c = test->param_value;
+	struct v4l2_hevc_enc_rbsp rbsp;
+	int ret;
+
+	hevc_rbsp_buffer(test, &rbsp);
+	ret = v4l2_hevc_enc_rbsp_slice_header(&rbsp, true, c->sps, c->pps,
+					      &c->sh);
+	hevc_rbsp_expect(test, &rbsp, ret, c->expected, c->expected_size);
+}
+
+/* Several NAL units in a row, as at the start of an access unit. */
+static void hevc_rbsp_access_unit(struct kunit *test)
+{
+	struct v4l2_hevc_enc_rbsp rbsp;
+	unsigned int offset = 0;
+	u8 *buf;
+	int ret;
+
+	buf = hevc_rbsp_buffer(test, &rbsp);
+
+	ret = v4l2_hevc_enc_rbsp_aud(&rbsp, true, V4L2_HEVC_AUD_PIC_TYPE_PI, 2);
+	KUNIT_ASSERT_EQ(test, ret, 0);
+	KUNIT_EXPECT_MEMEQ(test, buf + offset, aud, sizeof(aud));
+	offset += sizeof(aud);
+
+	ret = v4l2_hevc_enc_rbsp_vps(&rbsp, true, &sps_c_cfg, &video_c_cfg);
+	KUNIT_ASSERT_EQ(test, ret, 0);
+	KUNIT_EXPECT_MEMEQ(test, buf + offset, vps_c, sizeof(vps_c));
+	offset += sizeof(vps_c);
+
+	ret = v4l2_hevc_enc_rbsp_sps(&rbsp, true, &sps_c_cfg, &video_c_cfg);
+	KUNIT_ASSERT_EQ(test, ret, 0);
+	KUNIT_EXPECT_MEMEQ(test, buf + offset, sps_c, sizeof(sps_c));
+	offset += sizeof(sps_c);
+
+	ret = v4l2_hevc_enc_rbsp_pps(&rbsp, true, &sps_c_cfg, &pps_c_cfg);
+	KUNIT_ASSERT_EQ(test, ret, 0);
+	KUNIT_EXPECT_MEMEQ(test, buf + offset, pps_c, sizeof(pps_c));
+	offset += sizeof(pps_c);
+
+	KUNIT_EXPECT_EQ(test, v4l2_hevc_enc_rbsp_bytes(&rbsp), offset);
+
+	/* Without start codes, each NAL unit loses its first four bytes. */
+	v4l2_hevc_enc_rbsp_init(&rbsp, buf, BUF_SIZE);
+	ret = v4l2_hevc_enc_rbsp_pps(&rbsp, false, &sps_c_cfg, &pps_c_cfg);
+	KUNIT_ASSERT_EQ(test, ret, 0);
+	KUNIT_EXPECT_EQ(test, v4l2_hevc_enc_rbsp_bytes(&rbsp),
+			sizeof(pps_c) - 4);
+	KUNIT_EXPECT_MEMEQ(test, buf, pps_c + 4, sizeof(pps_c) - 4);
+}
+
+static struct kunit_case hevc_rbsp_test_cases[] = {
+	KUNIT_CASE(hevc_rbsp_exp_golomb),
+	KUNIT_CASE(hevc_rbsp_epb),
+	KUNIT_CASE(hevc_rbsp_errors),
+	KUNIT_CASE_PARAM(hevc_rbsp_vps_test, hevc_rbsp_ps_gen_params),
+	KUNIT_CASE_PARAM(hevc_rbsp_sps_test, hevc_rbsp_ps_gen_params),
+	KUNIT_CASE_PARAM(hevc_rbsp_pps_test, hevc_rbsp_ps_gen_params),
+	KUNIT_CASE_PARAM(hevc_rbsp_slice_test, hevc_rbsp_slice_gen_params),
+	KUNIT_CASE(hevc_rbsp_access_unit),
+	{}
+};
+
+static struct kunit_suite hevc_rbsp_test_suite = {
+	.name = "v4l2-hevc-enc-rbsp",
+	.test_cases = hevc_rbsp_test_cases,
+};
+
+kunit_test_suite(hevc_rbsp_test_suite);
+
+MODULE_DESCRIPTION("KUnit tests for the V4L2 HEVC encode RBSP writer");
+MODULE_LICENSE("GPL");

-- 
2.55.0



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

* [PATCH RFC 05/10] media: hevc: Add stateless encode core
  2026-11-10 10:07 [PATCH RFC 00/10] media: Add a V4L2 HEVC stateless encode uAPI and helpers Arun Iyengar via B4 Relay
                   ` (3 preceding siblings ...)
  2026-11-10 10:07 ` [PATCH RFC 04/10] media: hevc: Add KUnit tests for the " Arun Iyengar via B4 Relay
@ 2026-11-10 10:07 ` Arun Iyengar via B4 Relay
  2026-11-10 10:07 ` [PATCH RFC 06/10] media: hevc: Add KUnit tests for the " Arun Iyengar via B4 Relay
                   ` (4 subsequent siblings)
  9 siblings, 0 replies; 11+ messages in thread
From: Arun Iyengar via B4 Relay @ 2026-11-10 10:07 UTC (permalink / raw)
  To: Mauro Carvalho Chehab
  Cc: linux-media, linux-kernel, Paul Kocialkowski, Nicolas Dufresne,
	Hans Verkuil, Arun Iyengar

From: Arun Iyengar <aiyengar1990@gmail.com>

Add the kernel side of the stateless HEVC encode uAPI, with the same
driver interface as the H.264 stateless encode core: drivers call
v4l2_hevc_enc_init() and v4l2_hevc_enc_exit() when streaming starts and
stops, v4l2_hevc_enc_step() before starting the hardware and
v4l2_hevc_enc_complete() when it is done, and provide callbacks to
allocate reconstruction buffers and to constrain the parameters to what
the hardware supports.

For each picture, the core:
- reads the SPS, PPS and encode params controls and accommodates them:
  the coded size and conformance window come from the CAPTURE format and
  an optional crop rectangle, the profile, tier and level from the
  stateful HEVC controls (the lowest fitting level when there is no level
  control), the VUI from the colorimetry and frame interval, and features
  the hardware or the RBSP writer cannot do are turned off. The SPS and
  PPS actually used are written back to their controls. A changed SPS is
  only activated by the next IRAP picture;
- resolves the reference picture set: each reference timestamp is looked
  up among the reconstructed pictures it keeps, the short-term entries
  are sorted by POC distance and coded with their deltas, the long-term
  ones with their POC LSB and MSB cycle, and reference picture lists 0
  and 1 are built as in 8.3.4, limited to the number of entries the
  hardware supports. Drivers can also limit the number of negative,
  positive and long-term pictures, for hardware that writes the RPS from
  registers. It checks what a decoder would otherwise get wrong:
  references that are not in the DPB, long-term pictures used as
  short-term ones, POC values whose MSB could not be derived, and
  pictures that need references without having one;
- selects the QP: from the I/P/B QP controls without frame rate control,
  from the constant quality control in CQ mode, or from driver rate
  control callbacks for CBR and VBR;
- writes the AUD, VPS, SPS, PPS and slice segment header that the
  hardware does not write itself at the start of the CAPTURE buffer.

On completion, it drops the pictures that were not in the RPS, stores
the reconstruction of a reference picture in a free slot, sets the
buffer flags, patches nuh_temporal_id_plus1 into the slice NAL units of
hardware that cannot write it, and sets the feedback control so that it
gets stored in the request. Nothing changes in the reference state before
completion, so a failed or dropped encode run can simply be retried.

Signed-off-by: Arun Iyengar <aiyengar1990@gmail.com>
---
 drivers/media/v4l2-core/Kconfig         |    5 +
 drivers/media/v4l2-core/Makefile        |    1 +
 drivers/media/v4l2-core/v4l2-hevc-enc.c | 1418 +++++++++++++++++++++++++++++++
 include/media/v4l2-hevc-enc.h           |  284 +++++++
 4 files changed, 1708 insertions(+)

diff --git a/drivers/media/v4l2-core/Kconfig b/drivers/media/v4l2-core/Kconfig
index bc6bf06aa681..4c39bd943e81 100644
--- a/drivers/media/v4l2-core/Kconfig
+++ b/drivers/media/v4l2-core/Kconfig
@@ -57,6 +57,11 @@ config V4L2_H264_ENC
 config V4L2_HEVC_ENC_RBSP
 	tristate
 
+# Used by drivers that need v4l2-hevc-enc.ko
+config V4L2_HEVC_ENC
+	tristate
+	select V4L2_HEVC_ENC_RBSP
+
 config V4L2_HEVC_ENC_RBSP_KUNIT_TEST
 	tristate "KUnit tests for the V4L2 HEVC encode RBSP writer" if !KUNIT_ALL_TESTS
 	depends on KUNIT
diff --git a/drivers/media/v4l2-core/Makefile b/drivers/media/v4l2-core/Makefile
index 6bf89b90165f..153b8a6e74b8 100644
--- a/drivers/media/v4l2-core/Makefile
+++ b/drivers/media/v4l2-core/Makefile
@@ -32,6 +32,7 @@ obj-$(CONFIG_V4L2_FWNODE) += v4l2-fwnode.o
 obj-$(CONFIG_V4L2_H264) += v4l2-h264.o
 obj-$(CONFIG_V4L2_H264_ENC) += v4l2-h264-enc.o v4l2-h264-enc-rbsp.o \
 			       v4l2-h264-enc-rc.o
+obj-$(CONFIG_V4L2_HEVC_ENC) += v4l2-hevc-enc.o
 obj-$(CONFIG_V4L2_HEVC_ENC_RBSP) += v4l2-hevc-enc-rbsp.o
 obj-$(CONFIG_V4L2_HEVC_ENC_RBSP_KUNIT_TEST) += v4l2-hevc-enc-rbsp-kunit.o
 obj-$(CONFIG_V4L2_ISP) += v4l2-isp.o
diff --git a/drivers/media/v4l2-core/v4l2-hevc-enc.c b/drivers/media/v4l2-core/v4l2-hevc-enc.c
new file mode 100644
index 000000000000..e2aa7d2da96c
--- /dev/null
+++ b/drivers/media/v4l2-core/v4l2-hevc-enc.c
@@ -0,0 +1,1418 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * V4L2 HEVC Encode Core
+ *
+ * Kernel side of the stateless HEVC encoding uAPI: turns the HEVC encode
+ * controls into the parameter sets and slice segment headers of each
+ * picture, keeps the reconstructed reference pictures, resolves the
+ * reference picture set given by userspace and selects the QP.
+ *
+ * Section and table numbers refer to ITU-T Rec. H.265 (V10, 07/2024).
+ */
+
+#include <linux/bitops.h>
+#include <linux/export.h>
+#include <linux/kernel.h>
+#include <linux/math64.h>
+#include <linux/module.h>
+#include <linux/sort.h>
+#include <linux/v4l2-controls.h>
+#include <media/v4l2-ctrls.h>
+#include <media/v4l2-hevc.h>
+#include <media/v4l2-hevc-enc.h>
+#include <media/v4l2-hevc-enc-rbsp.h>
+#include <media/videobuf2-v4l2.h>
+
+#define RBSP_UPDATE_AUD			BIT(0)
+#define RBSP_UPDATE_PARAMETER_SETS	BIT(1)
+
+/* Table A.8: MaxLumaPs and MaxLumaSr (main tier is enough to pick a level) */
+static const struct {
+	u8 level_idc;
+	u32 max_luma_ps;
+	u64 max_luma_sr;
+} hevc_levels[] = {
+	{  30,    36864,     552960 },
+	{  60,   122880,    3686400 },
+	{  63,   245760,    7372800 },
+	{  90,   552960,   16588800 },
+	{  93,   983040,   33177600 },
+	{ 120,  2228224,   66846720 },
+	{ 123,  2228224,  133693440 },
+	{ 150,  8912896,  267386880 },
+	{ 153,  8912896,  534773760 },
+	{ 156,  8912896, 1069547520 },
+	{ 180, 35651584, 1069547520 },
+	{ 183, 35651584, 2139095040 },
+	{ 186, 35651584, 4278190080ULL },
+};
+
+/* V4L2_MPEG_VIDEO_HEVC_LEVEL_* to general_level_idc */
+static const u8 hevc_level_idc[] = {
+	30, 60, 63, 90, 93, 120, 123, 150, 153, 156, 180, 183, 186,
+};
+
+static struct v4l2_ctrl *enc_ctrl(struct v4l2_hevc_enc *enc, u32 id)
+{
+	return v4l2_ctrl_find(enc->ctrl_handler, id);
+}
+
+static int enc_ctrl_val(struct v4l2_hevc_enc *enc, u32 id, int def)
+{
+	struct v4l2_ctrl *ctrl = enc_ctrl(enc, id);
+
+	return ctrl ? ctrl->cur.val : def;
+}
+
+static int rec_buffer_alloc(struct v4l2_hevc_enc *enc,
+			    struct v4l2_hevc_enc_rec_buffer *buffer)
+{
+	int ret;
+
+	if (!enc->ops || !enc->ops->rec_buffer_alloc)
+		return -EOPNOTSUPP;
+
+	ret = enc->ops->rec_buffer_alloc(enc, buffer);
+	if (ret)
+		return ret;
+
+	buffer->allocated = true;
+
+	return 0;
+}
+
+static void rec_buffer_free(struct v4l2_hevc_enc *enc,
+			    struct v4l2_hevc_enc_rec_buffer *buffer)
+{
+	if (!buffer->allocated)
+		return;
+
+	if (enc->ops && enc->ops->rec_buffer_free)
+		enc->ops->rec_buffer_free(enc, buffer);
+
+	buffer->allocated = false;
+}
+
+static int ref_slots_grow(struct v4l2_hevc_enc *enc, unsigned int count)
+{
+	struct v4l2_hevc_enc_ref *ref = &enc->ref;
+	int ret;
+
+	count = min_t(unsigned int, count, ARRAY_SIZE(ref->dpb));
+
+	/*
+	 * Slots are only ever added, which avoids reallocating when the DPB
+	 * size changes back and forth, at the expense of memory.
+	 */
+	while (ref->slots_count < count) {
+		ret = rec_buffer_alloc(enc, &ref->dpb[ref->slots_count].buffer);
+		if (ret)
+			return ret;
+
+		ref->slots_count++;
+	}
+
+	return 0;
+}
+
+static void rec_buffers_free(struct v4l2_hevc_enc *enc)
+{
+	struct v4l2_hevc_enc_ref *ref = &enc->ref;
+	unsigned int i;
+
+	rec_buffer_free(enc, &ref->buffer_current);
+
+	for (i = 0; i < ref->slots_count; i++)
+		rec_buffer_free(enc, &ref->dpb[i].buffer);
+
+	ref->slots_count = 0;
+}
+
+/**
+ * v4l2_hevc_enc_init() - initialise the HEVC encode core
+ * @enc: the core context, with the driver fields set
+ *
+ * Call it when streaming starts. It allocates the reconstruction buffer of
+ * the current picture and the initial reference slots.
+ *
+ * Return: 0 or a negative error code.
+ */
+int v4l2_hevc_enc_init(struct v4l2_hevc_enc *enc)
+{
+	unsigned int slots = 0;
+	int ret;
+
+	if ((!enc->format && !enc->format_mplane) || !enc->timeperframe ||
+	    !enc->ctrl_handler)
+		return -EINVAL;
+
+	memset(&enc->state_active, 0, sizeof(enc->state_active));
+	memset(&enc->state_next, 0, sizeof(enc->state_next));
+	memset(&enc->ref, 0, sizeof(enc->ref));
+	enc->state_serial = 0;
+
+	if (!enc->max_l0_refs)
+		enc->max_l0_refs = 1;
+	if (!enc->max_l1_refs)
+		enc->max_l1_refs = 1;
+
+	ret = rec_buffer_alloc(enc, &enc->ref.buffer_current);
+	if (ret)
+		return ret;
+
+	if (enc->flags & (V4L2_HEVC_ENC_FLAG_INTER_PRED |
+			  V4L2_HEVC_ENC_FLAG_INTER_BIPRED))
+		slots = enc->ref_slots_count_init ?: 2;
+
+	ret = ref_slots_grow(enc, slots);
+	if (ret)
+		rec_buffers_free(enc);
+
+	return ret;
+}
+EXPORT_SYMBOL_GPL(v4l2_hevc_enc_init);
+
+/**
+ * v4l2_hevc_enc_exit() - release the HEVC encode core
+ * @enc: the core context
+ */
+void v4l2_hevc_enc_exit(struct v4l2_hevc_enc *enc)
+{
+	rec_buffers_free(enc);
+}
+EXPORT_SYMBOL_GPL(v4l2_hevc_enc_exit);
+
+static void enc_format(struct v4l2_hevc_enc *enc, unsigned int *width,
+		       unsigned int *height, unsigned int *colorspace,
+		       unsigned int *quantization, unsigned int *xfer_func,
+		       unsigned int *ycbcr_enc)
+{
+	if (enc->format_mplane) {
+		*width = enc->format_mplane->width;
+		*height = enc->format_mplane->height;
+		*colorspace = enc->format_mplane->colorspace;
+		*quantization = enc->format_mplane->quantization;
+		*xfer_func = enc->format_mplane->xfer_func;
+		*ycbcr_enc = enc->format_mplane->ycbcr_enc;
+	} else {
+		*width = enc->format->width;
+		*height = enc->format->height;
+		*colorspace = enc->format->colorspace;
+		*quantization = enc->format->quantization;
+		*xfer_func = enc->format->xfer_func;
+		*ycbcr_enc = enc->format->ycbcr_enc;
+	}
+}
+
+static void prepare_colour(struct v4l2_hevc_sps_video *video,
+			   unsigned int colorspace, unsigned int quantization,
+			   unsigned int xfer_func, unsigned int ycbcr_enc)
+{
+	if (colorspace == V4L2_COLORSPACE_DEFAULT &&
+	    quantization == V4L2_QUANTIZATION_DEFAULT &&
+	    xfer_func == V4L2_XFER_FUNC_DEFAULT &&
+	    ycbcr_enc == V4L2_YCBCR_ENC_DEFAULT)
+		return;
+
+	video->flags |= V4L2_HEVC_SPS_VIDEO_FLAG_VUI_PARAMETERS_PRESENT |
+			V4L2_HEVC_SPS_VIDEO_FLAG_VUI_VIDEO_SIGNAL_TYPE_PRESENT |
+			V4L2_HEVC_SPS_VIDEO_FLAG_VUI_COLOUR_DESCRIPTION_PRESENT;
+	video->video_format = V4L2_HEVC_VUI_VIDEO_FORMAT_UNSPECIFIED;
+
+	if (quantization == V4L2_QUANTIZATION_FULL_RANGE)
+		video->flags |= V4L2_HEVC_SPS_VIDEO_FLAG_VUI_VIDEO_FULL_RANGE;
+
+	switch (colorspace) {
+	case V4L2_COLORSPACE_SMPTE170M:
+		video->colour_primaries = V4L2_HEVC_VUI_COLOUR_SMPTE170M;
+		break;
+	case V4L2_COLORSPACE_SMPTE240M:
+		video->colour_primaries = V4L2_HEVC_VUI_COLOUR_SMPTE240M;
+		break;
+	case V4L2_COLORSPACE_REC709:
+	case V4L2_COLORSPACE_SRGB:
+	case V4L2_COLORSPACE_JPEG:
+		video->colour_primaries = V4L2_HEVC_VUI_COLOUR_BT709;
+		break;
+	case V4L2_COLORSPACE_470_SYSTEM_M:
+		video->colour_primaries = V4L2_HEVC_VUI_COLOUR_BT470_SYSTEM_M;
+		break;
+	case V4L2_COLORSPACE_470_SYSTEM_BG:
+		video->colour_primaries = V4L2_HEVC_VUI_COLOUR_BT470_SYSTEM_BG;
+		break;
+	case V4L2_COLORSPACE_BT2020:
+		video->colour_primaries = V4L2_HEVC_VUI_COLOUR_BT2020;
+		break;
+	case V4L2_COLORSPACE_DCI_P3:
+		video->colour_primaries = V4L2_HEVC_VUI_COLOUR_SMPTE431;
+		break;
+	default:
+		video->colour_primaries = V4L2_HEVC_VUI_COLOUR_UNSPECIFIED;
+		break;
+	}
+
+	switch (xfer_func) {
+	case V4L2_XFER_FUNC_709:
+		if (colorspace == V4L2_COLORSPACE_SMPTE170M)
+			video->transfer_characteristics =
+				V4L2_HEVC_VUI_TRANSFER_SMPTE170M;
+		else
+			video->transfer_characteristics =
+				V4L2_HEVC_VUI_TRANSFER_BT709;
+		break;
+	case V4L2_XFER_FUNC_SRGB:
+		video->transfer_characteristics = V4L2_HEVC_VUI_TRANSFER_SRGB;
+		break;
+	case V4L2_XFER_FUNC_SMPTE240M:
+		video->transfer_characteristics =
+			V4L2_HEVC_VUI_TRANSFER_SMPTE240M;
+		break;
+	case V4L2_XFER_FUNC_NONE:
+		video->transfer_characteristics = V4L2_HEVC_VUI_TRANSFER_LINEAR;
+		break;
+	case V4L2_XFER_FUNC_SMPTE2084:
+		video->transfer_characteristics =
+			V4L2_HEVC_VUI_TRANSFER_SMPTE2084;
+		break;
+	default:
+		video->transfer_characteristics =
+			V4L2_HEVC_VUI_TRANSFER_UNSPECIFIED;
+		break;
+	}
+
+	switch (ycbcr_enc) {
+	case V4L2_YCBCR_ENC_601:
+		if (colorspace == V4L2_COLORSPACE_SMPTE170M)
+			video->matrix_coeffs = V4L2_HEVC_VUI_MATRIX_SMPTE170M;
+		else
+			video->matrix_coeffs =
+				V4L2_HEVC_VUI_MATRIX_BT470_SYSTEM_BG;
+		break;
+	case V4L2_YCBCR_ENC_709:
+		video->matrix_coeffs = V4L2_HEVC_VUI_MATRIX_BT709;
+		break;
+	case V4L2_YCBCR_ENC_SMPTE240M:
+		video->matrix_coeffs = V4L2_HEVC_VUI_MATRIX_SMPTE240M;
+		break;
+	case V4L2_YCBCR_ENC_BT2020:
+		video->matrix_coeffs = V4L2_HEVC_VUI_MATRIX_BT2020_NCL;
+		break;
+	case V4L2_YCBCR_ENC_BT2020_CONST_LUM:
+		video->matrix_coeffs = V4L2_HEVC_VUI_MATRIX_BT2020_CL;
+		break;
+	default:
+		video->matrix_coeffs = V4L2_HEVC_VUI_MATRIX_UNSPECIFIED;
+		break;
+	}
+}
+
+static void prepare_ptl(struct v4l2_hevc_enc *enc,
+			struct v4l2_hevc_enc_state *state)
+{
+	struct v4l2_ctrl_hevc_sps *sps = &state->sps;
+	struct v4l2_hevc_ptl *ptl = &state->sps_video.ptl;
+	struct v4l2_fract *tpf = &state->timeperframe;
+	u64 luma_ps = (u64)sps->pic_width_in_luma_samples *
+		     sps->pic_height_in_luma_samples;
+	u64 luma_sr = 0;
+	unsigned int i;
+	int profile, level;
+
+	profile = enc_ctrl_val(enc, V4L2_CID_MPEG_VIDEO_HEVC_PROFILE,
+			       sps->bit_depth_luma_minus8 ?
+			       V4L2_MPEG_VIDEO_HEVC_PROFILE_MAIN_10 :
+			       V4L2_MPEG_VIDEO_HEVC_PROFILE_MAIN);
+
+	/*
+	 * A Main or Main Still Picture stream also conforms to the profiles
+	 * that contain it (A.3.2, A.3.4), as common encoders signal.
+	 */
+	switch (profile) {
+	case V4L2_MPEG_VIDEO_HEVC_PROFILE_MAIN_STILL_PICTURE:
+		ptl->general_profile_idc = V4L2_HEVC_PROFILE_IDC_MAIN_STILL_PICTURE;
+		ptl->general_profile_compatibility_flags =
+			BIT(31 - V4L2_HEVC_PROFILE_IDC_MAIN) |
+			BIT(31 - V4L2_HEVC_PROFILE_IDC_MAIN_10) |
+			BIT(31 - V4L2_HEVC_PROFILE_IDC_MAIN_STILL_PICTURE);
+		break;
+	case V4L2_MPEG_VIDEO_HEVC_PROFILE_MAIN_10:
+		ptl->general_profile_idc = V4L2_HEVC_PROFILE_IDC_MAIN_10;
+		ptl->general_profile_compatibility_flags =
+			BIT(31 - V4L2_HEVC_PROFILE_IDC_MAIN_10);
+		break;
+	default:
+		ptl->general_profile_idc = V4L2_HEVC_PROFILE_IDC_MAIN;
+		ptl->general_profile_compatibility_flags =
+			BIT(31 - V4L2_HEVC_PROFILE_IDC_MAIN) |
+			BIT(31 - V4L2_HEVC_PROFILE_IDC_MAIN_10);
+		break;
+	}
+
+	ptl->general_tier_flag =
+		enc_ctrl_val(enc, V4L2_CID_MPEG_VIDEO_HEVC_TIER,
+			     V4L2_MPEG_VIDEO_HEVC_TIER_MAIN) ==
+		V4L2_MPEG_VIDEO_HEVC_TIER_HIGH;
+	ptl->flags = V4L2_HEVC_PTL_FLAG_PROGRESSIVE_SOURCE |
+		     V4L2_HEVC_PTL_FLAG_FRAME_ONLY_CONSTRAINT;
+
+	level = enc_ctrl_val(enc, V4L2_CID_MPEG_VIDEO_HEVC_LEVEL, -1);
+	if (level >= 0 && level < ARRAY_SIZE(hevc_level_idc)) {
+		ptl->general_level_idc = hevc_level_idc[level];
+		return;
+	}
+
+	/* Without a level control, pick the lowest level that fits. */
+	if (tpf->numerator)
+		luma_sr = div_u64(luma_ps * tpf->denominator, tpf->numerator);
+
+	for (i = 0; i < ARRAY_SIZE(hevc_levels); i++) {
+		u64 max_dim_sq = 8ULL * hevc_levels[i].max_luma_ps;
+
+		if (luma_ps <= hevc_levels[i].max_luma_ps &&
+		    luma_sr <= hevc_levels[i].max_luma_sr &&
+		    (u64)sps->pic_width_in_luma_samples *
+		    sps->pic_width_in_luma_samples <= max_dim_sq &&
+		    (u64)sps->pic_height_in_luma_samples *
+		    sps->pic_height_in_luma_samples <= max_dim_sq)
+			break;
+	}
+
+	ptl->general_level_idc =
+		hevc_levels[min_t(unsigned int, i,
+				  ARRAY_SIZE(hevc_levels) - 1)].level_idc;
+}
+
+static int prepare_sps(struct v4l2_hevc_enc *enc,
+		       struct v4l2_hevc_enc_state *state)
+{
+	struct v4l2_ctrl_hevc_sps *sps = &state->sps;
+	struct v4l2_hevc_sps_video *video = &state->sps_video;
+	unsigned int width, height, colorspace, quantization, xfer, ycbcr;
+	unsigned int min_cb_size, coded_width, coded_height;
+	struct v4l2_rect visible;
+	struct v4l2_ctrl *ctrl;
+
+	ctrl = enc_ctrl(enc, V4L2_CID_STATELESS_HEVC_SPS);
+	if (!ctrl)
+		return -EINVAL;
+
+	memcpy(sps, ctrl->p_cur.p_hevc_sps, sizeof(*sps));
+
+	enc_format(enc, &width, &height, &colorspace, &quantization, &xfer,
+		   &ycbcr);
+
+	/* Only 4:2:0 and a single set of each parameter set are supported. */
+	sps->chroma_format_idc = 1;
+	sps->video_parameter_set_id = min_t(u8, sps->video_parameter_set_id, 15);
+	sps->seq_parameter_set_id = min_t(u8, sps->seq_parameter_set_id, 15);
+	sps->flags &= ~V4L2_HEVC_SPS_FLAG_SEPARATE_COLOUR_PLANE;
+
+	/* Reference picture sets are always coded in the slice header. */
+	sps->num_short_term_ref_pic_sets = 0;
+	sps->num_long_term_ref_pics_sps = 0;
+
+	if (!(enc->flags & V4L2_HEVC_ENC_FLAG_SCALING_LIST))
+		sps->flags &= ~V4L2_HEVC_SPS_FLAG_SCALING_LIST_ENABLED;
+
+	if (!(enc->flags & (V4L2_HEVC_ENC_FLAG_INTER_PRED |
+			    V4L2_HEVC_ENC_FLAG_INTER_BIPRED))) {
+		sps->sps_max_dec_pic_buffering_minus1 = 0;
+		sps->flags &= ~(V4L2_HEVC_SPS_FLAG_SPS_TEMPORAL_MVP_ENABLED |
+				V4L2_HEVC_SPS_FLAG_LONG_TERM_REF_PICS_PRESENT);
+	}
+
+	sps->sps_max_sub_layers_minus1 =
+		min_t(u8, sps->sps_max_sub_layers_minus1,
+		      V4L2_HEVC_MAX_SUB_LAYERS - 1);
+	sps->sps_max_dec_pic_buffering_minus1 =
+		min_t(u8, sps->sps_max_dec_pic_buffering_minus1,
+		      V4L2_HEVC_DPB_ENTRIES_NUM_MAX - 1);
+	sps->sps_max_num_reorder_pics =
+		min(sps->sps_max_num_reorder_pics,
+		    sps->sps_max_dec_pic_buffering_minus1);
+	sps->log2_max_pic_order_cnt_lsb_minus4 =
+		min_t(u8, sps->log2_max_pic_order_cnt_lsb_minus4, 12);
+
+	/* 16x16 to 64x64 coding tree blocks (A.4.1), 8x8 minimum CBs. */
+	sps->log2_min_luma_coding_block_size_minus3 =
+		min_t(u8, sps->log2_min_luma_coding_block_size_minus3, 3);
+	state->ctb_log2_size = sps->log2_min_luma_coding_block_size_minus3 + 3 +
+			       sps->log2_diff_max_min_luma_coding_block_size;
+	state->ctb_log2_size = clamp(state->ctb_log2_size, 4U, 6U);
+	sps->log2_diff_max_min_luma_coding_block_size =
+		state->ctb_log2_size - 3 -
+		sps->log2_min_luma_coding_block_size_minus3;
+
+	/* The coded size is the CAPTURE size, aligned to the minimum CB. */
+	min_cb_size = 8 << sps->log2_min_luma_coding_block_size_minus3;
+	coded_width = ALIGN(width, min_cb_size);
+	coded_height = ALIGN(height, min_cb_size);
+	sps->pic_width_in_luma_samples = coded_width;
+	sps->pic_height_in_luma_samples = coded_height;
+
+	state->width_ctbs = DIV_ROUND_UP(coded_width, 1 << state->ctb_log2_size);
+	state->height_ctbs = DIV_ROUND_UP(coded_height,
+					  1 << state->ctb_log2_size);
+
+	memset(video, 0, sizeof(*video));
+
+	/* The conformance window is in chroma samples for 4:2:0 (7.4.3.2.1). */
+	if (enc->crop)
+		visible = *enc->crop;
+	else
+		visible = (struct v4l2_rect){ 0, 0, width, height };
+
+	if (visible.left < 0 || visible.top < 0 || visible.left & 1 ||
+	    visible.top & 1 || visible.width & 1 || visible.height & 1 ||
+	    visible.left + visible.width > coded_width ||
+	    visible.top + visible.height > coded_height)
+		return -EINVAL;
+
+	if (visible.left || visible.top || visible.width != coded_width ||
+	    visible.height != coded_height) {
+		video->flags |= V4L2_HEVC_SPS_VIDEO_FLAG_CONFORMANCE_WINDOW;
+		video->conf_win_left_offset = visible.left / 2;
+		video->conf_win_top_offset = visible.top / 2;
+		video->conf_win_right_offset =
+			(coded_width - visible.left - visible.width) / 2;
+		video->conf_win_bottom_offset =
+			(coded_height - visible.top - visible.height) / 2;
+	}
+
+	/* Required with a single sub-layer (7.4.3.2.1). */
+	if (!sps->sps_max_sub_layers_minus1)
+		video->flags |= V4L2_HEVC_SPS_VIDEO_FLAG_TEMPORAL_ID_NESTING;
+
+	if (state->timeperframe.numerator && state->timeperframe.denominator) {
+		video->flags |= V4L2_HEVC_SPS_VIDEO_FLAG_VPS_TIMING_INFO_PRESENT |
+				V4L2_HEVC_SPS_VIDEO_FLAG_VUI_PARAMETERS_PRESENT |
+				V4L2_HEVC_SPS_VIDEO_FLAG_VUI_TIMING_INFO_PRESENT;
+		video->num_units_in_tick = state->timeperframe.numerator;
+		video->time_scale = state->timeperframe.denominator;
+	}
+
+	prepare_colour(video, colorspace, quantization, xfer, ycbcr);
+	prepare_ptl(enc, state);
+
+	return 0;
+}
+
+static int prepare_pps(struct v4l2_hevc_enc *enc,
+		       struct v4l2_hevc_enc_state *state)
+{
+	struct v4l2_ctrl_hevc_pps *pps = &state->pps;
+	const struct v4l2_ctrl_hevc_sps *sps = &state->sps;
+	int qp_bd_offset = 6 * sps->bit_depth_luma_minus8;
+	struct v4l2_ctrl *ctrl;
+	unsigned int i, sum;
+
+	ctrl = enc_ctrl(enc, V4L2_CID_STATELESS_HEVC_PPS);
+	if (!ctrl)
+		return -EINVAL;
+
+	memcpy(pps, ctrl->p_cur.p_hevc_pps, sizeof(*pps));
+
+	pps->pic_parameter_set_id = min_t(u8, pps->pic_parameter_set_id, 63);
+	pps->init_qp_minus26 = clamp_t(s8, pps->init_qp_minus26,
+				       -(26 + qp_bd_offset), 25);
+	pps->num_ref_idx_l0_default_active_minus1 =
+		min_t(u8, pps->num_ref_idx_l0_default_active_minus1,
+		      enc->max_l0_refs - 1);
+	pps->num_ref_idx_l1_default_active_minus1 =
+		min_t(u8, pps->num_ref_idx_l1_default_active_minus1,
+		      enc->max_l1_refs - 1);
+	pps->diff_cu_qp_delta_depth =
+		min_t(u8, pps->diff_cu_qp_delta_depth,
+		      sps->log2_diff_max_min_luma_coding_block_size);
+
+	/* Weighted prediction tables are not supported. */
+	pps->flags &= ~(V4L2_HEVC_PPS_FLAG_WEIGHTED_PRED |
+			V4L2_HEVC_PPS_FLAG_WEIGHTED_BIPRED);
+
+	/*
+	 * Entry points are only known after the encode run, so a slice
+	 * header written by the kernel cannot have any.
+	 */
+	if (!(enc->flags & V4L2_HEVC_ENC_FLAG_TILES) ||
+	    !(enc->flags & V4L2_HEVC_ENC_FLAG_HW_SLICE_HEADER))
+		pps->flags &= ~V4L2_HEVC_PPS_FLAG_TILES_ENABLED;
+	if (!(enc->flags & V4L2_HEVC_ENC_FLAG_ENTROPY_CODING_SYNC) ||
+	    !(enc->flags & V4L2_HEVC_ENC_FLAG_HW_SLICE_HEADER))
+		pps->flags &= ~V4L2_HEVC_PPS_FLAG_ENTROPY_CODING_SYNC_ENABLED;
+
+	if (!(pps->flags & V4L2_HEVC_PPS_FLAG_TILES_ENABLED)) {
+		pps->num_tile_columns_minus1 = 0;
+		pps->num_tile_rows_minus1 = 0;
+		pps->flags &= ~(V4L2_HEVC_PPS_FLAG_UNIFORM_SPACING |
+				V4L2_HEVC_PPS_FLAG_LOOP_FILTER_ACROSS_TILES_ENABLED);
+	} else {
+		if (pps->num_tile_columns_minus1 >= state->width_ctbs ||
+		    pps->num_tile_rows_minus1 >= state->height_ctbs ||
+		    (!pps->num_tile_columns_minus1 &&
+		     !pps->num_tile_rows_minus1))
+			return -EINVAL;
+
+		if (!(pps->flags & V4L2_HEVC_PPS_FLAG_UNIFORM_SPACING)) {
+			for (i = 0, sum = 0; i < pps->num_tile_columns_minus1; i++)
+				sum += pps->column_width_minus1[i] + 1;
+			if (sum >= state->width_ctbs)
+				return -EINVAL;
+
+			for (i = 0, sum = 0; i < pps->num_tile_rows_minus1; i++)
+				sum += pps->row_height_minus1[i] + 1;
+			if (sum >= state->height_ctbs)
+				return -EINVAL;
+		}
+	}
+
+	return 0;
+}
+
+static int prepare_encode(struct v4l2_hevc_enc *enc,
+			  struct v4l2_hevc_enc_state *state)
+{
+	struct v4l2_ctrl_hevc_encode_params *encode = &state->encode;
+	const struct v4l2_ctrl_hevc_sps *sps = &state->sps;
+	struct v4l2_ctrl *ctrl;
+
+	ctrl = enc_ctrl(enc, V4L2_CID_STATELESS_HEVC_ENCODE_PARAMS);
+	if (!ctrl)
+		return -EINVAL;
+
+	memcpy(encode, ctrl->p_cur.p_hevc_encode_params, sizeof(*encode));
+
+	/* The bitstream starts with an IRAP picture. */
+	if (!enc->state_serial && !v4l2_hevc_nal_is_irap(encode->nal_unit_type))
+		return -EINVAL;
+
+	if (encode->nuh_temporal_id_plus1 - 1 > sps->sps_max_sub_layers_minus1)
+		return -EINVAL;
+
+	if (encode->slice_type == V4L2_HEVC_SLICE_TYPE_B &&
+	    !(enc->flags & V4L2_HEVC_ENC_FLAG_INTER_BIPRED))
+		return -EINVAL;
+
+	/* Parameters for a P picture are also valid for an I picture. */
+	if (encode->slice_type == V4L2_HEVC_SLICE_TYPE_P &&
+	    !(enc->flags & V4L2_HEVC_ENC_FLAG_INTER_PRED))
+		encode->slice_type = V4L2_HEVC_SLICE_TYPE_I;
+
+	if (encode->slice_type == V4L2_HEVC_SLICE_TYPE_I)
+		encode->flags &=
+			~(V4L2_HEVC_ENCODE_FLAG_NUM_REF_IDX_ACTIVE_OVERRIDE |
+			  V4L2_HEVC_ENCODE_FLAG_SLICE_TEMPORAL_MVP_ENABLED);
+
+	encode->pic_parameter_set_id = state->pps.pic_parameter_set_id;
+
+	if (!(sps->flags & V4L2_HEVC_SPS_FLAG_SPS_TEMPORAL_MVP_ENABLED))
+		encode->flags &= ~V4L2_HEVC_ENCODE_FLAG_SLICE_TEMPORAL_MVP_ENABLED;
+
+	if (!(sps->flags & V4L2_HEVC_SPS_FLAG_SAMPLE_ADAPTIVE_OFFSET))
+		encode->flags &= ~(V4L2_HEVC_ENCODE_FLAG_SLICE_SAO_LUMA |
+				   V4L2_HEVC_ENCODE_FLAG_SLICE_SAO_CHROMA);
+
+	if (!(state->pps.flags &
+	      V4L2_HEVC_PPS_FLAG_DEBLOCKING_FILTER_OVERRIDE_ENABLED))
+		encode->flags &=
+			~(V4L2_HEVC_ENCODE_FLAG_DEBLOCKING_FILTER_OVERRIDE |
+			  V4L2_HEVC_ENCODE_FLAG_SLICE_DEBLOCKING_FILTER_DISABLED);
+
+	if (!(state->pps.flags &
+	      V4L2_HEVC_PPS_FLAG_PPS_SLICE_CHROMA_QP_OFFSETS_PRESENT)) {
+		encode->slice_cb_qp_offset = 0;
+		encode->slice_cr_qp_offset = 0;
+	}
+
+	return 0;
+}
+
+static void prepare_rc(struct v4l2_hevc_enc *enc,
+		       struct v4l2_hevc_enc_state *state)
+{
+	int qp_bd_offset = 6 * state->sps.bit_depth_luma_minus8;
+	struct v4l2_ctrl *ctrl;
+
+	state->frame_rc_enable =
+		enc_ctrl_val(enc, V4L2_CID_MPEG_VIDEO_FRAME_RC_ENABLE, 0);
+	state->qp_min = enc_ctrl_val(enc, V4L2_CID_MPEG_VIDEO_HEVC_MIN_QP,
+				     -qp_bd_offset);
+	state->qp_max = enc_ctrl_val(enc, V4L2_CID_MPEG_VIDEO_HEVC_MAX_QP, 51);
+	state->qp_i = enc_ctrl_val(enc, V4L2_CID_MPEG_VIDEO_HEVC_I_FRAME_QP, 30);
+	state->qp_p = enc_ctrl_val(enc, V4L2_CID_MPEG_VIDEO_HEVC_P_FRAME_QP,
+				   state->qp_i);
+	state->qp_b = enc_ctrl_val(enc, V4L2_CID_MPEG_VIDEO_HEVC_B_FRAME_QP,
+				   state->qp_p);
+
+	state->bitrate_mode = enc_ctrl_val(enc, V4L2_CID_MPEG_VIDEO_BITRATE_MODE,
+					   V4L2_MPEG_VIDEO_BITRATE_MODE_CQ);
+
+	ctrl = enc_ctrl(enc, V4L2_CID_MPEG_VIDEO_CONSTANT_QUALITY);
+	if (ctrl) {
+		state->quality = ctrl->cur.val;
+		state->quality_min = ctrl->minimum;
+		state->quality_max = ctrl->maximum;
+	}
+
+	state->bitrate = enc_ctrl_val(enc, V4L2_CID_MPEG_VIDEO_BITRATE, 0);
+	state->bitrate_peak = enc_ctrl_val(enc, V4L2_CID_MPEG_VIDEO_BITRATE_PEAK,
+					   state->bitrate);
+}
+
+static int state_prepare(struct v4l2_hevc_enc *enc)
+{
+	struct v4l2_hevc_enc_state *state = &enc->state_next;
+	struct v4l2_hevc_enc_state *active = &enc->state_active;
+	int ret;
+
+	memset(state, 0, sizeof(*state));
+	state->timeperframe = *enc->timeperframe;
+
+	ret = prepare_sps(enc, state);
+	if (ret)
+		return ret;
+
+	ret = prepare_pps(enc, state);
+	if (ret)
+		return ret;
+
+	ret = prepare_encode(enc, state);
+	if (ret)
+		return ret;
+
+	prepare_rc(enc, state);
+
+	if (enc->ops && enc->ops->state_constrain) {
+		ret = enc->ops->state_constrain(enc, state);
+		if (ret)
+			return ret;
+	}
+
+	/*
+	 * A new SPS can only be activated by an IRAP picture (7.4.2.4.2):
+	 * keep the active one until then.
+	 */
+	if (enc->state_serial &&
+	    !v4l2_hevc_nal_is_irap(state->encode.nal_unit_type) &&
+	    (memcmp(&state->sps, &active->sps, sizeof(state->sps)) ||
+	     memcmp(&state->sps_video, &active->sps_video,
+		    sizeof(state->sps_video)))) {
+		state->sps = active->sps;
+		state->sps_video = active->sps_video;
+		state->ctb_log2_size = active->ctb_log2_size;
+		state->width_ctbs = active->width_ctbs;
+		state->height_ctbs = active->height_ctbs;
+	}
+
+	return 0;
+}
+
+static int state_commit(struct v4l2_hevc_enc *enc)
+{
+	struct v4l2_hevc_enc_state *state = &enc->state_next;
+	struct v4l2_ctrl *ctrl;
+	int ret;
+
+	/* Report the parameter sets that are actually used. */
+	ctrl = enc_ctrl(enc, V4L2_CID_STATELESS_HEVC_SPS);
+	ret = v4l2_ctrl_s_ctrl_compound(ctrl, V4L2_CTRL_TYPE_HEVC_SPS,
+					&state->sps);
+	if (ret)
+		return ret;
+
+	ctrl = enc_ctrl(enc, V4L2_CID_STATELESS_HEVC_PPS);
+	ret = v4l2_ctrl_s_ctrl_compound(ctrl, V4L2_CTRL_TYPE_HEVC_PPS,
+					&state->pps);
+	if (ret)
+		return ret;
+
+	return 0;
+}
+
+static unsigned int rbsp_update(struct v4l2_hevc_enc *enc)
+{
+	struct v4l2_hevc_enc_state *state = &enc->state_next;
+	struct v4l2_hevc_enc_state *active = &enc->state_active;
+	unsigned int update = 0;
+
+	if (enc_ctrl_val(enc, V4L2_CID_MPEG_VIDEO_AU_DELIMITER, 0))
+		update |= RBSP_UPDATE_AUD;
+
+	if (!enc->state_serial ||
+	    memcmp(&state->sps, &active->sps, sizeof(state->sps)) ||
+	    memcmp(&state->sps_video, &active->sps_video,
+		   sizeof(state->sps_video)) ||
+	    memcmp(&state->pps, &active->pps, sizeof(state->pps)))
+		update |= RBSP_UPDATE_PARAMETER_SETS;
+
+	if (v4l2_hevc_nal_is_irap(state->encode.nal_unit_type) &&
+	    enc_ctrl_val(enc, V4L2_CID_MPEG_VIDEO_PREPEND_SPSPPS_TO_IDR, 0))
+		update |= RBSP_UPDATE_PARAMETER_SETS;
+
+	return update;
+}
+
+struct ref_entry {
+	unsigned int rps_index;
+	unsigned int dpb_index;
+	s32 delta;
+	bool used;
+};
+
+static int ref_entry_cmp(const void *a, const void *b)
+{
+	const struct ref_entry *ra = a, *rb = b;
+
+	/* Closest pictures first (7.4.8). */
+	if (abs(ra->delta) != abs(rb->delta))
+		return abs(ra->delta) < abs(rb->delta) ? -1 : 1;
+
+	return 0;
+}
+
+static int ref_entry_lt_cmp(const void *a, const void *b)
+{
+	const struct ref_entry *ra = a, *rb = b;
+
+	/* By increasing DeltaPocMsbCycleLt, kept in delta. */
+	if (ra->delta != rb->delta)
+		return ra->delta < rb->delta ? -1 : 1;
+
+	return 0;
+}
+
+static int ref_find(struct v4l2_hevc_enc *enc, u64 timestamp)
+{
+	struct v4l2_hevc_enc_ref *ref = &enc->ref;
+	unsigned int i;
+
+	for (i = 0; i < ref->slots_count; i++)
+		if (ref->dpb[i].active && ref->dpb[i].timestamp == timestamp)
+			return i;
+
+	return -ENOENT;
+}
+
+/*
+ * Build reference picture list 0 or 1 (8.3.4) from the current RPS, without
+ * modification: the temporary list repeats StCurrBefore, StCurrAfter and
+ * LtCurr (StCurrAfter first for list 1) until it has the active number of
+ * entries.
+ */
+static unsigned int ref_list_build(u8 *list, unsigned int count,
+				   const u8 *first, unsigned int first_count,
+				   const u8 *second, unsigned int second_count,
+				   const u8 *lt, unsigned int lt_count)
+{
+	unsigned int total = first_count + second_count + lt_count;
+	unsigned int i, j;
+
+	if (!total)
+		return 0;
+
+	for (i = 0; i < count;) {
+		for (j = 0; j < first_count && i < count; j++)
+			list[i++] = first[j];
+		for (j = 0; j < second_count && i < count; j++)
+			list[i++] = second[j];
+		for (j = 0; j < lt_count && i < count; j++)
+			list[i++] = lt[j];
+	}
+
+	return count;
+}
+
+static u8 ref_rps_index(const struct v4l2_ctrl_hevc_encode_params *encode,
+			const struct v4l2_hevc_enc_ref *ref,
+			unsigned int dpb_index)
+{
+	unsigned int i;
+
+	for (i = 0; i < encode->num_refs; i++)
+		if (ref->rps_dpb[i] == dpb_index)
+			return i;
+
+	return U8_MAX;
+}
+
+static int ref_step(struct v4l2_hevc_enc *enc)
+{
+	struct v4l2_hevc_enc_state *state = &enc->state_active;
+	struct v4l2_ctrl_hevc_encode_params *encode = &state->encode;
+	const struct v4l2_ctrl_hevc_sps *sps = &state->sps;
+	const struct v4l2_ctrl_hevc_pps *pps = &state->pps;
+	struct v4l2_hevc_enc_ref *ref = &enc->ref;
+	struct v4l2_hevc_slice_header *sh = &enc->slice;
+	struct v4l2_ctrl_hevc_encode_feedback *fb = &enc->feedback;
+	struct ref_entry neg[V4L2_HEVC_DPB_ENTRIES_NUM_MAX];
+	struct ref_entry pos[V4L2_HEVC_DPB_ENTRIES_NUM_MAX];
+	struct ref_entry lt[V4L2_HEVC_DPB_ENTRIES_NUM_MAX];
+	u8 curr_before[V4L2_HEVC_DPB_ENTRIES_NUM_MAX];
+	u8 curr_after[V4L2_HEVC_DPB_ENTRIES_NUM_MAX];
+	u8 curr_lt[V4L2_HEVC_DPB_ENTRIES_NUM_MAX];
+	unsigned int num_neg = 0, num_pos = 0, num_lt = 0;
+	unsigned int num_before = 0, num_after = 0, num_curr_lt = 0;
+	u32 max_poc_lsb = BIT(sps->log2_max_pic_order_cnt_lsb_minus4 + 4);
+	s32 poc = encode->pic_order_cnt_val;
+	s32 poc_msb = poc - (s32)(poc & (max_poc_lsb - 1));
+	unsigned int l0_count, l1_count, total, i;
+	int ret;
+
+	memset(sh, 0, sizeof(*sh));
+	ref->l0_count = 0;
+	ref->l1_count = 0;
+	ref->collocated = -1;
+
+	/* The current picture and the pictures it keeps (A.4.2). */
+	if (enc->flags & (V4L2_HEVC_ENC_FLAG_INTER_PRED |
+			  V4L2_HEVC_ENC_FLAG_INTER_BIPRED)) {
+		ret = ref_slots_grow(enc,
+				     sps->sps_max_dec_pic_buffering_minus1 + 1);
+		if (ret)
+			return ret;
+	}
+
+	/* BLA and IDR pictures empty the DPB, so their POC MSB is 0. */
+	if (v4l2_hevc_nal_is_bla(encode->nal_unit_type) ||
+	    v4l2_hevc_nal_is_idr(encode->nal_unit_type)) {
+		if (poc < 0 || poc >= max_poc_lsb)
+			return -EINVAL;
+		if (v4l2_hevc_nal_is_bla(encode->nal_unit_type))
+			encode->num_refs = 0;
+	} else if (!enc->state_serial) {
+		/* A CRA picture starting the bitstream (8.3.1). */
+		if (poc < 0 || poc >= max_poc_lsb)
+			return -EINVAL;
+	} else if (abs(poc - ref->prev_tid0_poc) >= max_poc_lsb / 2) {
+		/* The decoder could not derive the POC MSB (8.3.1). */
+		return -EINVAL;
+	}
+
+	if (encode->num_refs > sps->sps_max_dec_pic_buffering_minus1)
+		return -EINVAL;
+
+	for (i = 0; i < encode->num_refs; i++) {
+		const struct v4l2_hevc_encode_ref *r = &encode->refs[i];
+		bool used = r->flags & V4L2_HEVC_ENCODE_REF_FLAG_USED_BY_CURR_PIC;
+		struct v4l2_hevc_enc_dpb_entry *entry;
+		struct ref_entry e;
+		int index;
+
+		index = ref_find(enc, r->timestamp);
+		if (index < 0)
+			return -EINVAL;
+
+		entry = &ref->dpb[index];
+		ref->rps_dpb[i] = index;
+
+		e.rps_index = i;
+		e.dpb_index = index;
+		e.used = used;
+		e.delta = entry->pic_order_cnt_val - poc;
+
+		if (!e.delta)
+			return -EINVAL;
+
+		if (r->flags & V4L2_HEVC_ENCODE_REF_FLAG_LONG_TERM) {
+			s32 ref_msb = entry->pic_order_cnt_val -
+				      (s32)(entry->pic_order_cnt_val &
+					    (max_poc_lsb - 1));
+
+			if (!(sps->flags &
+			      V4L2_HEVC_SPS_FLAG_LONG_TERM_REF_PICS_PRESENT) ||
+			    ref_msb > poc_msb)
+				return -EINVAL;
+
+			e.delta = (poc_msb - ref_msb) / (s32)max_poc_lsb;
+			lt[num_lt++] = e;
+			continue;
+		}
+
+		/* A long-term picture cannot become short-term again. */
+		if (entry->long_term || abs(e.delta) > S16_MAX)
+			return -EINVAL;
+
+		if (e.delta < 0)
+			neg[num_neg++] = e;
+		else
+			pos[num_pos++] = e;
+	}
+
+	if (enc->rps_limits &&
+	    (num_neg > enc->rps_limits->num_negative_pics ||
+	     num_pos > enc->rps_limits->num_positive_pics ||
+	     num_lt > enc->rps_limits->num_long_term_pics))
+		return -EINVAL;
+
+	sort(neg, num_neg, sizeof(*neg), ref_entry_cmp, NULL);
+	sort(pos, num_pos, sizeof(*pos), ref_entry_cmp, NULL);
+	sort(lt, num_lt, sizeof(*lt), ref_entry_lt_cmp, NULL);
+
+	sh->st_rps.num_negative_pics = num_neg;
+	for (i = 0; i < num_neg; i++) {
+		s32 prev = i ? neg[i - 1].delta : 0;
+
+		sh->st_rps.delta_poc_s0_minus1[i] = prev - neg[i].delta - 1;
+		if (neg[i].used) {
+			sh->st_rps.used_by_curr_pic_s0 |= BIT(i);
+			curr_before[num_before++] = neg[i].dpb_index;
+		}
+	}
+
+	sh->st_rps.num_positive_pics = num_pos;
+	for (i = 0; i < num_pos; i++) {
+		s32 prev = i ? pos[i - 1].delta : 0;
+
+		sh->st_rps.delta_poc_s1_minus1[i] = pos[i].delta - prev - 1;
+		if (pos[i].used) {
+			sh->st_rps.used_by_curr_pic_s1 |= BIT(i);
+			curr_after[num_after++] = pos[i].dpb_index;
+		}
+	}
+
+	sh->num_long_term_pics = num_lt;
+	for (i = 0; i < num_lt; i++) {
+		struct v4l2_hevc_lt_pic *lt_pic = &sh->lt[i];
+		s32 ref_poc = ref->dpb[lt[i].dpb_index].pic_order_cnt_val;
+
+		lt_pic->poc_lsb_lt = ref_poc & (max_poc_lsb - 1);
+		lt_pic->used_by_curr_pic_lt = lt[i].used;
+		/* Always signal the MSB, so no lsb can be ambiguous. */
+		lt_pic->delta_poc_msb_present = true;
+		/* DeltaPocMsbCycleLt accumulates over the entries (7-52). */
+		lt_pic->delta_poc_msb_cycle_lt =
+			lt[i].delta - (i ? lt[i - 1].delta : 0);
+		if (lt[i].used)
+			curr_lt[num_curr_lt++] = lt[i].dpb_index;
+	}
+
+	total = num_before + num_after + num_curr_lt;
+
+	/* BLA and CRA pictures have NumPicTotalCurr 0, P/B slices do not. */
+	if ((v4l2_hevc_nal_is_irap(encode->nal_unit_type) && total) ||
+	    (encode->slice_type != V4L2_HEVC_SLICE_TYPE_I && !total))
+		return -EINVAL;
+
+	/* Reference picture lists. */
+
+	if (encode->flags & V4L2_HEVC_ENCODE_FLAG_NUM_REF_IDX_ACTIVE_OVERRIDE) {
+		l0_count = encode->num_ref_idx_l0_active_minus1 + 1;
+		l1_count = encode->num_ref_idx_l1_active_minus1 + 1;
+	} else {
+		l0_count = pps->num_ref_idx_l0_default_active_minus1 + 1;
+		l1_count = pps->num_ref_idx_l1_default_active_minus1 + 1;
+	}
+
+	l0_count = min(l0_count, enc->max_l0_refs);
+	l1_count = min(l1_count, enc->max_l1_refs);
+
+	if (encode->slice_type != V4L2_HEVC_SLICE_TYPE_I)
+		ref->l0_count = ref_list_build(ref->l0, l0_count,
+					       curr_before, num_before,
+					       curr_after, num_after,
+					       curr_lt, num_curr_lt);
+	if (encode->slice_type == V4L2_HEVC_SLICE_TYPE_B)
+		ref->l1_count = ref_list_build(ref->l1, l1_count,
+					       curr_after, num_after,
+					       curr_before, num_before,
+					       curr_lt, num_curr_lt);
+
+	if (ref->l0_count &&
+	    (ref->l0_count != pps->num_ref_idx_l0_default_active_minus1 + 1 ||
+	     (ref->l1_count &&
+	      ref->l1_count != pps->num_ref_idx_l1_default_active_minus1 + 1))) {
+		encode->flags |= V4L2_HEVC_ENCODE_FLAG_NUM_REF_IDX_ACTIVE_OVERRIDE;
+		encode->num_ref_idx_l0_active_minus1 = ref->l0_count - 1;
+		encode->num_ref_idx_l1_active_minus1 =
+			ref->l1_count ? ref->l1_count - 1 : 0;
+	} else {
+		encode->flags &= ~V4L2_HEVC_ENCODE_FLAG_NUM_REF_IDX_ACTIVE_OVERRIDE;
+	}
+
+	if (encode->flags & V4L2_HEVC_ENCODE_FLAG_SLICE_TEMPORAL_MVP_ENABLED) {
+		bool from_l0 = encode->slice_type != V4L2_HEVC_SLICE_TYPE_B ||
+			       encode->flags &
+			       V4L2_HEVC_ENCODE_FLAG_COLLOCATED_FROM_L0;
+		unsigned int count = from_l0 ? ref->l0_count : ref->l1_count;
+
+		if (encode->collocated_ref_idx >= count)
+			encode->collocated_ref_idx = 0;
+
+		ref->collocated = from_l0 ? ref->l0[encode->collocated_ref_idx] :
+					    ref->l1[encode->collocated_ref_idx];
+	}
+
+	/* Feedback: list entries as indices in the encode params refs[]. */
+	fb->num_ref_idx_l0_active = ref->l0_count;
+	fb->num_ref_idx_l1_active = ref->l1_count;
+	for (i = 0; i < ref->l0_count; i++)
+		fb->ref_idx_l0[i] = ref_rps_index(encode, ref, ref->l0[i]);
+	for (i = 0; i < ref->l1_count; i++)
+		fb->ref_idx_l1[i] = ref_rps_index(encode, ref, ref->l1[i]);
+
+	return 0;
+}
+
+static int rc_step(struct v4l2_hevc_enc *enc)
+{
+	struct v4l2_hevc_enc_state *state = &enc->state_active;
+	int qp_bd_offset = 6 * state->sps.bit_depth_luma_minus8;
+	u8 slice_type = state->encode.slice_type;
+	unsigned int range;
+	int qp, ret;
+
+	if (!state->frame_rc_enable) {
+		if (slice_type == V4L2_HEVC_SLICE_TYPE_I)
+			qp = state->qp_i;
+		else if (slice_type == V4L2_HEVC_SLICE_TYPE_P)
+			qp = state->qp_p;
+		else
+			qp = state->qp_b;
+	} else if (state->bitrate_mode == V4L2_MPEG_VIDEO_BITRATE_MODE_CQ) {
+		/* Map the quality range linearly onto the QP range. */
+		range = state->quality_max - state->quality_min;
+		qp = state->qp_max;
+		if (range)
+			qp -= (state->quality - state->quality_min) *
+			      (state->qp_max - state->qp_min) / range;
+	} else {
+		if (!enc->rc_ops || !enc->rc_ops->estimate)
+			return -EOPNOTSUPP;
+
+		ret = enc->rc_ops->estimate(enc, state, &qp);
+		if (ret)
+			return ret;
+	}
+
+	qp = clamp(qp, state->qp_min, state->qp_max);
+	enc->qp = clamp(qp, -qp_bd_offset, 51);
+
+	return 0;
+}
+
+static void slice_step(struct v4l2_hevc_enc *enc)
+{
+	struct v4l2_hevc_enc_state *state = &enc->state_active;
+	const struct v4l2_ctrl_hevc_encode_params *encode = &state->encode;
+	struct v4l2_hevc_slice_header *sh = &enc->slice;
+	u32 max_poc_lsb = BIT(state->sps.log2_max_pic_order_cnt_lsb_minus4 + 4);
+
+	/* The reference picture sets were filled by ref_step(). */
+	sh->nal_unit_type = encode->nal_unit_type;
+	sh->nuh_temporal_id_plus1 = encode->nuh_temporal_id_plus1;
+	sh->slice_type = encode->slice_type;
+	sh->slice_segment_address = 0;
+	sh->slice_pic_order_cnt_lsb = encode->pic_order_cnt_val &
+				      (max_poc_lsb - 1);
+	sh->num_ref_idx_l0_active_minus1 = encode->num_ref_idx_l0_active_minus1;
+	sh->num_ref_idx_l1_active_minus1 = encode->num_ref_idx_l1_active_minus1;
+	sh->collocated_ref_idx = encode->collocated_ref_idx;
+	sh->five_minus_max_num_merge_cand = encode->five_minus_max_num_merge_cand;
+	sh->slice_qp_delta = enc->qp - 26 - state->pps.init_qp_minus26;
+	sh->slice_cb_qp_offset = encode->slice_cb_qp_offset;
+	sh->slice_cr_qp_offset = encode->slice_cr_qp_offset;
+	sh->slice_beta_offset_div2 = encode->slice_beta_offset_div2;
+	sh->slice_tc_offset_div2 = encode->slice_tc_offset_div2;
+	sh->flags = encode->flags;
+}
+
+static int rbsp_step(struct v4l2_hevc_enc *enc, struct vb2_v4l2_buffer *buffer,
+		     unsigned int update)
+{
+	struct v4l2_hevc_enc_state *state = &enc->state_active;
+	struct v4l2_hevc_enc_rbsp *rbsp = &enc->rbsp;
+	u8 tid1 = state->encode.nuh_temporal_id_plus1;
+	unsigned int hw = enc->flags;
+	bool start_code;
+	u8 pic_type;
+	u8 *data;
+
+	enc->header_size = 0;
+
+	if (!(update & RBSP_UPDATE_AUD) || hw & V4L2_HEVC_ENC_FLAG_HW_AUD)
+		update &= ~RBSP_UPDATE_AUD;
+	if ((hw & V4L2_HEVC_ENC_FLAG_HW_VPS) && (hw & V4L2_HEVC_ENC_FLAG_HW_SPS) &&
+	    (hw & V4L2_HEVC_ENC_FLAG_HW_PPS))
+		update &= ~RBSP_UPDATE_PARAMETER_SETS;
+	if (!update && hw & V4L2_HEVC_ENC_FLAG_HW_SLICE_HEADER)
+		return 0;
+
+	data = vb2_plane_vaddr(&buffer->vb2_buf, 0);
+	if (!data)
+		return -EINVAL;
+
+	v4l2_hevc_enc_rbsp_init(rbsp, data,
+				vb2_plane_size(&buffer->vb2_buf, 0));
+
+	start_code = enc_ctrl_val(enc, V4L2_CID_STATELESS_HEVC_START_CODE,
+				  V4L2_STATELESS_HEVC_START_CODE_ANNEX_B) ==
+		     V4L2_STATELESS_HEVC_START_CODE_ANNEX_B;
+
+	if (update & RBSP_UPDATE_AUD) {
+		if (state->encode.slice_type == V4L2_HEVC_SLICE_TYPE_I)
+			pic_type = V4L2_HEVC_AUD_PIC_TYPE_I;
+		else if (state->encode.slice_type == V4L2_HEVC_SLICE_TYPE_P)
+			pic_type = V4L2_HEVC_AUD_PIC_TYPE_PI;
+		else
+			pic_type = V4L2_HEVC_AUD_PIC_TYPE_BPI;
+
+		v4l2_hevc_enc_rbsp_aud(rbsp, start_code, pic_type, tid1);
+	}
+
+	if (update & RBSP_UPDATE_PARAMETER_SETS) {
+		if (!(hw & V4L2_HEVC_ENC_FLAG_HW_VPS))
+			v4l2_hevc_enc_rbsp_vps(rbsp, start_code, &state->sps,
+					       &state->sps_video);
+		if (!(hw & V4L2_HEVC_ENC_FLAG_HW_SPS))
+			v4l2_hevc_enc_rbsp_sps(rbsp, start_code, &state->sps,
+					       &state->sps_video);
+		if (!(hw & V4L2_HEVC_ENC_FLAG_HW_PPS))
+			v4l2_hevc_enc_rbsp_pps(rbsp, start_code, &state->sps,
+					       &state->pps);
+	}
+
+	if (!(hw & V4L2_HEVC_ENC_FLAG_HW_SLICE_HEADER))
+		v4l2_hevc_enc_rbsp_slice_header(rbsp, start_code, &state->sps,
+						&state->pps, &enc->slice);
+
+	enc->header_size = v4l2_hevc_enc_rbsp_bytes(rbsp);
+
+	return v4l2_hevc_enc_rbsp_error(rbsp);
+}
+
+static void state_debug(struct v4l2_hevc_enc *enc)
+{
+	const struct v4l2_hevc_enc_state *state = &enc->state_active;
+	const struct v4l2_ctrl_hevc_encode_params *encode = &state->encode;
+
+	pr_debug("v4l2-hevc-enc: %c nal %u tid %u poc %d qp %d refs %u (l0 %u, l1 %u), %ux%u ctbs\n",
+		 v4l2_hevc_slice_type_char(encode->slice_type),
+		 encode->nal_unit_type, encode->nuh_temporal_id_plus1 - 1,
+		 encode->pic_order_cnt_val, enc->qp, encode->num_refs,
+		 enc->ref.l0_count, enc->ref.l1_count, state->width_ctbs,
+		 state->height_ctbs);
+}
+
+/**
+ * v4l2_hevc_enc_step() - prepare the encode run of a picture
+ * @enc: the core context
+ * @buffer: the CAPTURE buffer, whose timestamp is already the one of the
+ *	    OUTPUT buffer being encoded
+ *
+ * Call it from the m2m device_run callback, after the request controls
+ * were applied. It accommodates and reports the parameters, resolves the
+ * reference picture set, selects the QP and writes the headers at the
+ * start of @buffer. The driver then programs the hardware from
+ * &v4l2_hevc_enc.state_active, &v4l2_hevc_enc.ref (current reconstruction
+ * buffer, reference picture lists and collocated picture),
+ * &v4l2_hevc_enc.slice and &v4l2_hevc_enc.qp, and has it write from
+ * &v4l2_hevc_enc.header_size on. Nothing in the reference state changes
+ * until v4l2_hevc_enc_complete(), so a failed run can simply be dropped.
+ *
+ * Context: process context, takes the control handler lock.
+ * Return: 0 or a negative error code.
+ */
+int v4l2_hevc_enc_step(struct v4l2_hevc_enc *enc,
+		       struct vb2_v4l2_buffer *buffer)
+{
+	unsigned int update;
+	int ret;
+
+	ret = state_prepare(enc);
+	if (ret)
+		return ret;
+
+	update = rbsp_update(enc);
+
+	/* Parameter sets of a run that did not complete are sent again. */
+	if (enc->parameter_sets_pending)
+		update |= RBSP_UPDATE_PARAMETER_SETS;
+
+	ret = state_commit(enc);
+	if (ret)
+		return ret;
+
+	memcpy(&enc->state_active, &enc->state_next, sizeof(enc->state_active));
+
+	if (update & RBSP_UPDATE_PARAMETER_SETS)
+		enc->parameter_sets_pending = true;
+
+	memset(&enc->feedback, 0, sizeof(enc->feedback));
+
+	ret = ref_step(enc);
+	if (ret)
+		return ret;
+
+	ret = rc_step(enc);
+	if (ret)
+		return ret;
+
+	slice_step(enc);
+
+	ret = rbsp_step(enc, buffer, update);
+	if (ret)
+		return ret;
+
+	state_debug(enc);
+
+	return 0;
+}
+EXPORT_SYMBOL_GPL(v4l2_hevc_enc_step);
+
+/*
+ * Set nuh_temporal_id_plus1 in the NAL unit headers the hardware wrote.
+ * The second header byte is never 0, before or after, so this cannot
+ * create or remove a start code emulation.
+ */
+static void patch_temporal_id(u8 *data, unsigned int size, u8 tid1)
+{
+	unsigned int i;
+
+	for (i = 0; i + 4 < size; i++) {
+		if (data[i] || data[i + 1] || data[i + 2] != 1)
+			continue;
+
+		if (((data[i + 3] >> 1) & 0x3f) < V4L2_HEVC_NAL_VPS)
+			data[i + 4] = (data[i + 4] & ~0x7) | tid1;
+
+		i += 4;
+	}
+}
+
+static int ref_complete(struct v4l2_hevc_enc *enc, u64 timestamp)
+{
+	struct v4l2_hevc_enc_state *state = &enc->state_active;
+	const struct v4l2_ctrl_hevc_encode_params *encode = &state->encode;
+	struct v4l2_hevc_enc_ref *ref = &enc->ref;
+	struct v4l2_hevc_enc_dpb_entry *entry;
+	bool keep[V4L2_HEVC_DPB_ENTRIES_NUM_MAX] = { };
+	unsigned int i;
+
+	/* Pictures that are not in the RPS are no longer references. */
+	for (i = 0; i < encode->num_refs; i++) {
+		keep[ref->rps_dpb[i]] = true;
+		if (encode->refs[i].flags & V4L2_HEVC_ENCODE_REF_FLAG_LONG_TERM)
+			ref->dpb[ref->rps_dpb[i]].long_term = true;
+	}
+
+	for (i = 0; i < ref->slots_count; i++)
+		if (!keep[i])
+			ref->dpb[i].active = false;
+
+	/* Intra-only hardware keeps no reference pictures. */
+	if (!(encode->flags & V4L2_HEVC_ENCODE_FLAG_REFERENCE) ||
+	    !ref->slots_count)
+		return 0;
+
+	for (i = 0; i < ref->slots_count; i++)
+		if (!ref->dpb[i].active)
+			break;
+
+	if (WARN_ON(i == ref->slots_count))
+		return -ENOSPC;
+
+	entry = &ref->dpb[i];
+	swap(entry->buffer, ref->buffer_current);
+	entry->timestamp = timestamp;
+	entry->pic_order_cnt_val = encode->pic_order_cnt_val;
+	entry->active = true;
+	entry->long_term = false;
+
+	return 0;
+}
+
+/**
+ * v4l2_hevc_enc_complete() - complete the encode run of a picture
+ * @enc: the core context
+ * @buffer: the CAPTURE buffer, with its payload set
+ * @stats: statistics measured by the hardware, or NULL
+ *
+ * Call it once the hardware finished encoding the picture successfully,
+ * before completing the request. It updates the reference pictures and
+ * the rate control, sets the buffer flags and sets the feedback control so
+ * that it gets stored in the request.
+ *
+ * Context: process context, takes the control handler lock.
+ * Return: 0 or a negative error code.
+ */
+int v4l2_hevc_enc_complete(struct v4l2_hevc_enc *enc,
+			   struct vb2_v4l2_buffer *buffer,
+			   const struct v4l2_stateless_encode_stats *stats)
+{
+	struct v4l2_hevc_enc_state *state = &enc->state_active;
+	const struct v4l2_ctrl_hevc_encode_params *encode = &state->encode;
+	struct v4l2_ctrl_hevc_encode_feedback *fb = &enc->feedback;
+	unsigned long bytesused = vb2_get_plane_payload(&buffer->vb2_buf, 0);
+	u64 timestamp = buffer->vb2_buf.timestamp;
+	struct v4l2_ctrl *ctrl;
+	u8 nal_unit_type = encode->nal_unit_type;
+	u8 *data;
+	int ret;
+
+	if (WARN_ON(bytesused < enc->header_size))
+		return -EINVAL;
+
+	if (encode->nuh_temporal_id_plus1 != 1 &&
+	    enc->flags & V4L2_HEVC_ENC_FLAG_HW_SLICE_HEADER &&
+	    !(enc->flags & V4L2_HEVC_ENC_FLAG_HW_TEMPORAL_ID)) {
+		data = vb2_plane_vaddr(&buffer->vb2_buf, 0);
+		if (!data)
+			return -EINVAL;
+
+		patch_temporal_id(data + enc->header_size,
+				  bytesused - enc->header_size,
+				  encode->nuh_temporal_id_plus1);
+	}
+
+	buffer->flags &= ~(V4L2_BUF_FLAG_KEYFRAME | V4L2_BUF_FLAG_PFRAME |
+			   V4L2_BUF_FLAG_BFRAME);
+	if (encode->slice_type == V4L2_HEVC_SLICE_TYPE_I)
+		buffer->flags |= V4L2_BUF_FLAG_KEYFRAME;
+	else if (encode->slice_type == V4L2_HEVC_SLICE_TYPE_P)
+		buffer->flags |= V4L2_BUF_FLAG_PFRAME;
+	else
+		buffer->flags |= V4L2_BUF_FLAG_BFRAME;
+
+	ret = ref_complete(enc, timestamp);
+	if (ret)
+		return ret;
+
+	/* prevTid0Pic (8.3.1) */
+	if (encode->nuh_temporal_id_plus1 == 1 &&
+	    !v4l2_hevc_nal_is_leading(nal_unit_type) &&
+	    !v4l2_hevc_nal_is_sub_layer_non_ref(nal_unit_type))
+		enc->ref.prev_tid0_poc = encode->pic_order_cnt_val;
+
+	if (enc->rc_ops && enc->rc_ops->complete)
+		enc->rc_ops->complete(enc, state, bytesused, stats);
+
+	fb->timestamp = timestamp;
+	fb->pic_order_cnt_val = encode->pic_order_cnt_val;
+	fb->nal_unit_type = nal_unit_type;
+	fb->nuh_temporal_id_plus1 = encode->nuh_temporal_id_plus1;
+	fb->slice_type = encode->slice_type;
+	fb->slice_qp = enc->qp;
+	if (stats)
+		fb->stats = *stats;
+	fb->stats.header_size = enc->header_size;
+	fb->flags = 0;
+	if (encode->flags & V4L2_HEVC_ENCODE_FLAG_REFERENCE)
+		fb->flags |= V4L2_HEVC_ENCODE_FEEDBACK_FLAG_REFERENCE;
+
+	ctrl = enc_ctrl(enc, V4L2_CID_STATELESS_HEVC_ENCODE_FEEDBACK);
+	if (ctrl) {
+		ret = v4l2_ctrl_s_ctrl_compound(ctrl,
+						V4L2_CTRL_TYPE_HEVC_ENCODE_FEEDBACK,
+						fb);
+		if (ret)
+			return ret;
+	}
+
+	enc->parameter_sets_pending = false;
+	enc->state_serial++;
+
+	return 0;
+}
+EXPORT_SYMBOL_GPL(v4l2_hevc_enc_complete);
+
+MODULE_DESCRIPTION("V4L2 HEVC Encode Core");
+MODULE_LICENSE("GPL");
diff --git a/include/media/v4l2-hevc-enc.h b/include/media/v4l2-hevc-enc.h
new file mode 100644
index 000000000000..4797102e34b3
--- /dev/null
+++ b/include/media/v4l2-hevc-enc.h
@@ -0,0 +1,284 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * V4L2 HEVC Encode Core
+ */
+
+#ifndef _MEDIA_V4L2_HEVC_ENC_H
+#define _MEDIA_V4L2_HEVC_ENC_H
+
+#include <linux/types.h>
+#include <linux/v4l2-controls.h>
+#include <linux/videodev2.h>
+#include <media/v4l2-hevc-enc-rbsp.h>
+
+struct v4l2_ctrl_handler;
+struct vb2_v4l2_buffer;
+
+/* The hardware can encode P slices. */
+#define V4L2_HEVC_ENC_FLAG_INTER_PRED		BIT(0)
+/* The hardware can encode B slices. */
+#define V4L2_HEVC_ENC_FLAG_INTER_BIPRED		BIT(1)
+/* The hardware writes these NAL units or headers itself. */
+#define V4L2_HEVC_ENC_FLAG_HW_AUD		BIT(2)
+#define V4L2_HEVC_ENC_FLAG_HW_VPS		BIT(3)
+#define V4L2_HEVC_ENC_FLAG_HW_SPS		BIT(4)
+#define V4L2_HEVC_ENC_FLAG_HW_PPS		BIT(5)
+#define V4L2_HEVC_ENC_FLAG_HW_SLICE_HEADER	BIT(6)
+/*
+ * The NAL unit headers the hardware writes carry nuh_temporal_id_plus1.
+ * Without it, the core patches them after the encode run.
+ */
+#define V4L2_HEVC_ENC_FLAG_HW_TEMPORAL_ID	BIT(7)
+/* The hardware supports the default scaling lists. */
+#define V4L2_HEVC_ENC_FLAG_SCALING_LIST		BIT(8)
+/* The hardware supports tiles and wavefront parallel processing. */
+#define V4L2_HEVC_ENC_FLAG_TILES		BIT(9)
+#define V4L2_HEVC_ENC_FLAG_ENTROPY_CODING_SYNC	BIT(10)
+
+struct v4l2_hevc_enc;
+
+/**
+ * struct v4l2_hevc_enc_rps_limits - reference picture set size limits
+ *
+ * Hardware that writes slice headers from registers can only express a
+ * limited reference picture set.
+ *
+ * @num_negative_pics: maximum num_negative_pics
+ * @num_positive_pics: maximum num_positive_pics
+ * @num_long_term_pics: maximum num_long_term_pics
+ */
+struct v4l2_hevc_enc_rps_limits {
+	u8 num_negative_pics;
+	u8 num_positive_pics;
+	u8 num_long_term_pics;
+};
+
+/**
+ * struct v4l2_hevc_enc_rec_buffer - reconstruction buffer
+ *
+ * @private_data: driver data for the buffer (e.g. its DMA addresses)
+ * @allocated: whether the driver allocated the buffer
+ */
+struct v4l2_hevc_enc_rec_buffer {
+	void *private_data;
+	bool allocated;
+};
+
+/**
+ * struct v4l2_hevc_enc_dpb_entry - reconstructed picture kept as reference
+ *
+ * @buffer: the reconstruction buffer of the slot
+ * @timestamp: timestamp of the OUTPUT buffer the picture was encoded from
+ * @pic_order_cnt_val: picture order count of the picture
+ * @active: whether the slot holds a reference picture
+ * @long_term: whether the picture is marked as used for long-term reference
+ */
+struct v4l2_hevc_enc_dpb_entry {
+	struct v4l2_hevc_enc_rec_buffer buffer;
+	u64 timestamp;
+	s32 pic_order_cnt_val;
+	bool active;
+	bool long_term;
+};
+
+/**
+ * struct v4l2_hevc_enc_ref - reference picture state
+ *
+ * @buffer_current: reconstruction buffer of the picture being encoded
+ * @dpb: reference picture slots
+ * @slots_count: number of allocated slots in @dpb
+ * @rps_dpb: for each entry of the encode params reference picture set,
+ *	     the index of its slot in @dpb
+ * @l0: reference picture list 0, as indices in @dpb
+ * @l0_count: number of entries in @l0 (num_ref_idx_l0_active_minus1 + 1)
+ * @l1: reference picture list 1, as indices in @dpb
+ * @l1_count: number of entries in @l1
+ * @collocated: index in @dpb of the collocated picture, when temporal
+ *		motion vector prediction is enabled
+ * @prev_tid0_poc: picture order count of the previous TemporalId 0 picture
+ *		   (prevTid0Pic, 8.3.1)
+ */
+struct v4l2_hevc_enc_ref {
+	struct v4l2_hevc_enc_rec_buffer buffer_current;
+	struct v4l2_hevc_enc_dpb_entry dpb[V4L2_HEVC_DPB_ENTRIES_NUM_MAX];
+	unsigned int slots_count;
+
+	u8 rps_dpb[V4L2_HEVC_DPB_ENTRIES_NUM_MAX];
+	u8 l0[V4L2_HEVC_DPB_ENTRIES_NUM_MAX];
+	unsigned int l0_count;
+	u8 l1[V4L2_HEVC_DPB_ENTRIES_NUM_MAX];
+	unsigned int l1_count;
+	int collocated;
+
+	s32 prev_tid0_poc;
+};
+
+/**
+ * struct v4l2_hevc_enc_state - parameters of an encode run
+ *
+ * @sps: the sequence parameter set, after accommodation
+ * @sps_video: the VPS/SPS fields not in @sps
+ * @pps: the picture parameter set, after accommodation
+ * @encode: the encode parameters, after accommodation
+ * @timeperframe: the frame interval
+ * @ctb_log2_size: CtbLog2SizeY
+ * @width_ctbs: PicWidthInCtbsY
+ * @height_ctbs: PicHeightInCtbsY
+ * @frame_rc_enable: V4L2_CID_MPEG_VIDEO_FRAME_RC_ENABLE
+ * @qp_min: V4L2_CID_MPEG_VIDEO_HEVC_MIN_QP
+ * @qp_max: V4L2_CID_MPEG_VIDEO_HEVC_MAX_QP
+ * @qp_i: V4L2_CID_MPEG_VIDEO_HEVC_I_FRAME_QP
+ * @qp_p: V4L2_CID_MPEG_VIDEO_HEVC_P_FRAME_QP
+ * @qp_b: V4L2_CID_MPEG_VIDEO_HEVC_B_FRAME_QP
+ * @bitrate_mode: V4L2_CID_MPEG_VIDEO_BITRATE_MODE
+ * @quality: V4L2_CID_MPEG_VIDEO_CONSTANT_QUALITY
+ * @quality_min: minimum of V4L2_CID_MPEG_VIDEO_CONSTANT_QUALITY
+ * @quality_max: maximum of V4L2_CID_MPEG_VIDEO_CONSTANT_QUALITY
+ * @bitrate: V4L2_CID_MPEG_VIDEO_BITRATE
+ * @bitrate_peak: V4L2_CID_MPEG_VIDEO_BITRATE_PEAK
+ */
+struct v4l2_hevc_enc_state {
+	struct v4l2_ctrl_hevc_sps sps;
+	struct v4l2_hevc_sps_video sps_video;
+	struct v4l2_ctrl_hevc_pps pps;
+	struct v4l2_ctrl_hevc_encode_params encode;
+
+	struct v4l2_fract timeperframe;
+
+	unsigned int ctb_log2_size;
+	unsigned int width_ctbs;
+	unsigned int height_ctbs;
+
+	bool frame_rc_enable;
+	int qp_min;
+	int qp_max;
+	int qp_i;
+	int qp_p;
+	int qp_b;
+
+	int bitrate_mode;
+	unsigned int quality;
+	unsigned int quality_min;
+	unsigned int quality_max;
+	unsigned int bitrate;
+	unsigned int bitrate_peak;
+};
+
+/**
+ * struct v4l2_hevc_enc_ops - driver callbacks
+ *
+ * @state_constrain: adjust the state of the next encode run to what the
+ *		     hardware supports. Called after the core accommodated
+ *		     the controls.
+ * @rec_buffer_alloc: allocate a reconstruction buffer (with whatever side
+ *		      buffers the hardware keeps with a reference picture)
+ * @rec_buffer_free: free a reconstruction buffer
+ */
+struct v4l2_hevc_enc_ops {
+	int (*state_constrain)(struct v4l2_hevc_enc *enc,
+			       struct v4l2_hevc_enc_state *state);
+	int (*rec_buffer_alloc)(struct v4l2_hevc_enc *enc,
+				struct v4l2_hevc_enc_rec_buffer *buffer);
+	void (*rec_buffer_free)(struct v4l2_hevc_enc *enc,
+				struct v4l2_hevc_enc_rec_buffer *buffer);
+};
+
+/**
+ * struct v4l2_hevc_enc_rc_ops - driver rate control
+ *
+ * The core selects the QP itself for the direct QP and constant quality
+ * modes. These callbacks provide the constant and variable bitrate modes.
+ *
+ * @estimate: return the QP of the next picture in @qp
+ * @complete: account for a completed picture
+ */
+struct v4l2_hevc_enc_rc_ops {
+	int (*estimate)(struct v4l2_hevc_enc *enc,
+			const struct v4l2_hevc_enc_state *state, int *qp);
+	void (*complete)(struct v4l2_hevc_enc *enc,
+			 const struct v4l2_hevc_enc_state *state,
+			 unsigned long bytesused,
+			 const struct v4l2_stateless_encode_stats *stats);
+};
+
+/**
+ * struct v4l2_hevc_enc - HEVC stateless encode core context
+ *
+ * Fields up to @flags are set by the driver before v4l2_hevc_enc_init().
+ * The driver reads @state_active, @ref, @slice, @qp and @header_size after
+ * v4l2_hevc_enc_step() to program the hardware.
+ *
+ * @ops: driver callbacks
+ * @rc_ops: driver rate control, optional
+ * @private_data: driver data
+ * @format: CAPTURE format, if single-planar
+ * @format_mplane: CAPTURE format, if multi-planar
+ * @crop: visible rectangle within the coded size, optional
+ * @timeperframe: frame interval
+ * @ctrl_handler: control handler with the stateless HEVC controls
+ * @ref_slots_count_init: number of reference slots to allocate at init,
+ *			  0 for the default
+ * @max_l0_refs: maximum number of entries in reference picture list 0
+ *		 the hardware supports, 0 for 1
+ * @max_l1_refs: maximum number of entries in reference picture list 1
+ *		 the hardware supports, 0 for 1
+ * @rps_limits: reference picture set size limits of the hardware, or NULL
+ * @flags: see V4L2_HEVC_ENC_FLAG_{}
+ * @state_active: state of the current encode run
+ * @state_next: state being prepared
+ * @state_serial: number of pictures encoded since init
+ * @ref: reference picture state
+ * @rbsp: NAL unit writer
+ * @slice: slice segment header of the current picture
+ * @qp: SliceQpY of the current picture
+ * @header_size: number of bytes the core wrote at the start of the
+ *		 CAPTURE buffer. The hardware writes from that offset.
+ * @parameter_sets_pending: parameter sets were written for a picture that
+ *			    did not complete yet
+ * @feedback: feedback of the current picture
+ */
+struct v4l2_hevc_enc {
+	const struct v4l2_hevc_enc_ops *ops;
+	const struct v4l2_hevc_enc_rc_ops *rc_ops;
+	void *private_data;
+
+	struct v4l2_pix_format *format;
+	struct v4l2_pix_format_mplane *format_mplane;
+	const struct v4l2_rect *crop;
+	struct v4l2_fract *timeperframe;
+	struct v4l2_ctrl_handler *ctrl_handler;
+	unsigned int ref_slots_count_init;
+	unsigned int max_l0_refs;
+	unsigned int max_l1_refs;
+	const struct v4l2_hevc_enc_rps_limits *rps_limits;
+	unsigned int flags;
+
+	struct v4l2_hevc_enc_state state_active;
+	struct v4l2_hevc_enc_state state_next;
+	unsigned int state_serial;
+
+	struct v4l2_hevc_enc_ref ref;
+	struct v4l2_hevc_enc_rbsp rbsp;
+	struct v4l2_hevc_slice_header slice;
+	int qp;
+	unsigned int header_size;
+	bool parameter_sets_pending;
+
+	struct v4l2_ctrl_hevc_encode_feedback feedback;
+};
+
+int v4l2_hevc_enc_init(struct v4l2_hevc_enc *enc);
+void v4l2_hevc_enc_exit(struct v4l2_hevc_enc *enc);
+int v4l2_hevc_enc_step(struct v4l2_hevc_enc *enc,
+		       struct vb2_v4l2_buffer *buffer);
+int v4l2_hevc_enc_complete(struct v4l2_hevc_enc *enc,
+			   struct vb2_v4l2_buffer *buffer,
+			   const struct v4l2_stateless_encode_stats *stats);
+
+static inline struct v4l2_hevc_enc_rec_buffer *
+v4l2_hevc_enc_ref_buffer(struct v4l2_hevc_enc *enc, unsigned int dpb_index)
+{
+	return &enc->ref.dpb[dpb_index].buffer;
+}
+
+#endif

-- 
2.55.0



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

* [PATCH RFC 06/10] media: hevc: Add KUnit tests for the stateless encode core
  2026-11-10 10:07 [PATCH RFC 00/10] media: Add a V4L2 HEVC stateless encode uAPI and helpers Arun Iyengar via B4 Relay
                   ` (4 preceding siblings ...)
  2026-11-10 10:07 ` [PATCH RFC 05/10] media: hevc: Add stateless encode core Arun Iyengar via B4 Relay
@ 2026-11-10 10:07 ` Arun Iyengar via B4 Relay
  2026-11-10 10:07 ` [PATCH RFC 07/10] media: v4l2-hevc-enc: Add a constant bitrate mode Arun Iyengar via B4 Relay
                   ` (3 subsequent siblings)
  9 siblings, 0 replies; 11+ messages in thread
From: Arun Iyengar via B4 Relay @ 2026-11-10 10:07 UTC (permalink / raw)
  To: Mauro Carvalho Chehab
  Cc: linux-media, linux-kernel, Paul Kocialkowski, Nicolas Dufresne,
	Hans Verkuil, Arun Iyengar

From: Arun Iyengar <aiyengar1990@gmail.com>

Run short sequences of pictures through the HEVC stateless encode core,
with a real control handler and a fake CAPTURE buffer, and check:

- an IDR picture followed by P pictures that use, keep without using,
  drop and turn long-term their references: the coded RPS, reference
  picture list 0, collocated picture, DPB contents, QP, buffer flags and
  feedback;
- that invalid references (dropped pictures, long-term pictures used as
  short-term, POC values a decoder could not derive, B slices on P-only
  hardware) fail without changing the DPB;
- CRA and IDR pictures keeping and flushing references;
- hardware that writes its own slice headers without TemporalId: nothing
  is written for pictures without parameter sets, and
  nuh_temporal_id_plus1 is patched after the encode run;
- intra-only hardware, for which the SPS is accommodated and no
  reference slots are allocated;
- the control validation of the encode parameters, including its default
  value.

Signed-off-by: Arun Iyengar <aiyengar1990@gmail.com>
---
 drivers/media/v4l2-core/.kunitconfig          |   1 +
 drivers/media/v4l2-core/Kconfig               |  14 +
 drivers/media/v4l2-core/Makefile              |   1 +
 drivers/media/v4l2-core/v4l2-hevc-enc-kunit.c | 554 ++++++++++++++++++++++++++
 4 files changed, 570 insertions(+)

diff --git a/drivers/media/v4l2-core/.kunitconfig b/drivers/media/v4l2-core/.kunitconfig
index 4ef422b5e0a8..65aa43070053 100644
--- a/drivers/media/v4l2-core/.kunitconfig
+++ b/drivers/media/v4l2-core/.kunitconfig
@@ -3,3 +3,4 @@ CONFIG_MEDIA_SUPPORT=y
 CONFIG_MEDIA_SUPPORT_FILTER=n
 CONFIG_VIDEO_DEV=y
 CONFIG_V4L2_HEVC_ENC_RBSP_KUNIT_TEST=y
+CONFIG_V4L2_HEVC_ENC_KUNIT_TEST=y
diff --git a/drivers/media/v4l2-core/Kconfig b/drivers/media/v4l2-core/Kconfig
index 4c39bd943e81..eb245deb4622 100644
--- a/drivers/media/v4l2-core/Kconfig
+++ b/drivers/media/v4l2-core/Kconfig
@@ -75,6 +75,20 @@ config V4L2_HEVC_ENC_RBSP_KUNIT_TEST
 
 	  If unsure, say N.
 
+config V4L2_HEVC_ENC_KUNIT_TEST
+	tristate "KUnit tests for the V4L2 HEVC encode core" if !KUNIT_ALL_TESTS
+	depends on KUNIT && VIDEO_DEV
+	select V4L2_HEVC_ENC
+	select VIDEOBUF2_CORE
+	default KUNIT_ALL_TESTS
+	help
+	  Build the KUnit tests of the stateless HEVC encode core. They run
+	  short sequences of pictures through it and check the reference
+	  picture sets, reference picture lists, decoded picture buffer and
+	  feedback that result, and the validation of the encode parameters.
+
+	  If unsure, say N.
+
 # Used by drivers that need v4l2-vp9.ko
 config V4L2_VP9
 	tristate
diff --git a/drivers/media/v4l2-core/Makefile b/drivers/media/v4l2-core/Makefile
index 153b8a6e74b8..d57740d71fe1 100644
--- a/drivers/media/v4l2-core/Makefile
+++ b/drivers/media/v4l2-core/Makefile
@@ -35,6 +35,7 @@ obj-$(CONFIG_V4L2_H264_ENC) += v4l2-h264-enc.o v4l2-h264-enc-rbsp.o \
 obj-$(CONFIG_V4L2_HEVC_ENC) += v4l2-hevc-enc.o
 obj-$(CONFIG_V4L2_HEVC_ENC_RBSP) += v4l2-hevc-enc-rbsp.o
 obj-$(CONFIG_V4L2_HEVC_ENC_RBSP_KUNIT_TEST) += v4l2-hevc-enc-rbsp-kunit.o
+obj-$(CONFIG_V4L2_HEVC_ENC_KUNIT_TEST) += v4l2-hevc-enc-kunit.o
 obj-$(CONFIG_V4L2_ISP) += v4l2-isp.o
 obj-$(CONFIG_V4L2_JPEG_HELPER) += v4l2-jpeg.o
 obj-$(CONFIG_V4L2_MEM2MEM_DEV) += v4l2-mem2mem.o
diff --git a/drivers/media/v4l2-core/v4l2-hevc-enc-kunit.c b/drivers/media/v4l2-core/v4l2-hevc-enc-kunit.c
new file mode 100644
index 000000000000..15c65663d46c
--- /dev/null
+++ b/drivers/media/v4l2-core/v4l2-hevc-enc-kunit.c
@@ -0,0 +1,554 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * KUnit tests for the V4L2 HEVC encode core
+ *
+ * Runs short sequences of pictures through v4l2_hevc_enc_step() and
+ * v4l2_hevc_enc_complete() with a real control handler and a fake CAPTURE
+ * buffer, and checks the reference picture set, reference lists, decoded
+ * picture buffer, headers and feedback that result.
+ */
+
+#include <kunit/test.h>
+#include <linux/module.h>
+#include <linux/v4l2-controls.h>
+#include <media/v4l2-ctrls.h>
+#include <media/v4l2-hevc.h>
+#include <media/v4l2-hevc-enc.h>
+#include <media/videobuf2-v4l2.h>
+
+#define BUF_SIZE	4096
+
+struct hevc_enc_test {
+	struct v4l2_ctrl_handler hdl;
+	struct v4l2_hevc_enc enc;
+	struct v4l2_pix_format fmt;
+	struct v4l2_fract tpf;
+	struct vb2_queue queue;
+	struct vb2_v4l2_buffer buf;
+	u8 *data;
+	unsigned int rec_allocated;
+};
+
+static void *fake_vaddr(struct vb2_buffer *vb, void *buf_priv)
+{
+	return buf_priv;
+}
+
+static const struct vb2_mem_ops fake_mem_ops = {
+	.vaddr = fake_vaddr,
+};
+
+static int fake_rec_alloc(struct v4l2_hevc_enc *enc,
+			  struct v4l2_hevc_enc_rec_buffer *buffer)
+{
+	struct hevc_enc_test *t = enc->private_data;
+
+	t->rec_allocated++;
+	buffer->private_data = (void *)(uintptr_t)t->rec_allocated;
+
+	return 0;
+}
+
+static void fake_rec_free(struct v4l2_hevc_enc *enc,
+			  struct v4l2_hevc_enc_rec_buffer *buffer)
+{
+	struct hevc_enc_test *t = enc->private_data;
+
+	t->rec_allocated--;
+}
+
+static const struct v4l2_hevc_enc_ops fake_ops = {
+	.rec_buffer_alloc = fake_rec_alloc,
+	.rec_buffer_free = fake_rec_free,
+};
+
+static const u32 compound_ctrls[] = {
+	V4L2_CID_STATELESS_HEVC_SPS,
+	V4L2_CID_STATELESS_HEVC_PPS,
+	V4L2_CID_STATELESS_HEVC_ENCODE_PARAMS,
+	V4L2_CID_STATELESS_HEVC_ENCODE_FEEDBACK,
+};
+
+static const struct v4l2_ctrl_hevc_sps test_sps = {
+	.pic_width_in_luma_samples = 1280,
+	.pic_height_in_luma_samples = 720,
+	.log2_max_pic_order_cnt_lsb_minus4 = 4,
+	.sps_max_dec_pic_buffering_minus1 = 2,
+	.log2_diff_max_min_luma_coding_block_size = 3,
+	.log2_diff_max_min_luma_transform_block_size = 3,
+	.chroma_format_idc = 1,
+	.sps_max_sub_layers_minus1 = 1,
+	.flags = V4L2_HEVC_SPS_FLAG_SPS_TEMPORAL_MVP_ENABLED |
+		 V4L2_HEVC_SPS_FLAG_LONG_TERM_REF_PICS_PRESENT,
+};
+
+static struct hevc_enc_test *hevc_enc_test_init(struct kunit *test,
+						unsigned int flags)
+{
+	struct hevc_enc_test *t;
+	unsigned int i;
+	int ret;
+
+	t = kunit_kzalloc(test, sizeof(*t), GFP_KERNEL);
+	KUNIT_ASSERT_NOT_ERR_OR_NULL(test, t);
+	t->data = kunit_kzalloc(test, BUF_SIZE, GFP_KERNEL);
+	KUNIT_ASSERT_NOT_ERR_OR_NULL(test, t->data);
+
+	v4l2_ctrl_handler_init(&t->hdl, 8);
+	for (i = 0; i < ARRAY_SIZE(compound_ctrls); i++) {
+		struct v4l2_ctrl_config cfg = { .id = compound_ctrls[i] };
+
+		v4l2_ctrl_new_custom(&t->hdl, &cfg, NULL);
+	}
+	v4l2_ctrl_new_std(&t->hdl, NULL, V4L2_CID_MPEG_VIDEO_HEVC_I_FRAME_QP,
+			  0, 51, 1, 30);
+	v4l2_ctrl_new_std(&t->hdl, NULL, V4L2_CID_MPEG_VIDEO_HEVC_P_FRAME_QP,
+			  0, 51, 1, 33);
+	KUNIT_ASSERT_EQ(test, t->hdl.error, 0);
+
+	ret = v4l2_ctrl_s_ctrl_compound(v4l2_ctrl_find(&t->hdl,
+						       V4L2_CID_STATELESS_HEVC_SPS),
+					V4L2_CTRL_TYPE_HEVC_SPS, &test_sps);
+	KUNIT_ASSERT_EQ(test, ret, 0);
+
+	t->fmt.width = 1280;
+	t->fmt.height = 720;
+	t->tpf = (struct v4l2_fract){ 1, 30 };
+
+	t->queue.mem_ops = &fake_mem_ops;
+	t->buf.vb2_buf.vb2_queue = &t->queue;
+	t->buf.vb2_buf.num_planes = 1;
+	t->buf.vb2_buf.planes[0].mem_priv = t->data;
+	t->buf.vb2_buf.planes[0].length = BUF_SIZE;
+
+	t->enc.ops = &fake_ops;
+	t->enc.private_data = t;
+	t->enc.format = &t->fmt;
+	t->enc.timeperframe = &t->tpf;
+	t->enc.ctrl_handler = &t->hdl;
+	t->enc.flags = flags;
+
+	ret = v4l2_hevc_enc_init(&t->enc);
+	KUNIT_ASSERT_EQ(test, ret, 0);
+
+	return t;
+}
+
+static void hevc_enc_test_exit(struct hevc_enc_test *t)
+{
+	v4l2_hevc_enc_exit(&t->enc);
+	v4l2_ctrl_handler_free(&t->hdl);
+}
+
+static int hevc_enc_test_set(struct hevc_enc_test *t,
+			     const struct v4l2_ctrl_hevc_encode_params *p)
+{
+	return v4l2_ctrl_s_ctrl_compound(v4l2_ctrl_find(&t->hdl,
+							V4L2_CID_STATELESS_HEVC_ENCODE_PARAMS),
+					 V4L2_CTRL_TYPE_HEVC_ENCODE_PARAMS, p);
+}
+
+/* Encode one picture, with the hardware "writing" @hw_size bytes. */
+static int hevc_enc_test_frame(struct kunit *test, struct hevc_enc_test *t,
+			       const struct v4l2_ctrl_hevc_encode_params *p,
+			       u64 timestamp, unsigned int hw_size)
+{
+	int ret;
+
+	ret = hevc_enc_test_set(t, p);
+	KUNIT_ASSERT_EQ(test, ret, 0);
+
+	memset(t->data, 0, BUF_SIZE);
+	t->buf.vb2_buf.timestamp = timestamp;
+
+	ret = v4l2_hevc_enc_step(&t->enc, &t->buf);
+	if (ret)
+		return ret;
+
+	vb2_set_plane_payload(&t->buf.vb2_buf, 0,
+			      t->enc.header_size + hw_size);
+
+	return v4l2_hevc_enc_complete(&t->enc, &t->buf, NULL);
+}
+
+static const struct v4l2_ctrl_hevc_encode_feedback *
+hevc_enc_test_feedback(struct hevc_enc_test *t)
+{
+	return v4l2_ctrl_find(&t->hdl, V4L2_CID_STATELESS_HEVC_ENCODE_FEEDBACK)
+		->p_cur.p_hevc_encode_feedback;
+}
+
+static unsigned int hevc_enc_test_active(struct hevc_enc_test *t)
+{
+	unsigned int i, count = 0;
+
+	for (i = 0; i < t->enc.ref.slots_count; i++)
+		count += t->enc.ref.dpb[i].active;
+
+	return count;
+}
+
+static int hevc_enc_test_slot(struct hevc_enc_test *t, u64 timestamp)
+{
+	unsigned int i;
+
+	for (i = 0; i < t->enc.ref.slots_count; i++)
+		if (t->enc.ref.dpb[i].active &&
+		    t->enc.ref.dpb[i].timestamp == timestamp)
+			return i;
+
+	return -1;
+}
+
+/* Like VEPU580 slice header registers, without long-term pictures. */
+static const struct v4l2_hevc_enc_rps_limits no_lt_limits = {
+	.num_negative_pics = 4,
+};
+
+#define REF(ts, f)	{ .timestamp = (ts), .flags = (f) }
+#define USED		V4L2_HEVC_ENCODE_REF_FLAG_USED_BY_CURR_PIC
+#define LT		V4L2_HEVC_ENCODE_REF_FLAG_LONG_TERM
+
+/* IDR, then P pictures that keep, drop and turn references long-term. */
+static void hevc_enc_gop(struct kunit *test)
+{
+	struct hevc_enc_test *t =
+		hevc_enc_test_init(test, V4L2_HEVC_ENC_FLAG_INTER_PRED);
+	const struct v4l2_ctrl_hevc_encode_feedback *fb;
+	struct v4l2_hevc_slice_header *sh = &t->enc.slice;
+	struct v4l2_ctrl_hevc_encode_params p = {
+		.nal_unit_type = V4L2_HEVC_NAL_IDR_W_RADL,
+		.nuh_temporal_id_plus1 = 1,
+		.slice_type = V4L2_HEVC_SLICE_TYPE_I,
+		.flags = V4L2_HEVC_ENCODE_FLAG_REFERENCE,
+	};
+	static const u8 vps_start[] = { 0, 0, 0, 1, 0x40, 0x01 };
+	int ret;
+
+	/* POC 0: IDR, with the parameter sets. */
+	ret = hevc_enc_test_frame(test, t, &p, 1000, 100);
+	KUNIT_ASSERT_EQ(test, ret, 0);
+	KUNIT_EXPECT_GT(test, t->enc.header_size, 40);
+	KUNIT_EXPECT_MEMEQ(test, t->data, vps_start, sizeof(vps_start));
+	KUNIT_EXPECT_EQ(test, t->enc.qp, 30);
+	KUNIT_EXPECT_TRUE(test, t->buf.flags & V4L2_BUF_FLAG_KEYFRAME);
+	KUNIT_EXPECT_EQ(test, hevc_enc_test_active(t), 1);
+	fb = hevc_enc_test_feedback(t);
+	KUNIT_EXPECT_EQ(test, fb->timestamp, 1000);
+	KUNIT_EXPECT_EQ(test, fb->slice_qp, 30);
+	KUNIT_EXPECT_EQ(test, fb->stats.header_size, t->enc.header_size);
+	KUNIT_EXPECT_TRUE(test,
+			  fb->flags & V4L2_HEVC_ENCODE_FEEDBACK_FLAG_REFERENCE);
+
+	/* The current picture and the 3 DPB slots of the SPS. */
+	KUNIT_EXPECT_EQ(test, t->rec_allocated, 4);
+
+	/* POC 1: P referencing POC 0. Only a slice header is written. */
+	p.nal_unit_type = V4L2_HEVC_NAL_TRAIL_R;
+	p.slice_type = V4L2_HEVC_SLICE_TYPE_P;
+	p.pic_order_cnt_val = 1;
+	p.num_refs = 1;
+	p.refs[0] = (struct v4l2_hevc_encode_ref)REF(1000, USED);
+	p.flags |= V4L2_HEVC_ENCODE_FLAG_SLICE_TEMPORAL_MVP_ENABLED;
+	ret = hevc_enc_test_frame(test, t, &p, 2000, 100);
+	KUNIT_ASSERT_EQ(test, ret, 0);
+	KUNIT_EXPECT_EQ(test, t->enc.qp, 33);
+	KUNIT_EXPECT_EQ(test, t->data[4] >> 1, V4L2_HEVC_NAL_TRAIL_R);
+	KUNIT_EXPECT_LT(test, t->enc.header_size, 16);
+	KUNIT_EXPECT_EQ(test, sh->st_rps.num_negative_pics, 1);
+	KUNIT_EXPECT_EQ(test, sh->st_rps.delta_poc_s0_minus1[0], 0);
+	KUNIT_EXPECT_EQ(test, sh->st_rps.used_by_curr_pic_s0, 0x1);
+	KUNIT_EXPECT_EQ(test, sh->slice_pic_order_cnt_lsb, 1);
+	KUNIT_EXPECT_EQ(test, sh->slice_qp_delta, 33 - 26);
+	KUNIT_EXPECT_EQ(test, t->enc.ref.l0_count, 1);
+	KUNIT_EXPECT_EQ(test, t->enc.ref.collocated, t->enc.ref.l0[0]);
+	KUNIT_EXPECT_TRUE(test, t->buf.flags & V4L2_BUF_FLAG_PFRAME);
+	fb = hevc_enc_test_feedback(t);
+	KUNIT_EXPECT_EQ(test, fb->num_ref_idx_l0_active, 1);
+	KUNIT_EXPECT_EQ(test, fb->ref_idx_l0[0], 0);
+	KUNIT_EXPECT_EQ(test, hevc_enc_test_active(t), 2);
+
+	/*
+	 * POC 2: keep POC 0 without using it, use POC 1. The closest
+	 * picture comes first in the RPS whatever the order of refs[].
+	 */
+	p.pic_order_cnt_val = 2;
+	p.num_refs = 2;
+	p.refs[0] = (struct v4l2_hevc_encode_ref)REF(1000, 0);
+	p.refs[1] = (struct v4l2_hevc_encode_ref)REF(2000, USED);
+	ret = hevc_enc_test_frame(test, t, &p, 3000, 100);
+	KUNIT_ASSERT_EQ(test, ret, 0);
+	KUNIT_EXPECT_EQ(test, sh->st_rps.num_negative_pics, 2);
+	KUNIT_EXPECT_EQ(test, sh->st_rps.delta_poc_s0_minus1[0], 0);
+	KUNIT_EXPECT_EQ(test, sh->st_rps.delta_poc_s0_minus1[1], 0);
+	KUNIT_EXPECT_EQ(test, sh->st_rps.used_by_curr_pic_s0, 0x1);
+	KUNIT_EXPECT_EQ(test, t->enc.ref.l0[0], hevc_enc_test_slot(t, 2000));
+	KUNIT_EXPECT_EQ(test, hevc_enc_test_feedback(t)->ref_idx_l0[0], 1);
+	KUNIT_EXPECT_EQ(test, hevc_enc_test_active(t), 3);
+
+	/* POC 3: non-reference, uses POC 2, drops POC 1, POC 0 long-term. */
+	p.nal_unit_type = V4L2_HEVC_NAL_TRAIL_N;
+	p.pic_order_cnt_val = 3;
+	p.refs[0] = (struct v4l2_hevc_encode_ref)REF(3000, USED);
+	p.refs[1] = (struct v4l2_hevc_encode_ref)REF(1000, LT);
+	p.flags &= ~V4L2_HEVC_ENCODE_FLAG_REFERENCE;
+	ret = hevc_enc_test_frame(test, t, &p, 4000, 100);
+	KUNIT_ASSERT_EQ(test, ret, 0);
+	KUNIT_EXPECT_EQ(test, sh->st_rps.num_negative_pics, 1);
+	KUNIT_EXPECT_EQ(test, sh->num_long_term_pics, 1);
+	KUNIT_EXPECT_EQ(test, sh->lt[0].poc_lsb_lt, 0);
+	KUNIT_EXPECT_FALSE(test, sh->lt[0].used_by_curr_pic_lt);
+	KUNIT_EXPECT_EQ(test, sh->lt[0].delta_poc_msb_cycle_lt, 0);
+	KUNIT_EXPECT_EQ(test, hevc_enc_test_active(t), 2);
+	KUNIT_EXPECT_EQ(test, hevc_enc_test_slot(t, 2000), -1);
+	KUNIT_EXPECT_TRUE(test,
+			  t->enc.ref.dpb[hevc_enc_test_slot(t, 1000)].long_term);
+	KUNIT_EXPECT_FALSE(test, hevc_enc_test_feedback(t)->flags &
+			   V4L2_HEVC_ENCODE_FEEDBACK_FLAG_REFERENCE);
+
+	/* A long-term picture cannot be used as short-term again. */
+	p.pic_order_cnt_val = 4;
+	p.num_refs = 2;
+	p.refs[0] = (struct v4l2_hevc_encode_ref)REF(3000, USED);
+	p.refs[1] = (struct v4l2_hevc_encode_ref)REF(1000, 0);
+	KUNIT_EXPECT_EQ(test, hevc_enc_test_frame(test, t, &p, 5000, 100),
+			-EINVAL);
+
+	/* A picture that is not in the DPB (dropped, or never encoded). */
+	p.refs[1] = (struct v4l2_hevc_encode_ref)REF(2000, 0);
+	KUNIT_EXPECT_EQ(test, hevc_enc_test_frame(test, t, &p, 5000, 100),
+			-EINVAL);
+
+	/* A POC too far from the previous TemporalId 0 picture. */
+	p.num_refs = 1;
+	p.pic_order_cnt_val = 2 + 128;
+	KUNIT_EXPECT_EQ(test, hevc_enc_test_frame(test, t, &p, 5000, 100),
+			-EINVAL);
+
+	/* More references than the hardware can signal. */
+	p.pic_order_cnt_val = 4;
+	p.num_refs = 2;
+	p.refs[1] = (struct v4l2_hevc_encode_ref)REF(1000, LT);
+	t->enc.rps_limits = &no_lt_limits;
+	KUNIT_EXPECT_EQ(test, hevc_enc_test_frame(test, t, &p, 5000, 100),
+			-EINVAL);
+	t->enc.rps_limits = NULL;
+	p.num_refs = 1;
+
+	/* Failed runs left the DPB alone. */
+	KUNIT_EXPECT_EQ(test, hevc_enc_test_active(t), 2);
+
+	/* No B slices without INTER_BIPRED. */
+	p.pic_order_cnt_val = 4;
+	p.slice_type = V4L2_HEVC_SLICE_TYPE_B;
+	KUNIT_EXPECT_EQ(test, hevc_enc_test_frame(test, t, &p, 5000, 100),
+			-EINVAL);
+
+	/* A CRA keeps references for leading pictures but uses none. */
+	p.nal_unit_type = V4L2_HEVC_NAL_CRA_NUT;
+	p.slice_type = V4L2_HEVC_SLICE_TYPE_I;
+	p.pic_order_cnt_val = 8;
+	p.refs[0] = (struct v4l2_hevc_encode_ref)REF(3000, 0);
+	p.flags |= V4L2_HEVC_ENCODE_FLAG_REFERENCE;
+	ret = hevc_enc_test_frame(test, t, &p, 9000, 100);
+	KUNIT_ASSERT_EQ(test, ret, 0);
+	KUNIT_EXPECT_EQ(test, sh->st_rps.num_negative_pics, 1);
+	KUNIT_EXPECT_EQ(test, sh->st_rps.delta_poc_s0_minus1[0], 5);
+	KUNIT_EXPECT_EQ(test, sh->st_rps.used_by_curr_pic_s0, 0);
+	KUNIT_EXPECT_EQ(test, hevc_enc_test_active(t), 2);
+
+	/* An IDR empties the DPB. */
+	p.nal_unit_type = V4L2_HEVC_NAL_IDR_N_LP;
+	ret = hevc_enc_test_frame(test, t, &p, 10000, 100);
+	KUNIT_ASSERT_EQ(test, ret, 0);
+	KUNIT_EXPECT_EQ(test, hevc_enc_test_active(t), 1);
+	KUNIT_EXPECT_GE(test, hevc_enc_test_slot(t, 10000), 0);
+
+	hevc_enc_test_exit(t);
+	KUNIT_EXPECT_EQ(test, t->rec_allocated, 0);
+}
+
+/*
+ * Hardware that writes its own slice headers without TemporalId: the core
+ * patches nuh_temporal_id_plus1 and writes nothing for pictures without
+ * parameter sets.
+ */
+static void hevc_enc_hw_slice_header(struct kunit *test)
+{
+	struct hevc_enc_test *t =
+		hevc_enc_test_init(test, V4L2_HEVC_ENC_FLAG_INTER_PRED |
+				   V4L2_HEVC_ENC_FLAG_HW_SLICE_HEADER);
+	struct v4l2_ctrl_hevc_encode_params p = {
+		.nal_unit_type = V4L2_HEVC_NAL_IDR_W_RADL,
+		.nuh_temporal_id_plus1 = 1,
+		.slice_type = V4L2_HEVC_SLICE_TYPE_I,
+		.flags = V4L2_HEVC_ENCODE_FLAG_REFERENCE,
+	};
+	static const u8 hw_slice[] = { 0, 0, 0, 1, 0x04, 0x01, 0xd0, 0x00,
+				       0x00, 0x01, 0x04, 0x01, 0x80 };
+	static const u8 patched[] = { 0, 0, 0, 1, 0x04, 0x02, 0xd0, 0x00,
+				      0x00, 0x01, 0x04, 0x02, 0x80 };
+	unsigned int header_size;
+	int ret;
+
+	ret = hevc_enc_test_frame(test, t, &p, 1000, 0);
+	KUNIT_ASSERT_EQ(test, ret, 0);
+	header_size = t->enc.header_size;
+	KUNIT_EXPECT_GT(test, header_size, 0);
+
+	/* Parameter sets only: the buffer ends with the PPS trailing bits. */
+	KUNIT_EXPECT_NE(test, t->data[header_size - 1], 0);
+
+	p.nal_unit_type = V4L2_HEVC_NAL_TSA_N;
+	p.nuh_temporal_id_plus1 = 2;
+	p.slice_type = V4L2_HEVC_SLICE_TYPE_P;
+	p.pic_order_cnt_val = 1;
+	p.num_refs = 1;
+	p.refs[0] = (struct v4l2_hevc_encode_ref)REF(1000, USED);
+	p.flags = 0;
+	ret = hevc_enc_test_set(t, &p);
+	KUNIT_ASSERT_EQ(test, ret, 0);
+
+	t->buf.vb2_buf.timestamp = 2000;
+	ret = v4l2_hevc_enc_step(&t->enc, &t->buf);
+	KUNIT_ASSERT_EQ(test, ret, 0);
+	KUNIT_EXPECT_EQ(test, t->enc.header_size, 0);
+
+	memcpy(t->data, hw_slice, sizeof(hw_slice));
+	vb2_set_plane_payload(&t->buf.vb2_buf, 0, sizeof(hw_slice));
+	ret = v4l2_hevc_enc_complete(&t->enc, &t->buf, NULL);
+	KUNIT_ASSERT_EQ(test, ret, 0);
+	KUNIT_EXPECT_MEMEQ(test, t->data, patched, sizeof(patched));
+
+	hevc_enc_test_exit(t);
+}
+
+/* Intra-only hardware turns P pictures into I pictures and keeps no DPB. */
+static void hevc_enc_intra_only(struct kunit *test)
+{
+	struct hevc_enc_test *t = hevc_enc_test_init(test, 0);
+	struct v4l2_ctrl_hevc_encode_params p = {
+		.nal_unit_type = V4L2_HEVC_NAL_IDR_W_RADL,
+		.nuh_temporal_id_plus1 = 1,
+		.slice_type = V4L2_HEVC_SLICE_TYPE_I,
+		.flags = V4L2_HEVC_ENCODE_FLAG_REFERENCE,
+	};
+	int ret;
+
+	ret = hevc_enc_test_frame(test, t, &p, 1000, 10);
+	KUNIT_ASSERT_EQ(test, ret, 0);
+	KUNIT_EXPECT_EQ(test, t->rec_allocated, 1);
+	KUNIT_EXPECT_EQ(test, t->enc.ref.slots_count, 0);
+
+	/* The SPS was accommodated: no DPB, no TMVP. */
+	KUNIT_EXPECT_EQ(test,
+			v4l2_ctrl_find(&t->hdl, V4L2_CID_STATELESS_HEVC_SPS)
+			->p_cur.p_hevc_sps->sps_max_dec_pic_buffering_minus1,
+			0);
+
+	p.nal_unit_type = V4L2_HEVC_NAL_TRAIL_R;
+	p.slice_type = V4L2_HEVC_SLICE_TYPE_P;
+	p.pic_order_cnt_val = 1;
+	ret = hevc_enc_test_set(t, &p);
+	/* A P picture needs a used reference... */
+	KUNIT_EXPECT_EQ(test, ret, -EINVAL);
+
+	p.slice_type = V4L2_HEVC_SLICE_TYPE_I;
+	ret = hevc_enc_test_frame(test, t, &p, 2000, 10);
+	KUNIT_ASSERT_EQ(test, ret, 0);
+	KUNIT_EXPECT_EQ(test, t->enc.slice.slice_type, V4L2_HEVC_SLICE_TYPE_I);
+	KUNIT_EXPECT_EQ(test, t->enc.slice.st_rps.num_negative_pics, 0);
+
+	hevc_enc_test_exit(t);
+}
+
+/* Control validation of the encode parameters. */
+static void hevc_enc_validate(struct kunit *test)
+{
+	struct hevc_enc_test *t =
+		hevc_enc_test_init(test, V4L2_HEVC_ENC_FLAG_INTER_PRED);
+	const struct v4l2_ctrl_hevc_encode_params *cur =
+		v4l2_ctrl_find(&t->hdl, V4L2_CID_STATELESS_HEVC_ENCODE_PARAMS)
+		->p_cur.p_hevc_encode_params;
+	struct v4l2_ctrl_hevc_encode_params p = {
+		.nal_unit_type = V4L2_HEVC_NAL_TRAIL_R,
+		.nuh_temporal_id_plus1 = 1,
+		.slice_type = V4L2_HEVC_SLICE_TYPE_P,
+		.num_refs = 1,
+		.refs = { REF(1, USED) },
+	};
+	struct v4l2_ctrl_hevc_encode_params bad;
+
+	/* The default value is a valid IDR picture. */
+	KUNIT_EXPECT_EQ(test, cur->nal_unit_type, V4L2_HEVC_NAL_IDR_W_RADL);
+	KUNIT_EXPECT_EQ(test, cur->nuh_temporal_id_plus1, 1);
+
+	KUNIT_EXPECT_EQ(test, hevc_enc_test_set(t, &p), 0);
+	/* collocated_from_l0_flag is inferred for P slices. */
+	KUNIT_EXPECT_TRUE(test, cur->flags &
+			  V4L2_HEVC_ENCODE_FLAG_COLLOCATED_FROM_L0);
+
+	bad = p;
+	bad.nal_unit_type = V4L2_HEVC_NAL_VPS;
+	KUNIT_EXPECT_EQ(test, hevc_enc_test_set(t, &bad), -EINVAL);
+
+	bad = p;
+	bad.nal_unit_type = V4L2_HEVC_NAL_CRA_NUT;
+	KUNIT_EXPECT_EQ(test, hevc_enc_test_set(t, &bad), -EINVAL);
+
+	bad = p;
+	bad.nal_unit_type = V4L2_HEVC_NAL_TSA_R;
+	KUNIT_EXPECT_EQ(test, hevc_enc_test_set(t, &bad), -EINVAL);
+
+	bad = p;
+	bad.nuh_temporal_id_plus1 = 0;
+	KUNIT_EXPECT_EQ(test, hevc_enc_test_set(t, &bad), -EINVAL);
+
+	bad = p;
+	bad.refs[0].flags = 0;
+	KUNIT_EXPECT_EQ(test, hevc_enc_test_set(t, &bad), -EINVAL);
+
+	bad = p;
+	bad.num_refs = 2;
+	bad.refs[1] = bad.refs[0];
+	KUNIT_EXPECT_EQ(test, hevc_enc_test_set(t, &bad), -EINVAL);
+
+	bad = p;
+	bad.flags = BIT_ULL(40);
+	KUNIT_EXPECT_EQ(test, hevc_enc_test_set(t, &bad), -EINVAL);
+
+	/* An IDR has no RPS: the references and POC are dropped. */
+	bad = p;
+	bad.nal_unit_type = V4L2_HEVC_NAL_IDR_N_LP;
+	bad.slice_type = V4L2_HEVC_SLICE_TYPE_I;
+	bad.refs[0].flags = 0;
+	bad.pic_order_cnt_val = 5;
+	KUNIT_EXPECT_EQ(test, hevc_enc_test_set(t, &bad), 0);
+	KUNIT_EXPECT_EQ(test, cur->num_refs, 0);
+	KUNIT_EXPECT_EQ(test, cur->pic_order_cnt_val, 0);
+	KUNIT_EXPECT_EQ(test, cur->refs[0].timestamp, 0);
+
+	/* The first picture must be an IRAP picture. */
+	KUNIT_EXPECT_EQ(test, hevc_enc_test_frame(test, t, &p, 1, 10),
+			-EINVAL);
+
+	hevc_enc_test_exit(t);
+}
+
+static struct kunit_case hevc_enc_test_cases[] = {
+	KUNIT_CASE(hevc_enc_gop),
+	KUNIT_CASE(hevc_enc_hw_slice_header),
+	KUNIT_CASE(hevc_enc_intra_only),
+	KUNIT_CASE(hevc_enc_validate),
+	{}
+};
+
+static struct kunit_suite hevc_enc_test_suite = {
+	.name = "v4l2-hevc-enc",
+	.test_cases = hevc_enc_test_cases,
+};
+
+kunit_test_suite(hevc_enc_test_suite);
+
+MODULE_DESCRIPTION("KUnit tests for the V4L2 HEVC encode core");
+MODULE_LICENSE("GPL");

-- 
2.55.0



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

* [PATCH RFC 07/10] media: v4l2-hevc-enc: Add a constant bitrate mode
  2026-11-10 10:07 [PATCH RFC 00/10] media: Add a V4L2 HEVC stateless encode uAPI and helpers Arun Iyengar via B4 Relay
                   ` (5 preceding siblings ...)
  2026-11-10 10:07 ` [PATCH RFC 06/10] media: hevc: Add KUnit tests for the " Arun Iyengar via B4 Relay
@ 2026-11-10 10:07 ` Arun Iyengar via B4 Relay
  2026-11-10 10:07 ` [PATCH RFC 08/10] media: v4l2-hevc-enc: Add QP limits for the I pictures Arun Iyengar via B4 Relay
                   ` (2 subsequent siblings)
  9 siblings, 0 replies; 11+ messages in thread
From: Arun Iyengar via B4 Relay @ 2026-11-10 10:07 UTC (permalink / raw)
  To: Mauro Carvalho Chehab
  Cc: linux-media, linux-kernel, Paul Kocialkowski, Nicolas Dufresne,
	Hans Verkuil, Arun Iyengar

From: Arun Iyengar <aiyengar1990@gmail.com>

The HEVC encode core leaves the constant bitrate mode to the drivers, so
each driver needs its own frame rate control.

Provide the mode in the core with the codec independent rate control of
the H.264 encode core (v4l2-enc-cbr): one QP state for the stream, I
pictures 4 QP below the P pictures (the default of the Rockchip MPP
library for HEVC), picture size targets from a GOP plan with the I/P
size ratio and IDR period measured on the stream, and the deviation from
that plan spread over the next pictures. The size target of the current
picture is available to drivers whose hardware also controls the rate
within a picture.

A driver rate control with an estimate callback still replaces the mode.

Signed-off-by: Arun Iyengar <aiyengar1990@gmail.com>
---
 drivers/media/v4l2-core/Kconfig         |  1 +
 drivers/media/v4l2-core/v4l2-hevc-enc.c | 35 +++++++++++++++++++++++++++++++++
 include/media/v4l2-hevc-enc.h           | 11 +++++++++--
 3 files changed, 45 insertions(+), 2 deletions(-)

diff --git a/drivers/media/v4l2-core/Kconfig b/drivers/media/v4l2-core/Kconfig
index eb245deb4622..0de0bad58791 100644
--- a/drivers/media/v4l2-core/Kconfig
+++ b/drivers/media/v4l2-core/Kconfig
@@ -60,6 +60,7 @@ config V4L2_HEVC_ENC_RBSP
 # Used by drivers that need v4l2-hevc-enc.ko
 config V4L2_HEVC_ENC
 	tristate
+	select V4L2_ENC_CBR
 	select V4L2_HEVC_ENC_RBSP
 
 config V4L2_HEVC_ENC_RBSP_KUNIT_TEST
diff --git a/drivers/media/v4l2-core/v4l2-hevc-enc.c b/drivers/media/v4l2-core/v4l2-hevc-enc.c
index e2aa7d2da96c..a167c4af6288 100644
--- a/drivers/media/v4l2-core/v4l2-hevc-enc.c
+++ b/drivers/media/v4l2-core/v4l2-hevc-enc.c
@@ -18,6 +18,7 @@
 #include <linux/sort.h>
 #include <linux/v4l2-controls.h>
 #include <media/v4l2-ctrls.h>
+#include <media/v4l2-enc-cbr.h>
 #include <media/v4l2-hevc.h>
 #include <media/v4l2-hevc-enc.h>
 #include <media/v4l2-hevc-enc-rbsp.h>
@@ -26,6 +27,12 @@
 #define RBSP_UPDATE_AUD			BIT(0)
 #define RBSP_UPDATE_PARAMETER_SETS	BIT(1)
 
+/*
+ * Constant bitrate mode: I pictures 4 QP below the P pictures, the default
+ * of the Rockchip MPP library for HEVC.
+ */
+#define RC_CBR_QP_DELTA_INTRA		4
+
 /* Table A.8: MaxLumaPs and MaxLumaSr (main tier is enough to pick a level) */
 static const struct {
 	u8 level_idc;
@@ -150,6 +157,7 @@ int v4l2_hevc_enc_init(struct v4l2_hevc_enc *enc)
 	memset(&enc->state_next, 0, sizeof(enc->state_next));
 	memset(&enc->ref, 0, sizeof(enc->ref));
 	enc->state_serial = 0;
+	v4l2_enc_cbr_reset(&enc->cbr, RC_CBR_QP_DELTA_INTRA);
 
 	if (!enc->max_l0_refs)
 		enc->max_l0_refs = 1;
@@ -1052,6 +1060,18 @@ static int ref_step(struct v4l2_hevc_enc *enc)
 	return 0;
 }
 
+/*
+ * The core provides the constant bitrate mode unless the driver has its own
+ * rate control.
+ */
+static bool rc_core_cbr(const struct v4l2_hevc_enc *enc,
+			const struct v4l2_hevc_enc_state *state)
+{
+	return state->frame_rc_enable &&
+	       state->bitrate_mode == V4L2_MPEG_VIDEO_BITRATE_MODE_CBR &&
+	       !(enc->rc_ops && enc->rc_ops->estimate);
+}
+
 static int rc_step(struct v4l2_hevc_enc *enc)
 {
 	struct v4l2_hevc_enc_state *state = &enc->state_active;
@@ -1074,6 +1094,17 @@ static int rc_step(struct v4l2_hevc_enc *enc)
 		if (range)
 			qp -= (state->quality - state->quality_min) *
 			      (state->qp_max - state->qp_min) / range;
+	} else if (rc_core_cbr(enc, state)) {
+		ret = v4l2_enc_cbr_begin(&enc->cbr, state->bitrate,
+					 &state->timeperframe,
+					 state->sps.pic_width_in_luma_samples *
+					 state->sps.pic_height_in_luma_samples,
+					 slice_type == V4L2_HEVC_SLICE_TYPE_I,
+					 state->qp_min, state->qp_max);
+		if (ret)
+			return ret;
+
+		qp = v4l2_enc_cbr_qp(&enc->cbr);
 	} else {
 		if (!enc->rc_ops || !enc->rc_ops->estimate)
 			return -EOPNOTSUPP;
@@ -1382,6 +1413,10 @@ int v4l2_hevc_enc_complete(struct v4l2_hevc_enc *enc,
 	    !v4l2_hevc_nal_is_sub_layer_non_ref(nal_unit_type))
 		enc->ref.prev_tid0_poc = encode->pic_order_cnt_val;
 
+	if (rc_core_cbr(enc, state))
+		v4l2_enc_cbr_end(&enc->cbr, (u64)bytesused * 8,
+				 &state->timeperframe, state->qp_min,
+				 state->qp_max);
 	if (enc->rc_ops && enc->rc_ops->complete)
 		enc->rc_ops->complete(enc, state, bytesused, stats);
 
diff --git a/include/media/v4l2-hevc-enc.h b/include/media/v4l2-hevc-enc.h
index 4797102e34b3..3db131370c56 100644
--- a/include/media/v4l2-hevc-enc.h
+++ b/include/media/v4l2-hevc-enc.h
@@ -9,6 +9,7 @@
 #include <linux/types.h>
 #include <linux/v4l2-controls.h>
 #include <linux/videodev2.h>
+#include <media/v4l2-enc-cbr.h>
 #include <media/v4l2-hevc-enc-rbsp.h>
 
 struct v4l2_ctrl_handler;
@@ -186,8 +187,10 @@ struct v4l2_hevc_enc_ops {
 /**
  * struct v4l2_hevc_enc_rc_ops - driver rate control
  *
- * The core selects the QP itself for the direct QP and constant quality
- * modes. These callbacks provide the constant and variable bitrate modes.
+ * The core selects the QP itself for the direct QP, constant quality and
+ * constant bitrate modes. These callbacks replace the constant bitrate
+ * mode of the core (if @estimate is set) and provide the variable bitrate
+ * mode.
  *
  * @estimate: return the QP of the next picture in @qp
  * @complete: account for a completed picture
@@ -231,6 +234,9 @@ struct v4l2_hevc_enc_rc_ops {
  * @rbsp: NAL unit writer
  * @slice: slice segment header of the current picture
  * @qp: SliceQpY of the current picture
+ * @cbr: constant bitrate rate control of the core. In that mode,
+ *	 @cbr.target_bits is the size target of the current picture in bits,
+ *	 for hardware with a rate control within the picture.
  * @header_size: number of bytes the core wrote at the start of the
  *		 CAPTURE buffer. The hardware writes from that offset.
  * @parameter_sets_pending: parameter sets were written for a picture that
@@ -261,6 +267,7 @@ struct v4l2_hevc_enc {
 	struct v4l2_hevc_enc_rbsp rbsp;
 	struct v4l2_hevc_slice_header slice;
 	int qp;
+	struct v4l2_enc_cbr cbr;
 	unsigned int header_size;
 	bool parameter_sets_pending;
 

-- 
2.55.0



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

* [PATCH RFC 08/10] media: v4l2-hevc-enc: Add QP limits for the I pictures
  2026-11-10 10:07 [PATCH RFC 00/10] media: Add a V4L2 HEVC stateless encode uAPI and helpers Arun Iyengar via B4 Relay
                   ` (6 preceding siblings ...)
  2026-11-10 10:07 ` [PATCH RFC 07/10] media: v4l2-hevc-enc: Add a constant bitrate mode Arun Iyengar via B4 Relay
@ 2026-11-10 10:07 ` Arun Iyengar via B4 Relay
  2026-11-10 10:07 ` [PATCH RFC 09/10] media: v4l2-hevc-enc: Re-encode oversized pictures Arun Iyengar via B4 Relay
  2026-11-10 10:07 ` [PATCH RFC 10/10] media: docs: Document the HEVC stateless encode uAPI Arun Iyengar via B4 Relay
  9 siblings, 0 replies; 11+ messages in thread
From: Arun Iyengar via B4 Relay @ 2026-11-10 10:07 UTC (permalink / raw)
  To: Mauro Carvalho Chehab
  Cc: linux-media, linux-kernel, Paul Kocialkowski, Nicolas Dufresne,
	Hans Verkuil, Arun Iyengar

From: Arun Iyengar <aiyengar1990@gmail.com>

The frame rate control of the core limits all pictures to the range of
V4L2_CID_MPEG_VIDEO_HEVC_MIN_QP and V4L2_CID_MPEG_VIDEO_HEVC_MAX_QP. The
I pictures, coded several QP steps below the P pictures, then reach very
low QP values at high bitrates, where they cost many bits for little
quality.

Take the limits of the I pictures from V4L2_CID_MPEG_VIDEO_HEVC_I_FRAME_MIN_QP
and V4L2_CID_MPEG_VIDEO_HEVC_I_FRAME_MAX_QP when the driver has them, with
a frame rate control. Without these controls, nothing changes.

Signed-off-by: Arun Iyengar <aiyengar1990@gmail.com>
---
 drivers/media/v4l2-core/v4l2-hevc-enc.c | 24 +++++++++++++++++++++---
 include/media/v4l2-hevc-enc.h           |  7 +++++++
 2 files changed, 28 insertions(+), 3 deletions(-)

diff --git a/drivers/media/v4l2-core/v4l2-hevc-enc.c b/drivers/media/v4l2-core/v4l2-hevc-enc.c
index a167c4af6288..2e0ba832b868 100644
--- a/drivers/media/v4l2-core/v4l2-hevc-enc.c
+++ b/drivers/media/v4l2-core/v4l2-hevc-enc.c
@@ -645,6 +645,12 @@ static void prepare_rc(struct v4l2_hevc_enc *enc,
 	state->qp_min = enc_ctrl_val(enc, V4L2_CID_MPEG_VIDEO_HEVC_MIN_QP,
 				     -qp_bd_offset);
 	state->qp_max = enc_ctrl_val(enc, V4L2_CID_MPEG_VIDEO_HEVC_MAX_QP, 51);
+	state->qp_min_i = enc_ctrl_val(enc,
+				       V4L2_CID_MPEG_VIDEO_HEVC_I_FRAME_MIN_QP,
+				       state->qp_min);
+	state->qp_max_i = enc_ctrl_val(enc,
+				       V4L2_CID_MPEG_VIDEO_HEVC_I_FRAME_MAX_QP,
+				       state->qp_max);
 	state->qp_i = enc_ctrl_val(enc, V4L2_CID_MPEG_VIDEO_HEVC_I_FRAME_QP, 30);
 	state->qp_p = enc_ctrl_val(enc, V4L2_CID_MPEG_VIDEO_HEVC_P_FRAME_QP,
 				   state->qp_i);
@@ -1072,10 +1078,23 @@ static bool rc_core_cbr(const struct v4l2_hevc_enc *enc,
 	       !(enc->rc_ops && enc->rc_ops->estimate);
 }
 
+/* QP limits of the picture: the I picture ones with a frame rate control */
+static int rc_clamp_qp(const struct v4l2_hevc_enc_state *state, int qp)
+{
+	int qp_bd_offset = 6 * state->sps.bit_depth_luma_minus8;
+
+	if (state->frame_rc_enable &&
+	    state->encode.slice_type == V4L2_HEVC_SLICE_TYPE_I)
+		qp = clamp(qp, state->qp_min_i, state->qp_max_i);
+	else
+		qp = clamp(qp, state->qp_min, state->qp_max);
+
+	return clamp(qp, -qp_bd_offset, 51);
+}
+
 static int rc_step(struct v4l2_hevc_enc *enc)
 {
 	struct v4l2_hevc_enc_state *state = &enc->state_active;
-	int qp_bd_offset = 6 * state->sps.bit_depth_luma_minus8;
 	u8 slice_type = state->encode.slice_type;
 	unsigned int range;
 	int qp, ret;
@@ -1114,8 +1133,7 @@ static int rc_step(struct v4l2_hevc_enc *enc)
 			return ret;
 	}
 
-	qp = clamp(qp, state->qp_min, state->qp_max);
-	enc->qp = clamp(qp, -qp_bd_offset, 51);
+	enc->qp = rc_clamp_qp(state, qp);
 
 	return 0;
 }
diff --git a/include/media/v4l2-hevc-enc.h b/include/media/v4l2-hevc-enc.h
index 3db131370c56..f1d3da3269f1 100644
--- a/include/media/v4l2-hevc-enc.h
+++ b/include/media/v4l2-hevc-enc.h
@@ -128,6 +128,11 @@ struct v4l2_hevc_enc_ref {
  * @frame_rc_enable: V4L2_CID_MPEG_VIDEO_FRAME_RC_ENABLE
  * @qp_min: V4L2_CID_MPEG_VIDEO_HEVC_MIN_QP
  * @qp_max: V4L2_CID_MPEG_VIDEO_HEVC_MAX_QP
+ * @qp_min_i: V4L2_CID_MPEG_VIDEO_HEVC_I_FRAME_MIN_QP, @qp_min if the driver
+ *	      has no such control. Limits the I pictures with a frame rate
+ *	      control instead of @qp_min.
+ * @qp_max_i: V4L2_CID_MPEG_VIDEO_HEVC_I_FRAME_MAX_QP, @qp_max if the driver
+ *	      has no such control
  * @qp_i: V4L2_CID_MPEG_VIDEO_HEVC_I_FRAME_QP
  * @qp_p: V4L2_CID_MPEG_VIDEO_HEVC_P_FRAME_QP
  * @qp_b: V4L2_CID_MPEG_VIDEO_HEVC_B_FRAME_QP
@@ -153,6 +158,8 @@ struct v4l2_hevc_enc_state {
 	bool frame_rc_enable;
 	int qp_min;
 	int qp_max;
+	int qp_min_i;
+	int qp_max_i;
 	int qp_i;
 	int qp_p;
 	int qp_b;

-- 
2.55.0



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

* [PATCH RFC 09/10] media: v4l2-hevc-enc: Re-encode oversized pictures
  2026-11-10 10:07 [PATCH RFC 00/10] media: Add a V4L2 HEVC stateless encode uAPI and helpers Arun Iyengar via B4 Relay
                   ` (7 preceding siblings ...)
  2026-11-10 10:07 ` [PATCH RFC 08/10] media: v4l2-hevc-enc: Add QP limits for the I pictures Arun Iyengar via B4 Relay
@ 2026-11-10 10:07 ` Arun Iyengar via B4 Relay
  2026-11-10 10:07 ` [PATCH RFC 10/10] media: docs: Document the HEVC stateless encode uAPI Arun Iyengar via B4 Relay
  9 siblings, 0 replies; 11+ messages in thread
From: Arun Iyengar via B4 Relay @ 2026-11-10 10:07 UTC (permalink / raw)
  To: Mauro Carvalho Chehab
  Cc: linux-media, linux-kernel, Paul Kocialkowski, Nicolas Dufresne,
	Hans Verkuil, Arun Iyengar

From: Arun Iyengar <aiyengar1990@gmail.com>

A scene change in a P picture can make it many times larger than its size
target, and the peak bitrate around it can then exceed what a decoder
buffer of the stream takes. v4l2-enc-cbr can ask for a second pass of such
a picture at a higher QP (v4l2_enc_cbr_reencode()).

Provide v4l2_hevc_enc_reencode() in the HEVC encode core for drivers whose
hardware writes the slice header: called after the hardware finished a
picture and before v4l2_hevc_enc_complete(), it returns true with the new
QP in enc->qp when the constant bitrate mode wants the picture encoded
again. Hardware that takes the slice header from the core cannot use it:
that header holds the QP of the first pass.

Signed-off-by: Arun Iyengar <aiyengar1990@gmail.com>
---
 drivers/media/v4l2-core/v4l2-hevc-enc.c | 39 +++++++++++++++++++++++++++++++++
 include/media/v4l2-hevc-enc.h           |  2 ++
 2 files changed, 41 insertions(+)

diff --git a/drivers/media/v4l2-core/v4l2-hevc-enc.c b/drivers/media/v4l2-core/v4l2-hevc-enc.c
index 2e0ba832b868..d49cee336558 100644
--- a/drivers/media/v4l2-core/v4l2-hevc-enc.c
+++ b/drivers/media/v4l2-core/v4l2-hevc-enc.c
@@ -1369,6 +1369,45 @@ static int ref_complete(struct v4l2_hevc_enc *enc, u64 timestamp)
 	return 0;
 }
 
+/**
+ * v4l2_hevc_enc_reencode() - decide on encoding the picture a second time
+ * @enc: the core context
+ * @bytesused: size of the encoded picture in bytes, with the headers the core
+ *	       wrote
+ *
+ * In the constant bitrate mode of the core, a picture far above its size
+ * target can be encoded again with a higher QP (see v4l2_enc_cbr_reencode()).
+ * Drivers that can run the hardware again for the same picture call it after
+ * the hardware finished it and before v4l2_hevc_enc_complete(). If it returns
+ * true, @enc->qp holds the new QP and the driver encodes the picture again,
+ * with the same size target and the same headers, writing the slice data
+ * from the same offset. Only for hardware that writes the slice header
+ * (V4L2_HEVC_ENC_FLAG_HW_SLICE_HEADER): the slice header of the core holds the
+ * QP of the first pass.
+ *
+ * Return: true if the picture is to be encoded again.
+ */
+bool v4l2_hevc_enc_reencode(struct v4l2_hevc_enc *enc, unsigned int bytesused)
+{
+	struct v4l2_hevc_enc_state *state = &enc->state_active;
+	bool intra = state->encode.slice_type == V4L2_HEVC_SLICE_TYPE_I;
+	int qp_max = intra ? state->qp_max_i : state->qp_max;
+
+	if (!(enc->flags & V4L2_HEVC_ENC_FLAG_HW_SLICE_HEADER) ||
+	    !rc_core_cbr(enc, state))
+		return false;
+
+	if (!v4l2_enc_cbr_reencode(&enc->cbr, (u64)bytesused * 8,
+				   &state->timeperframe, enc->qp,
+				   min(qp_max, 51)))
+		return false;
+
+	enc->qp = rc_clamp_qp(state, v4l2_enc_cbr_qp(&enc->cbr));
+
+	return true;
+}
+EXPORT_SYMBOL_GPL(v4l2_hevc_enc_reencode);
+
 /**
  * v4l2_hevc_enc_complete() - complete the encode run of a picture
  * @enc: the core context
diff --git a/include/media/v4l2-hevc-enc.h b/include/media/v4l2-hevc-enc.h
index f1d3da3269f1..5be449e5ec03 100644
--- a/include/media/v4l2-hevc-enc.h
+++ b/include/media/v4l2-hevc-enc.h
@@ -285,6 +285,8 @@ int v4l2_hevc_enc_init(struct v4l2_hevc_enc *enc);
 void v4l2_hevc_enc_exit(struct v4l2_hevc_enc *enc);
 int v4l2_hevc_enc_step(struct v4l2_hevc_enc *enc,
 		       struct vb2_v4l2_buffer *buffer);
+bool v4l2_hevc_enc_reencode(struct v4l2_hevc_enc *enc,
+			    unsigned int bytesused);
 int v4l2_hevc_enc_complete(struct v4l2_hevc_enc *enc,
 			   struct vb2_v4l2_buffer *buffer,
 			   const struct v4l2_stateless_encode_stats *stats);

-- 
2.55.0



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

* [PATCH RFC 10/10] media: docs: Document the HEVC stateless encode uAPI
  2026-11-10 10:07 [PATCH RFC 00/10] media: Add a V4L2 HEVC stateless encode uAPI and helpers Arun Iyengar via B4 Relay
                   ` (8 preceding siblings ...)
  2026-11-10 10:07 ` [PATCH RFC 09/10] media: v4l2-hevc-enc: Re-encode oversized pictures Arun Iyengar via B4 Relay
@ 2026-11-10 10:07 ` Arun Iyengar via B4 Relay
  9 siblings, 0 replies; 11+ messages in thread
From: Arun Iyengar via B4 Relay @ 2026-11-10 10:07 UTC (permalink / raw)
  To: Mauro Carvalho Chehab
  Cc: linux-media, linux-kernel, Paul Kocialkowski, Nicolas Dufresne,
	Hans Verkuil, Arun Iyengar

From: Arun Iyengar <aiyengar1990@gmail.com>

Describe how a stateless HEVC encoder is used: the CAPTURE format and
what each buffer receives, which parts of the parameter sets the kernel
derives from the format, frame interval and stateful codec controls, how
the QP is selected, how reference pictures are identified and kept, and
how per-picture feedback is read back from completed requests.

Document the V4L2_CID_STATELESS_HEVC_ENCODE_PARAMS and
V4L2_CID_STATELESS_HEVC_ENCODE_FEEDBACK controls with their structures
and flags, and the codec-agnostic struct v4l2_stateless_encode_stats, and
add the new control types to the VIDIOC_QUERYCTRL and VIDIOC_G_EXT_CTRLS
references.

Signed-off-by: Arun Iyengar <aiyengar1990@gmail.com>
---
 .../media/v4l/ext-ctrls-codec-stateless.rst        | 411 +++++++++++++++++++++
 .../userspace-api/media/v4l/pixfmt-compressed.rst  |   3 +
 .../media/v4l/videodev2.h.rst.exceptions           |   2 +
 .../userspace-api/media/v4l/vidioc-g-ext-ctrls.rst |   8 +
 .../userspace-api/media/v4l/vidioc-queryctrl.rst   |  12 +
 5 files changed, 436 insertions(+)

diff --git a/Documentation/userspace-api/media/v4l/ext-ctrls-codec-stateless.rst b/Documentation/userspace-api/media/v4l/ext-ctrls-codec-stateless.rst
index d7a3b8ef03a7..177940a6f987 100644
--- a/Documentation/userspace-api/media/v4l/ext-ctrls-codec-stateless.rst
+++ b/Documentation/userspace-api/media/v4l/ext-ctrls-codec-stateless.rst
@@ -3079,6 +3079,417 @@ This structure contains all loop filter related parameters. See sections
       - Specifies if the short-term RPS is predicted from another short term RPS. See details in
         section 7.4.8 "Short-term reference picture set semantics" of the specification.
 
+.. _v4l2-codec-stateless-hevc-encode:
+
+.. rubric:: HEVC stateless encoding
+
+A stateless HEVC encoder exposes ``V4L2_PIX_FMT_HEVC_SLICE`` on its CAPTURE
+queue. Each CAPTURE buffer receives one complete access unit, with the start
+codes selected by ``V4L2_CID_STATELESS_HEVC_START_CODE``. Applications set
+the sequence and picture parameters with the ``V4L2_CID_STATELESS_HEVC_SPS``
+and ``V4L2_CID_STATELESS_HEVC_PPS`` controls, and the parameters of each
+picture with ``V4L2_CID_STATELESS_HEVC_ENCODE_PARAMS``, in the request of the
+OUTPUT buffer that holds the picture to encode.
+
+The kernel writes the video, sequence and picture parameter sets, and the
+slice segment headers, unless the hardware writes them itself. It derives the
+parts of the parameter sets that the SPS and PPS controls do not carry from
+the rest of the configuration:
+
+- the coded size from the CAPTURE format, aligned to the minimum coding block
+  size, and the conformance window from the difference with the visible size;
+- the profile, tier and level from ``V4L2_CID_MPEG_VIDEO_HEVC_PROFILE``,
+  ``V4L2_CID_MPEG_VIDEO_HEVC_TIER`` and ``V4L2_CID_MPEG_VIDEO_HEVC_LEVEL``
+  when the encoder exposes them (otherwise the lowest level that fits the
+  coded size and frame rate);
+- the VUI timing information from the frame interval set with
+  :ref:`VIDIOC_S_PARM <VIDIOC_G_PARM>`, and the colour description from the
+  colorimetry of the format.
+
+The values of the SPS and PPS controls are adjusted to what the encoder
+supports, and can be read back after the encode run. A changed SPS only takes
+effect with the next IRAP picture.
+
+The quantisation parameter comes from the stateful codec controls: from
+``V4L2_CID_MPEG_VIDEO_HEVC_I_FRAME_QP``, ``V4L2_CID_MPEG_VIDEO_HEVC_P_FRAME_QP``
+and ``V4L2_CID_MPEG_VIDEO_HEVC_B_FRAME_QP`` when
+``V4L2_CID_MPEG_VIDEO_FRAME_RC_ENABLE`` is 0, otherwise from the rate control
+mode selected by ``V4L2_CID_MPEG_VIDEO_BITRATE_MODE``, within
+``V4L2_CID_MPEG_VIDEO_HEVC_MIN_QP`` and ``V4L2_CID_MPEG_VIDEO_HEVC_MAX_QP``.
+These controls can be part of the request too.
+
+Reference pictures are identified by the timestamp of the OUTPUT buffer they
+were encoded from. The encoder keeps the reconstructed pictures internally:
+a picture is kept when it is encoded with
+``V4L2_HEVC_ENCODE_FLAG_REFERENCE`` and as long as each following picture
+lists it in its reference picture set. When an encode run fails, the set of
+kept pictures does not change.
+
+How each picture was encoded is reported by the read-only
+``V4L2_CID_STATELESS_HEVC_ENCODE_FEEDBACK`` control. Applications do not set
+it in requests, so its value at the end of the encode run is stored in the
+request, and can be read with :ref:`VIDIOC_G_EXT_CTRLS <VIDIOC_G_EXT_CTRLS>`
+once the request completed.
+
+``V4L2_CID_STATELESS_HEVC_ENCODE_PARAMS (struct)``
+    Specifies the parameters of the picture to encode: its NAL unit header,
+    the slice segment header syntax elements that are not derived from other
+    controls, and its reference picture set. These parameters are defined
+    according to :ref:`hevc`, sections 7.3.1.2 "NAL unit header syntax" and
+    7.3.6 "Slice segment header syntax". When the picture is encoded as
+    several slices, all of them use these parameters.
+
+.. c:type:: v4l2_ctrl_hevc_encode_params
+
+.. cssclass:: longtable
+
+.. flat-table:: struct v4l2_ctrl_hevc_encode_params
+    :header-rows:  0
+    :stub-columns: 0
+    :widths:       1 1 2
+
+    * - __u8
+      - ``nal_unit_type``
+      - NAL unit type of the slice segments of the picture, from Table 7-1 of
+        the specification: one of the VCL types 0 (TRAIL_N) to 9 (RASL_R)
+        and 16 (BLA_W_LP) to 21 (CRA_NUT). The first picture is an IRAP
+        picture (16 to 21).
+    * - __u8
+      - ``nuh_temporal_id_plus1``
+      - TemporalId of the picture plus 1, from 1 to
+        ``sps_max_sub_layers_minus1`` + 1. It is 1 for IRAP pictures and
+        greater than 1 for TSA and STSA pictures.
+    * - __u8
+      - ``slice_type``
+      - ``V4L2_HEVC_SLICE_TYPE_I``, ``V4L2_HEVC_SLICE_TYPE_P`` or
+        ``V4L2_HEVC_SLICE_TYPE_B``. IRAP pictures are I pictures. An encoder
+        that cannot encode P pictures encodes them as I pictures; one that
+        cannot encode B pictures rejects them.
+    * - __u8
+      - ``pic_parameter_set_id``
+      - Identifies the PPS in use. The encoder supports a single PPS and
+        sets the value of the PPS control.
+    * - __s32
+      - ``pic_order_cnt_val``
+      - PicOrderCntVal of the picture, as described in section 8.3.1
+        "Decoding process for picture order count". It is 0 for IDR
+        pictures, and from 0 to MaxPicOrderCntLsb - 1 for BLA pictures and
+        for a CRA picture that starts the bitstream. Otherwise it differs by
+        less than MaxPicOrderCntLsb / 2 from the picture order count of the
+        previous picture with TemporalId 0 that is not a RASL, RADL or
+        sub-layer non-reference picture, so that a decoder can derive it.
+    * - __u8
+      - ``num_ref_idx_l0_active_minus1``
+      - Number of entries of reference picture list 0, minus 1, used with
+        ``V4L2_HEVC_ENCODE_FLAG_NUM_REF_IDX_ACTIVE_OVERRIDE``. Otherwise the
+        PPS default applies. The encoder limits it to what it supports.
+    * - __u8
+      - ``num_ref_idx_l1_active_minus1``
+      - Same for reference picture list 1.
+    * - __u8
+      - ``collocated_ref_idx``
+      - Reference index of the collocated picture used for temporal motion
+        vector prediction, in list 0 when
+        ``V4L2_HEVC_ENCODE_FLAG_COLLOCATED_FROM_L0`` is set, in list 1
+        otherwise.
+    * - __u8
+      - ``five_minus_max_num_merge_cand``
+      - Maximum number of merging motion vector prediction candidates
+        subtracted from 5.
+    * - __s8
+      - ``slice_cb_qp_offset``
+      - Difference added to ``pps_cb_qp_offset``, used when the PPS has
+        ``V4L2_HEVC_PPS_FLAG_PPS_SLICE_CHROMA_QP_OFFSETS_PRESENT``.
+    * - __s8
+      - ``slice_cr_qp_offset``
+      - Difference added to ``pps_cr_qp_offset``, used likewise.
+    * - __s8
+      - ``slice_beta_offset_div2``
+      - Deblocking parameter offset for beta divided by 2, used with
+        ``V4L2_HEVC_ENCODE_FLAG_DEBLOCKING_FILTER_OVERRIDE``.
+    * - __s8
+      - ``slice_tc_offset_div2``
+      - Deblocking parameter offset for tC divided by 2, used likewise.
+    * - __u8
+      - ``num_refs``
+      - Number of valid entries in ``refs``, up to
+        ``sps_max_dec_pic_buffering_minus1``.
+    * - __u8
+      - ``reserved[7]``
+      - Applications and drivers must set this to zero.
+    * - struct :c:type:`v4l2_hevc_encode_ref`
+      - ``refs[V4L2_HEVC_DPB_ENTRIES_NUM_MAX]``
+      - The reference picture set of the picture: every previously encoded
+        picture that the current picture or a following picture uses for
+        prediction, in any order. The encoder computes the picture order
+        count differences, codes the RPS and builds the reference picture
+        lists without modification, as described in section 8.3.4. Pictures
+        that are not listed are removed from the decoded picture buffer.
+        IDR pictures have no reference picture set and BLA pictures discard
+        it. An encoder that uses a single reference picture uses the one
+        entry that is used by the current picture.
+    * - __u64
+      - ``flags``
+      - See :ref:`HEVC Encode Parameters Flags <hevc_encode_params_flags>`
+
+.. c:type:: v4l2_hevc_encode_ref
+
+.. flat-table:: struct v4l2_hevc_encode_ref
+    :header-rows:  0
+    :stub-columns: 0
+    :widths:       1 1 2
+
+    * - __u64
+      - ``timestamp``
+      - Timestamp of the OUTPUT buffer from which the reference picture was
+        encoded. Use the :c:func:`v4l2_timeval_to_ns()` function to convert
+        the struct :c:type:`timeval` in struct :c:type:`v4l2_buffer` to a
+        __u64.
+    * - __u8
+      - ``flags``
+      - See :ref:`HEVC Encode Reference Flags <hevc_encode_ref_flags>`
+    * - __u8
+      - ``reserved[7]``
+      - Applications and drivers must set this to zero.
+
+.. _hevc_encode_ref_flags:
+
+``HEVC Encode Reference Flags``
+
+.. cssclass:: longtable
+
+.. flat-table::
+    :header-rows:  0
+    :stub-columns: 0
+    :widths:       1 1 2
+
+    * - ``V4L2_HEVC_ENCODE_REF_FLAG_USED_BY_CURR_PIC``
+      - 0x01
+      - The current picture uses the reference picture for inter prediction
+        (it is in PocStCurrBefore, PocStCurrAfter or PocLtCurr). Otherwise it
+        is only kept for following pictures. P and B pictures have at least
+        one used reference, IRAP pictures have none.
+    * - ``V4L2_HEVC_ENCODE_REF_FLAG_LONG_TERM``
+      - 0x02
+      - The reference picture is a long-term reference picture. This
+        requires ``V4L2_HEVC_SPS_FLAG_LONG_TERM_REF_PICS_PRESENT``. Once a
+        picture is a long-term reference picture, it stays one.
+
+.. _hevc_encode_params_flags:
+
+``HEVC Encode Parameters Flags``
+
+.. cssclass:: longtable
+
+.. flat-table::
+    :header-rows:  0
+    :stub-columns: 0
+    :widths:       1 1 2
+
+    * - ``V4L2_HEVC_ENCODE_FLAG_SLICE_SAO_LUMA``
+      - 0x00000001
+      - slice_sao_luma_flag. Ignored unless the SPS enables SAO.
+    * - ``V4L2_HEVC_ENCODE_FLAG_SLICE_SAO_CHROMA``
+      - 0x00000002
+      - slice_sao_chroma_flag. Ignored unless the SPS enables SAO.
+    * - ``V4L2_HEVC_ENCODE_FLAG_SLICE_TEMPORAL_MVP_ENABLED``
+      - 0x00000004
+      - slice_temporal_mvp_enabled_flag. Ignored unless the SPS enables
+        temporal motion vector prediction.
+    * - ``V4L2_HEVC_ENCODE_FLAG_MVD_L1_ZERO``
+      - 0x00000008
+      - mvd_l1_zero_flag, for B pictures.
+    * - ``V4L2_HEVC_ENCODE_FLAG_CABAC_INIT``
+      - 0x00000010
+      - cabac_init_flag, used when the PPS has
+        ``V4L2_HEVC_PPS_FLAG_CABAC_INIT_PRESENT``.
+    * - ``V4L2_HEVC_ENCODE_FLAG_COLLOCATED_FROM_L0``
+      - 0x00000020
+      - collocated_from_l0_flag, for B pictures. Always set for P pictures.
+    * - ``V4L2_HEVC_ENCODE_FLAG_SLICE_DEBLOCKING_FILTER_DISABLED``
+      - 0x00000080
+      - slice_deblocking_filter_disabled_flag, used with
+        ``V4L2_HEVC_ENCODE_FLAG_DEBLOCKING_FILTER_OVERRIDE``.
+    * - ``V4L2_HEVC_ENCODE_FLAG_SLICE_LOOP_FILTER_ACROSS_SLICES_ENABLED``
+      - 0x00000100
+      - slice_loop_filter_across_slices_enabled_flag.
+    * - ``V4L2_HEVC_ENCODE_FLAG_REFERENCE``
+      - 0x00010000
+      - Keep the reconstructed picture so that following pictures can
+        reference it. Without this flag, the encoder may not store the
+        reconstruction at all.
+    * - ``V4L2_HEVC_ENCODE_FLAG_NO_OUTPUT_OF_PRIOR_PICS``
+      - 0x00020000
+      - no_output_of_prior_pics_flag, for IRAP pictures.
+    * - ``V4L2_HEVC_ENCODE_FLAG_NO_PIC_OUTPUT``
+      - 0x00040000
+      - pic_output_flag is 0, when the PPS has
+        ``V4L2_HEVC_PPS_FLAG_OUTPUT_FLAG_PRESENT``.
+    * - ``V4L2_HEVC_ENCODE_FLAG_NUM_REF_IDX_ACTIVE_OVERRIDE``
+      - 0x00080000
+      - Use ``num_ref_idx_l0_active_minus1`` and
+        ``num_ref_idx_l1_active_minus1`` instead of the PPS defaults.
+    * - ``V4L2_HEVC_ENCODE_FLAG_DEBLOCKING_FILTER_OVERRIDE``
+      - 0x00100000
+      - deblocking_filter_override_flag, used when the PPS has
+        ``V4L2_HEVC_PPS_FLAG_DEBLOCKING_FILTER_OVERRIDE_ENABLED``.
+
+The flags that carry a slice header syntax element have the same values as
+the ``V4L2_HEVC_SLICE_PARAMS_FLAG_*`` flags of the same element.
+
+``V4L2_CID_STATELESS_HEVC_ENCODE_FEEDBACK (struct)``
+    Read-only. Describes how a picture was encoded: the values the encoder
+    used where it adjusted or derived them, and statistics measured while
+    encoding. The encoder sets it when the encode run completes, so that it
+    is stored in the request.
+
+.. c:type:: v4l2_ctrl_hevc_encode_feedback
+
+.. cssclass:: longtable
+
+.. flat-table:: struct v4l2_ctrl_hevc_encode_feedback
+    :header-rows:  0
+    :stub-columns: 0
+    :widths:       1 1 2
+
+    * - __u64
+      - ``timestamp``
+      - Timestamp of the OUTPUT buffer that was encoded.
+    * - __s32
+      - ``pic_order_cnt_val``
+      - Picture order count of the picture.
+    * - __u8
+      - ``nal_unit_type``
+      - NAL unit type of the slice segments.
+    * - __u8
+      - ``nuh_temporal_id_plus1``
+      - TemporalId of the picture plus 1.
+    * - __u8
+      - ``slice_type``
+      - Slice type that was used.
+    * - __s8
+      - ``slice_qp``
+      - Luma quantisation parameter of the first slice (SliceQpY).
+    * - __u8
+      - ``num_ref_idx_l0_active``
+      - Number of entries in reference picture list 0.
+    * - __u8
+      - ``num_ref_idx_l1_active``
+      - Number of entries in reference picture list 1.
+    * - __u8
+      - ``reserved0[2]``
+      - Applications and drivers must set this to zero.
+    * - __u8
+      - ``ref_idx_l0[V4L2_HEVC_DPB_ENTRIES_NUM_MAX]``
+      - For each entry of reference picture list 0, its index in the
+        ``refs`` array of the encode parameters.
+    * - __u8
+      - ``ref_idx_l1[V4L2_HEVC_DPB_ENTRIES_NUM_MAX]``
+      - Same for reference picture list 1.
+    * - __u8
+      - ``reserved1[4]``
+      - Applications and drivers must set this to zero.
+    * - struct :c:type:`v4l2_stateless_encode_stats`
+      - ``stats``
+      - Statistics measured while encoding the picture.
+    * - __u64
+      - ``flags``
+      - See :ref:`HEVC Encode Feedback Flags <hevc_encode_feedback_flags>`
+
+.. _hevc_encode_feedback_flags:
+
+``HEVC Encode Feedback Flags``
+
+.. cssclass:: longtable
+
+.. flat-table::
+    :header-rows:  0
+    :stub-columns: 0
+    :widths:       1 1 2
+
+    * - ``V4L2_HEVC_ENCODE_FEEDBACK_FLAG_REFERENCE``
+      - 0x00000001
+      - The reconstructed picture was kept for reference.
+
+.. c:type:: v4l2_stateless_encode_stats
+
+.. cssclass:: longtable
+
+.. flat-table:: struct v4l2_stateless_encode_stats
+    :header-rows:  0
+    :stub-columns: 0
+    :widths:       1 1 2
+
+    * - __u32
+      - ``flags``
+      - Which statistics are valid, see
+        :ref:`Stateless Encode Statistics Flags <stateless_encode_stats_flags>`.
+        The fields of the statistics that are not valid are zero.
+    * - __u32
+      - ``header_size``
+      - Number of bytes written by the kernel at the start of the CAPTURE
+        buffer (parameter sets, access unit delimiter, slice header) before
+        the data produced by the hardware. Always valid.
+    * - __u32
+      - ``qp_sum``
+      - Sum of the luma quantisation parameters of the blocks counted in
+        ``qp_count``.
+    * - __u32
+      - ``qp_count``
+      - Number of blocks summed in ``qp_sum``: ``qp_sum`` / ``qp_count`` is
+        the average QP of the picture.
+    * - __s8
+      - ``qp_min``
+      - Smallest luma quantisation parameter used in the picture.
+    * - __s8
+      - ``qp_max``
+      - Largest luma quantisation parameter used in the picture.
+    * - __u8
+      - ``reserved0[6]``
+      - Applications and drivers must set this to zero.
+    * - __u64
+      - ``sse_luma``
+      - Sum of squared errors between the source and reconstructed luma
+        samples, from which the PSNR of the picture can be computed.
+    * - __u32
+      - ``num_intra_blocks``
+      - Number of 8x8 luma blocks coded with intra prediction.
+    * - __u32
+      - ``num_inter_blocks``
+      - Number of 8x8 luma blocks coded with inter prediction, skipped
+        blocks included.
+    * - __u32
+      - ``num_skip_blocks``
+      - Number of 8x8 luma blocks coded in skip mode, counted in
+        ``num_inter_blocks`` too.
+    * - __u32
+      - ``reserved1``
+      - Applications and drivers must set this to zero.
+
+.. _stateless_encode_stats_flags:
+
+``Stateless Encode Statistics Flags``
+
+.. cssclass:: longtable
+
+.. flat-table::
+    :header-rows:  0
+    :stub-columns: 0
+    :widths:       1 1 2
+
+    * - ``V4L2_STATELESS_ENCODE_STATS_FLAG_QP``
+      - 0x00000001
+      - ``qp_sum``, ``qp_count``, ``qp_min`` and ``qp_max`` are valid.
+    * - ``V4L2_STATELESS_ENCODE_STATS_FLAG_SSE_LUMA``
+      - 0x00000002
+      - ``sse_luma`` is valid.
+    * - ``V4L2_STATELESS_ENCODE_STATS_FLAG_BLOCKS``
+      - 0x00000004
+      - ``num_intra_blocks`` and ``num_inter_blocks`` are valid.
+    * - ``V4L2_STATELESS_ENCODE_STATS_FLAG_SKIP_BLOCKS``
+      - 0x00000008
+      - ``num_skip_blocks`` is valid.
+
 .. _v4l2-codec-stateless-av1:
 
 ``V4L2_CID_STATELESS_AV1_SEQUENCE (struct)``
diff --git a/Documentation/userspace-api/media/v4l/pixfmt-compressed.rst b/Documentation/userspace-api/media/v4l/pixfmt-compressed.rst
index 235f955d3cd5..7483444ec823 100644
--- a/Documentation/userspace-api/media/v4l/pixfmt-compressed.rst
+++ b/Documentation/userspace-api/media/v4l/pixfmt-compressed.rst
@@ -221,6 +221,9 @@ Compressed Formats
 	See the :ref:`associated Codec Control IDs <v4l2-codec-stateless-hevc>`.
 	Buffers associated with this pixel format must contain the appropriate
 	number of macroblocks to decode a full corresponding frame.
+	Stateless video encoders use this pixel format on their CAPTURE queue:
+	each buffer then receives one complete access unit, as described in
+	:ref:`HEVC stateless encoding <v4l2-codec-stateless-hevc-encode>`.
     * .. _V4L2-PIX-FMT-FWHT:
 
       - ``V4L2_PIX_FMT_FWHT``
diff --git a/Documentation/userspace-api/media/v4l/videodev2.h.rst.exceptions b/Documentation/userspace-api/media/v4l/videodev2.h.rst.exceptions
index 6182b4e2d2ee..6c8ac563413f 100644
--- a/Documentation/userspace-api/media/v4l/videodev2.h.rst.exceptions
+++ b/Documentation/userspace-api/media/v4l/videodev2.h.rst.exceptions
@@ -168,6 +168,8 @@ replace symbol V4L2_CTRL_TYPE_HEVC_PPS :c:type:`V4L.v4l2_ctrl_type`
 replace symbol V4L2_CTRL_TYPE_HEVC_SLICE_PARAMS :c:type:`V4L.v4l2_ctrl_type`
 replace symbol V4L2_CTRL_TYPE_HEVC_SCALING_MATRIX :c:type:`V4L.v4l2_ctrl_type`
 replace symbol V4L2_CTRL_TYPE_HEVC_DECODE_PARAMS :c:type:`V4L.v4l2_ctrl_type`
+replace symbol V4L2_CTRL_TYPE_HEVC_ENCODE_PARAMS :c:type:`V4L.v4l2_ctrl_type`
+replace symbol V4L2_CTRL_TYPE_HEVC_ENCODE_FEEDBACK :c:type:`V4L.v4l2_ctrl_type`
 replace symbol V4L2_CTRL_TYPE_AV1_SEQUENCE :c:type:`V4L.v4l2_ctrl_type`
 replace symbol V4L2_CTRL_TYPE_AV1_TILE_GROUP_ENTRY :c:type:`V4L.v4l2_ctrl_type`
 replace symbol V4L2_CTRL_TYPE_AV1_FRAME :c:type:`V4L.v4l2_ctrl_type`
diff --git a/Documentation/userspace-api/media/v4l/vidioc-g-ext-ctrls.rst b/Documentation/userspace-api/media/v4l/vidioc-g-ext-ctrls.rst
index d78328152b75..0cc67e210359 100644
--- a/Documentation/userspace-api/media/v4l/vidioc-g-ext-ctrls.rst
+++ b/Documentation/userspace-api/media/v4l/vidioc-g-ext-ctrls.rst
@@ -286,6 +286,14 @@ still cause this situation.
       - ``p_hevc_decode_params``
       - A pointer to a struct :c:type:`v4l2_ctrl_hevc_decode_params`. Valid if this
         control is of type ``V4L2_CTRL_TYPE_HEVC_DECODE_PARAMS``.
+    * - struct :c:type:`v4l2_ctrl_hevc_encode_params` *
+      - ``p_hevc_encode_params``
+      - A pointer to a struct :c:type:`v4l2_ctrl_hevc_encode_params`. Valid if this
+        control is of type ``V4L2_CTRL_TYPE_HEVC_ENCODE_PARAMS``.
+    * - struct :c:type:`v4l2_ctrl_hevc_encode_feedback` *
+      - ``p_hevc_encode_feedback``
+      - A pointer to a struct :c:type:`v4l2_ctrl_hevc_encode_feedback`. Valid if this
+        control is of type ``V4L2_CTRL_TYPE_HEVC_ENCODE_FEEDBACK``.
     * - struct :c:type:`v4l2_ctrl_av1_sequence` *
       - ``p_av1_sequence``
       - A pointer to a struct :c:type:`v4l2_ctrl_av1_sequence`. Valid if this control is
diff --git a/Documentation/userspace-api/media/v4l/vidioc-queryctrl.rst b/Documentation/userspace-api/media/v4l/vidioc-queryctrl.rst
index 82c8b52e771c..cd9ef6b559a4 100644
--- a/Documentation/userspace-api/media/v4l/vidioc-queryctrl.rst
+++ b/Documentation/userspace-api/media/v4l/vidioc-queryctrl.rst
@@ -543,6 +543,18 @@ See also the examples in :ref:`control`.
       - n/a
       - A struct :c:type:`v4l2_ctrl_hevc_ext_sps_st_rps`, containing HEVC
 	extended Short-Term RPS for stateless video decoders.
+    * - ``V4L2_CTRL_TYPE_HEVC_ENCODE_PARAMS``
+      - n/a
+      - n/a
+      - n/a
+      - A struct :c:type:`v4l2_ctrl_hevc_encode_params`, containing HEVC
+	encoding parameters for stateless video encoders.
+    * - ``V4L2_CTRL_TYPE_HEVC_ENCODE_FEEDBACK``
+      - n/a
+      - n/a
+      - n/a
+      - A struct :c:type:`v4l2_ctrl_hevc_encode_feedback`, containing HEVC
+	encoding feedback from stateless video encoders.
     * - ``V4L2_CTRL_TYPE_VP9_COMPRESSED_HDR``
       - n/a
       - n/a

-- 
2.55.0



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

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

Thread overview: 11+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2026-11-10 10:07 [PATCH RFC 00/10] media: Add a V4L2 HEVC stateless encode uAPI and helpers Arun Iyengar via B4 Relay
2026-11-10 10:07 ` [PATCH RFC 01/10] media: hevc: Add H.265 syntax definitions Arun Iyengar via B4 Relay
2026-11-10 10:07 ` [PATCH RFC 02/10] media: uapi: Add HEVC stateless encode support Arun Iyengar via B4 Relay
2026-11-10 10:07 ` [PATCH RFC 03/10] media: hevc: Add stateless encode RBSP writer Arun Iyengar via B4 Relay
2026-11-10 10:07 ` [PATCH RFC 04/10] media: hevc: Add KUnit tests for the " Arun Iyengar via B4 Relay
2026-11-10 10:07 ` [PATCH RFC 05/10] media: hevc: Add stateless encode core Arun Iyengar via B4 Relay
2026-11-10 10:07 ` [PATCH RFC 06/10] media: hevc: Add KUnit tests for the " Arun Iyengar via B4 Relay
2026-11-10 10:07 ` [PATCH RFC 07/10] media: v4l2-hevc-enc: Add a constant bitrate mode Arun Iyengar via B4 Relay
2026-11-10 10:07 ` [PATCH RFC 08/10] media: v4l2-hevc-enc: Add QP limits for the I pictures Arun Iyengar via B4 Relay
2026-11-10 10:07 ` [PATCH RFC 09/10] media: v4l2-hevc-enc: Re-encode oversized pictures Arun Iyengar via B4 Relay
2026-11-10 10:07 ` [PATCH RFC 10/10] media: docs: Document the HEVC stateless encode uAPI Arun Iyengar via B4 Relay

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

all inboxes | Powered by JetHome®