new file mode 100644
@@ -0,0 +1,66 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Copyright (c) 2020-2021, The Linux Foundation. All rights reserved.
+ * Copyright (c) 2023 Qualcomm Innovation Center, Inc. All rights reserved.
+ */
+
+#ifndef _VENUS_HFI_H_
+#define _VENUS_HFI_H_
+
+#include <linux/clk-provider.h>
+#include <linux/clk.h>
+#include <linux/irqreturn.h>
+#include <linux/regulator/consumer.h>
+
+#include "msm_vidc_core.h"
+#include "msm_vidc_inst.h"
+#include "msm_vidc_internal.h"
+
+#define VIDC_MAX_PC_SKIP_COUNT 10
+
+struct vidc_buffer_addr_info {
+ enum msm_vidc_buffer_type buffer_type;
+ u32 buffer_size;
+ u32 num_buffers;
+ u32 align_device_addr;
+ u32 extradata_addr;
+ u32 extradata_size;
+ u32 response_required;
+};
+
+int __strict_check(struct msm_vidc_core *core,
+ const char *function);
+int venus_hfi_session_property(struct msm_vidc_inst *inst,
+ u32 pkt_type, u32 flags, u32 port,
+ u32 payload_type, void *payload,
+ u32 payload_size);
+int venus_hfi_session_command(struct msm_vidc_inst *inst,
+ u32 cmd, enum msm_vidc_port_type port,
+ u32 payload_type, void *payload, u32 payload_size);
+int venus_hfi_queue_buffer(struct msm_vidc_inst *inst,
+ struct msm_vidc_buffer *buffer);
+int venus_hfi_release_buffer(struct msm_vidc_inst *inst,
+ struct msm_vidc_buffer *buffer);
+int venus_hfi_start(struct msm_vidc_inst *inst, enum msm_vidc_port_type port);
+int venus_hfi_stop(struct msm_vidc_inst *inst, enum msm_vidc_port_type port);
+int venus_hfi_session_close(struct msm_vidc_inst *inst);
+int venus_hfi_session_open(struct msm_vidc_inst *inst);
+int venus_hfi_session_pause(struct msm_vidc_inst *inst, enum msm_vidc_port_type port);
+int venus_hfi_session_resume(struct msm_vidc_inst *inst,
+ enum msm_vidc_port_type port, u32 payload);
+int venus_hfi_session_drain(struct msm_vidc_inst *inst, enum msm_vidc_port_type port);
+int venus_hfi_session_set_codec(struct msm_vidc_inst *inst);
+int venus_hfi_core_init(struct msm_vidc_core *core);
+int venus_hfi_core_deinit(struct msm_vidc_core *core, bool force);
+int venus_hfi_suspend(struct msm_vidc_core *core);
+int venus_hfi_reserve_hardware(struct msm_vidc_inst *inst, u32 duration);
+int venus_hfi_scale_clocks(struct msm_vidc_inst *inst, u64 freq);
+int venus_hfi_scale_buses(struct msm_vidc_inst *inst, u64 bw_ddr, u64 bw_llcc);
+int venus_hfi_set_ir_period(struct msm_vidc_inst *inst, u32 ir_type,
+ enum msm_vidc_inst_capability_type cap_id);
+void venus_hfi_pm_work_handler(struct work_struct *work);
+irqreturn_t venus_hfi_isr(int irq, void *data);
+irqreturn_t venus_hfi_isr_handler(int irq, void *data);
+int __prepare_pc(struct msm_vidc_core *core);
+
+#endif // _VENUS_HFI_H_
new file mode 100644
@@ -0,0 +1,1503 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (c) 2020-2021, The Linux Foundation. All rights reserved.
+ * Copyright (c) 2022-2023 Qualcomm Innovation Center, Inc. All rights reserved.
+ */
+
+#include <linux/firmware.h>
+#include <linux/firmware/qcom/qcom_scm.h>
+#include <linux/iommu.h>
+#include <linux/iopoll.h>
+#include <linux/irqreturn.h>
+#include <linux/of_address.h>
+#include <linux/soc/qcom/llcc-qcom.h>
+#include <linux/soc/qcom/mdt_loader.h>
+#include <linux/soc/qcom/smem.h>
+
+#include "firmware.h"
+#include "hfi_packet.h"
+#include "msm_vidc_control.h"
+#include "msm_vidc_core.h"
+#include "msm_vidc_debug.h"
+#include "msm_vidc_driver.h"
+#include "msm_vidc_memory.h"
+#include "msm_vidc_platform.h"
+#include "msm_vidc_power.h"
+#include "msm_vidc_state.h"
+#include "venus_hfi.h"
+#include "venus_hfi_queue.h"
+#include "venus_hfi_response.h"
+
+#define update_offset(offset, val) ((offset) += (val))
+#define update_timestamp(ts, val) \
+ do { \
+ do_div((ts), NSEC_PER_USEC); \
+ (ts) += (val); \
+ (ts) *= NSEC_PER_USEC; \
+ } while (0)
+
+static int __resume(struct msm_vidc_core *core);
+static int __suspend(struct msm_vidc_core *core);
+
+static void __fatal_error(bool fatal)
+{
+ WARN_ON(fatal);
+}
+
+int __strict_check(struct msm_vidc_core *core, const char *function)
+{
+ bool fatal = !mutex_is_locked(&core->lock);
+
+ __fatal_error(fatal);
+
+ if (fatal)
+ d_vpr_e("%s: strict check failed\n", function);
+
+ return fatal ? -EINVAL : 0;
+}
+
+static bool __valdiate_session(struct msm_vidc_core *core,
+ struct msm_vidc_inst *inst, const char *func)
+{
+ bool valid = false;
+ struct msm_vidc_inst *temp;
+ int rc = 0;
+
+ rc = __strict_check(core, __func__);
+ if (rc)
+ return false;
+
+ list_for_each_entry(temp, &core->instances, list) {
+ if (temp == inst) {
+ valid = true;
+ break;
+ }
+ }
+ if (!valid)
+ i_vpr_e(inst, "%s: invalid session\n", func);
+
+ return valid;
+}
+
+static void __schedule_power_collapse_work(struct msm_vidc_core *core)
+{
+ if (!core->capabilities[SW_PC].value) {
+ d_vpr_l("software power collapse not enabled\n");
+ return;
+ }
+
+ if (!mod_delayed_work(core->pm_workq, &core->pm_work,
+ msecs_to_jiffies(core->capabilities[SW_PC_DELAY].value))) {
+ d_vpr_h("power collapse already scheduled\n");
+ } else {
+ d_vpr_l("power collapse scheduled for %d ms\n",
+ core->capabilities[SW_PC_DELAY].value);
+ }
+}
+
+static void __cancel_power_collapse_work(struct msm_vidc_core *core)
+{
+ if (!core->capabilities[SW_PC].value)
+ return;
+
+ cancel_delayed_work(&core->pm_work);
+}
+
+static void __flush_debug_queue(struct msm_vidc_core *core,
+ u8 *packet, u32 packet_size)
+{
+ u8 *log;
+ struct hfi_debug_header *pkt;
+ bool local_packet = false;
+ enum vidc_msg_prio_fw log_level_fw = msm_fw_debug;
+
+ if (!packet || !packet_size) {
+ packet = vzalloc(VIDC_IFACEQ_VAR_HUGE_PKT_SIZE);
+ if (!packet) {
+ d_vpr_e("%s: allocation failed\n", __func__);
+ return;
+ }
+ packet_size = VIDC_IFACEQ_VAR_HUGE_PKT_SIZE;
+
+ local_packet = true;
+
+ /*
+ * Local packet is used when error occurred.
+ * It is good to print these logs to printk as well.
+ */
+ log_level_fw |= FW_PRINTK;
+ }
+
+ while (!venus_hfi_queue_dbg_read(core, packet)) {
+ pkt = (struct hfi_debug_header *)packet;
+
+ if (pkt->size < sizeof(struct hfi_debug_header)) {
+ d_vpr_e("%s: invalid pkt size %d\n",
+ __func__, pkt->size);
+ continue;
+ }
+ if (pkt->size >= packet_size) {
+ d_vpr_e("%s: pkt size[%d] >= packet_size[%d]\n",
+ __func__, pkt->size, packet_size);
+ continue;
+ }
+
+ packet[pkt->size] = '\0';
+ /*
+ * All fw messages starts with new line character. This
+ * causes dprintk to print this message in two lines
+ * in the kernel log. Ignoring the first character
+ * from the message fixes this to print it in a single
+ * line.
+ */
+ log = (u8 *)packet + sizeof(struct hfi_debug_header) + 1;
+ dprintk_firmware(log_level_fw, "%s", log);
+ }
+
+ if (local_packet)
+ vfree(packet);
+}
+
+static int __cmdq_write(struct msm_vidc_core *core, void *pkt)
+{
+ int rc;
+
+ rc = __resume(core);
+ if (rc)
+ return rc;
+
+ rc = venus_hfi_queue_cmd_write(core, pkt);
+ if (!rc)
+ __schedule_power_collapse_work(core);
+
+ return rc;
+}
+
+static int __sys_set_debug(struct msm_vidc_core *core, u32 debug)
+{
+ int rc = 0;
+
+ rc = hfi_packet_sys_debug_config(core, core->packet,
+ core->packet_size, debug);
+ if (rc)
+ goto exit;
+
+ rc = __cmdq_write(core, core->packet);
+ if (rc)
+ goto exit;
+
+exit:
+ if (rc)
+ d_vpr_e("Debug mode setting to FW failed\n");
+
+ return rc;
+}
+
+static int __sys_set_power_control(struct msm_vidc_core *core, bool enable)
+{
+ int rc = 0;
+
+ if (!is_core_sub_state(core, CORE_SUBSTATE_GDSC_HANDOFF)) {
+ d_vpr_e("%s: skipping as power control hanfoff was not done\n",
+ __func__);
+ return rc;
+ }
+
+ if (!core_in_valid_state(core)) {
+ d_vpr_e("%s: invalid core state %s\n",
+ __func__, core_state_name(core->state));
+ return rc;
+ }
+
+ rc = hfi_packet_sys_intraframe_powercollapse(core, core->packet,
+ core->packet_size, enable);
+ if (rc)
+ return rc;
+
+ rc = __cmdq_write(core, core->packet);
+ if (rc)
+ return rc;
+
+ rc = msm_vidc_change_core_sub_state(core, 0, CORE_SUBSTATE_FW_PWR_CTRL, __func__);
+ if (rc)
+ return rc;
+
+ d_vpr_h("%s: set hardware power control successful\n", __func__);
+
+ return rc;
+}
+
+int __prepare_pc(struct msm_vidc_core *core)
+{
+ int rc = 0;
+
+ rc = hfi_packet_sys_pc_prep(core, core->packet, core->packet_size);
+ if (rc) {
+ d_vpr_e("Failed to create sys pc prep pkt\n");
+ goto err_pc_prep;
+ }
+
+ if (__cmdq_write(core, core->packet))
+ rc = -ENOTEMPTY;
+ if (rc)
+ d_vpr_e("Failed to prepare venus for power off");
+err_pc_prep:
+ return rc;
+}
+
+static int __power_collapse(struct msm_vidc_core *core, bool force)
+{
+ int rc = 0;
+
+ if (!is_core_sub_state(core, CORE_SUBSTATE_POWER_ENABLE)) {
+ d_vpr_h("%s: Power already disabled\n", __func__);
+ goto exit;
+ }
+
+ if (!core_in_valid_state(core)) {
+ d_vpr_e("%s: Core not in init state\n", __func__);
+ return -EINVAL;
+ }
+
+ __flush_debug_queue(core, (!force ? core->packet : NULL), core->packet_size);
+
+ rc = call_iris_op(core, prepare_pc, core);
+ if (rc)
+ goto skip_power_off;
+
+ rc = __suspend(core);
+ if (rc)
+ d_vpr_e("Failed __suspend\n");
+
+exit:
+ return rc;
+
+skip_power_off:
+ d_vpr_e("%s: skipped\n", __func__);
+ return -EAGAIN;
+}
+
+static int __release_subcaches(struct msm_vidc_core *core)
+{
+ int rc = 0;
+ struct subcache_info *sinfo;
+ struct hfi_buffer buf;
+
+ if (!is_sys_cache_present(core))
+ return 0;
+
+ if (!core->resource->subcache_set.set_to_fw) {
+ d_vpr_h("Subcaches not set to Venus\n");
+ return 0;
+ }
+
+ rc = hfi_create_header(core->packet, core->packet_size,
+ 0, core->header_id++);
+ if (rc)
+ return rc;
+
+ memset(&buf, 0, sizeof(struct hfi_buffer));
+ buf.type = HFI_BUFFER_SUBCACHE;
+ buf.flags = HFI_BUF_HOST_FLAG_RELEASE;
+
+ venus_hfi_for_each_subcache_reverse(core, sinfo) {
+ if (!sinfo->isactive)
+ continue;
+
+ buf.index = sinfo->subcache->slice_id;
+ buf.buffer_size = sinfo->subcache->slice_size;
+
+ rc = hfi_create_packet(core->packet,
+ core->packet_size,
+ HFI_CMD_BUFFER,
+ HFI_BUF_HOST_FLAG_NONE,
+ HFI_PAYLOAD_STRUCTURE,
+ HFI_PORT_NONE,
+ core->packet_id++,
+ &buf,
+ sizeof(buf));
+ if (rc)
+ return rc;
+ }
+
+ /* Set resource to Venus for activated subcaches */
+ rc = __cmdq_write(core, core->packet);
+ if (rc)
+ return rc;
+
+ venus_hfi_for_each_subcache_reverse(core, sinfo) {
+ if (!sinfo->isactive)
+ continue;
+
+ d_vpr_h("%s: release Subcache id %d size %lu done\n",
+ __func__, sinfo->subcache->slice_id,
+ sinfo->subcache->slice_size);
+ }
+ core->resource->subcache_set.set_to_fw = false;
+
+ return 0;
+}
+
+static int __set_subcaches(struct msm_vidc_core *core)
+{
+ int rc = 0;
+ struct subcache_info *sinfo;
+ struct hfi_buffer buf;
+
+ if (!is_sys_cache_present(core))
+ return 0;
+
+ if (core->resource->subcache_set.set_to_fw) {
+ d_vpr_h("Subcaches already set to Venus\n");
+ return 0;
+ }
+
+ rc = hfi_create_header(core->packet, core->packet_size,
+ 0, core->header_id++);
+ if (rc)
+ goto err_fail_set_subacaches;
+
+ memset(&buf, 0, sizeof(struct hfi_buffer));
+ buf.type = HFI_BUFFER_SUBCACHE;
+ buf.flags = HFI_BUF_HOST_FLAG_NONE;
+
+ venus_hfi_for_each_subcache(core, sinfo) {
+ if (!sinfo->isactive)
+ continue;
+ buf.index = sinfo->subcache->slice_id;
+ buf.buffer_size = sinfo->subcache->slice_size;
+
+ rc = hfi_create_packet(core->packet,
+ core->packet_size,
+ HFI_CMD_BUFFER,
+ HFI_BUF_HOST_FLAG_NONE,
+ HFI_PAYLOAD_STRUCTURE,
+ HFI_PORT_NONE,
+ core->packet_id++,
+ &buf,
+ sizeof(buf));
+ if (rc)
+ goto err_fail_set_subacaches;
+ }
+
+ /* Set resource to Venus for activated subcaches */
+ rc = __cmdq_write(core, core->packet);
+ if (rc)
+ goto err_fail_set_subacaches;
+
+ venus_hfi_for_each_subcache(core, sinfo) {
+ if (!sinfo->isactive)
+ continue;
+ d_vpr_h("%s: set Subcache id %d size %lu done\n",
+ __func__, sinfo->subcache->slice_id,
+ sinfo->subcache->slice_size);
+ }
+ core->resource->subcache_set.set_to_fw = true;
+
+ return 0;
+
+err_fail_set_subacaches:
+ call_res_op(core, llcc, core, false);
+ return rc;
+}
+
+static int __venus_power_off(struct msm_vidc_core *core)
+{
+ int rc = 0;
+
+ if (!is_core_sub_state(core, CORE_SUBSTATE_POWER_ENABLE))
+ return 0;
+
+ rc = call_iris_op(core, power_off, core);
+ if (rc) {
+ d_vpr_e("Failed to power off, err: %d\n", rc);
+ return rc;
+ }
+ msm_vidc_change_core_sub_state(core, CORE_SUBSTATE_POWER_ENABLE, 0, __func__);
+
+ return rc;
+}
+
+static int __venus_power_on(struct msm_vidc_core *core)
+{
+ int rc = 0;
+
+ if (is_core_sub_state(core, CORE_SUBSTATE_POWER_ENABLE))
+ return 0;
+
+ rc = call_iris_op(core, power_on, core);
+ if (rc) {
+ d_vpr_e("Failed to power on, err: %d\n", rc);
+ return rc;
+ }
+
+ rc = msm_vidc_change_core_sub_state(core, 0, CORE_SUBSTATE_POWER_ENABLE, __func__);
+
+ return rc;
+}
+
+static int __suspend(struct msm_vidc_core *core)
+{
+ int rc = 0;
+
+ if (!is_core_sub_state(core, CORE_SUBSTATE_POWER_ENABLE)) {
+ d_vpr_h("Power already disabled\n");
+ return 0;
+ }
+
+ rc = __strict_check(core, __func__);
+ if (rc)
+ return rc;
+
+ d_vpr_h("Entering suspend\n");
+
+ rc = fw_suspend(core);
+ if (rc) {
+ d_vpr_e("Failed to suspend video core %d\n", rc);
+ goto err_tzbsp_suspend;
+ }
+
+ call_res_op(core, llcc, core, false);
+
+ __venus_power_off(core);
+ d_vpr_h("Venus power off\n");
+ return rc;
+
+err_tzbsp_suspend:
+ return rc;
+}
+
+static int __resume(struct msm_vidc_core *core)
+{
+ int rc = 0;
+
+ if (is_core_sub_state(core, CORE_SUBSTATE_POWER_ENABLE)) {
+ goto exit;
+ } else if (!core_in_valid_state(core)) {
+ d_vpr_e("%s: core not in valid state\n", __func__);
+ return -EINVAL;
+ }
+
+ rc = __strict_check(core, __func__);
+ if (rc)
+ return rc;
+
+ d_vpr_h("Resuming from power collapse\n");
+ /* reset handoff done from core sub_state */
+ rc = msm_vidc_change_core_sub_state(core, CORE_SUBSTATE_GDSC_HANDOFF, 0, __func__);
+ if (rc)
+ return rc;
+ /* reset hw pwr ctrl from core sub_state */
+ rc = msm_vidc_change_core_sub_state(core, CORE_SUBSTATE_FW_PWR_CTRL, 0, __func__);
+ if (rc)
+ return rc;
+
+ rc = __venus_power_on(core);
+ if (rc) {
+ d_vpr_e("Failed to power on venus\n");
+ goto err_venus_power_on;
+ }
+
+ /* Reboot the firmware */
+ rc = fw_resume(core);
+ if (rc) {
+ d_vpr_e("Failed to resume video core %d\n", rc);
+ goto err_set_video_state;
+ }
+
+ /*
+ * Hand off control of regulators to h/w _after_ loading fw.
+ * Note that the GDSC will turn off when switching from normal
+ * (s/w triggered) to fast (HW triggered) unless the h/w vote is
+ * present.
+ */
+ call_res_op(core, gdsc_hw_ctrl, core);
+
+ /* Wait for boot completion */
+ rc = call_iris_op(core, boot_firmware, core);
+ if (rc) {
+ d_vpr_e("Failed to reset venus core\n");
+ goto err_reset_core;
+ }
+
+ __sys_set_debug(core, (msm_fw_debug & FW_LOGMASK) >> FW_LOGSHIFT);
+
+ rc = call_res_op(core, llcc, core, true);
+ if (rc) {
+ d_vpr_e("Failed to activate subcache\n");
+ goto err_reset_core;
+ }
+ __set_subcaches(core);
+
+ rc = __sys_set_power_control(core, true);
+ if (rc) {
+ d_vpr_e("%s: set power control failed\n", __func__);
+ call_res_op(core, gdsc_sw_ctrl, core);
+ rc = 0;
+ }
+
+ d_vpr_h("Resumed from power collapse\n");
+exit:
+ /* Don't reset skip_pc_count for SYS_PC_PREP cmd */
+ //if (core->last_packet_type != HFI_CMD_SYS_PC_PREP)
+ // core->skip_pc_count = 0;
+ return rc;
+err_reset_core:
+ fw_suspend(core);
+err_set_video_state:
+ __venus_power_off(core);
+err_venus_power_on:
+ d_vpr_e("Failed to resume from power collapse\n");
+ return rc;
+}
+
+int __load_fw(struct msm_vidc_core *core)
+{
+ int rc = 0;
+
+ /* clear all substates */
+ msm_vidc_change_core_sub_state(core, CORE_SUBSTATE_MAX - 1, 0, __func__);
+
+ rc = __venus_power_on(core);
+ if (rc) {
+ d_vpr_e("%s: power on failed\n", __func__);
+ goto fail_power;
+ }
+
+ rc = fw_load(core);
+ if (rc)
+ goto fail_load_fw;
+
+ /*
+ * Hand off control of regulators to h/w _after_ loading fw.
+ * Note that the GDSC will turn off when switching from normal
+ * (s/w triggered) to fast (HW triggered) unless the h/w vote is
+ * present.
+ */
+ call_res_op(core, gdsc_hw_ctrl, core);
+
+ return rc;
+fail_load_fw:
+ __venus_power_off(core);
+fail_power:
+ return rc;
+}
+
+void __unload_fw(struct msm_vidc_core *core)
+{
+ if (!core->resource->fw_cookie)
+ return;
+
+ cancel_delayed_work(&core->pm_work);
+ fw_unload(core);
+ __venus_power_off(core);
+
+ /* clear all substates */
+ msm_vidc_change_core_sub_state(core, CORE_SUBSTATE_MAX - 1, 0, __func__);
+}
+
+static int __response_handler(struct msm_vidc_core *core)
+{
+ int rc = 0;
+
+ if (call_iris_op(core, watchdog, core, core->intr_status)) {
+ struct hfi_packet pkt = {.type = HFI_SYS_ERROR_WD_TIMEOUT};
+
+ core_lock(core, __func__);
+ msm_vidc_change_core_state(core, MSM_VIDC_CORE_ERROR, __func__);
+ /* mark cpu watchdog error */
+ msm_vidc_change_core_sub_state(core, 0, CORE_SUBSTATE_CPU_WATCHDOG, __func__);
+ d_vpr_e("%s: CPU WD error received\n", __func__);
+ core_unlock(core, __func__);
+
+ return handle_system_error(core, &pkt);
+ }
+
+ memset(core->response_packet, 0, core->packet_size);
+ while (!venus_hfi_queue_msg_read(core, core->response_packet)) {
+ rc = handle_response(core, core->response_packet);
+ if (rc)
+ continue;
+ /* check for system error */
+ if (core->state != MSM_VIDC_CORE_INIT)
+ break;
+ memset(core->response_packet, 0, core->packet_size);
+ }
+
+ __schedule_power_collapse_work(core);
+ __flush_debug_queue(core, core->response_packet, core->packet_size);
+
+ return rc;
+}
+
+irqreturn_t venus_hfi_isr(int irq, void *data)
+{
+ disable_irq_nosync(irq);
+ return IRQ_WAKE_THREAD;
+}
+
+irqreturn_t venus_hfi_isr_handler(int irq, void *data)
+{
+ struct msm_vidc_core *core = data;
+ int num_responses = 0, rc = 0;
+
+ if (!core) {
+ d_vpr_e("%s: invalid params\n", __func__);
+ return IRQ_NONE;
+ }
+
+ core_lock(core, __func__);
+ rc = __resume(core);
+ if (rc) {
+ d_vpr_e("%s: Power on failed\n", __func__);
+ core_unlock(core, __func__);
+ goto exit;
+ }
+ call_iris_op(core, clear_interrupt, core);
+ core_unlock(core, __func__);
+
+ num_responses = __response_handler(core);
+
+exit:
+ if (!call_iris_op(core, watchdog, core, core->intr_status))
+ enable_irq(irq);
+
+ return IRQ_HANDLED;
+}
+
+void venus_hfi_pm_work_handler(struct work_struct *work)
+{
+ int rc = 0;
+ struct msm_vidc_core *core;
+
+ core = container_of(work, struct msm_vidc_core, pm_work.work);
+
+ core_lock(core, __func__);
+ d_vpr_h("%s: try power collapse\n", __func__);
+ /*
+ * It is ok to check this variable outside the lock since
+ * it is being updated in this context only
+ */
+ if (core->skip_pc_count >= VIDC_MAX_PC_SKIP_COUNT) {
+ d_vpr_e("Failed to PC for %d times\n",
+ core->skip_pc_count);
+ core->skip_pc_count = 0;
+ msm_vidc_change_core_state(core, MSM_VIDC_CORE_ERROR, __func__);
+ /* mark video hw unresponsive */
+ msm_vidc_change_core_sub_state(core, 0,
+ CORE_SUBSTATE_VIDEO_UNRESPONSIVE, __func__);
+ /* do core deinit to handle error */
+ msm_vidc_core_deinit_locked(core, true);
+ goto unlock;
+ }
+
+ /* core already deinited - skip power collapse */
+ if (is_core_state(core, MSM_VIDC_CORE_DEINIT)) {
+ d_vpr_e("%s: invalid core state %s\n",
+ __func__, core_state_name(core->state));
+ goto unlock;
+ }
+
+ rc = __power_collapse(core, false);
+ switch (rc) {
+ case 0:
+ core->skip_pc_count = 0;
+ /* Cancel pending delayed works if any */
+ __cancel_power_collapse_work(core);
+ d_vpr_h("%s: power collapse successful!\n", __func__);
+ break;
+ case -EBUSY:
+ core->skip_pc_count = 0;
+ d_vpr_h("%s: retry PC as dsp is busy\n", __func__);
+ __schedule_power_collapse_work(core);
+ break;
+ case -EAGAIN:
+ core->skip_pc_count++;
+ d_vpr_e("%s: retry power collapse (count %d)\n",
+ __func__, core->skip_pc_count);
+ __schedule_power_collapse_work(core);
+ break;
+ default:
+ d_vpr_e("%s: power collapse failed\n", __func__);
+ break;
+ }
+unlock:
+ core_unlock(core, __func__);
+}
+
+static int __sys_init(struct msm_vidc_core *core)
+{
+ int rc = 0;
+
+ rc = hfi_packet_sys_init(core, core->packet, core->packet_size);
+ if (rc)
+ return rc;
+
+ rc = __cmdq_write(core, core->packet);
+ if (rc)
+ return rc;
+
+ return 0;
+}
+
+static int __sys_image_version(struct msm_vidc_core *core)
+{
+ int rc = 0;
+
+ rc = hfi_packet_image_version(core, core->packet, core->packet_size);
+ if (rc)
+ return rc;
+
+ rc = __cmdq_write(core, core->packet);
+ if (rc)
+ return rc;
+
+ return 0;
+}
+
+int venus_hfi_core_init(struct msm_vidc_core *core)
+{
+ int rc = 0;
+
+ d_vpr_h("%s(): core %pK\n", __func__, core);
+
+ rc = __strict_check(core, __func__);
+ if (rc)
+ return rc;
+
+ rc = venus_hfi_queue_init(core);
+ if (rc)
+ goto error;
+
+ rc = __load_fw(core);
+ if (rc)
+ goto error;
+
+ rc = call_iris_op(core, boot_firmware, core);
+ if (rc)
+ goto error;
+
+ rc = call_res_op(core, llcc, core, true);
+ if (rc)
+ goto error;
+
+ rc = __sys_init(core);
+ if (rc)
+ goto error;
+
+ rc = __sys_image_version(core);
+ if (rc)
+ goto error;
+
+ rc = __sys_set_debug(core, (msm_fw_debug & FW_LOGMASK) >> FW_LOGSHIFT);
+ if (rc)
+ goto error;
+
+ rc = __set_subcaches(core);
+ if (rc)
+ goto error;
+
+ rc = __sys_set_power_control(core, true);
+ if (rc) {
+ d_vpr_e("%s: set power control failed\n", __func__);
+ call_res_op(core, gdsc_sw_ctrl, core);
+ rc = 0;
+ }
+
+ d_vpr_h("%s(): successful\n", __func__);
+ return 0;
+
+error:
+ d_vpr_e("%s(): failed\n", __func__);
+ return rc;
+}
+
+int venus_hfi_core_deinit(struct msm_vidc_core *core, bool force)
+{
+ int rc = 0;
+
+ d_vpr_h("%s(): core %pK\n", __func__, core);
+ rc = __strict_check(core, __func__);
+ if (rc)
+ return rc;
+
+ if (is_core_state(core, MSM_VIDC_CORE_DEINIT))
+ return 0;
+ __resume(core);
+ __flush_debug_queue(core, (!force ? core->packet : NULL), core->packet_size);
+ __release_subcaches(core);
+ call_res_op(core, llcc, core, false);
+ __unload_fw(core);
+ /**
+ * coredump need to be called after firmware unload, coredump also
+ * copying queues memory. So need to be called before queues deinit.
+ */
+ if (msm_vidc_fw_dump)
+ fw_coredump(core);
+
+ return 0;
+}
+
+int venus_hfi_suspend(struct msm_vidc_core *core)
+{
+ int rc = 0;
+
+ rc = __strict_check(core, __func__);
+ if (rc)
+ return rc;
+
+ if (!core->capabilities[SW_PC].value) {
+ d_vpr_h("Skip suspending venus\n");
+ return 0;
+ }
+
+ d_vpr_h("Suspending Venus\n");
+ rc = __power_collapse(core, true);
+ if (!rc) {
+ /* Cancel pending delayed works if any */
+ __cancel_power_collapse_work(core);
+ } else {
+ d_vpr_e("%s: Venus is busy\n", __func__);
+ rc = -EBUSY;
+ }
+
+ return rc;
+}
+
+int venus_hfi_session_open(struct msm_vidc_inst *inst)
+{
+ int rc = 0;
+ struct msm_vidc_core *core;
+
+ if (!inst->packet) {
+ d_vpr_e("%s: invalid params\n", __func__);
+ return -EINVAL;
+ }
+ core = inst->core;
+ core_lock(core, __func__);
+
+ if (!__valdiate_session(core, inst, __func__)) {
+ rc = -EINVAL;
+ goto unlock;
+ }
+
+ __sys_set_debug(core, (msm_fw_debug & FW_LOGMASK) >> FW_LOGSHIFT);
+
+ rc = hfi_packet_session_command(inst,
+ HFI_CMD_OPEN,
+ (HFI_HOST_FLAGS_RESPONSE_REQUIRED |
+ HFI_HOST_FLAGS_INTR_REQUIRED),
+ HFI_PORT_NONE,
+ 0, /* session_id */
+ HFI_PAYLOAD_U32,
+ &inst->session_id, /* payload */
+ sizeof(u32));
+ if (rc)
+ goto unlock;
+
+ rc = __cmdq_write(inst->core, inst->packet);
+ if (rc)
+ goto unlock;
+
+unlock:
+ core_unlock(core, __func__);
+ return rc;
+}
+
+int venus_hfi_session_set_codec(struct msm_vidc_inst *inst)
+{
+ int rc = 0;
+ struct msm_vidc_core *core;
+ u32 codec;
+
+ if (!inst->packet) {
+ d_vpr_e("%s: invalid params\n", __func__);
+ return -EINVAL;
+ }
+ core = inst->core;
+ core_lock(core, __func__);
+
+ if (!__valdiate_session(core, inst, __func__)) {
+ rc = -EINVAL;
+ goto unlock;
+ }
+
+ rc = hfi_create_header(inst->packet, inst->packet_size,
+ inst->session_id, core->header_id++);
+ if (rc)
+ goto unlock;
+
+ codec = get_hfi_codec(inst);
+ rc = hfi_create_packet(inst->packet, inst->packet_size,
+ HFI_PROP_CODEC,
+ HFI_HOST_FLAGS_NONE,
+ HFI_PAYLOAD_U32_ENUM,
+ HFI_PORT_NONE,
+ core->packet_id++,
+ &codec,
+ sizeof(u32));
+ if (rc)
+ goto unlock;
+
+ rc = __cmdq_write(inst->core, inst->packet);
+ if (rc)
+ goto unlock;
+
+unlock:
+ core_unlock(core, __func__);
+ return rc;
+}
+
+int venus_hfi_session_property(struct msm_vidc_inst *inst,
+ u32 pkt_type, u32 flags, u32 port,
+ u32 payload_type, void *payload, u32 payload_size)
+{
+ int rc = 0;
+ struct msm_vidc_core *core;
+
+ if (!inst->packet) {
+ d_vpr_e("%s: invalid params\n", __func__);
+ return -EINVAL;
+ }
+ core = inst->core;
+ core_lock(core, __func__);
+
+ if (!__valdiate_session(core, inst, __func__)) {
+ rc = -EINVAL;
+ goto unlock;
+ }
+
+ rc = hfi_create_header(inst->packet, inst->packet_size,
+ inst->session_id, core->header_id++);
+ if (rc)
+ goto unlock;
+ rc = hfi_create_packet(inst->packet, inst->packet_size,
+ pkt_type,
+ flags,
+ payload_type,
+ port,
+ core->packet_id++,
+ payload,
+ payload_size);
+ if (rc)
+ goto unlock;
+
+ rc = __cmdq_write(inst->core, inst->packet);
+ if (rc)
+ goto unlock;
+
+unlock:
+ core_unlock(core, __func__);
+ return rc;
+}
+
+int venus_hfi_session_close(struct msm_vidc_inst *inst)
+{
+ int rc = 0;
+ struct msm_vidc_core *core;
+
+ if (!inst->packet) {
+ d_vpr_e("%s: invalid params\n", __func__);
+ return -EINVAL;
+ }
+ core = inst->core;
+ core_lock(core, __func__);
+
+ if (!__valdiate_session(core, inst, __func__)) {
+ rc = -EINVAL;
+ goto unlock;
+ }
+
+ rc = hfi_packet_session_command(inst,
+ HFI_CMD_CLOSE,
+ (HFI_HOST_FLAGS_RESPONSE_REQUIRED |
+ HFI_HOST_FLAGS_INTR_REQUIRED |
+ HFI_HOST_FLAGS_NON_DISCARDABLE),
+ HFI_PORT_NONE,
+ inst->session_id,
+ HFI_PAYLOAD_NONE,
+ NULL,
+ 0);
+ if (rc)
+ goto unlock;
+
+ rc = __cmdq_write(inst->core, inst->packet);
+ if (rc)
+ goto unlock;
+
+unlock:
+ core_unlock(core, __func__);
+ return rc;
+}
+
+int venus_hfi_start(struct msm_vidc_inst *inst, enum msm_vidc_port_type port)
+{
+ int rc = 0;
+ struct msm_vidc_core *core;
+
+ if (!inst->packet) {
+ d_vpr_e("%s: invalid params\n", __func__);
+ return -EINVAL;
+ }
+ core = inst->core;
+ core_lock(core, __func__);
+
+ if (!__valdiate_session(core, inst, __func__)) {
+ rc = -EINVAL;
+ goto unlock;
+ }
+
+ if (port != INPUT_PORT && port != OUTPUT_PORT) {
+ i_vpr_e(inst, "%s: invalid port %d\n", __func__, port);
+ goto unlock;
+ }
+
+ rc = hfi_packet_session_command(inst,
+ HFI_CMD_START,
+ (HFI_HOST_FLAGS_RESPONSE_REQUIRED |
+ HFI_HOST_FLAGS_INTR_REQUIRED),
+ get_hfi_port(inst, port),
+ inst->session_id,
+ HFI_PAYLOAD_NONE,
+ NULL,
+ 0);
+ if (rc)
+ goto unlock;
+
+ rc = __cmdq_write(inst->core, inst->packet);
+ if (rc)
+ goto unlock;
+
+unlock:
+ core_unlock(core, __func__);
+ return rc;
+}
+
+int venus_hfi_stop(struct msm_vidc_inst *inst, enum msm_vidc_port_type port)
+{
+ int rc = 0;
+ struct msm_vidc_core *core;
+
+ if (!inst->packet) {
+ d_vpr_e("%s: invalid params\n", __func__);
+ return -EINVAL;
+ }
+ core = inst->core;
+ core_lock(core, __func__);
+
+ if (!__valdiate_session(core, inst, __func__)) {
+ rc = -EINVAL;
+ goto unlock;
+ }
+
+ if (port != INPUT_PORT && port != OUTPUT_PORT) {
+ i_vpr_e(inst, "%s: invalid port %d\n", __func__, port);
+ goto unlock;
+ }
+
+ rc = hfi_packet_session_command(inst,
+ HFI_CMD_STOP,
+ (HFI_HOST_FLAGS_RESPONSE_REQUIRED |
+ HFI_HOST_FLAGS_INTR_REQUIRED |
+ HFI_HOST_FLAGS_NON_DISCARDABLE),
+ get_hfi_port(inst, port),
+ inst->session_id,
+ HFI_PAYLOAD_NONE,
+ NULL,
+ 0);
+ if (rc)
+ goto unlock;
+
+ rc = __cmdq_write(inst->core, inst->packet);
+ if (rc)
+ goto unlock;
+
+unlock:
+ core_unlock(core, __func__);
+ return rc;
+}
+
+int venus_hfi_session_pause(struct msm_vidc_inst *inst, enum msm_vidc_port_type port)
+{
+ int rc = 0;
+ struct msm_vidc_core *core;
+
+ if (!inst->packet) {
+ d_vpr_e("%s: invalid params\n", __func__);
+ return -EINVAL;
+ }
+ core = inst->core;
+ core_lock(core, __func__);
+
+ if (!__valdiate_session(core, inst, __func__)) {
+ rc = -EINVAL;
+ goto unlock;
+ }
+
+ if (port != INPUT_PORT && port != OUTPUT_PORT) {
+ i_vpr_e(inst, "%s: invalid port %d\n", __func__, port);
+ goto unlock;
+ }
+
+ rc = hfi_packet_session_command(inst,
+ HFI_CMD_PAUSE,
+ (HFI_HOST_FLAGS_RESPONSE_REQUIRED |
+ HFI_HOST_FLAGS_INTR_REQUIRED),
+ get_hfi_port(inst, port),
+ inst->session_id,
+ HFI_PAYLOAD_NONE,
+ NULL,
+ 0);
+ if (rc)
+ goto unlock;
+
+ rc = __cmdq_write(inst->core, inst->packet);
+ if (rc)
+ goto unlock;
+
+unlock:
+ core_unlock(core, __func__);
+ return rc;
+}
+
+int venus_hfi_session_resume(struct msm_vidc_inst *inst,
+ enum msm_vidc_port_type port, u32 payload)
+{
+ int rc = 0;
+ struct msm_vidc_core *core;
+
+ if (!inst->packet) {
+ d_vpr_e("%s: invalid params\n", __func__);
+ return -EINVAL;
+ }
+ core = inst->core;
+ core_lock(core, __func__);
+
+ if (!__valdiate_session(core, inst, __func__)) {
+ rc = -EINVAL;
+ goto unlock;
+ }
+
+ if (port != INPUT_PORT && port != OUTPUT_PORT) {
+ i_vpr_e(inst, "%s: invalid port %d\n", __func__, port);
+ goto unlock;
+ }
+
+ rc = hfi_packet_session_command(inst,
+ HFI_CMD_RESUME,
+ (HFI_HOST_FLAGS_RESPONSE_REQUIRED |
+ HFI_HOST_FLAGS_INTR_REQUIRED),
+ get_hfi_port(inst, port),
+ inst->session_id,
+ HFI_PAYLOAD_U32,
+ &payload,
+ sizeof(u32));
+ if (rc)
+ goto unlock;
+
+ rc = __cmdq_write(inst->core, inst->packet);
+ if (rc)
+ goto unlock;
+
+unlock:
+ core_unlock(core, __func__);
+ return rc;
+}
+
+int venus_hfi_session_drain(struct msm_vidc_inst *inst, enum msm_vidc_port_type port)
+{
+ int rc = 0;
+ struct msm_vidc_core *core;
+
+ if (!inst->packet) {
+ d_vpr_e("%s: invalid params\n", __func__);
+ return -EINVAL;
+ }
+ core = inst->core;
+ core_lock(core, __func__);
+
+ if (!__valdiate_session(core, inst, __func__)) {
+ rc = -EINVAL;
+ goto unlock;
+ }
+
+ if (port != INPUT_PORT) {
+ i_vpr_e(inst, "%s: invalid port %d\n", __func__, port);
+ goto unlock;
+ }
+
+ rc = hfi_packet_session_command(inst,
+ HFI_CMD_DRAIN,
+ (HFI_HOST_FLAGS_RESPONSE_REQUIRED |
+ HFI_HOST_FLAGS_INTR_REQUIRED |
+ HFI_HOST_FLAGS_NON_DISCARDABLE),
+ get_hfi_port(inst, port),
+ inst->session_id,
+ HFI_PAYLOAD_NONE,
+ NULL,
+ 0);
+ if (rc)
+ goto unlock;
+
+ rc = __cmdq_write(inst->core, inst->packet);
+ if (rc)
+ goto unlock;
+
+unlock:
+ core_unlock(core, __func__);
+ return rc;
+}
+
+int venus_hfi_session_command(struct msm_vidc_inst *inst,
+ u32 cmd, enum msm_vidc_port_type port, u32 payload_type,
+ void *payload, u32 payload_size)
+{
+ int rc = 0;
+ struct msm_vidc_core *core;
+
+ if (!inst->packet) {
+ d_vpr_e("%s: invalid params\n", __func__);
+ return -EINVAL;
+ }
+ core = inst->core;
+ core_lock(core, __func__);
+
+ if (!__valdiate_session(core, inst, __func__)) {
+ rc = -EINVAL;
+ goto unlock;
+ }
+
+ rc = hfi_create_header(inst->packet, inst->packet_size,
+ inst->session_id,
+ core->header_id++);
+ if (rc)
+ goto unlock;
+
+ rc = hfi_create_packet(inst->packet, inst->packet_size,
+ cmd,
+ (HFI_HOST_FLAGS_RESPONSE_REQUIRED |
+ HFI_HOST_FLAGS_INTR_REQUIRED),
+ payload_type,
+ get_hfi_port(inst, port),
+ core->packet_id++,
+ payload,
+ payload_size);
+ if (rc)
+ goto unlock;
+
+ rc = __cmdq_write(inst->core, inst->packet);
+ if (rc)
+ goto unlock;
+
+unlock:
+ core_unlock(core, __func__);
+ return rc;
+}
+
+int venus_hfi_queue_buffer(struct msm_vidc_inst *inst,
+ struct msm_vidc_buffer *buffer)
+{
+ int rc = 0;
+ struct msm_vidc_core *core;
+ struct hfi_buffer hfi_buffer;
+
+ if (!inst->packet) {
+ d_vpr_e("%s: invalid params\n", __func__);
+ return -EINVAL;
+ }
+ core = inst->core;
+ core_lock(core, __func__);
+
+ if (!__valdiate_session(core, inst, __func__)) {
+ rc = -EINVAL;
+ goto unlock;
+ }
+
+ rc = get_hfi_buffer(inst, buffer, &hfi_buffer);
+ if (rc)
+ goto unlock;
+
+ rc = hfi_create_header(inst->packet, inst->packet_size,
+ inst->session_id, core->header_id++);
+ if (rc)
+ goto unlock;
+
+ rc = hfi_create_packet(inst->packet,
+ inst->packet_size,
+ HFI_CMD_BUFFER,
+ HFI_HOST_FLAGS_INTR_REQUIRED,
+ HFI_PAYLOAD_STRUCTURE,
+ get_hfi_port_from_buffer_type(inst, buffer->type),
+ core->packet_id++,
+ &hfi_buffer,
+ sizeof(hfi_buffer));
+ if (rc)
+ goto unlock;
+
+ rc = __cmdq_write(inst->core, inst->packet);
+ if (rc)
+ goto unlock;
+
+unlock:
+ core_unlock(core, __func__);
+ return rc;
+}
+
+int venus_hfi_release_buffer(struct msm_vidc_inst *inst,
+ struct msm_vidc_buffer *buffer)
+{
+ int rc = 0;
+ struct msm_vidc_core *core;
+ struct hfi_buffer hfi_buffer;
+
+ if (!inst->packet || !buffer) {
+ d_vpr_e("%s: invalid params\n", __func__);
+ return -EINVAL;
+ }
+ core = inst->core;
+ core_lock(core, __func__);
+
+ if (!__valdiate_session(core, inst, __func__)) {
+ rc = -EINVAL;
+ goto unlock;
+ }
+
+ rc = get_hfi_buffer(inst, buffer, &hfi_buffer);
+ if (rc)
+ goto unlock;
+
+ /* add release flag */
+ hfi_buffer.flags |= HFI_BUF_HOST_FLAG_RELEASE;
+
+ rc = hfi_create_header(inst->packet, inst->packet_size,
+ inst->session_id, core->header_id++);
+ if (rc)
+ goto unlock;
+
+ rc = hfi_create_packet(inst->packet,
+ inst->packet_size,
+ HFI_CMD_BUFFER,
+ (HFI_HOST_FLAGS_RESPONSE_REQUIRED |
+ HFI_HOST_FLAGS_INTR_REQUIRED),
+ HFI_PAYLOAD_STRUCTURE,
+ get_hfi_port_from_buffer_type(inst, buffer->type),
+ core->packet_id++,
+ &hfi_buffer,
+ sizeof(hfi_buffer));
+ if (rc)
+ goto unlock;
+
+ rc = __cmdq_write(inst->core, inst->packet);
+ if (rc)
+ goto unlock;
+
+unlock:
+ core_unlock(core, __func__);
+ return rc;
+}
+
+int venus_hfi_scale_clocks(struct msm_vidc_inst *inst, u64 freq)
+{
+ int rc = 0;
+ struct msm_vidc_core *core;
+
+ core = inst->core;
+
+ core_lock(core, __func__);
+ rc = __resume(core);
+ if (rc) {
+ i_vpr_e(inst, "%s: Resume from power collapse failed\n", __func__);
+ goto exit;
+ }
+ rc = call_res_op(core, set_clks, core, freq);
+ if (rc)
+ goto exit;
+
+exit:
+ core_unlock(core, __func__);
+
+ return rc;
+}
+
+int venus_hfi_scale_buses(struct msm_vidc_inst *inst, u64 bw_ddr, u64 bw_llcc)
+{
+ int rc = 0;
+ struct msm_vidc_core *core;
+
+ core = inst->core;
+
+ core_lock(core, __func__);
+ rc = __resume(core);
+ if (rc) {
+ i_vpr_e(inst, "%s: Resume from power collapse failed\n", __func__);
+ goto exit;
+ }
+ rc = call_res_op(core, set_bw, core, bw_ddr, bw_llcc);
+ if (rc)
+ goto exit;
+
+exit:
+ core_unlock(core, __func__);
+
+ return rc;
+}
+
+int venus_hfi_set_ir_period(struct msm_vidc_inst *inst, u32 ir_type,
+ enum msm_vidc_inst_capability_type cap_id)
+{
+ int rc = 0;
+ struct msm_vidc_core *core;
+ u32 ir_period, sync_frame_req = 0;
+
+ core = inst->core;
+
+ core_lock(core, __func__);
+
+ ir_period = inst->capabilities[cap_id].value;
+
+ rc = hfi_create_header(inst->packet, inst->packet_size,
+ inst->session_id, core->header_id++);
+ if (rc)
+ goto exit;
+
+ /* Request sync frame if ir period enabled dynamically */
+ if (!inst->ir_enabled) {
+ inst->ir_enabled = ((ir_period > 0) ? true : false);
+ if (inst->ir_enabled && inst->bufq[OUTPUT_PORT].vb2q->streaming) {
+ sync_frame_req = HFI_SYNC_FRAME_REQUEST_WITH_PREFIX_SEQ_HDR;
+ rc = hfi_create_packet(inst->packet, inst->packet_size,
+ HFI_PROP_REQUEST_SYNC_FRAME,
+ HFI_HOST_FLAGS_NONE,
+ HFI_PAYLOAD_U32_ENUM,
+ msm_vidc_get_port_info(inst, REQUEST_I_FRAME),
+ core->packet_id++,
+ &sync_frame_req,
+ sizeof(u32));
+ if (rc)
+ goto exit;
+ }
+ }
+
+ rc = hfi_create_packet(inst->packet, inst->packet_size,
+ ir_type,
+ HFI_HOST_FLAGS_NONE,
+ HFI_PAYLOAD_U32,
+ msm_vidc_get_port_info(inst, cap_id),
+ core->packet_id++,
+ &ir_period,
+ sizeof(u32));
+ if (rc)
+ goto exit;
+
+ rc = __cmdq_write(inst->core, inst->packet);
+ if (rc) {
+ i_vpr_e(inst, "%s: failed to set inst->capabilities[%d] %s to fw\n",
+ __func__, cap_id, cap_name(cap_id));
+ goto exit;
+ }
+
+exit:
+ core_unlock(core, __func__);
+
+ return rc;
+}