diff --git a/drivers/media/platform/qcom/vcodec/iris/Makefile b/drivers/media/platform/qcom/vcodec/iris/Makefile
index 85fdf63..7fdee5b 100644
--- a/drivers/media/platform/qcom/vcodec/iris/Makefile
+++ b/drivers/media/platform/qcom/vcodec/iris/Makefile
@@ -6,6 +6,7 @@ iris-objs += iris_probe.o \
              iris_vidc.o \
              iris_vdec.o \
              iris_state.o \
+             iris_ctrls.o \
              iris_helpers.o \
              iris_hfi.o \
              iris_hfi_response.o \
diff --git a/drivers/media/platform/qcom/vcodec/iris/iris_core.h b/drivers/media/platform/qcom/vcodec/iris/iris_core.h
index c125bce..30f7ad7 100644
--- a/drivers/media/platform/qcom/vcodec/iris/iris_core.h
+++ b/drivers/media/platform/qcom/vcodec/iris/iris_core.h
@@ -50,8 +50,10 @@
  * @header_id: id of packet header
  * @packet_id: id of packet
  * @vpu_ops: a pointer to vpu ops
+ * @dec_codecs_count: supported codec count for decoder
  * @platform_data: a structure for platform data
  * @cap: an array for supported core capabilities
+ * @inst_caps: a pointer to supported instance capabilities
  * @instances: a list_head of all instances
  * @intr_status: interrupt status
  */
@@ -89,8 +91,10 @@ struct iris_core {
 	u32					header_id;
 	u32					packet_id;
 	const struct vpu_ops			*vpu_ops;
+	u32					dec_codecs_count;
 	struct platform_data			*platform_data;
 	struct plat_core_cap			cap[CORE_CAP_MAX + 1];
+	struct plat_inst_caps			*inst_caps;
 	struct list_head			instances;
 	u32					intr_status;
 };
diff --git a/drivers/media/platform/qcom/vcodec/iris/iris_ctrls.c b/drivers/media/platform/qcom/vcodec/iris/iris_ctrls.c
new file mode 100644
index 0000000..6b7aeaa
--- /dev/null
+++ b/drivers/media/platform/qcom/vcodec/iris/iris_ctrls.c
@@ -0,0 +1,679 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (c) 2022-2023 Qualcomm Innovation Center, Inc. All rights reserved.
+ */
+#include <linux/vmalloc.h>
+#include <linux/types.h>
+#include <linux/list.h>
+
+#include "platform_common.h"
+#include "iris_helpers.h"
+#include "iris_hfi_packet.h"
+#include "iris_hfi.h"
+#include "iris_ctrls.h"
+
+#define MIN_CAPTURE_BUFFERS 4
+#define MIN_OUTPUT_BUFFERS 4
+
+static inline bool are_all_childrens_visited(struct plat_inst_cap *cap,
+					     bool lookup[INST_CAP_MAX])
+{
+	bool found = true;
+	int i;
+
+	for (i = 0; i < MAX_NUM_CHILD; i++) {
+		if (cap->children[i] == INST_CAP_NONE)
+			continue;
+
+		if (!lookup[cap->children[i]]) {
+			found = false;
+			break;
+		}
+	}
+
+	return found;
+}
+
+static bool is_valid_cap_id(enum plat_inst_cap_type cap_id)
+{
+	return cap_id > INST_CAP_NONE && cap_id < INST_CAP_MAX;
+}
+
+static enum plat_inst_cap_type get_cap_id(struct iris_inst *inst, u32 id)
+{
+	enum plat_inst_cap_type iter = INST_CAP_NONE;
+	enum plat_inst_cap_type cap_id = INST_CAP_NONE;
+
+	do {
+		if (inst->cap[iter].v4l2_id == id) {
+			cap_id = inst->cap[iter].cap_id;
+			break;
+		}
+		iter++;
+	} while (iter < INST_CAP_MAX);
+
+	return cap_id;
+}
+
+static int add_node_list(struct list_head *list, enum plat_inst_cap_type cap_id)
+{
+	struct cap_entry *entry = NULL;
+
+	entry = kzalloc(sizeof(*entry), GFP_KERNEL);
+	if (!entry)
+		return -ENOMEM;
+
+	INIT_LIST_HEAD(&entry->list);
+	entry->cap_id = cap_id;
+	list_add(&entry->list, list);
+
+	return 0;
+}
+
+static int add_children(struct list_head *list,
+			struct iris_inst *inst,
+			enum plat_inst_cap_type cap_id)
+{
+	struct plat_inst_cap *cap;
+	int i, ret = 0;
+
+	cap = &inst->cap[cap_id];
+
+	for (i = 0; i < MAX_NUM_CHILD; i++) {
+		if (!cap->children[i])
+			break;
+
+		if (!is_valid_cap_id(cap->children[i]))
+			continue;
+
+		ret = add_node_list(list, cap->children[i]);
+		if (ret)
+			return ret;
+	}
+
+	return ret;
+}
+
+static int adjust_cap(struct iris_inst *inst,
+		      enum plat_inst_cap_type cap_id,
+		      struct v4l2_ctrl *ctrl)
+{
+	struct plat_inst_cap *cap;
+
+	cap = &inst->cap[cap_id];
+	if (!inst->cap[cap_id].cap_id)
+		return 0;
+
+	if (!cap->adjust) {
+		if (ctrl)
+			inst->cap[cap_id].value = ctrl->val;
+		return 0;
+	}
+
+	return cap->adjust(inst, ctrl);
+}
+
+static int adjust_dynamic_property(struct iris_inst *inst,
+				   enum plat_inst_cap_type cap_id,
+				   struct v4l2_ctrl *ctrl,
+				   struct list_head *firmware_list,
+				   struct list_head *children_list,
+				   bool cap_present[INST_CAP_MAX])
+{
+	struct cap_entry *entry = NULL, *temp = NULL;
+	struct plat_inst_cap *cap;
+	s32 prev_value;
+	int ret;
+
+	cap = &inst->cap[0];
+
+	if (!(cap[cap_id].flags & CAP_FLAG_DYNAMIC_ALLOWED))
+		return -EBUSY;
+
+	prev_value = cap[cap_id].value;
+	ret = adjust_cap(inst, cap_id, ctrl);
+	if (ret)
+		return ret;
+
+	ret = add_node_list(firmware_list, cap_id);
+	if (ret)
+		return ret;
+	cap_present[cap->cap_id] = true;
+
+	if (cap[cap_id].value == prev_value)
+		return 0;
+
+	ret = add_children(children_list, inst, cap_id);
+	if (ret)
+		return ret;
+
+	list_for_each_entry_safe(entry, temp, children_list, list) {
+		if (!cap[entry->cap_id].adjust) {
+			list_del_init(&entry->list);
+			kfree(entry);
+			continue;
+		}
+
+		prev_value = cap[entry->cap_id].value;
+		ret = adjust_cap(inst, entry->cap_id, NULL);
+		if (ret)
+			return ret;
+
+		if (cap[entry->cap_id].value != prev_value) {
+			if (!cap_present[cap->cap_id]) {
+				ret = add_node_list(firmware_list, cap_id);
+				if (ret)
+					return ret;
+				cap_present[cap->cap_id] = true;
+			}
+
+			ret = add_children(children_list, inst, entry->cap_id);
+			if (ret)
+				return ret;
+		}
+
+		list_del_init(&entry->list);
+		kfree(entry);
+	}
+
+	if (!list_empty(children_list))
+		return -EINVAL;
+
+	return ret;
+}
+
+static int set_dynamic_property(struct iris_inst *inst,
+				struct list_head *firmware_list)
+{
+	struct cap_entry *entry = NULL, *temp = NULL;
+	struct plat_inst_cap *cap;
+	int ret = 0;
+
+	list_for_each_entry_safe(entry, temp, firmware_list, list) {
+		cap = &inst->cap[entry->cap_id];
+		if (cap->set) {
+			ret = cap->set(inst, entry->cap_id);
+			if (ret)
+				return -EINVAL;
+		}
+		list_del_init(&entry->list);
+		kfree(entry);
+	}
+
+	return ret;
+}
+
+static int vdec_op_g_volatile_ctrl(struct v4l2_ctrl *ctrl)
+{
+	enum plat_inst_cap_type cap_id;
+	struct iris_inst *inst = NULL;
+	int ret = 0;
+
+	switch (ctrl->id) {
+	case V4L2_CID_MIN_BUFFERS_FOR_CAPTURE:
+		ctrl->val = MIN_CAPTURE_BUFFERS;
+		break;
+	case V4L2_CID_MIN_BUFFERS_FOR_OUTPUT:
+		ctrl->val = MIN_OUTPUT_BUFFERS;
+		break;
+	default:
+		inst = container_of(ctrl->handler, struct iris_inst, ctrl_handler);
+		cap_id = get_cap_id(inst, ctrl->id);
+		if (is_valid_cap_id(cap_id))
+			ctrl->val = inst->cap[cap_id].value;
+		else
+			ret = -EINVAL;
+	}
+
+	return ret;
+}
+
+static int vdec_op_s_ctrl(struct v4l2_ctrl *ctrl)
+{
+	struct cap_entry *entry = NULL, *temp = NULL;
+	struct list_head children_list, firmware_list;
+	enum plat_inst_cap_type cap_id;
+	bool cap_present[INST_CAP_MAX];
+	struct plat_inst_cap *cap;
+	struct iris_inst *inst;
+	int ret = 0;
+
+	inst = container_of(ctrl->handler, struct iris_inst, ctrl_handler);
+	cap = &inst->cap[0];
+
+	INIT_LIST_HEAD(&firmware_list);
+	INIT_LIST_HEAD(&children_list);
+	memset(&cap_present, 0, sizeof(cap_present));
+
+	cap_id = get_cap_id(inst, ctrl->id);
+	if (!is_valid_cap_id(cap_id))
+		return -EINVAL;
+
+	cap[cap_id].flags |= CAP_FLAG_CLIENT_SET;
+
+	if (!inst->vb2q_src->streaming) {
+		inst->cap[cap_id].value = ctrl->val;
+	} else {
+		ret = adjust_dynamic_property(inst, cap_id, ctrl,
+					      &firmware_list, &children_list,
+					      cap_present);
+		if (ret)
+			goto free_list;
+
+		ret = set_dynamic_property(inst, &firmware_list);
+	}
+
+free_list:
+	list_for_each_entry_safe(entry, temp, &children_list, list) {
+		list_del_init(&entry->list);
+		kfree(entry);
+	}
+
+	list_for_each_entry_safe(entry, temp, &firmware_list, list) {
+		list_del_init(&entry->list);
+		kfree(entry);
+	}
+
+	return ret;
+}
+
+static const struct v4l2_ctrl_ops ctrl_ops = {
+	.s_ctrl = vdec_op_s_ctrl,
+	.g_volatile_ctrl = vdec_op_g_volatile_ctrl,
+};
+
+int ctrls_init(struct iris_inst *inst)
+{
+	int num_ctrls = 0, ctrl_idx = 0;
+	struct plat_inst_cap *cap;
+	struct iris_core *core;
+	int idx = 0;
+	int ret = 0;
+
+	core = inst->core;
+	cap = &inst->cap[0];
+
+	for (idx = 0; idx < INST_CAP_MAX; idx++) {
+		if (cap[idx].v4l2_id)
+			num_ctrls++;
+	}
+	if (!num_ctrls)
+		return -EINVAL;
+
+	ret = v4l2_ctrl_handler_init(&inst->ctrl_handler,
+				     INST_CAP_MAX * core->dec_codecs_count);
+	if (ret)
+		return ret;
+
+	for (idx = 0; idx < INST_CAP_MAX; idx++) {
+		struct v4l2_ctrl *ctrl;
+
+		if (!cap[idx].v4l2_id)
+			continue;
+
+		if (ctrl_idx >= num_ctrls) {
+			ret = -EINVAL;
+			goto error;
+		}
+
+		if (cap[idx].flags & CAP_FLAG_MENU) {
+			ctrl = v4l2_ctrl_new_std_menu(&inst->ctrl_handler,
+						      &ctrl_ops,
+						      cap[idx].v4l2_id,
+						      cap[idx].max,
+						      ~(cap[idx].step_or_mask),
+						      cap[idx].value);
+		} else {
+			ctrl = v4l2_ctrl_new_std(&inst->ctrl_handler,
+						 &ctrl_ops,
+						 cap[idx].v4l2_id,
+						 cap[idx].min,
+						 cap[idx].max,
+						 cap[idx].step_or_mask,
+						 cap[idx].value);
+		}
+		if (!ctrl) {
+			ret = -EINVAL;
+			goto error;
+		}
+
+		ret = inst->ctrl_handler.error;
+		if (ret)
+			goto error;
+
+		if ((cap[idx].flags & CAP_FLAG_VOLATILE) ||
+		    (ctrl->id == V4L2_CID_MIN_BUFFERS_FOR_CAPTURE ||
+		     ctrl->id == V4L2_CID_MIN_BUFFERS_FOR_OUTPUT))
+			ctrl->flags |= V4L2_CTRL_FLAG_VOLATILE;
+
+		ctrl->flags |= V4L2_CTRL_FLAG_EXECUTE_ON_WRITE;
+		ctrl_idx++;
+	}
+	inst->num_ctrls = num_ctrls;
+
+	return 0;
+error:
+	v4l2_ctrl_handler_free(&inst->ctrl_handler);
+
+	return ret;
+}
+
+int iris_init_core_caps(struct iris_core *core)
+{
+	struct plat_core_cap *core_platform_data;
+	int i, num_core_caps;
+
+	core_platform_data = core->platform_data->core_data;
+	if (!core_platform_data)
+		return -EINVAL;
+
+	num_core_caps = core->platform_data->core_data_size;
+
+	for (i = 0; i < num_core_caps && i < CORE_CAP_MAX; i++) {
+		core->cap[core_platform_data[i].type].type = core_platform_data[i].type;
+		core->cap[core_platform_data[i].type].value = core_platform_data[i].value;
+	}
+
+	return 0;
+}
+
+static int update_inst_capability(struct plat_inst_cap *in,
+				  struct plat_inst_caps *capability)
+{
+	if (!in || !capability)
+		return -EINVAL;
+
+	if (in->cap_id >= INST_CAP_MAX)
+		return -EINVAL;
+
+	capability->cap[in->cap_id].cap_id = in->cap_id;
+	capability->cap[in->cap_id].min = in->min;
+	capability->cap[in->cap_id].max = in->max;
+	capability->cap[in->cap_id].step_or_mask = in->step_or_mask;
+	capability->cap[in->cap_id].value = in->value;
+	capability->cap[in->cap_id].flags = in->flags;
+	capability->cap[in->cap_id].v4l2_id = in->v4l2_id;
+	capability->cap[in->cap_id].hfi_id = in->hfi_id;
+	memcpy(capability->cap[in->cap_id].children, in->children,
+	       sizeof(capability->cap[in->cap_id].children));
+	capability->cap[in->cap_id].adjust = in->adjust;
+	capability->cap[in->cap_id].set = in->set;
+
+	return 0;
+}
+
+int iris_init_instance_caps(struct iris_core *core)
+{
+	struct plat_inst_cap *inst_plat_cap_data = NULL;
+	u8 dec_codecs_count = 0;
+	int num_inst_cap;
+	u32 dec_valid_codecs;
+	int i, j, check_bit = 0;
+	int ret = 0;
+
+	inst_plat_cap_data = core->platform_data->inst_cap_data;
+	if (!inst_plat_cap_data)
+		return -EINVAL;
+
+	dec_valid_codecs = core->cap[DEC_CODECS].value;
+	dec_codecs_count = hweight32(dec_valid_codecs);
+	core->dec_codecs_count = dec_codecs_count;
+
+	core->inst_caps = devm_kzalloc(core->dev,
+				       dec_codecs_count * sizeof(struct plat_inst_caps),
+				       GFP_KERNEL);
+	if (!core->inst_caps)
+		return -ENOMEM;
+
+	for (i = 0; i < dec_codecs_count; i++) {
+		while (check_bit < (sizeof(dec_valid_codecs) * 8)) {
+			if (dec_valid_codecs & BIT(check_bit)) {
+				core->inst_caps[i].codec = dec_valid_codecs &
+						BIT(check_bit);
+				check_bit++;
+				break;
+			}
+			check_bit++;
+		}
+	}
+
+	num_inst_cap = core->platform_data->inst_cap_data_size;
+
+	for (i = 0; i < num_inst_cap; i++) {
+		for (j = 0; j < dec_codecs_count; j++) {
+			if ((inst_plat_cap_data[i].codec &
+				core->inst_caps[j].codec)) {
+				ret = update_inst_capability(&inst_plat_cap_data[i],
+							     &core->inst_caps[j]);
+				if (ret)
+					return ret;
+			}
+		}
+	}
+
+	return ret;
+}
+
+int get_inst_capability(struct iris_inst *inst)
+{
+	struct iris_core *core;
+	int i;
+
+	core = inst->core;
+
+	for (i = 0; i < core->dec_codecs_count; i++) {
+		if (core->inst_caps[i].codec == inst->codec) {
+			memcpy(&inst->cap[0], &core->inst_caps[i].cap[0],
+			       (INST_CAP_MAX + 1) * sizeof(struct plat_inst_cap));
+		}
+	}
+
+	return 0;
+}
+
+int prepare_dependency_list(struct iris_inst *inst)
+{
+	struct cap_entry *entry = NULL, *temp = NULL;
+	struct plat_inst_cap *cap, *temp_cap;
+	int caps_to_prepare, pending_list_counter,
+		pending_at_start = 0;
+	struct list_head prepared_list, pending_list;
+	bool is_prepared[INST_CAP_MAX];
+	bool is_pending[INST_CAP_MAX];
+	int i, ret = 0;
+
+	cap = &inst->cap[0];
+
+	if (!list_empty(&inst->caps_list))
+		return 0;
+
+	INIT_LIST_HEAD(&prepared_list);
+	INIT_LIST_HEAD(&pending_list);
+	memset(&is_prepared, 0, sizeof(is_prepared));
+	memset(&is_pending, 0, sizeof(is_pending));
+
+	for (i = 1; i < INST_CAP_MAX; i++) {
+		temp_cap = &cap[i];
+		if (!is_valid_cap_id(temp_cap->cap_id))
+			continue;
+
+		if (!temp_cap->children[0]) {
+			if (!is_prepared[temp_cap->cap_id]) {
+				ret = add_node_list(&prepared_list, temp_cap->cap_id);
+				if (ret)
+					goto free_list;
+				is_prepared[temp_cap->cap_id] = true;
+			}
+		} else {
+			if (!is_pending[temp_cap->cap_id]) {
+				ret = add_node_list(&pending_list, temp_cap->cap_id);
+				if (ret)
+					goto free_list;
+				is_pending[temp_cap->cap_id] = true;
+			}
+		}
+	}
+
+	list_for_each_entry(entry, &pending_list, list)
+		pending_at_start++;
+
+	caps_to_prepare = pending_at_start;
+	pending_list_counter = pending_at_start;
+
+	list_for_each_entry_safe(entry, temp, &pending_list, list) {
+		list_del_init(&entry->list);
+		is_pending[entry->cap_id] = false;
+		pending_list_counter--;
+		temp_cap = &cap[entry->cap_id];
+
+		if (are_all_childrens_visited(temp_cap, is_prepared)) {
+			list_add(&entry->list, &prepared_list);
+			is_prepared[entry->cap_id] = true;
+			caps_to_prepare--;
+		} else {
+			list_add_tail(&entry->list, &pending_list);
+			is_pending[entry->cap_id] = true;
+		}
+
+		if (!pending_list_counter) {
+			if (pending_at_start == caps_to_prepare) {
+				ret = -EINVAL;
+				goto free_list;
+			}
+			pending_at_start = caps_to_prepare;
+			pending_list_counter = caps_to_prepare;
+		}
+	}
+
+	if (!list_empty(&pending_list)) {
+		ret = -EINVAL;
+		goto free_list;
+	}
+
+	list_replace_init(&prepared_list, &inst->caps_list);
+
+free_list:
+	list_for_each_entry_safe(entry, temp, &pending_list, list) {
+		list_del_init(&entry->list);
+		kfree(entry);
+	}
+	list_for_each_entry_safe(entry, temp, &prepared_list, list) {
+		list_del_init(&entry->list);
+		kfree(entry);
+	}
+
+	return ret;
+}
+
+int set_u32_enum(struct iris_inst *inst,
+		 enum plat_inst_cap_type cap_id)
+{
+	u32 hfi_value = inst->cap[cap_id].value;
+	u32 hfi_id = inst->cap[cap_id].hfi_id;
+
+	return iris_hfi_set_property(inst, hfi_id, HFI_HOST_FLAGS_NONE,
+				     get_port_info(inst, cap_id),
+				     HFI_PAYLOAD_U32_ENUM,
+				     &hfi_value, sizeof(u32));
+}
+
+int set_u32(struct iris_inst *inst,
+	    enum plat_inst_cap_type cap_id)
+{
+	u32 hfi_value = inst->cap[cap_id].value;
+	u32 hfi_id = inst->cap[cap_id].hfi_id;
+
+	return iris_hfi_set_property(inst, hfi_id, HFI_HOST_FLAGS_NONE,
+				     get_port_info(inst, cap_id),
+				     HFI_PAYLOAD_U32,
+				     &hfi_value, sizeof(u32));
+}
+
+int set_stage(struct iris_inst *inst,
+	      enum plat_inst_cap_type cap_id)
+{
+	struct v4l2_format *inp_f;
+	u32 work_mode = STAGE_2;
+	u32 width, height;
+	u32 hfi_id;
+
+	hfi_id = inst->cap[cap_id].hfi_id;
+
+	inp_f = inst->fmt_src;
+	height = inp_f->fmt.pix_mp.height;
+	width = inp_f->fmt.pix_mp.width;
+	if (res_is_less_than(width, height, 1280, 720))
+		work_mode = STAGE_1;
+
+	return iris_hfi_set_property(inst, hfi_id, HFI_HOST_FLAGS_NONE,
+				     get_port_info(inst, cap_id),
+				     HFI_PAYLOAD_U32,
+				     &work_mode, sizeof(u32));
+}
+
+int set_pipe(struct iris_inst *inst,
+	     enum plat_inst_cap_type cap_id)
+{
+	u32 work_route, hfi_id;
+
+	work_route = inst->cap[PIPE].value;
+	hfi_id = inst->cap[cap_id].hfi_id;
+
+	return iris_hfi_set_property(inst, hfi_id, HFI_HOST_FLAGS_NONE,
+				     get_port_info(inst, cap_id),
+				     HFI_PAYLOAD_U32,
+				     &work_route, sizeof(u32));
+}
+
+int adjust_v4l2_properties(struct iris_inst *inst)
+{
+	struct cap_entry *entry = NULL, *temp = NULL;
+	int ret = 0;
+
+	list_for_each_entry_safe(entry, temp, &inst->caps_list, list) {
+		ret = adjust_cap(inst, entry->cap_id, NULL);
+		if (ret)
+			return ret;
+	}
+
+	return ret;
+}
+
+int adjust_output_order(struct iris_inst *inst, struct v4l2_ctrl *ctrl)
+{
+	s32 display_delay = -1, display_delay_enable = -1;
+	u32 adjusted_value;
+
+	adjusted_value = ctrl ? ctrl->val :
+		inst->cap[OUTPUT_ORDER].value;
+
+	display_delay = inst->cap[DISPLAY_DELAY].value;
+	display_delay_enable = inst->cap[DISPLAY_DELAY_ENABLE].value;
+
+	if (display_delay_enable && !display_delay)
+		adjusted_value = 1;
+
+	inst->cap[OUTPUT_ORDER].value = adjusted_value;
+
+	return 0;
+}
+
+int adjust_profile(struct iris_inst *inst, struct v4l2_ctrl *ctrl)
+{
+	u32 adjusted_value;
+	s32 pix_fmt = -1;
+
+	adjusted_value = ctrl ? ctrl->val :
+		inst->cap[PROFILE].value;
+
+	pix_fmt = inst->cap[PIX_FMTS].value;
+
+	if (pix_fmt == FMT_TP10C)
+		adjusted_value = V4L2_MPEG_VIDEO_HEVC_PROFILE_MAIN_10;
+	else
+		adjusted_value = V4L2_MPEG_VIDEO_HEVC_PROFILE_MAIN;
+
+	inst->cap[PROFILE].value = adjusted_value;
+
+	return 0;
+}
diff --git a/drivers/media/platform/qcom/vcodec/iris/iris_ctrls.h b/drivers/media/platform/qcom/vcodec/iris/iris_ctrls.h
new file mode 100644
index 0000000..0f67f4f
--- /dev/null
+++ b/drivers/media/platform/qcom/vcodec/iris/iris_ctrls.h
@@ -0,0 +1,30 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Copyright (c) 2022-2023 Qualcomm Innovation Center, Inc. All rights reserved.
+ */
+
+#ifndef _IRIS_CTRLS_H_
+#define _IRIS_CTRLS_H_
+
+#include "iris_instance.h"
+
+struct cap_entry {
+	struct list_head list;
+	enum plat_inst_cap_type cap_id;
+};
+
+int set_u32_enum(struct iris_inst *inst, enum plat_inst_cap_type cap_id);
+int set_stage(struct iris_inst *inst, enum plat_inst_cap_type cap_id);
+int set_pipe(struct iris_inst *inst, enum plat_inst_cap_type cap_id);
+int set_u32(struct iris_inst *inst, enum plat_inst_cap_type cap_id);
+int adjust_output_order(struct iris_inst *inst, struct v4l2_ctrl *ctrl);
+int adjust_profile(struct iris_inst *inst, struct v4l2_ctrl *ctrl);
+int vidc_ctrl_handler_deinit(struct iris_inst *inst);
+int prepare_dependency_list(struct iris_inst *inst);
+int iris_init_instance_caps(struct iris_core *core);
+int iris_init_core_caps(struct iris_core *core);
+int get_inst_capability(struct iris_inst *inst);
+int adjust_v4l2_properties(struct iris_inst *inst);
+int ctrls_init(struct iris_inst *inst);
+
+#endif
diff --git a/drivers/media/platform/qcom/vcodec/iris/iris_helpers.c b/drivers/media/platform/qcom/vcodec/iris/iris_helpers.c
index 872268d..8d8bc3a 100644
--- a/drivers/media/platform/qcom/vcodec/iris/iris_helpers.c
+++ b/drivers/media/platform/qcom/vcodec/iris/iris_helpers.c
@@ -5,6 +5,7 @@
 
 #include "iris_helpers.h"
 #include "iris_hfi.h"
+#include "iris_hfi_packet.h"
 #include "iris_instance.h"
 
 int check_core_lock(struct iris_core *core)
@@ -16,23 +17,29 @@ int check_core_lock(struct iris_core *core)
 	return fatal ? -EINVAL : 0;
 }
 
-int iris_init_core_caps(struct iris_core *core)
+bool res_is_less_than(u32 width, u32 height,
+		      u32 ref_width, u32 ref_height)
 {
-	struct plat_core_cap *core_platform_data;
-	int i, num_core_caps;
+	u32 num_mbs = NUM_MBS_PER_FRAME(height, width);
+	u32 max_side = max(ref_width, ref_height);
 
-	core_platform_data = core->platform_data->core_data;
-	if (!core_platform_data)
-		return -EINVAL;
+	if (num_mbs < NUM_MBS_PER_FRAME(ref_height, ref_width) &&
+	    width < max_side &&
+	    height < max_side)
+		return true;
 
-	num_core_caps = core->platform_data->core_data_size;
+	return false;
+}
 
-	for (i = 0; i < num_core_caps && i < CORE_CAP_MAX; i++) {
-		core->cap[core_platform_data[i].type].type = core_platform_data[i].type;
-		core->cap[core_platform_data[i].type].value = core_platform_data[i].value;
-	}
+u32 get_port_info(struct iris_inst *inst,
+		  enum plat_inst_cap_type cap_id)
+{
+	if (inst->cap[cap_id].flags & CAP_FLAG_INPUT_PORT)
+		return HFI_PORT_BITSTREAM;
+	else if (inst->cap[cap_id].flags & CAP_FLAG_OUTPUT_PORT)
+		return HFI_PORT_RAW;
 
-	return 0;
+	return HFI_PORT_NONE;
 }
 
 static int process_inst_timeout(struct iris_inst *inst)
diff --git a/drivers/media/platform/qcom/vcodec/iris/iris_helpers.h b/drivers/media/platform/qcom/vcodec/iris/iris_helpers.h
index b9a6485..60c79124 100644
--- a/drivers/media/platform/qcom/vcodec/iris/iris_helpers.h
+++ b/drivers/media/platform/qcom/vcodec/iris/iris_helpers.h
@@ -6,11 +6,22 @@
 #ifndef _IRIS_HELPERS_H_
 #define _IRIS_HELPERS_H_
 
+#include <linux/align.h>
+#include <linux/types.h>
+
 #include "iris_core.h"
 #include "iris_instance.h"
+#include "platform_common.h"
+
+#define NUM_MBS_PER_FRAME(__height, __width) \
+	((ALIGN(__height, 16) / 16) * (ALIGN(__width, 16) / 16))
 
 int check_core_lock(struct iris_core *core);
-int iris_init_core_caps(struct iris_core *core);
+bool res_is_less_than(u32 width, u32 height,
+		      u32 ref_width, u32 ref_height);
+u32 get_port_info(struct iris_inst *inst,
+		  enum plat_inst_cap_type cap_id);
+
 int close_session(struct iris_inst *inst);
 
 #endif
diff --git a/drivers/media/platform/qcom/vcodec/iris/iris_hfi.c b/drivers/media/platform/qcom/vcodec/iris/iris_hfi.c
index 4b8d8c7..24ddb98 100644
--- a/drivers/media/platform/qcom/vcodec/iris/iris_hfi.c
+++ b/drivers/media/platform/qcom/vcodec/iris/iris_hfi.c
@@ -10,6 +10,7 @@
 #include "iris_hfi.h"
 #include "iris_hfi_packet.h"
 #include "iris_hfi_response.h"
+#include "iris_helpers.h"
 #include "vpu_common.h"
 
 static int iris_hfi_queue_cmd_write(struct iris_core *core, void *pkt)
@@ -280,3 +281,31 @@ irqreturn_t iris_hfi_isr_handler(int irq, void *data)
 
 	return IRQ_HANDLED;
 }
+
+int iris_hfi_set_property(struct iris_inst *inst,
+			  u32 packet_type, u32 flag, u32 plane, u32 payload_type,
+			  void *payload, u32 payload_size)
+{
+	struct iris_core *core;
+	int ret;
+
+	core = inst->core;
+	mutex_lock(&core->lock);
+
+	ret = hfi_packet_session_property(inst,
+					  packet_type,
+					  flag,
+					  plane,
+					  payload_type,
+					  payload,
+					  payload_size);
+	if (ret)
+		goto unlock;
+
+	ret = iris_hfi_queue_cmd_write(core, inst->packet);
+
+unlock:
+	mutex_unlock(&core->lock);
+
+	return ret;
+}
diff --git a/drivers/media/platform/qcom/vcodec/iris/iris_hfi.h b/drivers/media/platform/qcom/vcodec/iris/iris_hfi.h
index 8a62986..bf991bb 100644
--- a/drivers/media/platform/qcom/vcodec/iris/iris_hfi.h
+++ b/drivers/media/platform/qcom/vcodec/iris/iris_hfi.h
@@ -13,6 +13,9 @@ int iris_hfi_core_init(struct iris_core *core);
 int iris_hfi_core_deinit(struct iris_core *core);
 int iris_hfi_session_open(struct iris_inst *inst);
 int iris_hfi_session_close(struct iris_inst *inst);
+int iris_hfi_set_property(struct iris_inst *inst,
+			  u32 packet_type, u32 flag, u32 plane, u32 payload_type,
+			  void *payload, u32 payload_size);
 
 irqreturn_t iris_hfi_isr(int irq, void *data);
 irqreturn_t iris_hfi_isr_handler(int irq, void *data);
diff --git a/drivers/media/platform/qcom/vcodec/iris/iris_hfi_packet.c b/drivers/media/platform/qcom/vcodec/iris/iris_hfi_packet.c
index 1ed572e..749d978 100644
--- a/drivers/media/platform/qcom/vcodec/iris/iris_hfi_packet.c
+++ b/drivers/media/platform/qcom/vcodec/iris/iris_hfi_packet.c
@@ -239,3 +239,29 @@ int hfi_packet_session_command(struct iris_inst *inst, u32 pkt_type,
 
 	return ret;
 }
+
+int hfi_packet_session_property(struct iris_inst *inst,
+				u32 pkt_type, u32 flags, u32 port,
+				u32 payload_type, void *payload, u32 payload_size)
+{
+	struct iris_core *core;
+	int ret;
+
+	core = inst->core;
+
+	ret = hfi_create_header(inst->packet, inst->packet_size,
+				inst->session_id, core->header_id++);
+	if (ret)
+		return ret;
+
+	ret = hfi_create_packet(inst->packet, inst->packet_size,
+				pkt_type,
+				flags,
+				payload_type,
+				port,
+				core->packet_id++,
+				payload,
+				payload_size);
+
+	return ret;
+}
diff --git a/drivers/media/platform/qcom/vcodec/iris/iris_hfi_packet.h b/drivers/media/platform/qcom/vcodec/iris/iris_hfi_packet.h
index 9e476e9..bea7ed9 100644
--- a/drivers/media/platform/qcom/vcodec/iris/iris_hfi_packet.h
+++ b/drivers/media/platform/qcom/vcodec/iris/iris_hfi_packet.h
@@ -74,5 +74,8 @@ int hfi_packet_session_command(struct iris_inst *inst, u32 pkt_type,
 			       u32 flags, u32 port, u32 session_id,
 			       u32 payload_type, void *payload,
 			       u32 payload_size);
+int hfi_packet_session_property(struct iris_inst *inst,
+				u32 pkt_type, u32 flags, u32 port,
+				u32 payload_type, void *payload, u32 payload_size);
 
 #endif
diff --git a/drivers/media/platform/qcom/vcodec/iris/iris_instance.h b/drivers/media/platform/qcom/vcodec/iris/iris_instance.h
index 1bbb533..f6a3066 100644
--- a/drivers/media/platform/qcom/vcodec/iris/iris_instance.h
+++ b/drivers/media/platform/qcom/vcodec/iris/iris_instance.h
@@ -10,6 +10,7 @@
 
 #include "iris_core.h"
 #include "iris_common.h"
+#include "platform_common.h"
 
 /**
  * struct iris_inst - holds per video instance parameters
@@ -27,6 +28,10 @@
  * @packet: HFI packet
  * @packet_size: HFI packet size
  * @completions: structure of signal completions
+ * @cap: array of supported instance capabilities
+ * @num_ctrls: supported number of controls
+ * @caps_list: list head of capability
+ * @codec: codec type
  */
 
 struct iris_inst {
@@ -43,6 +48,10 @@ struct iris_inst {
 	void				*packet;
 	u32				packet_size;
 	struct completion		completions[MAX_SIGNAL];
+	struct plat_inst_cap		cap[INST_CAP_MAX + 1];
+	u32				num_ctrls;
+	struct list_head		caps_list;
+	enum codec_type			codec;
 };
 
 #endif
diff --git a/drivers/media/platform/qcom/vcodec/iris/iris_probe.c b/drivers/media/platform/qcom/vcodec/iris/iris_probe.c
index 8c591da..50fb93e 100644
--- a/drivers/media/platform/qcom/vcodec/iris/iris_probe.c
+++ b/drivers/media/platform/qcom/vcodec/iris/iris_probe.c
@@ -13,6 +13,7 @@
 #include "iris_hfi.h"
 #include "resources.h"
 #include "iris_vidc.h"
+#include "iris_ctrls.h"
 
 static int init_iris_isr(struct iris_core *core)
 {
@@ -158,6 +159,13 @@ static int iris_probe(struct platform_device *pdev)
 		return ret;
 	}
 
+	ret = iris_init_instance_caps(core);
+	if (ret) {
+		dev_err_probe(core->dev, ret,
+			      "%s: init inst caps failed with %d\n", __func__, ret);
+		return ret;
+	}
+
 	ret = v4l2_device_register(dev, &core->v4l2_dev);
 	if (ret)
 		return ret;
diff --git a/drivers/media/platform/qcom/vcodec/iris/iris_vidc.c b/drivers/media/platform/qcom/vcodec/iris/iris_vidc.c
index d02da8b..3a26edb 100644
--- a/drivers/media/platform/qcom/vcodec/iris/iris_vidc.c
+++ b/drivers/media/platform/qcom/vcodec/iris/iris_vidc.c
@@ -9,6 +9,7 @@
 #include "iris_instance.h"
 #include "iris_vdec.h"
 #include "iris_vidc.h"
+#include "iris_ctrls.h"
 
 static int vidc_v4l2_fh_init(struct iris_inst *inst)
 {
@@ -174,6 +175,10 @@ int vidc_open(struct file *filp)
 	if (ret)
 		goto fail_free_inst;
 
+	INIT_LIST_HEAD(&inst->caps_list);
+	for (i = 0; i < MAX_SIGNAL; i++)
+		init_completion(&inst->completions[i]);
+
 	ret = vidc_v4l2_fh_init(inst);
 	if (ret)
 		goto fail_remove_session;
@@ -184,6 +189,14 @@ int vidc_open(struct file *filp)
 	if (ret)
 		goto fail_inst_deinit;
 
+	ret = get_inst_capability(inst);
+	if (ret)
+		goto fail_queue_deinit;
+
+	ret = ctrls_init(inst);
+	if (ret)
+		goto fail_queue_deinit;
+
 	ret = iris_hfi_session_open(inst);
 	if (ret) {
 		dev_err(core->dev, "%s: session open failed\n", __func__);
@@ -194,7 +207,9 @@ int vidc_open(struct file *filp)
 	return 0;
 
 fail_core_deinit:
+	v4l2_ctrl_handler_free(&inst->ctrl_handler);
 	iris_core_deinit(core);
+fail_queue_deinit:
 	vidc_vb2_queue_deinit(inst);
 fail_inst_deinit:
 	vdec_inst_deinit(inst);
@@ -216,6 +231,7 @@ int vidc_close(struct file *filp)
 	if (!inst)
 		return -EINVAL;
 
+	v4l2_ctrl_handler_free(&inst->ctrl_handler);
 	vdec_inst_deinit(inst);
 	close_session(inst);
 	vidc_vb2_queue_deinit(inst);
diff --git a/drivers/media/platform/qcom/vcodec/iris/platform_common.h b/drivers/media/platform/qcom/vcodec/iris/platform_common.h
index e478b02..8305c65 100644
--- a/drivers/media/platform/qcom/vcodec/iris/platform_common.h
+++ b/drivers/media/platform/qcom/vcodec/iris/platform_common.h
@@ -7,8 +7,10 @@
 #define _PLATFORM_COMMON_H_
 
 #include <linux/bits.h>
+#include <media/v4l2-ctrls.h>
 
 struct iris_core;
+struct iris_inst;
 
 #define HW_RESPONSE_TIMEOUT_VALUE     (1000)
 
@@ -17,6 +19,7 @@ struct iris_core;
 
 #define CODED_FRAMES_PROGRESSIVE 0x0
 #define CODED_FRAMES_INTERLACE 0x1
+#define MAX_NUM_CHILD         10
 
 #define DEFAULT_MAX_HOST_BUF_COUNT			64
 #define DEFAULT_MAX_HOST_BURST_BUF_COUNT		256
@@ -163,6 +166,16 @@ struct plat_inst_cap {
 	u32 v4l2_id;
 	u32 hfi_id;
 	enum plat_inst_cap_flags flags;
+	enum plat_inst_cap_type children[MAX_NUM_CHILD];
+	int (*adjust)(struct iris_inst *inst,
+		      struct v4l2_ctrl *ctrl);
+	int (*set)(struct iris_inst *inst,
+		   enum plat_inst_cap_type cap_id);
+};
+
+struct plat_inst_caps {
+	enum codec_type codec;
+	struct plat_inst_cap cap[INST_CAP_MAX + 1];
 };
 
 struct platform_data {
diff --git a/drivers/media/platform/qcom/vcodec/iris/platform_sm8550.c b/drivers/media/platform/qcom/vcodec/iris/platform_sm8550.c
index c75017e..0759ac5 100644
--- a/drivers/media/platform/qcom/vcodec/iris/platform_sm8550.c
+++ b/drivers/media/platform/qcom/vcodec/iris/platform_sm8550.c
@@ -9,6 +9,7 @@
 #include <media/v4l2-ctrls.h>
 
 #include "hfi_defines.h"
+#include "iris_ctrls.h"
 #include "platform_common.h"
 #include "resources.h"
 
@@ -47,7 +48,16 @@ static struct plat_inst_cap instance_cap_data_sm8550[] = {
 		FMT_NV12 | FMT_NV21 | FMT_NV12C,
 		FMT_NV12C},
 
-	{PIX_FMTS, HEVC | VP9,
+	{PIX_FMTS, HEVC,
+		FMT_NV12,
+		FMT_TP10C,
+		FMT_NV12 | FMT_NV21 | FMT_NV12C | FMT_TP10C,
+		FMT_NV12C,
+		0, 0,
+		CAP_FLAG_NONE,
+		{PROFILE}},
+
+	{PIX_FMTS, VP9,
 		FMT_NV12,
 		FMT_TP10C,
 		FMT_NV12 | FMT_NV21 | FMT_NV12C | FMT_TP10C,
@@ -88,7 +98,10 @@ static struct plat_inst_cap instance_cap_data_sm8550[] = {
 		V4L2_MPEG_VIDEO_H264_PROFILE_HIGH,
 		V4L2_CID_MPEG_VIDEO_H264_PROFILE,
 		HFI_PROP_PROFILE,
-		CAP_FLAG_OUTPUT_PORT | CAP_FLAG_MENU},
+		CAP_FLAG_OUTPUT_PORT | CAP_FLAG_MENU,
+		{0},
+		NULL,
+		set_u32_enum},
 
 	{PROFILE, HEVC,
 		V4L2_MPEG_VIDEO_HEVC_PROFILE_MAIN,
@@ -99,7 +112,10 @@ static struct plat_inst_cap instance_cap_data_sm8550[] = {
 		V4L2_MPEG_VIDEO_HEVC_PROFILE_MAIN,
 		V4L2_CID_MPEG_VIDEO_HEVC_PROFILE,
 		HFI_PROP_PROFILE,
-		CAP_FLAG_OUTPUT_PORT | CAP_FLAG_MENU},
+		CAP_FLAG_OUTPUT_PORT | CAP_FLAG_MENU,
+		{0},
+		adjust_profile,
+		set_u32_enum},
 
 	{PROFILE, VP9,
 		V4L2_MPEG_VIDEO_VP9_PROFILE_0,
@@ -109,7 +125,10 @@ static struct plat_inst_cap instance_cap_data_sm8550[] = {
 		V4L2_MPEG_VIDEO_VP9_PROFILE_0,
 		V4L2_CID_MPEG_VIDEO_VP9_PROFILE,
 		HFI_PROP_PROFILE,
-		CAP_FLAG_OUTPUT_PORT | CAP_FLAG_MENU},
+		CAP_FLAG_OUTPUT_PORT | CAP_FLAG_MENU,
+		{0},
+		NULL,
+		set_u32_enum},
 
 	{LEVEL, H264,
 		V4L2_MPEG_VIDEO_H264_LEVEL_1_0,
@@ -137,7 +156,10 @@ static struct plat_inst_cap instance_cap_data_sm8550[] = {
 		V4L2_MPEG_VIDEO_H264_LEVEL_6_1,
 		V4L2_CID_MPEG_VIDEO_H264_LEVEL,
 		HFI_PROP_LEVEL,
-		CAP_FLAG_OUTPUT_PORT | CAP_FLAG_MENU},
+		CAP_FLAG_OUTPUT_PORT | CAP_FLAG_MENU,
+		{0},
+		NULL,
+		set_u32_enum},
 
 	{LEVEL, HEVC,
 		V4L2_MPEG_VIDEO_HEVC_LEVEL_1,
@@ -158,7 +180,10 @@ static struct plat_inst_cap instance_cap_data_sm8550[] = {
 		V4L2_MPEG_VIDEO_HEVC_LEVEL_6_1,
 		V4L2_CID_MPEG_VIDEO_HEVC_LEVEL,
 		HFI_PROP_LEVEL,
-		CAP_FLAG_OUTPUT_PORT | CAP_FLAG_MENU},
+		CAP_FLAG_OUTPUT_PORT | CAP_FLAG_MENU,
+		{0},
+		NULL,
+		set_u32_enum},
 
 	{LEVEL, VP9,
 		V4L2_MPEG_VIDEO_VP9_LEVEL_1_0,
@@ -178,7 +203,10 @@ static struct plat_inst_cap instance_cap_data_sm8550[] = {
 		V4L2_MPEG_VIDEO_VP9_LEVEL_6_0,
 		V4L2_CID_MPEG_VIDEO_VP9_LEVEL,
 		HFI_PROP_LEVEL,
-		CAP_FLAG_OUTPUT_PORT | CAP_FLAG_MENU},
+		CAP_FLAG_OUTPUT_PORT | CAP_FLAG_MENU,
+		{0},
+		NULL,
+		set_u32_enum},
 
 	{HEVC_TIER, HEVC,
 		V4L2_MPEG_VIDEO_HEVC_TIER_MAIN,
@@ -188,46 +216,69 @@ static struct plat_inst_cap instance_cap_data_sm8550[] = {
 		V4L2_MPEG_VIDEO_HEVC_TIER_HIGH,
 		V4L2_CID_MPEG_VIDEO_HEVC_TIER,
 		HFI_PROP_TIER,
-		CAP_FLAG_OUTPUT_PORT | CAP_FLAG_MENU},
+		CAP_FLAG_OUTPUT_PORT | CAP_FLAG_MENU,
+		{0},
+		NULL,
+		set_u32_enum},
 
 	{DISPLAY_DELAY_ENABLE, CODECS_ALL,
 		0, 1, 1, 0,
 		V4L2_CID_MPEG_VIDEO_DEC_DISPLAY_DELAY_ENABLE,
 		HFI_PROP_DECODE_ORDER_OUTPUT,
-		CAP_FLAG_INPUT_PORT},
+		CAP_FLAG_INPUT_PORT,
+		{OUTPUT_ORDER},
+		NULL,
+		NULL},
 
 	{DISPLAY_DELAY, CODECS_ALL,
 		0, 1, 1, 0,
 		V4L2_CID_MPEG_VIDEO_DEC_DISPLAY_DELAY,
 		HFI_PROP_DECODE_ORDER_OUTPUT,
-		CAP_FLAG_INPUT_PORT},
+		CAP_FLAG_INPUT_PORT,
+		{OUTPUT_ORDER},
+		NULL,
+		NULL},
 
 	{OUTPUT_ORDER, CODECS_ALL,
 		0, 1, 1, 0,
 		0,
 		HFI_PROP_DECODE_ORDER_OUTPUT,
-		CAP_FLAG_INPUT_PORT},
+		CAP_FLAG_INPUT_PORT,
+		{0},
+		adjust_output_order,
+		set_u32},
 
 	{INPUT_BUF_HOST_MAX_COUNT, CODECS_ALL,
 		DEFAULT_MAX_HOST_BUF_COUNT, DEFAULT_MAX_HOST_BURST_BUF_COUNT,
 		1, DEFAULT_MAX_HOST_BUF_COUNT,
 		0,
 		HFI_PROP_BUFFER_HOST_MAX_COUNT,
-		CAP_FLAG_INPUT_PORT},
+		CAP_FLAG_INPUT_PORT,
+		{0},
+		NULL,
+		set_u32},
 
 	{STAGE, CODECS_ALL,
 		STAGE_1,
 		STAGE_2, 1,
 		STAGE_2,
 		0,
-		HFI_PROP_STAGE},
+		HFI_PROP_STAGE,
+		CAP_FLAG_NONE,
+		{0},
+		NULL,
+		set_stage},
 
 	{PIPE, CODECS_ALL,
 		PIPE_1,
 		PIPE_4, 1,
 		PIPE_4,
 		0,
-		HFI_PROP_PIPE},
+		HFI_PROP_PIPE,
+		CAP_FLAG_NONE,
+		{0},
+		NULL,
+		set_pipe},
 
 	{POC, H264, 0, 2, 1, 1,
 		0,
@@ -252,7 +303,10 @@ static struct plat_inst_cap instance_cap_data_sm8550[] = {
 		0, 1, 1, 1,
 		0,
 		HFI_PROP_DEC_START_FROM_RAP_FRAME,
-		CAP_FLAG_INPUT_PORT},
+		CAP_FLAG_INPUT_PORT,
+		{0},
+		NULL,
+		set_u32},
 };
 
 static const struct bus_info sm8550_bus_table[] = {
