--- /dev/null
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (c) 2025 Qualcomm Innovation Center, Inc. All rights reserved.
+ */
+
+#include <linux/iopoll.h>
+#include <linux/reset.h>
+
+#include "iris_instance.h"
+#include "iris_vpu_common.h"
+#include "iris_vpu_register_defines.h"
+
+#define AON_WRAPPER_MVP_NOC_RESET_SYNCRST (AON_MVP_NOC_RESET + 0x08)
+#define CPU_CS_APV_BRIDGE_SYNC_RESET (CPU_BASE_OFFS + 0x174)
+#define MVP_NOC_RESET_REQ_MASK 0x70103
+#define VPU_IDLE_BITS 0x7103
+#define WRAPPER_EFUSE_MONITOR (WRAPPER_BASE_OFFS + 0x08)
+
+#define APV_CLK_HALT BIT(1)
+#define CORE_CLK_HALT BIT(0)
+#define CORE_PWR_ON BIT(1)
+#define DISABLE_VIDEO_APV_BIT BIT(27)
+#define DISABLE_VIDEO_VPP1_BIT BIT(28)
+#define DISABLE_VIDEO_VPP0_BIT BIT(29)
+
+static int iris_vpu4x_genpd_set_hwmode(struct iris_core *core, bool hw_mode, u32 efuse_value)
+{
+ int ret;
+
+ ret = dev_pm_genpd_set_hwmode(core->pmdomain_tbl->pd_devs[IRIS_HW_POWER_DOMAIN], hw_mode);
+ if (ret)
+ return ret;
+
+ if (!(efuse_value & DISABLE_VIDEO_VPP0_BIT)) {
+ ret = dev_pm_genpd_set_hwmode(core->pmdomain_tbl->pd_devs
+ [IRIS_VPP0_HW_POWER_DOMAIN], hw_mode);
+ if (ret)
+ goto restore_hw_domain_mode;
+ }
+
+ if (!(efuse_value & DISABLE_VIDEO_VPP1_BIT)) {
+ ret = dev_pm_genpd_set_hwmode(core->pmdomain_tbl->pd_devs
+ [IRIS_VPP1_HW_POWER_DOMAIN], hw_mode);
+ if (ret)
+ goto restore_vpp0_domain_mode;
+ }
+
+ if (!(efuse_value & DISABLE_VIDEO_APV_BIT)) {
+ ret = dev_pm_genpd_set_hwmode(core->pmdomain_tbl->pd_devs
+ [IRIS_APV_HW_POWER_DOMAIN], hw_mode);
+ if (ret)
+ goto restore_vpp1_domain_mode;
+ }
+
+ return 0;
+
+restore_vpp1_domain_mode:
+ if (!(efuse_value & DISABLE_VIDEO_VPP1_BIT))
+ dev_pm_genpd_set_hwmode(core->pmdomain_tbl->pd_devs[IRIS_VPP1_HW_POWER_DOMAIN],
+ !hw_mode);
+restore_vpp0_domain_mode:
+ if (!(efuse_value & DISABLE_VIDEO_VPP0_BIT))
+ dev_pm_genpd_set_hwmode(core->pmdomain_tbl->pd_devs[IRIS_VPP0_HW_POWER_DOMAIN],
+ !hw_mode);
+restore_hw_domain_mode:
+ dev_pm_genpd_set_hwmode(core->pmdomain_tbl->pd_devs[IRIS_HW_POWER_DOMAIN], !hw_mode);
+
+ return ret;
+}
+
+static int iris_vpu4x_power_on_apv(struct iris_core *core)
+{
+ int ret;
+
+ ret = iris_enable_power_domains(core,
+ core->pmdomain_tbl->pd_devs[IRIS_APV_HW_POWER_DOMAIN]);
+ if (ret)
+ return ret;
+
+ ret = iris_prepare_enable_clock(core, IRIS_APV_HW_CLK);
+ if (ret)
+ goto disable_apv_hw_power_domain;
+
+ return 0;
+
+disable_apv_hw_power_domain:
+ iris_disable_power_domains(core, core->pmdomain_tbl->pd_devs[IRIS_APV_HW_POWER_DOMAIN]);
+
+ return ret;
+}
+
+static void iris_vpu4x_power_off_apv(struct iris_core *core)
+{
+ bool handshake_done, handshake_busy;
+ u32 value, count = 0;
+ int ret;
+
+ value = readl(core->reg_base + WRAPPER_CORE_CLOCK_CONFIG);
+
+ if (value & APV_CLK_HALT)
+ writel(0x0, core->reg_base + WRAPPER_CORE_CLOCK_CONFIG);
+
+ do {
+ writel(REQ_POWER_DOWN_PREP, core->reg_base + AON_WRAPPER_MVP_NOC_LPI_CONTROL);
+ usleep_range(10, 20);
+ value = readl(core->reg_base + AON_WRAPPER_MVP_NOC_LPI_STATUS);
+
+ handshake_done = value & NOC_LPI_STATUS_DONE;
+ handshake_busy = value & (NOC_LPI_STATUS_DENY | NOC_LPI_STATUS_ACTIVE);
+
+ if (handshake_done || !handshake_busy)
+ break;
+
+ writel(0x0, core->reg_base + AON_WRAPPER_MVP_NOC_LPI_CONTROL);
+ usleep_range(10, 20);
+
+ } while (++count < 1000);
+
+ if (!handshake_done && handshake_busy)
+ dev_err(core->dev, "LPI handshake timeout\n");
+
+ writel(0x080200, core->reg_base + AON_WRAPPER_MVP_NOC_RESET_REQ);
+ ret = readl_poll_timeout(core->reg_base + AON_WRAPPER_MVP_NOC_RESET_ACK,
+ value, value & 0x080200, 200, 2000);
+ if (ret)
+ goto disable_clocks_and_power;
+
+ writel(0x0, core->reg_base + AON_WRAPPER_MVP_NOC_RESET_SYNCRST);
+ writel(0x0, core->reg_base + AON_WRAPPER_MVP_NOC_RESET_REQ);
+ ret = readl_poll_timeout(core->reg_base + AON_WRAPPER_MVP_NOC_RESET_ACK,
+ value, value == 0x0, 200, 2000);
+ if (ret)
+ goto disable_clocks_and_power;
+
+ writel(CORE_BRIDGE_SW_RESET | CORE_BRIDGE_HW_RESET_DISABLE, core->reg_base +
+ CPU_CS_APV_BRIDGE_SYNC_RESET);
+ writel(CORE_BRIDGE_HW_RESET_DISABLE, core->reg_base + CPU_CS_APV_BRIDGE_SYNC_RESET);
+ writel(0x0, core->reg_base + CPU_CS_APV_BRIDGE_SYNC_RESET);
+
+disable_clocks_and_power:
+ iris_disable_unprepare_clock(core, IRIS_APV_HW_CLK);
+ iris_disable_power_domains(core, core->pmdomain_tbl->pd_devs[IRIS_APV_HW_POWER_DOMAIN]);
+}
+
+static void iris_vpu4x_ahb_sync_reset_apv(struct iris_core *core)
+{
+ writel(CORE_BRIDGE_SW_RESET | CORE_BRIDGE_HW_RESET_DISABLE, core->reg_base +
+ CPU_CS_APV_BRIDGE_SYNC_RESET);
+ writel(CORE_BRIDGE_HW_RESET_DISABLE, core->reg_base + CPU_CS_APV_BRIDGE_SYNC_RESET);
+ writel(0x0, core->reg_base + CPU_CS_APV_BRIDGE_SYNC_RESET);
+}
+
+static void iris_vpu4x_ahb_sync_reset_hardware(struct iris_core *core)
+{
+ writel(CORE_BRIDGE_SW_RESET | CORE_BRIDGE_HW_RESET_DISABLE, core->reg_base +
+ CPU_CS_AHB_BRIDGE_SYNC_RESET);
+ writel(CORE_BRIDGE_HW_RESET_DISABLE, core->reg_base + CPU_CS_AHB_BRIDGE_SYNC_RESET);
+ writel(0x0, core->reg_base + CPU_CS_AHB_BRIDGE_SYNC_RESET);
+}
+
+static int iris_vpu4x_enable_hardware_clocks(struct iris_core *core, u32 efuse_value)
+{
+ int ret;
+
+ ret = iris_prepare_enable_clock(core, IRIS_AXI_CLK);
+ if (ret)
+ return ret;
+
+ ret = iris_prepare_enable_clock(core, IRIS_HW_FREERUN_CLK);
+ if (ret)
+ goto disable_axi_clock;
+
+ ret = iris_prepare_enable_clock(core, IRIS_HW_CLK);
+ if (ret)
+ goto disable_hw_free_run_clock;
+
+ ret = iris_prepare_enable_clock(core, IRIS_BSE_HW_CLK);
+ if (ret)
+ goto disable_hw_clock;
+
+ if (!(efuse_value & DISABLE_VIDEO_VPP0_BIT)) {
+ ret = iris_prepare_enable_clock(core, IRIS_VPP0_HW_CLK);
+ if (ret)
+ goto disable_bse_hw_clock;
+ }
+
+ if (!(efuse_value & DISABLE_VIDEO_VPP1_BIT)) {
+ ret = iris_prepare_enable_clock(core, IRIS_VPP1_HW_CLK);
+ if (ret)
+ goto disable_vpp0_hw_clock;
+ }
+
+ return 0;
+
+disable_vpp0_hw_clock:
+ if (!(efuse_value & DISABLE_VIDEO_VPP0_BIT))
+ iris_disable_unprepare_clock(core, IRIS_VPP0_HW_CLK);
+disable_bse_hw_clock:
+ iris_disable_unprepare_clock(core, IRIS_BSE_HW_CLK);
+disable_hw_clock:
+ iris_disable_unprepare_clock(core, IRIS_HW_CLK);
+disable_hw_free_run_clock:
+ iris_disable_unprepare_clock(core, IRIS_HW_FREERUN_CLK);
+disable_axi_clock:
+ iris_disable_unprepare_clock(core, IRIS_AXI_CLK);
+
+ return ret;
+}
+
+static void iris_vpu4x_disable_hardware_clocks(struct iris_core *core, u32 efuse_value)
+{
+ if (!(efuse_value & DISABLE_VIDEO_VPP1_BIT))
+ iris_disable_unprepare_clock(core, IRIS_VPP1_HW_CLK);
+
+ if (!(efuse_value & DISABLE_VIDEO_VPP0_BIT))
+ iris_disable_unprepare_clock(core, IRIS_VPP0_HW_CLK);
+
+ iris_disable_unprepare_clock(core, IRIS_BSE_HW_CLK);
+ iris_disable_unprepare_clock(core, IRIS_HW_CLK);
+ iris_disable_unprepare_clock(core, IRIS_HW_FREERUN_CLK);
+ iris_disable_unprepare_clock(core, IRIS_AXI_CLK);
+}
+
+static int iris_vpu4x_power_on_hardware(struct iris_core *core)
+{
+ u32 efuse_value = readl(core->reg_base + WRAPPER_EFUSE_MONITOR);
+ int ret;
+
+ ret = iris_enable_power_domains(core, core->pmdomain_tbl->pd_devs[IRIS_HW_POWER_DOMAIN]);
+ if (ret)
+ return ret;
+
+ if (!(efuse_value & DISABLE_VIDEO_VPP0_BIT)) {
+ ret = iris_enable_power_domains(core, core->pmdomain_tbl->pd_devs
+ [IRIS_VPP0_HW_POWER_DOMAIN]);
+ if (ret)
+ goto disable_hw_power_domain;
+ }
+
+ if (!(efuse_value & DISABLE_VIDEO_VPP1_BIT)) {
+ ret = iris_enable_power_domains(core, core->pmdomain_tbl->pd_devs
+ [IRIS_VPP1_HW_POWER_DOMAIN]);
+ if (ret)
+ goto disable_vpp0_power_domain;
+ }
+
+ ret = iris_vpu4x_enable_hardware_clocks(core, efuse_value);
+ if (ret)
+ goto disable_vpp1_power_domain;
+
+ if (!(efuse_value & DISABLE_VIDEO_APV_BIT)) {
+ ret = iris_vpu4x_power_on_apv(core);
+ if (ret)
+ goto disable_hw_clocks;
+
+ iris_vpu4x_ahb_sync_reset_apv(core);
+ }
+
+ iris_vpu4x_ahb_sync_reset_hardware(core);
+
+ ret = iris_vpu4x_genpd_set_hwmode(core, true, efuse_value);
+ if (ret)
+ goto disable_apv_power_domain;
+
+ return 0;
+
+disable_apv_power_domain:
+ if (!(efuse_value & DISABLE_VIDEO_APV_BIT))
+ iris_vpu4x_power_off_apv(core);
+disable_hw_clocks:
+ iris_vpu4x_disable_hardware_clocks(core, efuse_value);
+disable_vpp1_power_domain:
+ if (!(efuse_value & DISABLE_VIDEO_VPP1_BIT))
+ iris_disable_power_domains(core, core->pmdomain_tbl->pd_devs
+ [IRIS_VPP1_HW_POWER_DOMAIN]);
+disable_vpp0_power_domain:
+ if (!(efuse_value & DISABLE_VIDEO_VPP0_BIT))
+ iris_disable_power_domains(core, core->pmdomain_tbl->pd_devs
+ [IRIS_VPP0_HW_POWER_DOMAIN]);
+disable_hw_power_domain:
+ iris_disable_power_domains(core, core->pmdomain_tbl->pd_devs[IRIS_HW_POWER_DOMAIN]);
+
+ return ret;
+}
+
+static void iris_vpu4x_power_off_hardware(struct iris_core *core)
+{
+ u32 efuse_value = readl(core->reg_base + WRAPPER_EFUSE_MONITOR);
+ bool handshake_done, handshake_busy;
+ u32 value, count = 0;
+ int ret;
+
+ iris_vpu4x_genpd_set_hwmode(core, false, efuse_value);
+
+ if (!(efuse_value & DISABLE_VIDEO_APV_BIT))
+ iris_vpu4x_power_off_apv(core);
+
+ value = readl(core->reg_base + WRAPPER_CORE_POWER_STATUS);
+
+ if (!(value & CORE_PWR_ON))
+ goto disable_clocks_and_power;
+
+ value = readl(core->reg_base + WRAPPER_CORE_CLOCK_CONFIG);
+
+ if (value & CORE_CLK_HALT)
+ writel(0x0, core->reg_base + WRAPPER_CORE_CLOCK_CONFIG);
+
+ readl_poll_timeout(core->reg_base + VCODEC_SS_IDLE_STATUSN, value,
+ value & VPU_IDLE_BITS, 2000, 20000);
+
+ do {
+ writel(REQ_POWER_DOWN_PREP, core->reg_base + AON_WRAPPER_MVP_NOC_LPI_CONTROL);
+ usleep_range(10, 20);
+ value = readl(core->reg_base + AON_WRAPPER_MVP_NOC_LPI_STATUS);
+
+ handshake_done = value & NOC_LPI_STATUS_DONE;
+ handshake_busy = value & (NOC_LPI_STATUS_DENY | NOC_LPI_STATUS_ACTIVE);
+
+ if (handshake_done || !handshake_busy)
+ break;
+
+ writel(0x0, core->reg_base + AON_WRAPPER_MVP_NOC_LPI_CONTROL);
+ usleep_range(10, 20);
+
+ } while (++count < 1000);
+
+ if (!handshake_done && handshake_busy)
+ dev_err(core->dev, "LPI handshake timeout\n");
+
+ writel(MVP_NOC_RESET_REQ_MASK, core->reg_base + AON_WRAPPER_MVP_NOC_RESET_REQ);
+ ret = readl_poll_timeout(core->reg_base + AON_WRAPPER_MVP_NOC_RESET_ACK,
+ value, value & MVP_NOC_RESET_REQ_MASK, 200, 2000);
+ if (ret)
+ goto disable_clocks_and_power;
+
+ writel(0x0, core->reg_base + AON_WRAPPER_MVP_NOC_RESET_SYNCRST);
+ writel(0x0, core->reg_base + AON_WRAPPER_MVP_NOC_RESET_REQ);
+ ret = readl_poll_timeout(core->reg_base + AON_WRAPPER_MVP_NOC_RESET_ACK,
+ value, value == 0x0, 200, 2000);
+ if (ret)
+ goto disable_clocks_and_power;
+
+ writel(CORE_BRIDGE_SW_RESET | CORE_BRIDGE_HW_RESET_DISABLE, core->reg_base +
+ CPU_CS_AHB_BRIDGE_SYNC_RESET);
+ writel(CORE_BRIDGE_HW_RESET_DISABLE, core->reg_base + CPU_CS_AHB_BRIDGE_SYNC_RESET);
+ writel(0x0, core->reg_base + CPU_CS_AHB_BRIDGE_SYNC_RESET);
+
+disable_clocks_and_power:
+ iris_vpu4x_disable_hardware_clocks(core, efuse_value);
+
+ if (!(efuse_value & DISABLE_VIDEO_VPP1_BIT))
+ iris_disable_power_domains(core, core->pmdomain_tbl->pd_devs
+ [IRIS_VPP1_HW_POWER_DOMAIN]);
+
+ if (!(efuse_value & DISABLE_VIDEO_VPP0_BIT))
+ iris_disable_power_domains(core, core->pmdomain_tbl->pd_devs
+ [IRIS_VPP0_HW_POWER_DOMAIN]);
+
+ iris_disable_power_domains(core, core->pmdomain_tbl->pd_devs[IRIS_HW_POWER_DOMAIN]);
+}
+
+const struct vpu_ops iris_vpu4x_ops = {
+ .power_off_hw = iris_vpu4x_power_off_hardware,
+ .power_on_hw = iris_vpu4x_power_on_hardware,
+ .power_off_controller = iris_vpu35_vpu4x_power_off_controller,
+ .power_on_controller = iris_vpu35_vpu4x_power_on_controller,
+ .program_bootup_registers = iris_vpu35_vpu4x_program_bootup_registers,
+ .calc_freq = iris_vpu3x_vpu4x_calculate_frequency,
+};