[PATCH 13/14] media: apple: avd: add hevc support

Sofus Forstreuter sofus.c at icloud.com
Fri Sep 18 06:15:18 PDT 2026


The fluster score is 143/147 for JCT-VC-HEVC_V1.

RPS_E_qualcomm_5, WPP_D_ericsson_MAIN_2 and WPP_D_ericsson_MAIN10_2 are
failing, but should be supported. WPP tests fail at random when
increasing job count in fluster.

Signed-off-by: Sofus Forstreuter <sofus.c at icloud.com>
---
 drivers/media/platform/apple/avd/Makefile   |    2 +-
 drivers/media/platform/apple/avd/avd-hevc.c | 1326 +++++++++++++++++++++++++++
 drivers/media/platform/apple/avd/avd-v4l2.c |   70 ++
 drivers/media/platform/apple/avd/avd.h      |    6 +
 4 files changed, 1403 insertions(+), 1 deletion(-)

diff --git a/drivers/media/platform/apple/avd/Makefile b/drivers/media/platform/apple/avd/Makefile
index 54ea67a589cd..6109d12b8452 100644
--- a/drivers/media/platform/apple/avd/Makefile
+++ b/drivers/media/platform/apple/avd/Makefile
@@ -1,4 +1,4 @@
 # SPDX-License-Identifier: GPL-2.0-only
 
-apple-avd-y := avd-drv.o avd-v4l2.o avd-hw.o avd-h264.o avd-vp9.o
+apple-avd-y := avd-drv.o avd-v4l2.o avd-hw.o avd-h264.o avd-vp9.o avd-hevc.o
 obj-$(CONFIG_VIDEO_APPLE_AVD) += apple-avd.o
diff --git a/drivers/media/platform/apple/avd/avd-hevc.c b/drivers/media/platform/apple/avd/avd-hevc.c
new file mode 100644
index 000000000000..d4815c031f7d
--- /dev/null
+++ b/drivers/media/platform/apple/avd/avd-hevc.c
@@ -0,0 +1,1326 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Apple Video Decoder HEVC driver
+ *
+ * Copyright (C) 2026 The Asahi Linux Contributors
+ * Copyright (C) 2026 Sofus Forstreuter <sofus.c at icloud.com>
+ * Copyright (C) 2023 Eileen Yoon <eyn at gmx.com>
+ *
+ * Copyright (c) 2014 Rockchip Electronics Co., Ltd.
+ *	Hertz Wong <hertz.wong at rock-chips.com>
+ *	Herman Chen <herman.chen at rock-chips.com>
+ *
+ * Copyright (C) 2014 Google, Inc.
+ *	Tomasz Figa <tfiga at chromium.org>
+ */
+
+#include <media/videobuf2-dma-contig.h>
+
+#include "avd.h"
+#include "avd-inst.h"
+
+#define NEW_TILE_ID	BIT(0)
+#define NEW_SLICE	BIT(1)
+
+#define HEVC_TR_INTRA(v)	FIELD_PREP(GENMASK(4, 1), v)
+#define HEVC_TR_INTER(v)	FIELD_PREP(GENMASK(7, 4), v)
+
+#define HEVC_PCM_EN(v)		FIELD_PREP(BIT(12), !!(v))
+#define HEVC_PCM_BD_LUMA(v)	FIELD_PREP(GENMASK(11, 8), v)
+#define HEVC_PCM_BD_CHROMA(v)	FIELD_PREP(GENMASK(7, 4), v)
+#define HEVC_PCM_CB_MIN_LUMA(v)	FIELD_PREP(GENMASK(3, 2), v)
+#define HEVC_PCM_CB_LUMA(v)	FIELD_PREP(GENMASK(1, 0), v)
+
+#define HEVC_UNK_ISM_EN(v)	FIELD_PREP(BIT(9), !!(v))
+#define HEVC_UNK_FLAG		FIELD_PREP(BIT(3), 1)
+
+#define HEVC_CTB_SIZE(v)	FIELD_PREP(GENMASK(8, 3), v)
+#define HEVC_MERGE_LV(v)	FIELD_PREP(GENMASK(11, 9), v)
+#define HEVC_FLAG_ENTROPY_EN(v)	FIELD_PREP(BIT(12), !!(v))
+#define HEVC_FLAG_TILES_EN(v)	FIELD_PREP(BIT(13), !!(v))
+#define HEVC_FLAG_TQB_EN(v)	FIELD_PREP(BIT(14), !!(v))
+#define HEVC_CU_QP_DD(v)	FIELD_PREP(GENMASK(16, 15), v)
+#define HEVC_FLAG_CU_QP_EN(v)	FIELD_PREP(BIT(17), !!(v))
+#define HEVC_FLAG_TSKIP_EN(v)	FIELD_PREP(BIT(18), !!(v))
+#define HEVC_FLAG_CI_PRED(v)	FIELD_PREP(BIT(19), !!(v))
+#define HEVC_FLAG_SDH_EN(v)	FIELD_PREP(BIT(20), !!(v))
+#define HEVC_FLAG_TMVP_EN(v)	FIELD_PREP(BIT(21), !!(v))
+
+#define HEVC_SCL_DIMS	0x127ffff
+
+static inline u32 mv_color_size(u32 w, u32 h)
+{
+	/* this will waste some memory when max cu size != 64 */
+	return DIV_ROUND_UP(w, 64) * DIV_ROUND_UP(h, 64) * 256;
+}
+
+struct avd_hevc_tile_info {
+	u16 col_width[22];
+	u16 row_height[22];
+	u32 col_bd[23];
+	u32 row_bd[23];
+	u32 *ctb_addr_rs_to_ts;
+	u32 *tile_ids;
+};
+
+struct avd_hevc_run {
+	struct avd_run base;
+	const struct v4l2_ctrl_hevc_slice_params *sl;
+	const struct v4l2_ctrl_hevc_decode_params *decode;
+	const struct v4l2_ctrl_hevc_sps *sps;
+	const struct v4l2_ctrl_hevc_pps *pps;
+	const struct v4l2_ctrl_hevc_scaling_matrix *scaling_matrix;
+	const u32 *entry_point_offsets;
+	int num_entry_point_offsets;
+	int num_slices;
+	struct run_addr {
+		dma_addr_t mv_color;
+	} addresses;
+	struct avd_hevc_tile_info tile_info;
+};
+
+struct avd_hevc_ctx {
+	struct avd_h264_bufs {
+		struct avd_buf mv_above_info;
+		struct avd_buf az_above;
+		struct avd_buf ip_above;
+		struct avd_buf lf_above;
+		struct avd_buf lf_above_info;
+		struct avd_buf lf_left;
+		struct avd_buf lf_left_info;
+		struct avd_buf sw_left;
+	} bufs;
+	int submit_num;
+};
+
+static void stream_refs(struct avd_ctx *ctx, struct avd_hevc_run *run)
+{
+	const struct v4l2_ctrl_hevc_decode_params *decode = run->decode;
+	const struct v4l2_ctrl_hevc_slice_params *sl = &run->sl[0];
+	const struct v4l2_hevc_dpb_entry *dpb;
+	struct avd_hevc_ctx *hevc_ctx = ctx->priv;
+	struct avd_decoded_buffer *dst, *ref_buf;
+
+	dst = vb2_to_avd_decoded_buf(&run->base.bufs.dst->vb2_buf);
+
+	push(0, "");
+	pusha(hevc_ctx->bufs.mv_above_info.addr, "mv_above_info", 7);
+	pusha(run->addresses.mv_color, "mv_color", 0);
+
+	push(0, "");
+	push(0, "");
+	push(0, "");
+	push(0, "");
+
+	for (int i = 0; i < decode->num_active_dpb_entries; i++) {
+		dpb = &decode->dpb[i];
+
+		ref_buf = avd_get_ref_buf(ctx, &dst->base.vb, dpb->timestamp);
+
+		dma_addr_t comp_addr = vb2_dma_contig_plane_dma_addr(
+					       &ref_buf->base.vb.vb2_buf, 0) +
+				       ref_buf->comp.start_offset;
+
+		push(AVD_REF_NUM(decode->num_active_dpb_entries - 1) |
+			     AVD_REF_FLAG_CONST |
+			     AVD_REF_FLAG_LONG(
+				     dpb->flags &
+				     V4L2_HEVC_DPB_ENTRY_LONG_TERM_REFERENCE) |
+			     AVD_REF_DELTA_POC(sl->slice_pic_order_cnt -
+					       dpb->pic_order_cnt_val),
+		     "hdr_d0_ref_hdr");
+
+		push_comp(ctx, comp_addr, ref_buf->comp.offsets);
+	}
+}
+
+static void set_scaling_lists(struct avd_ctx *ctx, struct avd_hevc_run *run)
+{
+	const struct v4l2_ctrl_hevc_scaling_matrix *s = run->scaling_matrix;
+	int i, j, k;
+
+	u8 (*dc_16x16)[3] = (u8(*)[3])s->scaling_list_dc_coef_16x16;
+	u8 (*sc_4x4)[4][4] = (u8(*)[4][4])s->scaling_list_4x4;
+	u8 (*sc_8x8)[2][4][8] = (u8(*)[2][4][8])s->scaling_list_8x8;
+	u8 (*sc_16x16)[2][4][8] = (u8(*)[2][4][8])s->scaling_list_16x16;
+	u8 (*sc_32x32)[2][4][8] = (u8(*)[2][4][8])s->scaling_list_32x32;
+
+	/*
+	 * this should presumably be how many of each are enabled?
+	 * or some other scaling related thing
+	 */
+	push(HEVC_SCL_DIMS, "hdr_7c_pps_scl_dims");
+
+	for (i = 0; i < 2; i++)
+		push(AVD_SCALING_I2(dc_16x16[i][0]) |
+			     AVD_SCALING_I1(dc_16x16[i][1]) |
+			     AVD_SCALING_I0(dc_16x16[i][2]),
+		     "dc_16x16");
+
+	for (i = 0; i < 2; i++)
+		push(AVD_SCALING_I2(s->scaling_list_dc_coef_32x32[i]),
+		     "dc_32x32");
+
+	/* transposed in stride 4 */
+	for (i = 0; i < 6; i++)
+		for (j = 0; j < 4; j++)
+			push(AVD_SCALING_I3(sc_4x4[i][0][j]) |
+				     AVD_SCALING_I2(sc_4x4[i][1][j]) |
+				     AVD_SCALING_I1(sc_4x4[i][2][j]) |
+				     AVD_SCALING_I0(sc_4x4[i][3][j]),
+			     "scaling_4x4");
+
+	/* transposed in stride 8 */
+	for (i = 0; i < 6; i++)
+		for (j = 0; j < 2; j++)
+			for (k = 0; k < 8; k++)
+				push(AVD_SCALING_I3(sc_8x8[i][j][0][k]) |
+					     AVD_SCALING_I2(
+						     sc_8x8[i][j][1][k]) |
+					     AVD_SCALING_I1(
+						     sc_8x8[i][j][2][k]) |
+					     AVD_SCALING_I0(sc_8x8[i][j][3][k]),
+				     "scaling_8x8");
+
+	for (i = 0; i < 6; i++)
+		for (j = 0; j < 2; j++)
+			for (k = 0; k < 8; k++)
+				push(AVD_SCALING_I3(sc_16x16[i][j][0][k]) |
+					     AVD_SCALING_I2(
+						     sc_16x16[i][j][1][k]) |
+					     AVD_SCALING_I1(
+						     sc_16x16[i][j][2][k]) |
+					     AVD_SCALING_I0(
+						     sc_16x16[i][j][3][k]),
+				     "scaling_16x16");
+
+	for (i = 0; i < 2; i++)
+		for (j = 0; j < 2; j++)
+			for (k = 0; k < 8; k++)
+				push(AVD_SCALING_I3(sc_32x32[i][j][0][k]) |
+					     AVD_SCALING_I2(
+						     sc_32x32[i][j][1][k]) |
+					     AVD_SCALING_I1(
+						     sc_32x32[i][j][2][k]) |
+					     AVD_SCALING_I0(
+						     sc_32x32[i][j][3][k]),
+				     "scaling_32x32");
+}
+
+static void hevc_set_flags(struct avd_ctx *ctx, struct avd_hevc_run *run)
+{
+	const struct v4l2_ctrl_hevc_decode_params *decode = run->decode;
+	const struct v4l2_ctrl_hevc_sps *sps = run->sps;
+	const struct v4l2_ctrl_hevc_pps *pps = run->pps;
+
+	u32 log2_ctb_size = ((sps->log2_min_luma_coding_block_size_minus3) +
+			     sps->log2_diff_max_min_luma_coding_block_size);
+
+	push(HEVC_PCM_EN(sps->flags & V4L2_HEVC_SPS_FLAG_PCM_ENABLED) |
+		     HEVC_PCM_BD_LUMA(sps->pcm_sample_bit_depth_luma_minus1) |
+		     HEVC_PCM_BD_CHROMA(
+			     sps->pcm_sample_bit_depth_chroma_minus1) |
+		     HEVC_PCM_CB_MIN_LUMA(
+			     sps->log2_min_pcm_luma_coding_block_size_minus3) |
+		     HEVC_PCM_CB_LUMA(
+			     sps->log2_diff_max_min_pcm_luma_coding_block_size +
+			     sps->log2_min_pcm_luma_coding_block_size_minus3),
+	     "hdr_30_sps_pcm");
+
+	/*
+	 * RExt sets a few new flags here
+	 */
+	push(HEVC_UNK_FLAG |
+		     HEVC_UNK_ISM_EN(
+			     sps->flags &
+			     V4L2_HEVC_SPS_FLAG_STRONG_INTRA_SMOOTHING_ENABLED),
+	     "hdr_34_sps_flags");
+
+	push(HEVC_CTB_SIZE(log2_ctb_size) |
+		     HEVC_MERGE_LV(pps->log2_parallel_merge_level_minus2) |
+		     HEVC_FLAG_ENTROPY_EN(
+			     pps->flags &
+			     V4L2_HEVC_PPS_FLAG_ENTROPY_CODING_SYNC_ENABLED) |
+		     HEVC_FLAG_TILES_EN(pps->flags &
+					V4L2_HEVC_PPS_FLAG_TILES_ENABLED) |
+		     HEVC_FLAG_TQB_EN(
+			     pps->flags &
+			     V4L2_HEVC_PPS_FLAG_TRANSQUANT_BYPASS_ENABLED) |
+		     HEVC_CU_QP_DD(log2_ctb_size -
+				   pps->diff_cu_qp_delta_depth) |
+		     HEVC_FLAG_CU_QP_EN(
+			     pps->flags &
+			     V4L2_HEVC_PPS_FLAG_CU_QP_DELTA_ENABLED) |
+		     HEVC_FLAG_TSKIP_EN(
+			     pps->flags &
+			     V4L2_HEVC_PPS_FLAG_TRANSFORM_SKIP_ENABLED) |
+		     HEVC_FLAG_CI_PRED(
+			     pps->flags &
+			     V4L2_HEVC_PPS_FLAG_CONSTRAINED_INTRA_PRED) |
+		     HEVC_FLAG_SDH_EN(
+			     pps->flags &
+			     V4L2_HEVC_PPS_FLAG_SIGN_DATA_HIDING_ENABLED) |
+		     HEVC_FLAG_TMVP_EN(
+			     !(decode->flags &
+			       V4L2_HEVC_DECODE_PARAM_FLAG_IDR_PIC) &&
+			     sps->flags &
+				     V4L2_HEVC_SPS_FLAG_SPS_TEMPORAL_MVP_ENABLED),
+	     "hdr_5c_pps_flags");
+
+	push(AVD_HDR_H26X_QP_OFFSET_CB(pps->pps_cb_qp_offset) |
+		     AVD_HDR_H26X_QP_OFFSET_CR(pps->pps_cr_qp_offset),
+	     "hdr_60_pps_qp");
+
+	push(0, "hdr_64_zero");
+	push(0, "hdr_68_zero");
+	push(0, "hdr_6c_zero");
+	push(0, "hdr_70_zero");
+	push(0, "hdr_74_zero");
+	push(0, "hdr_78_zero");
+}
+
+static void set_header(struct avd_ctx *ctx, struct avd_hevc_run *run)
+{
+	const struct v4l2_ctrl_hevc_sps *sps = run->sps;
+	struct avd_dev *avd = ctx->dev;
+	struct avd_hevc_ctx *hevc_ctx = ctx->priv;
+	u32 bytesperline;
+	u32 width = sps->pic_width_in_luma_samples;
+	u32 height = sps->pic_height_in_luma_samples;
+
+	bool is_intra = run->sl[0].slice_type == V4L2_HEVC_SLICE_TYPE_I;
+
+	push(AVD_OP_HDR | AVD_OP_HDR_FLAG_DECOMP(ctx->decomp) |
+		     AVD_OP_HDR_FLAG_INTRA(is_intra) | AVD_OP_HDR_CONST |
+		     AVD_OP_HDR_FLAG_PIPE_STATE(
+			     !(avd->variant->quirks & AVD_QUIRK_NO_PIPE_STATE)),
+	     "hdr_34_start_hdr");
+
+	push(AVD_HDR_CODEC_MODE(AVD_CODEC_HEVC), "hdr_50_mode");
+	push(AVD_HDR_HEIGHT(height - 1) | AVD_HDR_WIDTH(width - 1),
+	     "hdr_54_height_width");
+	push(0, "hdr_58_pixfmt_zero");
+
+	push(AVD_HDR_HEIGHT((height - 1) >> 3) |
+		     AVD_HDR_WIDTH((width - 1) >> 3),
+	     "hdr_28_height_width_shift3");
+
+	push(AVD_HDR_COMMON_CHROMA_FORMAT(sps->chroma_format_idc) |
+		     AVD_HDR_COMMON_BIT_DEPTH_C(sps->bit_depth_chroma_minus8) |
+		     AVD_HDR_COMMON_BIT_DEPTH_L(sps->bit_depth_luma_minus8) |
+		     AVD_HDR_COMMON_MIN_LUMA_CBS(
+			     sps->log2_min_luma_coding_block_size_minus3) |
+		     AVD_HDR_COMMON_LUMA_CBS(
+			     sps->log2_diff_max_min_luma_coding_block_size +
+			     sps->log2_min_luma_coding_block_size_minus3) |
+		     AVD_HDR_COMMON_MIN_LUMA_TBS(
+			     sps->log2_min_luma_transform_block_size_minus2) |
+		     AVD_HDR_COMMON_LUMA_TBS(
+			     sps->log2_diff_max_min_luma_transform_block_size +
+			     + sps->log2_min_luma_transform_block_size_minus2) |
+		     HEVC_TR_INTER(sps->max_transform_hierarchy_depth_inter) |
+		     HEVC_TR_INTRA(sps->max_transform_hierarchy_depth_intra) |
+		     AVD_HDR_COMMON_FLAG0(sps->flags &
+					  V4L2_HEVC_SPS_FLAG_AMP_ENABLED),
+	     "hdr_2c_sps_txfm");
+
+	hevc_set_flags(ctx, run);
+
+	push(AVD_HDR_FEAT_H26X | AVD_HDR_FEAT_COMMON |
+		     AVD_HDR_FEAT_PIPE_STATE_EN(
+			     !(avd->variant->quirks & AVD_QUIRK_NO_PIPE_STATE)),
+	     "hdr_98_const_30");
+
+	push(0, "");
+	push(0, "");
+
+	if (avd->variant->revision == 3)
+		push(0, "");
+
+	push(0, "");
+	push(0, "");
+
+	if (avd->variant->revision == 3)
+		push(0, "");
+	else if (!(avd->variant->quirks & AVD_QUIRK_NO_PIPE_STATE))
+		pusha(ctx->pipe_state.addr, "pipe_state", 0);
+
+	pusha(hevc_ctx->bufs.ip_above.addr, "ip_above", 0);
+	pusha(hevc_ctx->bufs.lf_above.addr, "lf_above", 1);
+	pusha(hevc_ctx->bufs.lf_above_info.addr, "lf_above_info", 2);
+	pusha(hevc_ctx->bufs.lf_left.addr, "lf_left", 3);
+	pusha(hevc_ctx->bufs.lf_left_info.addr, "lf_left_info", 4);
+	pusha(hevc_ctx->bufs.az_above.addr, "az_above", 8);
+	pusha(hevc_ctx->bufs.sw_left.addr, "sw_left", 9);
+
+	push(0, "");
+
+	push_comp(ctx, run->base.comp_out, ctx->comp.offsets);
+
+	pusha((u64)0, "packed_fmt_scratch", 0);
+
+	bytesperline = ctx->decoded_fmt.fmt.pix_mp.plane_fmt[0].bytesperline;
+	if (avd->variant->quirks & AVD_QUIRK_LSR)
+		bytesperline = bytesperline >> 4;
+
+	pusha(run->base.y_out, "y_out", 0);
+	push(bytesperline, "y_bpl");
+	pusha(run->base.uv_out, "uv_out", 0);
+	push(bytesperline, "uv_bpl");
+	push(0, "");
+	push(AVD_HDR_HEIGHT(height - 1) | AVD_HDR_WIDTH(width - 1),
+	     "hdr_54_height_width");
+
+	if (!is_intra)
+		stream_refs(ctx, run);
+
+	if ((sps->flags & V4L2_HEVC_SPS_FLAG_SCALING_LIST_ENABLED))
+		set_scaling_lists(ctx, run);
+	else
+		push(0, "");
+}
+
+static void stream_weights(struct avd_ctx *ctx, struct avd_hevc_run *run,
+			   const struct v4l2_ctrl_hevc_slice_params *sl)
+{
+	int luma_weight_denom, chroma_weight_denom;
+	u8 chroma_log2_weight_denom;
+	const struct v4l2_ctrl_hevc_pps *pps = run->pps;
+	const struct v4l2_hevc_pred_weight_table *pred = &sl->pred_weight_table;
+	bool has_luma_weights =
+		((pps->flags & V4L2_HEVC_PPS_FLAG_WEIGHTED_PRED) &&
+		 sl->slice_type == V4L2_HEVC_SLICE_TYPE_P) ||
+		((pps->flags & V4L2_HEVC_PPS_FLAG_WEIGHTED_BIPRED) &&
+		 sl->slice_type == V4L2_HEVC_SLICE_TYPE_B);
+
+	const s8(*delta_chroma_weights)[2];
+	const s8(*chroma_offsets)[2];
+	const s8 *delta_luma_weights;
+	const s8 *luma_offsets;
+
+	if (!has_luma_weights) {
+		push(AVD_OP_WEIGHTS_HDR, "slc_76c_cmd_weights_denom");
+		return;
+	}
+
+	chroma_log2_weight_denom = pred->luma_log2_weight_denom +
+				   pred->delta_chroma_log2_weight_denom;
+
+	push(AVD_OP_WEIGHTS_HDR | AVD_OP_WEIGHTS_HDR_FLAG1(!has_luma_weights) |
+		     AVD_OP_WEIGHTS_HDR_FLAG0(has_luma_weights) |
+		     AVD_OP_WEIGHTS_HDR_LUMA(pred->luma_log2_weight_denom) |
+		     AVD_OP_WEIGHTS_HDR_CHROMA(chroma_log2_weight_denom),
+	     "slc_76c_cmd_weights_denom");
+
+	luma_weight_denom = 1 << pred->luma_log2_weight_denom;
+	chroma_weight_denom = 1 << chroma_log2_weight_denom;
+
+	/* comes from 7.4.7.3 */
+
+	for (int y = 0; y < 2; y++) {
+		if (y == 1 && sl->slice_type != V4L2_HEVC_SLICE_TYPE_B)
+			break;
+		luma_offsets = y == 0 ? pred->luma_offset_l0 :
+					pred->luma_offset_l1;
+		delta_luma_weights =
+			(s8(*))(y == 0 ? pred->delta_luma_weight_l0 :
+					 pred->delta_luma_weight_l1);
+		chroma_offsets = (s8(*)[2])(y == 0 ? pred->chroma_offset_l0 :
+						     pred->chroma_offset_l1);
+		delta_chroma_weights =
+			(s8(*)[2])(y == 0 ? pred->delta_chroma_weight_l0 :
+					    pred->delta_chroma_weight_l1);
+
+		int to = y == 0 ? sl->num_ref_idx_l0_active_minus1 :
+				  sl->num_ref_idx_l1_active_minus1;
+		for (int i = 0; i < to + 1; i++) {
+			if (delta_luma_weights[i] != 0 ||
+			    luma_offsets[i] != 0) {
+				push(AVD_OP_WEIGHTS | AVD_OP_WEIGHTS_IDENT(1) |
+					     AVD_OP_WEIGHTS_LIST_IDX(y) |
+					     AVD_OP_WEIGHTS_INDEX(i) |
+					     AVD_OP_WEIGHTS_WEIGHT(
+						     delta_luma_weights[i] +
+						     luma_weight_denom),
+				     "slc_luma_weights");
+				push(AVD_OP_OFFSETS | AVD_OP_OFFSETS_OFFSET(
+							      luma_offsets[i]),
+				     "slc_luma_offsets");
+			}
+
+			if (delta_chroma_weights[i][0] != 0 ||
+			    chroma_offsets[i][0] != 0 ||
+			    delta_chroma_weights[i][1] != 0 ||
+			    chroma_offsets[i][1] != 0) {
+				push(AVD_OP_WEIGHTS | AVD_OP_WEIGHTS_IDENT(2) |
+					     AVD_OP_WEIGHTS_LIST_IDX(y) |
+					     AVD_OP_WEIGHTS_INDEX(i) |
+					     AVD_OP_WEIGHTS_WEIGHT(
+						     delta_chroma_weights[i][0] +
+						     chroma_weight_denom),
+				     "slc_chroma_weights[0]");
+				push(AVD_OP_OFFSETS |
+					     AVD_OP_OFFSETS_OFFSET(
+						     chroma_offsets[i][0]),
+				     "slc_chroma_offsets[0]");
+				push(AVD_OP_WEIGHTS | AVD_OP_WEIGHTS_IDENT(3) |
+					     AVD_OP_WEIGHTS_LIST_IDX(y) |
+					     AVD_OP_WEIGHTS_INDEX(i) |
+					     AVD_OP_WEIGHTS_WEIGHT(
+						     delta_chroma_weights[i][1] +
+						     chroma_weight_denom),
+				     "slc_chroma_weights[1]");
+				push(AVD_OP_OFFSETS |
+					     AVD_OP_OFFSETS_OFFSET(
+						     chroma_offsets[i][1]),
+				     "slc_chroma_offsets[1]");
+			}
+		}
+	}
+}
+
+static void stream_slice_dqtblk(struct avd_ctx *ctx, struct avd_hevc_run *run,
+				const struct v4l2_ctrl_hevc_slice_params *sl)
+{
+	const struct v4l2_ctrl_hevc_pps *pps = run->pps;
+	const struct v4l2_ctrl_hevc_sps *sps = run->sps;
+
+	push(AVD_OP_QP |
+		     AVD_OP_QP_VAL(pps->init_qp_minus26 + 26 +
+				   sl->slice_qp_delta) |
+		     AVD_OP_QP_CB_OFF(pps->pps_cb_qp_offset +
+				      sl->slice_cb_qp_offset) |
+		     AVD_OP_QP_CR_OFF(pps->pps_cr_qp_offset +
+				      sl->slice_cr_qp_offset),
+	     "slc_bcc_cmd_quantization");
+
+	push(AVD_OP_DBLK |
+		     AVD_OP_DBLK_FLAG_SAO_CHROMA(
+			     sl->flags &
+			     V4L2_HEVC_SLICE_PARAMS_FLAG_SLICE_SAO_CHROMA) |
+		     AVD_OP_DBLK_FLAG_SAO_LUMA(
+			     sl->flags &
+			     V4L2_HEVC_SLICE_PARAMS_FLAG_SLICE_SAO_LUMA) |
+		     AVD_OP_DBLK_OFF0(sl->slice_tc_offset_div2) |
+		     AVD_OP_DBLK_OFF1(sl->slice_beta_offset_div2) |
+		     /* i wonder what this should actually be */
+		     AVD_OP_DBLK_FLAG_EN(
+			     !(sl->flags &
+			       V4L2_HEVC_SLICE_PARAMS_FLAG_SLICE_DEBLOCKING_FILTER_DISABLED) &&
+			     (sps->flags &
+				      V4L2_HEVC_SPS_FLAG_STRONG_INTRA_SMOOTHING_ENABLED ||
+			      sl->flags &
+				      V4L2_HEVC_SLICE_PARAMS_FLAG_SLICE_SAO_LUMA ||
+			      pps->flags &
+				      (V4L2_HEVC_PPS_FLAG_DEBLOCKING_FILTER_OVERRIDE_ENABLED |
+				       V4L2_HEVC_PPS_FLAG_DEBLOCKING_FILTER_CONTROL_PRESENT))) |
+		     AVD_OP_DBLK_FLAG_FULL_EN(
+			     sl->flags &
+			     V4L2_HEVC_SLICE_PARAMS_FLAG_SLICE_LOOP_FILTER_ACROSS_SLICES_ENABLED) |
+		     AVD_OP_DBLK_FLAG_TILES_EN(
+			     !(pps->flags & V4L2_HEVC_PPS_FLAG_TILES_ENABLED) ||
+			     (pps->flags &
+			      V4L2_HEVC_PPS_FLAG_LOOP_FILTER_ACROSS_TILES_ENABLED)) |
+		     AVD_OP_DBLK_FLAG_PCM_EN(
+			     (sps->flags & V4L2_HEVC_SPS_FLAG_PCM_ENABLED) &&
+			     !(sps->flags &
+			       V4L2_HEVC_SPS_FLAG_PCM_LOOP_FILTER_DISABLED)),
+	     "slc_bd0_cmd_deblocking_filter");
+
+	if (sl->slice_type == V4L2_HEVC_SLICE_TYPE_B ||
+	    sl->slice_type == V4L2_HEVC_SLICE_TYPE_P) {
+		for (int i = 0; i < sl->num_ref_idx_l0_active_minus1 + 1; i++)
+			push(AVD_OP_REF | AVD_OP_REF_LIST_IDX(0) |
+				     AVD_OP_REF_LOOP_IDX(i) |
+				     AVD_OP_REF_DBP_IDX(sl->ref_idx_l0[i]),
+			     "reference_frames_l0");
+		if (sl->slice_type == V4L2_HEVC_SLICE_TYPE_B)
+			for (int i = 0;
+			     i < sl->num_ref_idx_l1_active_minus1 + 1; i++)
+				push(AVD_OP_REF | AVD_OP_REF_LIST_IDX(1) |
+					     AVD_OP_REF_LOOP_IDX(i) |
+					     AVD_OP_REF_DBP_IDX(
+						     sl->ref_idx_l1[i]),
+				     "reference_frames_l1");
+
+		stream_weights(ctx, run, sl);
+	}
+}
+
+static void stream_slice_mv(struct avd_ctx *ctx, struct avd_hevc_run *run,
+			    const struct v4l2_ctrl_hevc_slice_params *sl,
+			    bool is_first)
+{
+	const struct v4l2_ctrl_hevc_decode_params *decode = run->decode;
+	struct avd_decoded_buffer *dst, *ref;
+	bool ref_valid;
+	const u8 *ref_list;
+
+	if (sl->slice_type == V4L2_HEVC_SLICE_TYPE_I) {
+		push(AVD_OP_SL_REF |
+			     AVD_OP_SL_REF_SLICE_I(sl->slice_type ==
+						   V4L2_HEVC_SLICE_TYPE_I),
+		     "slc_a8c_cmd_ref_type");
+		return;
+	}
+	/* bidirectional prediction */
+
+	ref_list = sl->slice_type == V4L2_HEVC_SLICE_TYPE_P ? sl->ref_idx_l0 :
+		   sl->flags & V4L2_HEVC_SLICE_PARAMS_FLAG_COLLOCATED_FROM_L0 ?
+							      sl->ref_idx_l0 :
+							      sl->ref_idx_l1;
+
+	dst = vb2_to_avd_decoded_buf(&run->base.bufs.dst->vb2_buf);
+	ref = avd_get_ref_buf(
+		ctx, &dst->base.vb,
+		decode->dpb[ref_list[sl->collocated_ref_idx]].timestamp);
+
+	ref_valid = !(sl->flags &
+		      V4L2_HEVC_SLICE_PARAMS_FLAG_DEPENDENT_SLICE_SEGMENT) &&
+		    (sl->flags &
+		     V4L2_HEVC_SLICE_PARAMS_FLAG_SLICE_TEMPORAL_MVP_ENABLED) &&
+		    is_first && !ref->hevc.is_intra;
+
+	push(AVD_OP_SL_REF |
+		     AVD_OP_SL_REF_MAX_MERGE(
+			     5 - sl->five_minus_max_num_merge_cand) |
+		     AVD_OP_SL_REF_FLAG_CABAC(
+			     sl->flags &
+			     V4L2_HEVC_SLICE_PARAMS_FLAG_CABAC_INIT) |
+		     AVD_OP_SL_REF_FLAG0(
+			     (sl->flags &
+			      V4L2_HEVC_SLICE_PARAMS_FLAG_SLICE_TEMPORAL_MVP_ENABLED) &&
+			     !(sl->flags &
+			       V4L2_HEVC_SLICE_PARAMS_FLAG_COLLOCATED_FROM_L0)) |
+		     AVD_OP_SL_REF_FLAG1(
+			     !(sl->flags &
+			       V4L2_HEVC_SLICE_PARAMS_FLAG_MVD_L1_ZERO)) |
+		     AVD_OP_SL_REF_FLAG2(
+			     (sl->flags &
+			      V4L2_HEVC_SLICE_PARAMS_FLAG_SLICE_TEMPORAL_MVP_ENABLED) ||
+			     (sl->flags &
+			      V4L2_HEVC_SLICE_PARAMS_FLAG_DEPENDENT_SLICE_SEGMENT)) |
+		     AVD_OP_SL_REF_NUM_L0(sl->num_ref_idx_l0_active_minus1) |
+		     AVD_OP_SL_REF_NUM_L1(sl->num_ref_idx_l1_active_minus1) |
+		     AVD_OP_SL_REF_SLICE_P(sl->slice_type ==
+					   V4L2_HEVC_SLICE_TYPE_P) |
+		     AVD_OP_SL_REF_SLICE_B(ref_valid),
+	     "slc_a8c_cmd_ref_type");
+
+	if (ref_valid) {
+		dma_addr_t mv_color_addr =
+			vb2_dma_contig_plane_dma_addr(&ref->base.vb.vb2_buf,
+						      0) +
+			(ref->base.vb.planes[0].length -
+			 mv_color_size(fmt_width(ctx), fmt_height(ctx)));
+		pusha(mv_color_addr, "slc_bd4_sps_tile_addr2_lsb8",
+		      decode->dpb[ref_list[sl->collocated_ref_idx]]
+			      .pic_order_cnt_val);
+	}
+}
+
+static void set_slice(struct avd_ctx *ctx, struct avd_hevc_run *run,
+		      const struct v4l2_ctrl_hevc_slice_params *sl, u32 size,
+		      u32 offset, u32 flags)
+{
+	dma_addr_t slc_addr =
+		run->base.coded_in + offset + sl->data_byte_offset;
+	push(AVD_OP_CODED_DATA | flags | AVD_OP_CODED_DATA_ADDR(slc_addr >> 32),
+	     "cm3_cmd_set_coded_slice");
+	push((u32)(slc_addr & 0xffffffff), "slc_bd8_slice_addr");
+	push(size, "slc_bdc_slice_size");
+}
+
+static int submit_slice_segment(struct avd_ctx *ctx, struct avd_hevc_run *run,
+				const struct v4l2_ctrl_hevc_slice_params *sl,
+				int row, int col, u32 col_bd[23],
+				u32 row_bd[23], u32 pic_in_cts_width,
+				u32 pic_in_cts_height, bool first_slice,
+				bool hflip, bool vflip, u32 coded_flags,
+				u32 last_tile_block)
+{
+	const struct v4l2_ctrl_hevc_pps *pps = run->pps;
+	u32 tb_x, tb_y, tile_block, tile_boundary;
+
+	if (coded_flags & NEW_SLICE) {
+		tb_x = sl->slice_segment_addr % pic_in_cts_width;
+		tb_y = sl->slice_segment_addr / pic_in_cts_width;
+
+		tile_block = AVD_OP_SL_LOC_Y(tb_y) | AVD_OP_SL_LOC_X(tb_x);
+
+		if (!(sl->flags &
+		      V4L2_HEVC_SLICE_PARAMS_FLAG_DEPENDENT_SLICE_SEGMENT))
+			last_tile_block = tile_block;
+
+		/*
+		 * tile block start
+		 * CABAC window
+		 */
+		push(AVD_OP_SL_LOC | last_tile_block, "cm3_cmd_set_cabac_xy");
+
+		stream_slice_dqtblk(ctx, run, sl);
+	} else {
+		tile_boundary = AVD_OP_SL_LOC_Y(row_bd[row]) |
+				AVD_OP_SL_LOC_X(col_bd[col]);
+	}
+
+	if (coded_flags & NEW_TILE_ID) {
+		push(AVD_OP_SL_DIM_START |
+			     (coded_flags & NEW_SLICE ? tile_block :
+							tile_boundary),
+		     "cm3_cmd_set_ctb_xy");
+
+		/* tile boundary end */
+		if (pps->flags & V4L2_HEVC_PPS_FLAG_TILES_ENABLED)
+			push(AVD_SL_DIM_END_ROW((hflip ? 4 : 0) |
+						(vflip ? 8 : 0)) |
+				     AVD_SL_DIM_END_COL(col) |
+				     AVD_SL_DIM_END_Y(row_bd[row + 1] - 1) |
+				     AVD_SL_DIM_END_X(col_bd[col + 1] - 1),
+			     "cm3_set_ctb_xy");
+		else /* first slice, one CTB */
+			push(AVD_SL_DIM_END_Y(pic_in_cts_height - 1) |
+				     AVD_SL_DIM_END_X(pic_in_cts_width - 1),
+			     "cm3_set_ctb_xy");
+	}
+
+	if (coded_flags & NEW_SLICE)
+		stream_slice_mv(ctx, run, sl, first_slice);
+
+	/* current tile block / boundary ?? */
+	/* Unlike entropy, motion vector window resets every time */
+	push(AVD_SL_DIM_END_COL(1) |
+		     (coded_flags & NEW_SLICE ? tile_block : tile_boundary),
+	     "cm3_set_mv_xy");
+
+	return last_tile_block;
+}
+
+static void compute_tiles_uniform(struct avd_hevc_run *run,
+				  u16 log2_min_cb_size, u16 width, u16 height,
+				  s32 pic_in_cts_width, s32 pic_in_cts_height,
+				  u16 *column_width, u16 *row_height)
+{
+	const struct v4l2_ctrl_hevc_pps *pps = run->pps;
+	int i;
+
+	for (i = 0; i < pps->num_tile_columns_minus1 + 1; i++)
+		column_width[i] = ((i + 1) * pic_in_cts_width) /
+					  (pps->num_tile_columns_minus1 + 1) -
+				  (i * pic_in_cts_width) /
+					  (pps->num_tile_columns_minus1 + 1);
+
+	for (i = 0; i < pps->num_tile_rows_minus1 + 1; i++)
+		row_height[i] = ((i + 1) * pic_in_cts_height) /
+					(pps->num_tile_rows_minus1 + 1) -
+				(i * pic_in_cts_height) /
+					(pps->num_tile_rows_minus1 + 1);
+}
+
+static void compute_tiles_non_uniform(struct avd_hevc_run *run,
+				      u16 log2_min_cb_size, u16 width,
+				      u16 height, s32 pic_in_cts_width,
+				      s32 pic_in_cts_height, u16 *column_width,
+				      u16 *row_height)
+{
+	const struct v4l2_ctrl_hevc_pps *pps = run->pps;
+	s32 sum = 0;
+	int i;
+
+	for (i = 0; i < pps->num_tile_columns_minus1; i++) {
+		column_width[i] = pps->column_width_minus1[i] + 1;
+		sum += column_width[i];
+	}
+	column_width[i] = pic_in_cts_width - sum;
+
+	sum = 0;
+	for (i = 0; i < pps->num_tile_rows_minus1; i++) {
+		row_height[i] = pps->row_height_minus1[i] + 1;
+		sum += row_height[i];
+	}
+	row_height[i] = pic_in_cts_height - sum;
+}
+
+static void compute_bd(struct avd_hevc_run *run, u32 *col_bd, u32 *row_bd,
+		       u16 *column_width, u16 *row_height)
+{
+	const struct v4l2_ctrl_hevc_pps *pps = run->pps;
+	int i;
+
+	for (col_bd[0] = 0, i = 0; i <= pps->num_tile_columns_minus1; i++)
+		col_bd[i + 1] = col_bd[i] + column_width[i];
+
+	for (row_bd[0] = 0, i = 0; i <= pps->num_tile_rows_minus1; i++)
+		row_bd[i + 1] = row_bd[i] + row_height[i];
+}
+
+static void compute_rs_to_ts(struct avd_hevc_run *run, u32 pic_in_ctbs_size,
+			     u32 pic_in_ctbs_width, u32 *col_bd, u32 *row_bd,
+			     u16 *col_width, u16 *row_height,
+			     u32 *ctb_addr_rs_to_ts)
+{
+	const struct v4l2_ctrl_hevc_pps *pps = run->pps;
+	int i, j, tb_x, tb_y, tile_x = 0, tile_y = 0;
+	u32 ctb_addr_rs;
+
+	for (ctb_addr_rs = 0; ctb_addr_rs < pic_in_ctbs_size; ctb_addr_rs++) {
+		tb_x = ctb_addr_rs % pic_in_ctbs_width;
+		tb_y = ctb_addr_rs / pic_in_ctbs_width;
+		for (i = 0; i <= pps->num_tile_columns_minus1; i++)
+			if (tb_x >= col_bd[i])
+				tile_x = i;
+		for (j = 0; j <= pps->num_tile_rows_minus1; j++)
+			if (tb_y >= row_bd[j])
+				tile_y = j;
+		ctb_addr_rs_to_ts[ctb_addr_rs] = 0;
+		for (i = 0; i < tile_x; i++)
+			ctb_addr_rs_to_ts[ctb_addr_rs] +=
+				row_height[tile_y] * col_width[i];
+		for (j = 0; j < tile_y; j++)
+			ctb_addr_rs_to_ts[ctb_addr_rs] +=
+				pic_in_ctbs_width * row_height[j];
+		ctb_addr_rs_to_ts[ctb_addr_rs] +=
+			(tb_y - row_bd[tile_y]) * col_width[tile_x] + tb_x -
+			col_bd[tile_x];
+	}
+}
+
+static void compute_tile_ids(struct avd_hevc_run *run, u32 pic_in_ctbs_width,
+			     u32 *col_bd, u32 *row_bd, u32 *ctb_addr_rs_to_ts,
+			     u32 *tile_ids)
+{
+	const struct v4l2_ctrl_hevc_pps *pps = run->pps;
+	int j, i, y, x;
+	u32 tile_idx;
+
+	for (j = 0, tile_idx = 0; j <= pps->num_tile_rows_minus1; j++)
+		for (i = 0; i <= pps->num_tile_columns_minus1; i++, tile_idx++)
+			for (y = row_bd[j]; y < row_bd[j + 1]; y++)
+				for (x = col_bd[i]; x < col_bd[i + 1]; x++)
+					tile_ids[ctb_addr_rs_to_ts
+							 [y * pic_in_ctbs_width +
+							  x]] = tile_idx;
+}
+
+struct sl_ctx {
+	u32 ctx_col;
+	u32 ctx_row;
+	s32 q1_col;
+	s32 q1_row;
+};
+
+static void stream_slices(struct avd_ctx *ctx, struct avd_hevc_run *run)
+{
+	const struct v4l2_ctrl_hevc_sps *sps = run->sps;
+	const struct v4l2_ctrl_hevc_pps *pps = run->pps;
+	struct avd_hevc_ctx *hevc_ctx = ctx->priv;
+	const struct v4l2_ctrl_hevc_slice_params *sl;
+	struct avd_hevc_tile_info *tile_info = &run->tile_info;
+	bool tiles_enabled, first_slice, first_segment;
+	bool hflip, vflip;
+	int slice_segment_offset, entry_point_idx = 0, pos = 0, offset = 0;
+	int row, col, i, s, to;
+	int slice_flag, size, new_offset;
+	int tile_id, last_tile_id;
+	u16 log2_min_cb_size, width, height;
+	s32 max_cu_width, pic_in_ctbs_width, pic_in_ctbs_height;
+	u32 num_cols, last_tile_block = 0;
+	struct sl_ctx last = {
+		.q1_col = -1,
+		.q1_row = -1,
+	};
+
+	width = sps->pic_width_in_luma_samples;
+	height = sps->pic_height_in_luma_samples;
+
+	tiles_enabled = !!(pps->flags & V4L2_HEVC_PPS_FLAG_TILES_ENABLED);
+
+	log2_min_cb_size = sps->log2_min_luma_coding_block_size_minus3 + 3;
+
+	num_cols = pps->num_tile_columns_minus1 + 1;
+
+	max_cu_width = 1 << (sps->log2_diff_max_min_luma_coding_block_size +
+			     log2_min_cb_size);
+	pic_in_ctbs_width = (width + max_cu_width - 1) / max_cu_width;
+	pic_in_ctbs_height = (height + max_cu_width - 1) / max_cu_width;
+
+	for (s = 0; s < run->num_slices; s++) {
+		sl = &run->sl[s];
+		slice_segment_offset = 0;
+		to = tiles_enabled ? sl->num_entry_point_offsets + 1 : 1;
+		/*
+		 * since we are the only one using entry_point_offsets verify
+		 * that we actually have enough.
+		 */
+		if (tiles_enabled &&
+		    sl->num_entry_point_offsets + entry_point_idx >
+			    run->num_entry_point_offsets + 1) {
+			return;
+		}
+
+		for (i = 0; i < to; i++) {
+			first_segment = i == 0;
+			first_slice = s == 0;
+
+			ctx->job.num++;
+
+			if (tiles_enabled && to > 1) {
+				if (i < sl->num_entry_point_offsets) {
+					size = run->entry_point_offsets
+						       [entry_point_idx++];
+					new_offset = size;
+				} else {
+					size = sl->bit_size / 8 -
+					       sl->data_byte_offset -
+					       slice_segment_offset;
+					new_offset =
+						size + sl->data_byte_offset;
+				}
+			} else {
+				size = (sl->bit_size) / 8 -
+				       sl->data_byte_offset;
+				new_offset = size + sl->data_byte_offset;
+			}
+
+			tile_id = tile_info->tile_ids
+					  [tile_info->ctb_addr_rs_to_ts
+						   [sl->slice_segment_addr]];
+			last_tile_id =
+				first_slice ?
+					-1 :
+					tile_info->tile_ids
+						[tile_info->ctb_addr_rs_to_ts
+							 [run->sl[s - 1]
+								  .slice_segment_addr]];
+
+			slice_flag = 0;
+			if (first_slice || tile_id != last_tile_id ||
+			    !first_segment)
+				slice_flag |= NEW_TILE_ID;
+
+			if (first_segment)
+				slice_flag |= NEW_SLICE;
+
+			vflip = false;
+			hflip = false;
+
+			row = pos / num_cols;
+			col = pos % num_cols;
+
+			/*
+			 * in the JCT-VC-HEVC_V1 tests only TILES_B_Cisco_1
+			 * seems affected and it only seems to need hflip.
+			 *
+			 * If there is a spec equivalent or a better way to
+			 * represent this, it could not be found.
+			 */
+			if (slice_flag & NEW_TILE_ID) {
+				if ((col >= last.ctx_col &&
+				     row > last.ctx_row) ||
+				    (col <= last.q1_col && row > last.q1_row))
+					vflip = true;
+
+				if (!(slice_flag & NEW_SLICE)) {
+					if (row && row == last.ctx_row + 1) {
+						hflip = true;
+						if (!vflip) {
+							last.q1_row = row;
+							last.q1_col = col;
+						}
+					}
+				} else {
+					last.ctx_row = row;
+					last.ctx_col = col;
+					last.q1_row = -1;
+					last.q1_col = -1;
+				}
+			}
+
+			set_slice(
+				ctx, run, sl, size,
+				offset + slice_segment_offset,
+				/* makes WPP more reliable? But not really ?? */
+				(sl->flags & V4L2_HEVC_SLICE_PARAMS_FLAG_DEPENDENT_SLICE_SEGMENT ?
+					 slice_flag & ~NEW_SLICE :
+					 slice_flag)
+					<< 13);
+
+			last_tile_block = submit_slice_segment(
+				ctx, run, sl, row, col, tile_info->col_bd,
+				tile_info->row_bd, pic_in_ctbs_width,
+				pic_in_ctbs_height, first_slice, hflip, vflip,
+				slice_flag, last_tile_block);
+
+			if (slice_flag & NEW_TILE_ID)
+				pos++;
+
+			slice_segment_offset += new_offset;
+		}
+		offset += sl->bit_size / 8;
+	}
+
+	hevc_ctx->submit_num = pos;
+}
+
+static void update_dec_buf_info(struct avd_decoded_buffer *buf,
+				const struct v4l2_ctrl_hevc_slice_params *sl)
+{
+	buf->hevc.is_intra = sl->slice_type == V4L2_HEVC_SLICE_TYPE_I;
+}
+
+static void avd_hevc_adjust_decoded_fmt(struct avd_ctx *ctx,
+					struct v4l2_pix_format_mplane *pix_mp)
+{
+	pix_mp->plane_fmt[0].sizeimage +=
+		mv_color_size(pix_mp->width, pix_mp->height);
+}
+
+static enum avd_image_fmt avd_hevc_get_image_fmt(struct avd_ctx *ctx,
+						 struct v4l2_ctrl *ctrl)
+{
+	const struct v4l2_ctrl_hevc_sps *sps = ctrl->p_new.p_hevc_sps;
+
+	if (ctrl->id != V4L2_CID_STATELESS_HEVC_SPS)
+		return AVD_IMG_FMT_ANY;
+
+	/*
+	 * TODO: we can do up to 4:4:4 12 bit
+	 * not sure if v4l2 supports RExt
+	 */
+
+	if (sps->bit_depth_luma_minus8 == 0) {
+		if (sps->chroma_format_idc == 2)
+			return AVD_IMG_FMT_422_8BIT;
+		else
+			return AVD_IMG_FMT_420_8BIT;
+	} else if (sps->bit_depth_luma_minus8 == 2) {
+		if (sps->chroma_format_idc == 2)
+			return AVD_IMG_FMT_422_10BIT;
+		else
+			return AVD_IMG_FMT_420_10BIT;
+	}
+
+	return AVD_IMG_FMT_ANY;
+}
+
+static int avd_hevc_validate_sps(struct avd_ctx *ctx,
+				 const struct v4l2_ctrl_hevc_sps *sps)
+{
+	if (sps->pic_width_in_luma_samples > ctx->coded_fmt.fmt.pix_mp.width ||
+	    sps->pic_height_in_luma_samples > ctx->coded_fmt.fmt.pix_mp.height)
+		return -EINVAL;
+
+	if (sps->bit_depth_chroma_minus8 != sps->bit_depth_luma_minus8)
+		return -EINVAL;
+
+	if (sps->bit_depth_luma_minus8 > 2)
+		/* not implemented */
+		return -EINVAL;
+
+	return 0;
+}
+
+static int avd_hevc_start(struct avd_ctx *ctx)
+{
+	struct avd_hevc_ctx *hevc_ctx;
+
+	hevc_ctx = kzalloc_obj(*hevc_ctx, GFP_KERNEL);
+	if (!hevc_ctx)
+		return -ENOMEM;
+
+	ctx->priv = hevc_ctx;
+
+	return 0;
+}
+
+static int avd_hevc_alloc_scratch(struct avd_ctx *ctx, struct avd_hevc_run *run)
+{
+	struct avd_hevc_ctx *hevc_ctx = ctx->priv;
+	struct avd_dev *avd = ctx->dev;
+	const struct v4l2_ctrl_hevc_sps *sps = run->sps;
+	const struct v4l2_ctrl_hevc_pps *pps = run->pps;
+	struct avd_hevc_tile_info *tile_info = &run->tile_info;
+	int i, ret, w, bit_depth, max_col = 0, max_row = 0, cols, rows;
+	int log2_min_cb_size, max_cu_width;
+
+	cols = pps->num_tile_columns_minus1 + 1;
+	rows = pps->num_tile_rows_minus1 + 1;
+	bit_depth = sps->bit_depth_luma_minus8 + 8;
+	w = sps->pic_width_in_luma_samples;
+
+	log2_min_cb_size = sps->log2_min_luma_coding_block_size_minus3 + 3;
+	max_cu_width = 1 << (sps->log2_diff_max_min_luma_coding_block_size +
+			     log2_min_cb_size);
+
+	for (i = 0; i < cols; i++)
+		max_col = tile_info->col_width[i] > max_col ?
+				  tile_info->col_width[i] :
+				  max_col;
+
+	for (i = 0; i < rows; i++)
+		max_row = tile_info->row_height[i] > max_row ?
+				  tile_info->row_height[i] :
+				  max_row;
+
+	ret = avd_buf_alloc(avd, &hevc_ctx->bufs.ip_above,
+			    ((max_col * max_cu_width) / 4) * bit_depth);
+	if (ret)
+		return ret;
+
+	ret = avd_buf_alloc(avd, &hevc_ctx->bufs.mv_above_info,
+			    ((max_col * max_cu_width) / 16) * 20);
+	if (ret)
+		return ret;
+
+	ret = avd_buf_alloc(avd, &hevc_ctx->bufs.lf_above,
+			    DIV_ROUND_UP(w, 16) * bit_depth * (10 + 6) +
+				    (cols - 1) * 256);
+	if (ret)
+		return ret;
+
+	ret = avd_buf_alloc(avd, &hevc_ctx->bufs.lf_above_info,
+			    DIV_ROUND_UP(w + 7, 16) * 36 + cols * 128);
+	if (ret)
+		return ret;
+
+	if (pps->flags & V4L2_HEVC_PPS_FLAG_TILES_ENABLED) {
+		ret = avd_buf_alloc(avd, &hevc_ctx->bufs.lf_left,
+				    ((max_row * max_cu_width) / 4) * 36 +
+					    144 /* why? */);
+		if (ret)
+			return ret;
+		ret = avd_buf_alloc(avd, &hevc_ctx->bufs.lf_left_info,
+				    ((max_row * max_cu_width) / 4) * 9);
+		if (ret)
+			return ret;
+
+		ret = avd_buf_alloc(avd, &hevc_ctx->bufs.az_above,
+				    /* not sure if its cols or rows */
+				    DIV_ROUND_UP(w, 64) * 144 +
+					    (cols - 1) * 128);
+		if (ret)
+			return ret;
+
+		ret = avd_buf_alloc(avd, &hevc_ctx->bufs.sw_left,
+				    ((max_row * max_cu_width) / 4) * 216);
+		if (ret)
+			return ret;
+	} else {
+		ret = avd_buf_alloc(avd, &hevc_ctx->bufs.az_above,
+				    DIV_ROUND_UP(w + 7, 16) * 4 * bit_depth);
+		if (ret)
+			return ret;
+	}
+
+	return 0;
+}
+
+static int avd_hevc_compute_tiles(struct avd_ctx *ctx, struct avd_hevc_run *run)
+{
+	const struct v4l2_ctrl_hevc_sps *sps = run->sps;
+	const struct v4l2_ctrl_hevc_pps *pps = run->pps;
+	bool tiles_enabled;
+	struct avd_hevc_tile_info *tile_info = &run->tile_info;
+	u16 log2_min_cb_size, width, height;
+	s32 max_cu_width, pic_in_ctbs_width, pic_in_ctbs_height,
+		pic_in_ctbs_size;
+
+	width = sps->pic_width_in_luma_samples;
+	height = sps->pic_height_in_luma_samples;
+
+	log2_min_cb_size = sps->log2_min_luma_coding_block_size_minus3 + 3;
+
+	max_cu_width = 1 << (sps->log2_diff_max_min_luma_coding_block_size +
+			     log2_min_cb_size);
+	pic_in_ctbs_width = (width + max_cu_width - 1) / max_cu_width;
+	pic_in_ctbs_height = (height + max_cu_width - 1) / max_cu_width;
+	pic_in_ctbs_size = pic_in_ctbs_height * pic_in_ctbs_width;
+
+	tiles_enabled = !!(pps->flags & V4L2_HEVC_PPS_FLAG_TILES_ENABLED);
+
+	tile_info->ctb_addr_rs_to_ts = kzalloc_objs(
+		*tile_info->ctb_addr_rs_to_ts, pic_in_ctbs_size, GFP_KERNEL);
+	if (!tile_info->ctb_addr_rs_to_ts)
+		return -ENOMEM;
+
+	tile_info->tile_ids = kzalloc_objs(*tile_info->tile_ids,
+					   pic_in_ctbs_size, GFP_KERNEL);
+	if (!tile_info->tile_ids)
+		return -ENOMEM;
+
+	if (tiles_enabled) {
+		if (pps->flags & V4L2_HEVC_PPS_FLAG_UNIFORM_SPACING) {
+			compute_tiles_uniform(run, log2_min_cb_size, width,
+					      height, pic_in_ctbs_width,
+					      pic_in_ctbs_height,
+					      tile_info->col_width,
+					      tile_info->row_height);
+		} else {
+			compute_tiles_non_uniform(run, log2_min_cb_size, width,
+						  height, pic_in_ctbs_width,
+						  pic_in_ctbs_height,
+						  tile_info->col_width,
+						  tile_info->row_height);
+		}
+
+		compute_bd(run, tile_info->col_bd, tile_info->row_bd,
+			   tile_info->col_width, tile_info->row_height);
+
+		/*
+		 * 6.5.1 CTB raster and tile scanning conversion process
+		 * (6-7) and (6-9)
+		 *
+		 * we really only need to know if tileidx != last tileidx
+		 * so maybe there is a better way
+		 */
+		compute_rs_to_ts(run, pic_in_ctbs_size, pic_in_ctbs_width,
+				 tile_info->col_bd, tile_info->row_bd,
+				 tile_info->col_width, tile_info->row_height,
+				 tile_info->ctb_addr_rs_to_ts);
+		compute_tile_ids(run, pic_in_ctbs_width, tile_info->col_bd,
+				 tile_info->row_bd,
+				 tile_info->ctb_addr_rs_to_ts,
+				 tile_info->tile_ids);
+	} else {
+		tile_info->col_width[0] =
+			(width + max_cu_width - 1) / max_cu_width;
+		tile_info->row_height[0] =
+			(height + max_cu_width - 1) / max_cu_width;
+	}
+
+	return 0;
+}
+
+static void avd_hevc_stop(struct avd_ctx *ctx)
+{
+	struct avd_hevc_ctx *hevc_ctx = ctx->priv;
+	struct avd_dev *avd = ctx->dev;
+
+	if (!hevc_ctx)
+		return;
+
+	avd_buf_free(avd, &hevc_ctx->bufs.mv_above_info);
+	avd_buf_free(avd, &hevc_ctx->bufs.az_above);
+	avd_buf_free(avd, &hevc_ctx->bufs.ip_above);
+	avd_buf_free(avd, &hevc_ctx->bufs.lf_above);
+	avd_buf_free(avd, &hevc_ctx->bufs.lf_above_info);
+	avd_buf_free(avd, &hevc_ctx->bufs.lf_left);
+	avd_buf_free(avd, &hevc_ctx->bufs.lf_left_info);
+	avd_buf_free(avd, &hevc_ctx->bufs.sw_left);
+
+	kfree(hevc_ctx);
+}
+
+static int avd_hevc_run_preamble(struct avd_ctx *ctx, struct avd_hevc_run *run)
+{
+	struct v4l2_ctrl *ctrl;
+	u32 dst_len, mv_color_len;
+
+	ctrl = v4l2_ctrl_find(&ctx->ctrl_hdl,
+			      V4L2_CID_STATELESS_HEVC_DECODE_PARAMS);
+	run->decode = ctrl ? ctrl->p_cur.p : NULL;
+	ctrl = v4l2_ctrl_find(&ctx->ctrl_hdl,
+			      V4L2_CID_STATELESS_HEVC_SLICE_PARAMS);
+	run->sl = ctrl ? ctrl->p_cur.p : NULL;
+	run->num_slices = ctrl ? ctrl->new_elems : 0;
+	ctrl = v4l2_ctrl_find(&ctx->ctrl_hdl, V4L2_CID_STATELESS_HEVC_SPS);
+	run->sps = ctrl ? ctrl->p_cur.p : NULL;
+	ctrl = v4l2_ctrl_find(&ctx->ctrl_hdl, V4L2_CID_STATELESS_HEVC_PPS);
+	run->pps = ctrl ? ctrl->p_cur.p : NULL;
+	ctrl = v4l2_ctrl_find(&ctx->ctrl_hdl,
+			      V4L2_CID_STATELESS_HEVC_SCALING_MATRIX);
+	run->scaling_matrix = ctrl ? ctrl->p_cur.p : NULL;
+
+	ctrl = v4l2_ctrl_find(&ctx->ctrl_hdl,
+			      V4L2_CID_STATELESS_HEVC_ENTRY_POINT_OFFSETS);
+	run->entry_point_offsets = ctrl ? ctrl->p_cur.p : NULL;
+	if (!run->entry_point_offsets)
+		return -EINVAL;
+	run->num_entry_point_offsets = ctrl ? ctrl->new_elems : 0;
+
+	avd_run_preamble(ctx, &run->base);
+
+	dst_len = run->base.bufs.dst->vb2_buf.planes[0].length;
+
+	mv_color_len = mv_color_size(fmt_width(ctx), fmt_height(ctx));
+
+	run->addresses.mv_color = run->base.y_out + (dst_len - mv_color_len);
+	return 0;
+}
+
+static int avd_hevc_run(struct avd_ctx *ctx)
+{
+	struct avd_hevc_run run;
+	struct avd_decoded_buffer *dst;
+	int ret;
+
+	ret = avd_hevc_run_preamble(ctx, &run);
+	if (ret)
+		return ret;
+
+	dst = vb2_to_avd_decoded_buf(&run.base.bufs.dst->vb2_buf);
+	update_dec_buf_info(dst, &run.sl[0]);
+
+	ret = avd_hevc_compute_tiles(ctx, &run);
+	if (ret)
+		goto done;
+
+	ret = avd_hevc_alloc_scratch(ctx, &run);
+	if (ret)
+		goto done;
+
+	ret = avd_init_job(ctx, AVD_CODEC_HEVC,
+			   run.num_slices + run.num_entry_point_offsets + 1);
+	if (ret)
+		goto done;
+
+	set_header(ctx, &run);
+	stream_slices(ctx, &run);
+	avd_run_postamble(ctx, &run.base);
+
+	ret = avd_submit_job(ctx);
+done:
+	kfree(run.tile_info.ctb_addr_rs_to_ts);
+	kfree(run.tile_info.tile_ids);
+	kfree(ctx->job.segments);
+	ctx->job.segments = NULL;
+	return ret;
+}
+
+static int avd_hevc_try_ctrl(struct avd_ctx *ctx, struct v4l2_ctrl *ctrl)
+{
+	if (ctrl->id == V4L2_CID_STATELESS_HEVC_SPS)
+		return avd_hevc_validate_sps(ctx, ctrl->p_new.p_hevc_sps);
+
+	return 0;
+}
+
+static void avd_hevc_submit(struct avd_ctx *ctx)
+{
+	struct avd_hevc_ctx *hevc_ctx = ctx->priv;
+	struct avd_dev *avd = ctx->dev;
+
+	writel(AVD_OP_EXEC |
+		       AVD_OP_EXEC_FLAG_START_REV4(avd->variant->revision ==
+						   4) |
+		       AVD_OP_EXEC_FLAG_START_REV3(avd->variant->revision ==
+						   3) |
+		       AVD_OP_EXEC_FIFO_IDX(ctx->fifo_idx) |
+		       AVD_OP_EXEC_FIFO_MASK(avd->variant->fifo_slots),
+	       avd->ctrl + avd->variant->submit_offset);
+	for (int i = 0; i < hevc_ctx->submit_num - 1; i++) {
+		writel(AVD_OP_EXEC | AVD_OP_EXEC_FIFO_IDX(ctx->fifo_idx) |
+			       AVD_OP_EXEC_FIFO_MASK(avd->variant->fifo_slots),
+		       avd->ctrl + avd->variant->submit_offset);
+	}
+}
+
+const struct avd_coded_fmt_ops avd_hevc_fmt_ops = {
+	.adjust_decoded_fmt = avd_hevc_adjust_decoded_fmt,
+	.start = avd_hevc_start,
+	.stop = avd_hevc_stop,
+	.run = avd_hevc_run,
+	.submit = avd_hevc_submit,
+	.try_ctrl = avd_hevc_try_ctrl,
+	.get_image_fmt = avd_hevc_get_image_fmt,
+};
diff --git a/drivers/media/platform/apple/avd/avd-v4l2.c b/drivers/media/platform/apple/avd/avd-v4l2.c
index 52dcb70b340c..5f2a31f8b9d4 100644
--- a/drivers/media/platform/apple/avd/avd-v4l2.c
+++ b/drivers/media/platform/apple/avd/avd-v4l2.c
@@ -173,6 +173,62 @@ const struct v4l2_ctrl_ops avd_ctrl_ops = {
 	.s_ctrl = avd_s_ctrl,
 };
 
+static const struct avd_ctrl_desc avd_hevc_ctrl_descs[] = {
+	{
+		.cfg.id = V4L2_CID_STATELESS_HEVC_SLICE_PARAMS,
+		.cfg.flags = V4L2_CTRL_FLAG_DYNAMIC_ARRAY,
+		.cfg.type = V4L2_CTRL_TYPE_HEVC_SLICE_PARAMS,
+		.cfg.dims = { 600 },
+	},
+	{
+		.cfg.id = V4L2_CID_STATELESS_HEVC_SPS,
+		.cfg.ops = &avd_ctrl_ops,
+	},
+	{
+		.cfg.id = V4L2_CID_STATELESS_HEVC_PPS,
+	},
+	{
+		.cfg.id = V4L2_CID_STATELESS_HEVC_SCALING_MATRIX,
+	},
+	{
+		.cfg.id = V4L2_CID_STATELESS_HEVC_DECODE_PARAMS,
+	},
+	{
+		.cfg.id = V4L2_CID_STATELESS_HEVC_DECODE_MODE,
+		.cfg.min = V4L2_STATELESS_HEVC_DECODE_MODE_FRAME_BASED,
+		.cfg.max = V4L2_STATELESS_HEVC_DECODE_MODE_FRAME_BASED,
+		.cfg.def = V4L2_STATELESS_HEVC_DECODE_MODE_FRAME_BASED,
+	},
+	{
+		.cfg.id = V4L2_CID_STATELESS_HEVC_START_CODE,
+		.cfg.min = V4L2_STATELESS_HEVC_START_CODE_NONE,
+		.cfg.def = V4L2_STATELESS_HEVC_START_CODE_NONE,
+		.cfg.max = V4L2_STATELESS_HEVC_START_CODE_NONE,
+	},
+	{
+		.cfg.id = V4L2_CID_STATELESS_HEVC_ENTRY_POINT_OFFSETS,
+		.cfg.dims = { 256 },
+		.cfg.max = 0xffffffff,
+		.cfg.step = 1,
+	},
+	{
+		.cfg.id = V4L2_CID_MPEG_VIDEO_HEVC_PROFILE,
+		.cfg.min = V4L2_MPEG_VIDEO_HEVC_PROFILE_MAIN,
+		.cfg.max = V4L2_MPEG_VIDEO_HEVC_PROFILE_MAIN_10,
+		.cfg.def = V4L2_MPEG_VIDEO_HEVC_PROFILE_MAIN,
+	},
+	{
+		.cfg.id = V4L2_CID_MPEG_VIDEO_HEVC_LEVEL,
+		.cfg.min = V4L2_MPEG_VIDEO_HEVC_LEVEL_1,
+		.cfg.max = V4L2_MPEG_VIDEO_HEVC_LEVEL_5_1,
+	},
+};
+
+static const struct avd_ctrls avd_hevc_ctrls = {
+	.ctrls = avd_hevc_ctrl_descs,
+	.num_ctrls = ARRAY_SIZE(avd_hevc_ctrl_descs),
+};
+
 static const struct avd_ctrl_desc avd_h264_ctrl_descs[] = {
 	{
 		.cfg.id = V4L2_CID_STATELESS_H264_DECODE_PARAMS,
@@ -251,6 +307,20 @@ static const struct avd_ctrls avd_vp9_ctrls = {
 };
 
 static const struct avd_coded_fmt_desc avd_coded_fmts[] = {
+	{
+		.fourcc = V4L2_PIX_FMT_HEVC_SLICE,
+		.frmsize = {
+			.min_width = 64,
+			.max_width = 16384,
+			.step_width = 64,
+			.min_height = 64,
+			.max_height = 16384,
+			.step_height = 16,
+		},
+		.ctrls = &avd_hevc_ctrls,
+		.ops = &avd_hevc_fmt_ops,
+		.capability = AVD_CAPABILITY_HEVC,
+	},
 	{
 		.fourcc = V4L2_PIX_FMT_H264_SLICE,
 		.frmsize = {
diff --git a/drivers/media/platform/apple/avd/avd.h b/drivers/media/platform/apple/avd/avd.h
index facf23d151c0..4f284a2d962c 100644
--- a/drivers/media/platform/apple/avd/avd.h
+++ b/drivers/media/platform/apple/avd/avd.h
@@ -97,6 +97,10 @@ struct avd_vp9_decoded_buffer_info {
 	unsigned int bit_depth : 4;
 };
 
+struct avd_hevc_decoded_buffer_info {
+	bool is_intra;
+};
+
 struct avd_comp {
 	u32 size;
 	/* offset to start of compressed data */
@@ -111,6 +115,7 @@ struct avd_decoded_buffer {
 	struct avd_comp comp;
 	union {
 		struct avd_vp9_decoded_buffer_info vp9;
+		struct avd_hevc_decoded_buffer_info hevc;
 	};
 };
 
@@ -247,6 +252,7 @@ void avd_run_preamble(struct avd_ctx *ctx, struct avd_run *run);
 void avd_run_postamble(struct avd_ctx *ctx, struct avd_run *run);
 
 extern const struct avd_coded_fmt_ops avd_h264_fmt_ops;
+extern const struct avd_coded_fmt_ops avd_hevc_fmt_ops;
 extern const struct avd_coded_fmt_ops avd_vp9_fmt_ops;
 
 extern const struct v4l2_ctrl_ops avd_ctrl_ops;

-- 
2.55.0




More information about the linux-arm-kernel mailing list