root/drivers/media/platform/qcom/iris/iris_vpu_common.c
// SPDX-License-Identifier: GPL-2.0-only
/*
 * Copyright (c) 2022-2024 Qualcomm Innovation Center, Inc. All rights reserved.
 */

#include <linux/iopoll.h>
#include <linux/pm_opp.h>
#include <linux/reset.h>

#include "iris_core.h"
#include "iris_instance.h"
#include "iris_vpu_common.h"
#include "iris_vpu_register_defines.h"

#define AON_WRAPPER_MVP_VIDEO_CTL_NOC_LPI_CONTROL       (AON_BASE_OFFS + 0x2C)
#define AON_WRAPPER_MVP_VIDEO_CTL_NOC_LPI_STATUS        (AON_BASE_OFFS + 0x30)

#define CTRL_INIT                               (CPU_CS_BASE_OFFS + 0x48)
#define CTRL_STATUS                             (CPU_CS_BASE_OFFS + 0x4C)

#define CTRL_INIT_IDLE_MSG_BMSK                 0x40000000
#define CTRL_ERROR_STATUS__M                    0xfe
#define CTRL_STATUS_PC_READY                    0x100

#define QTBL_INFO                               (CPU_CS_BASE_OFFS + 0x50)
#define QTBL_ENABLE                             BIT(0)

#define QTBL_ADDR                               (CPU_CS_BASE_OFFS + 0x54)
#define CPU_CS_SCIACMDARG3                      (CPU_CS_BASE_OFFS + 0x58)
#define SFR_ADDR                                (CPU_CS_BASE_OFFS + 0x5C)
#define UC_REGION_ADDR                          (CPU_CS_BASE_OFFS + 0x64)
#define UC_REGION_SIZE                          (CPU_CS_BASE_OFFS + 0x68)

static void iris_vpu_interrupt_init(struct iris_core *core)
{
        u32 mask_val;

        mask_val = readl(core->reg_base + WRAPPER_INTR_MASK);
        mask_val &= ~(WRAPPER_INTR_MASK_A2HWD_BMSK |
                      WRAPPER_INTR_MASK_A2HCPU_BMSK);
        writel(mask_val, core->reg_base + WRAPPER_INTR_MASK);
}

static void iris_vpu_setup_ucregion_memory_map(struct iris_core *core)
{
        u32 queue_size, value;
        const struct vpu_ops *vpu_ops = core->iris_platform_data->vpu_ops;

        /* Iris hardware requires 4K queue alignment */
        queue_size = ALIGN(sizeof(struct iris_hfi_queue_table_header) +
                (IFACEQ_QUEUE_SIZE * IFACEQ_NUMQ), SZ_4K);

        value = (u32)core->iface_q_table_daddr;
        writel(value, core->reg_base + UC_REGION_ADDR);

        /* Iris hardware requires 1M queue alignment */
        value = ALIGN(SFR_SIZE + queue_size, SZ_1M);
        writel(value, core->reg_base + UC_REGION_SIZE);

        value = (u32)core->iface_q_table_daddr;
        writel(value, core->reg_base + QTBL_ADDR);

        writel(QTBL_ENABLE, core->reg_base + QTBL_INFO);

        if (core->sfr_daddr) {
                value = (u32)core->sfr_daddr + core->iris_platform_data->core_arch;
                writel(value, core->reg_base + SFR_ADDR);
        }

        if (vpu_ops->program_bootup_registers)
                vpu_ops->program_bootup_registers(core);
}

int iris_vpu_boot_firmware(struct iris_core *core)
{
        u32 ctrl_init = BIT(0), ctrl_status = 0, count = 0, max_tries = 1000;

        iris_vpu_setup_ucregion_memory_map(core);

        writel(ctrl_init, core->reg_base + CTRL_INIT);
        writel(0x1, core->reg_base + CPU_CS_SCIACMDARG3);

        while (!ctrl_status && count < max_tries) {
                ctrl_status = readl(core->reg_base + CTRL_STATUS);
                if ((ctrl_status & CTRL_ERROR_STATUS__M) == 0x4) {
                        dev_err(core->dev, "invalid setting for uc_region\n");
                        break;
                }

                usleep_range(50, 100);
                count++;
        }

        if (count >= max_tries) {
                dev_err(core->dev, "error booting up iris firmware\n");
                return -ETIME;
        }

        writel(HOST2XTENSA_INTR_ENABLE, core->reg_base + CPU_CS_H2XSOFTINTEN);
        writel(0x0, core->reg_base + CPU_CS_X2RPMH);

        return 0;
}

void iris_vpu_raise_interrupt(struct iris_core *core)
{
        writel(1 << CPU_IC_SOFTINT_H2A_SHFT, core->reg_base + CPU_IC_SOFTINT);
}

void iris_vpu_clear_interrupt(struct iris_core *core)
{
        u32 intr_status, mask;

        intr_status = readl(core->reg_base + WRAPPER_INTR_STATUS);
        mask = (WRAPPER_INTR_STATUS_A2H_BMSK |
                WRAPPER_INTR_STATUS_A2HWD_BMSK |
                CTRL_INIT_IDLE_MSG_BMSK);

        if (intr_status & mask)
                core->intr_status |= intr_status;

        writel(CLEAR_XTENSA2HOST_INTR, core->reg_base + CPU_CS_A2HSOFTINTCLR);
}

int iris_vpu_watchdog(struct iris_core *core, u32 intr_status)
{
        if (intr_status & WRAPPER_INTR_STATUS_A2HWD_BMSK) {
                dev_err(core->dev, "received watchdog interrupt\n");
                return -ETIME;
        }

        return 0;
}

int iris_vpu_prepare_pc(struct iris_core *core)
{
        u32 wfi_status, idle_status, pc_ready;
        u32 ctrl_status, val = 0;
        int ret;

        ctrl_status = readl(core->reg_base + CTRL_STATUS);
        pc_ready = ctrl_status & CTRL_STATUS_PC_READY;
        idle_status = ctrl_status & BIT(30);
        if (pc_ready)
                return 0;

        wfi_status = readl(core->reg_base + WRAPPER_TZ_CPU_STATUS);
        wfi_status &= BIT(0);
        if (!wfi_status || !idle_status)
                goto skip_power_off;

        ret = core->hfi_ops->sys_pc_prep(core);
        if (ret)
                goto skip_power_off;

        ret = readl_poll_timeout(core->reg_base + CTRL_STATUS, val,
                                 val & CTRL_STATUS_PC_READY, 250, 2500);
        if (ret)
                goto skip_power_off;

        ret = readl_poll_timeout(core->reg_base + WRAPPER_TZ_CPU_STATUS,
                                 val, val & BIT(0), 250, 2500);
        if (ret)
                goto skip_power_off;

        return 0;

skip_power_off:
        ctrl_status = readl(core->reg_base + CTRL_STATUS);
        wfi_status = readl(core->reg_base + WRAPPER_TZ_CPU_STATUS);
        wfi_status &= BIT(0);
        dev_err(core->dev, "skip power collapse, wfi=%#x, idle=%#x, pcr=%#x, ctrl=%#x)\n",
                wfi_status, idle_status, pc_ready, ctrl_status);

        return -EAGAIN;
}

int iris_vpu_power_off_controller(struct iris_core *core)
{
        u32 val = 0;
        int ret;

        writel(MSK_SIGNAL_FROM_TENSILICA | MSK_CORE_POWER_ON, core->reg_base + CPU_CS_X2RPMH);

        if (!core->iris_platform_data->no_aon) {
                writel(REQ_POWER_DOWN_PREP, core->reg_base + AON_WRAPPER_MVP_NOC_LPI_CONTROL);

                ret = readl_poll_timeout(core->reg_base + AON_WRAPPER_MVP_NOC_LPI_STATUS,
                                         val, val & BIT(0), 200, 2000);
                if (ret)
                        goto disable_power;
        }

        writel(REQ_POWER_DOWN_PREP, core->reg_base + WRAPPER_IRIS_CPU_NOC_LPI_CONTROL);

        ret = readl_poll_timeout(core->reg_base + WRAPPER_IRIS_CPU_NOC_LPI_STATUS,
                                 val, val & BIT(0), 200, 2000);
        if (ret)
                goto disable_power;

        writel(0x0, core->reg_base + WRAPPER_DEBUG_BRIDGE_LPI_CONTROL);

        ret = readl_poll_timeout(core->reg_base + WRAPPER_DEBUG_BRIDGE_LPI_STATUS,
                                 val, val == 0, 200, 2000);
        if (ret)
                goto disable_power;

        writel(CTL_AXI_CLK_HALT | CTL_CLK_HALT,
               core->reg_base + WRAPPER_TZ_CTL_AXI_CLOCK_CONFIG);
        writel(RESET_HIGH, core->reg_base + WRAPPER_TZ_QNS4PDXFIFO_RESET);
        writel(0x0, core->reg_base + WRAPPER_TZ_QNS4PDXFIFO_RESET);
        writel(0x0, core->reg_base + WRAPPER_TZ_CTL_AXI_CLOCK_CONFIG);

disable_power:
        iris_disable_unprepare_clock(core, IRIS_AHB_CLK);
        iris_disable_unprepare_clock(core, IRIS_CTRL_CLK);
        iris_disable_unprepare_clock(core, IRIS_AXI_CLK);
        iris_disable_power_domains(core, core->pmdomain_tbl->pd_devs[IRIS_CTRL_POWER_DOMAIN]);

        return 0;
}

void iris_vpu_power_off_hw(struct iris_core *core)
{
        dev_pm_genpd_set_hwmode(core->pmdomain_tbl->pd_devs[IRIS_HW_POWER_DOMAIN], false);
        iris_disable_power_domains(core, core->pmdomain_tbl->pd_devs[IRIS_HW_POWER_DOMAIN]);
        iris_disable_unprepare_clock(core, IRIS_HW_AHB_CLK);
        iris_disable_unprepare_clock(core, IRIS_HW_CLK);
}

void iris_vpu_power_off(struct iris_core *core)
{
        iris_opp_set_rate(core->dev, 0);
        core->iris_platform_data->vpu_ops->power_off_hw(core);
        core->iris_platform_data->vpu_ops->power_off_controller(core);
        iris_unset_icc_bw(core);

        if (!iris_vpu_watchdog(core, core->intr_status))
                disable_irq_nosync(core->irq);
}

int iris_vpu_power_on_controller(struct iris_core *core)
{
        u32 rst_tbl_size = core->iris_platform_data->clk_rst_tbl_size;
        int ret;

        ret = iris_enable_power_domains(core, core->pmdomain_tbl->pd_devs[IRIS_CTRL_POWER_DOMAIN]);
        if (ret)
                return ret;

        ret = reset_control_bulk_reset(rst_tbl_size, core->resets);
        if (ret)
                goto err_disable_power;

        ret = iris_prepare_enable_clock(core, IRIS_AXI_CLK);
        if (ret)
                goto err_disable_power;

        ret = iris_prepare_enable_clock(core, IRIS_CTRL_CLK);
        if (ret)
                goto err_disable_axi_clock;

        ret = iris_prepare_enable_clock(core, IRIS_AHB_CLK);
        if (ret && ret != -ENOENT)
                goto err_disable_ctrl_clock;

        return 0;

err_disable_ctrl_clock:
        iris_disable_unprepare_clock(core, IRIS_CTRL_CLK);
err_disable_axi_clock:
        iris_disable_unprepare_clock(core, IRIS_AXI_CLK);
err_disable_power:
        iris_disable_power_domains(core, core->pmdomain_tbl->pd_devs[IRIS_CTRL_POWER_DOMAIN]);

        return ret;
}

int iris_vpu_power_on_hw(struct iris_core *core)
{
        int ret;

        ret = iris_enable_power_domains(core, core->pmdomain_tbl->pd_devs[IRIS_HW_POWER_DOMAIN]);
        if (ret)
                return ret;

        ret = iris_prepare_enable_clock(core, IRIS_HW_CLK);
        if (ret)
                goto err_disable_power;

        ret = iris_prepare_enable_clock(core, IRIS_HW_AHB_CLK);
        if (ret && ret != -ENOENT)
                goto err_disable_hw_clock;

        ret = dev_pm_genpd_set_hwmode(core->pmdomain_tbl->pd_devs[IRIS_HW_POWER_DOMAIN], true);
        if (ret)
                goto err_disable_hw_ahb_clock;

        return 0;

err_disable_hw_ahb_clock:
        iris_disable_unprepare_clock(core, IRIS_HW_AHB_CLK);
err_disable_hw_clock:
        iris_disable_unprepare_clock(core, IRIS_HW_CLK);
err_disable_power:
        iris_disable_power_domains(core, core->pmdomain_tbl->pd_devs[IRIS_HW_POWER_DOMAIN]);

        return ret;
}

int iris_vpu35_vpu4x_power_off_controller(struct iris_core *core)
{
        u32 clk_rst_tbl_size = core->iris_platform_data->clk_rst_tbl_size;
        bool handshake_done, handshake_busy;
        u32 count = 0, val = 0;
        int ret;

        writel(MSK_SIGNAL_FROM_TENSILICA | MSK_CORE_POWER_ON, core->reg_base + CPU_CS_X2RPMH);

        writel(REQ_POWER_DOWN_PREP, core->reg_base + WRAPPER_IRIS_CPU_NOC_LPI_CONTROL);

        ret = readl_poll_timeout(core->reg_base + WRAPPER_IRIS_CPU_NOC_LPI_STATUS,
                                 val, val & BIT(0), 200, 2000);
        if (ret)
                goto disable_power;

        writel(0, core->reg_base + WRAPPER_IRIS_CPU_NOC_LPI_CONTROL);

        /* Retry up to 1000 times as recommended by hardware documentation */
        do {
                /* set MNoC to low power */
                writel(REQ_POWER_DOWN_PREP, core->reg_base +
                       AON_WRAPPER_MVP_VIDEO_CTL_NOC_LPI_CONTROL);
                usleep_range(10, 20);
                val = readl(core->reg_base + AON_WRAPPER_MVP_VIDEO_CTL_NOC_LPI_STATUS);

                handshake_done = val & NOC_LPI_STATUS_DONE;
                handshake_busy = val & (NOC_LPI_STATUS_DENY | NOC_LPI_STATUS_ACTIVE);

                if (handshake_done || !handshake_busy)
                        break;

                writel(0, core->reg_base + AON_WRAPPER_MVP_VIDEO_CTL_NOC_LPI_CONTROL);
                usleep_range(10, 20);

        } while (++count < 1000);

        if (!handshake_done && handshake_busy)
                dev_err(core->dev, "LPI handshake timeout\n");

        ret = readl_poll_timeout(core->reg_base + AON_WRAPPER_MVP_VIDEO_CTL_NOC_LPI_STATUS,
                                 val, val & BIT(0), 200, 2000);
        if (ret)
                goto disable_power;

        writel(0, core->reg_base + AON_WRAPPER_MVP_VIDEO_CTL_NOC_LPI_CONTROL);

        writel(0, core->reg_base + WRAPPER_DEBUG_BRIDGE_LPI_CONTROL);

        readl_poll_timeout(core->reg_base + WRAPPER_DEBUG_BRIDGE_LPI_STATUS,
                           val, val == 0, 200, 2000);

disable_power:
        iris_disable_unprepare_clock(core, IRIS_CTRL_CLK);
        iris_disable_unprepare_clock(core, IRIS_CTRL_FREERUN_CLK);
        iris_disable_unprepare_clock(core, IRIS_AXI1_CLK);

        iris_disable_power_domains(core, core->pmdomain_tbl->pd_devs[IRIS_CTRL_POWER_DOMAIN]);

        reset_control_bulk_reset(clk_rst_tbl_size, core->resets);

        return 0;
}

int iris_vpu35_vpu4x_power_on_controller(struct iris_core *core)
{
        int ret;

        ret = iris_enable_power_domains(core, core->pmdomain_tbl->pd_devs[IRIS_CTRL_POWER_DOMAIN]);
        if (ret)
                return ret;

        ret = iris_prepare_enable_clock(core, IRIS_AXI1_CLK);
        if (ret)
                goto err_disable_power;

        ret = iris_prepare_enable_clock(core, IRIS_CTRL_FREERUN_CLK);
        if (ret)
                goto err_disable_axi1_clk;

        ret = iris_prepare_enable_clock(core, IRIS_CTRL_CLK);
        if (ret)
                goto err_disable_ctrl_free_clk;

        return 0;

err_disable_ctrl_free_clk:
        iris_disable_unprepare_clock(core, IRIS_CTRL_FREERUN_CLK);
err_disable_axi1_clk:
        iris_disable_unprepare_clock(core, IRIS_AXI1_CLK);
err_disable_power:
        iris_disable_power_domains(core, core->pmdomain_tbl->pd_devs[IRIS_CTRL_POWER_DOMAIN]);

        return ret;
}

void iris_vpu35_vpu4x_program_bootup_registers(struct iris_core *core)
{
        writel(0x1, core->reg_base + WRAPPER_IRIS_VCODEC_VPU_WRAPPER_SPARE_0);
}

u64 iris_vpu3x_vpu4x_calculate_frequency(struct iris_inst *inst, size_t data_size)
{
        struct platform_inst_caps *caps = inst->core->iris_platform_data->inst_caps;
        struct v4l2_format *inp_f = inst->fmt_src;
        u32 height, width, mbs_per_second, mbpf;
        u64 fw_cycles, fw_vpp_cycles;
        u64 vsp_cycles, vpp_cycles;
        u32 fps = DEFAULT_FPS;

        width = max(inp_f->fmt.pix_mp.width, inst->crop.width);
        height = max(inp_f->fmt.pix_mp.height, inst->crop.height);

        mbpf = NUM_MBS_PER_FRAME(height, width);
        mbs_per_second = mbpf * fps;

        fw_cycles = fps * caps->mb_cycles_fw;
        fw_vpp_cycles = fps * caps->mb_cycles_fw_vpp;

        vpp_cycles = mult_frac(mbs_per_second, caps->mb_cycles_vpp, (u32)inst->fw_caps[PIPE].value);
        /* 21 / 20 is minimum overhead factor */
        vpp_cycles += max(div_u64(vpp_cycles, 20), fw_vpp_cycles);

        /* 1.059 is multi-pipe overhead */
        if (inst->fw_caps[PIPE].value > 1)
                vpp_cycles += div_u64(vpp_cycles * 59, 1000);

        vsp_cycles = fps * data_size * 8;
        vsp_cycles = div_u64(vsp_cycles, 2);
        /* VSP FW overhead 1.05 */
        vsp_cycles = div_u64(vsp_cycles * 21, 20);

        if (inst->fw_caps[STAGE].value == STAGE_1)
                vsp_cycles = vsp_cycles * 3;

        return max3(vpp_cycles, vsp_cycles, fw_cycles);
}

int iris_vpu_power_on(struct iris_core *core)
{
        u32 freq;
        int ret;

        ret = iris_set_icc_bw(core, INT_MAX);
        if (ret)
                goto err;

        ret = core->iris_platform_data->vpu_ops->power_on_controller(core);
        if (ret)
                goto err_unvote_icc;

        ret = core->iris_platform_data->vpu_ops->power_on_hw(core);
        if (ret)
                goto err_power_off_ctrl;

        freq = core->power.clk_freq ? core->power.clk_freq :
                                      (u32)ULONG_MAX;

        iris_opp_set_rate(core->dev, freq);

        core->iris_platform_data->set_preset_registers(core);

        iris_vpu_interrupt_init(core);
        core->intr_status = 0;
        enable_irq(core->irq);

        return 0;

err_power_off_ctrl:
        core->iris_platform_data->vpu_ops->power_off_controller(core);
err_unvote_icc:
        iris_unset_icc_bw(core);
err:
        dev_err(core->dev, "power on failed\n");

        return ret;
}