root/drivers/media/platform/qcom/iris/iris_vpu4x.c
// 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,
};