root/drivers/media/platform/qcom/venus/hfi.c
// SPDX-License-Identifier: GPL-2.0-only
/*
 * Copyright (c) 2012-2016, The Linux Foundation. All rights reserved.
 * Copyright (C) 2017 Linaro Ltd.
 */
#include <linux/slab.h>
#include <linux/mutex.h>
#include <linux/list.h>
#include <linux/completion.h>
#include <linux/platform_device.h>
#include <linux/videodev2.h>

#include "core.h"
#include "hfi.h"
#include "hfi_cmds.h"
#include "hfi_venus.h"

#define TIMEOUT         msecs_to_jiffies(1000)

static u32 to_codec_type(u32 pixfmt)
{
        switch (pixfmt) {
        case V4L2_PIX_FMT_H264:
        case V4L2_PIX_FMT_H264_NO_SC:
                return HFI_VIDEO_CODEC_H264;
        case V4L2_PIX_FMT_H263:
                return HFI_VIDEO_CODEC_H263;
        case V4L2_PIX_FMT_MPEG1:
                return HFI_VIDEO_CODEC_MPEG1;
        case V4L2_PIX_FMT_MPEG2:
                return HFI_VIDEO_CODEC_MPEG2;
        case V4L2_PIX_FMT_MPEG4:
                return HFI_VIDEO_CODEC_MPEG4;
        case V4L2_PIX_FMT_VC1_ANNEX_G:
        case V4L2_PIX_FMT_VC1_ANNEX_L:
                return HFI_VIDEO_CODEC_VC1;
        case V4L2_PIX_FMT_VP8:
                return HFI_VIDEO_CODEC_VP8;
        case V4L2_PIX_FMT_VP9:
                return HFI_VIDEO_CODEC_VP9;
        case V4L2_PIX_FMT_XVID:
                return HFI_VIDEO_CODEC_DIVX;
        case V4L2_PIX_FMT_HEVC:
                return HFI_VIDEO_CODEC_HEVC;
        default:
                return 0;
        }
}

int hfi_core_init(struct venus_core *core)
{
        int ret = 0;

        mutex_lock(&core->lock);

        if (core->state >= CORE_INIT)
                goto unlock;

        reinit_completion(&core->done);

        ret = core->ops->core_init(core);
        if (ret)
                goto unlock;

        ret = wait_for_completion_timeout(&core->done, TIMEOUT);
        if (!ret) {
                ret = -ETIMEDOUT;
                goto unlock;
        }

        ret = 0;

        if (core->error != HFI_ERR_NONE) {
                ret = -EIO;
                goto unlock;
        }

        core->state = CORE_INIT;
unlock:
        mutex_unlock(&core->lock);
        return ret;
}

int hfi_core_deinit(struct venus_core *core, bool blocking)
{
        int ret = 0, empty;

        mutex_lock(&core->lock);

        if (core->state == CORE_UNINIT)
                goto unlock;

        empty = list_empty(&core->instances);

        if (!empty && !blocking) {
                ret = -EBUSY;
                goto unlock;
        }

        if (!empty) {
                mutex_unlock(&core->lock);
                wait_var_event(&core->insts_count,
                               !atomic_read(&core->insts_count));
                mutex_lock(&core->lock);
        }

        if (!core->ops)
                goto unlock;

        ret = core->ops->core_deinit(core);

        if (!ret)
                core->state = CORE_UNINIT;

unlock:
        mutex_unlock(&core->lock);
        return ret;
}

int hfi_core_suspend(struct venus_core *core)
{
        if (core->state != CORE_INIT)
                return 0;

        return core->ops->suspend(core);
}

int hfi_core_resume(struct venus_core *core, bool force)
{
        if (!force && core->state != CORE_INIT)
                return 0;

        return core->ops->resume(core);
}

int hfi_core_trigger_ssr(struct venus_core *core, u32 type)
{
        return core->ops->core_trigger_ssr(core, type);
}

static int wait_session_msg(struct venus_inst *inst)
{
        int ret;

        ret = wait_for_completion_timeout(&inst->done, TIMEOUT);
        if (!ret)
                return -ETIMEDOUT;

        if (inst->error != HFI_ERR_NONE)
                return -EIO;

        return 0;
}

int hfi_session_create(struct venus_inst *inst, const struct hfi_inst_ops *ops)
{
        struct venus_core *core = inst->core;
        bool max;
        int ret;

        if (!ops)
                return -EINVAL;

        inst->state = INST_UNINIT;
        init_completion(&inst->done);
        inst->ops = ops;

        mutex_lock(&core->lock);

        if (test_bit(0, &inst->core->sys_error)) {
                ret = -EIO;
                goto unlock;
        }

        max = atomic_add_unless(&core->insts_count, 1,
                                core->max_sessions_supported);
        if (!max) {
                ret = -EAGAIN;
        } else {
                list_add_tail(&inst->list, &core->instances);
                ret = 0;
        }

unlock:
        mutex_unlock(&core->lock);

        return ret;
}
EXPORT_SYMBOL_GPL(hfi_session_create);

int hfi_session_init(struct venus_inst *inst, u32 pixfmt)
{
        struct venus_core *core = inst->core;
        const struct hfi_ops *ops = core->ops;
        int ret;

        /*
         * If core shutdown is in progress or if we are in system
         * recovery, return an error as during system error recovery
         * session_init() can't pass successfully
         */
        mutex_lock(&core->lock);
        if (!core->ops || test_bit(0, &inst->core->sys_error)) {
                mutex_unlock(&core->lock);
                return -EIO;
        }
        mutex_unlock(&core->lock);

        if (inst->state != INST_UNINIT)
                return -EALREADY;

        inst->hfi_codec = to_codec_type(pixfmt);
        reinit_completion(&inst->done);

        ret = ops->session_init(inst, inst->session_type, inst->hfi_codec);
        if (ret)
                return ret;

        ret = wait_session_msg(inst);
        if (ret)
                return ret;

        inst->state = INST_INIT;

        return 0;
}
EXPORT_SYMBOL_GPL(hfi_session_init);

void hfi_session_destroy(struct venus_inst *inst)
{
        struct venus_core *core = inst->core;

        mutex_lock(&core->lock);
        list_del_init(&inst->list);
        if (atomic_dec_and_test(&core->insts_count))
                wake_up_var(&core->insts_count);
        mutex_unlock(&core->lock);
}
EXPORT_SYMBOL_GPL(hfi_session_destroy);

int hfi_session_deinit(struct venus_inst *inst)
{
        const struct hfi_ops *ops = inst->core->ops;
        int ret;

        if (inst->state == INST_UNINIT)
                return 0;

        if (inst->state < INST_INIT)
                return -EINVAL;

        if (test_bit(0, &inst->core->sys_error))
                goto done;

        reinit_completion(&inst->done);

        ret = ops->session_end(inst);
        if (ret)
                return ret;

        ret = wait_session_msg(inst);
        if (ret)
                return ret;

done:
        inst->state = INST_UNINIT;

        return 0;
}
EXPORT_SYMBOL_GPL(hfi_session_deinit);

int hfi_session_start(struct venus_inst *inst)
{
        const struct hfi_ops *ops = inst->core->ops;
        int ret;

        if (test_bit(0, &inst->core->sys_error))
                return -EIO;

        if (inst->state != INST_LOAD_RESOURCES)
                return -EINVAL;

        reinit_completion(&inst->done);

        ret = ops->session_start(inst);
        if (ret)
                return ret;

        ret = wait_session_msg(inst);
        if (ret)
                return ret;

        inst->state = INST_START;

        return 0;
}
EXPORT_SYMBOL_GPL(hfi_session_start);

int hfi_session_stop(struct venus_inst *inst)
{
        const struct hfi_ops *ops = inst->core->ops;
        int ret;

        if (test_bit(0, &inst->core->sys_error))
                return -EIO;

        if (inst->state != INST_START)
                return -EINVAL;

        reinit_completion(&inst->done);

        ret = ops->session_stop(inst);
        if (ret)
                return ret;

        ret = wait_session_msg(inst);
        if (ret)
                return ret;

        inst->state = INST_STOP;

        return 0;
}
EXPORT_SYMBOL_GPL(hfi_session_stop);

int hfi_session_continue(struct venus_inst *inst)
{
        struct venus_core *core = inst->core;

        if (test_bit(0, &inst->core->sys_error))
                return -EIO;

        if (core->res->hfi_version == HFI_VERSION_1XX)
                return 0;

        return core->ops->session_continue(inst);
}
EXPORT_SYMBOL_GPL(hfi_session_continue);

int hfi_session_abort(struct venus_inst *inst)
{
        const struct hfi_ops *ops = inst->core->ops;
        int ret;

        if (test_bit(0, &inst->core->sys_error))
                return -EIO;

        reinit_completion(&inst->done);

        ret = ops->session_abort(inst);
        if (ret)
                return ret;

        ret = wait_session_msg(inst);
        if (ret)
                return ret;

        return 0;
}
EXPORT_SYMBOL_GPL(hfi_session_abort);

int hfi_session_load_res(struct venus_inst *inst)
{
        const struct hfi_ops *ops = inst->core->ops;
        int ret;

        if (test_bit(0, &inst->core->sys_error))
                return -EIO;

        if (inst->state != INST_INIT)
                return -EINVAL;

        reinit_completion(&inst->done);

        ret = ops->session_load_res(inst);
        if (ret)
                return ret;

        ret = wait_session_msg(inst);
        if (ret)
                return ret;

        inst->state = INST_LOAD_RESOURCES;

        return 0;
}

int hfi_session_unload_res(struct venus_inst *inst)
{
        const struct hfi_ops *ops = inst->core->ops;
        int ret;

        if (test_bit(0, &inst->core->sys_error))
                return -EIO;

        if (inst->state != INST_STOP)
                return -EINVAL;

        reinit_completion(&inst->done);

        ret = ops->session_release_res(inst);
        if (ret)
                return ret;

        ret = wait_session_msg(inst);
        if (ret)
                return ret;

        inst->state = INST_RELEASE_RESOURCES;

        return 0;
}
EXPORT_SYMBOL_GPL(hfi_session_unload_res);

int hfi_session_flush(struct venus_inst *inst, u32 type, bool block)
{
        const struct hfi_ops *ops = inst->core->ops;
        int ret;

        if (test_bit(0, &inst->core->sys_error))
                return -EIO;

        reinit_completion(&inst->done);

        ret = ops->session_flush(inst, type);
        if (ret)
                return ret;

        if (block) {
                ret = wait_session_msg(inst);
                if (ret)
                        return ret;
        }

        return 0;
}
EXPORT_SYMBOL_GPL(hfi_session_flush);

int hfi_session_set_buffers(struct venus_inst *inst, struct hfi_buffer_desc *bd)
{
        const struct hfi_ops *ops = inst->core->ops;

        if (test_bit(0, &inst->core->sys_error))
                return -EIO;

        return ops->session_set_buffers(inst, bd);
}

int hfi_session_unset_buffers(struct venus_inst *inst,
                              struct hfi_buffer_desc *bd)
{
        const struct hfi_ops *ops = inst->core->ops;
        int ret;

        if (test_bit(0, &inst->core->sys_error))
                return -EIO;

        reinit_completion(&inst->done);

        ret = ops->session_unset_buffers(inst, bd);
        if (ret)
                return ret;

        if (!bd->response_required)
                return 0;

        ret = wait_session_msg(inst);
        if (ret)
                return ret;

        return 0;
}

int hfi_session_get_property(struct venus_inst *inst, u32 ptype,
                             union hfi_get_property *hprop)
{
        const struct hfi_ops *ops = inst->core->ops;
        int ret;

        if (test_bit(0, &inst->core->sys_error))
                return -EIO;

        if (inst->state < INST_INIT || inst->state >= INST_STOP)
                return -EINVAL;

        reinit_completion(&inst->done);

        ret = ops->session_get_property(inst, ptype);
        if (ret)
                return ret;

        ret = wait_session_msg(inst);
        if (ret)
                return ret;

        *hprop = inst->hprop;

        return 0;
}
EXPORT_SYMBOL_GPL(hfi_session_get_property);

int hfi_session_set_property(struct venus_inst *inst, u32 ptype, void *pdata)
{
        const struct hfi_ops *ops = inst->core->ops;

        if (test_bit(0, &inst->core->sys_error))
                return -EIO;

        if (inst->state < INST_INIT || inst->state >= INST_STOP)
                return -EINVAL;

        return ops->session_set_property(inst, ptype, pdata);
}
EXPORT_SYMBOL_GPL(hfi_session_set_property);

int hfi_session_process_buf(struct venus_inst *inst, struct hfi_frame_data *fd)
{
        const struct hfi_ops *ops = inst->core->ops;

        if (test_bit(0, &inst->core->sys_error))
                return -EIO;

        if (fd->buffer_type == HFI_BUFFER_INPUT)
                return ops->session_etb(inst, fd);
        else if (fd->buffer_type == HFI_BUFFER_OUTPUT ||
                 fd->buffer_type == HFI_BUFFER_OUTPUT2)
                return ops->session_ftb(inst, fd);

        return -EINVAL;
}
EXPORT_SYMBOL_GPL(hfi_session_process_buf);

irqreturn_t hfi_isr_thread(int irq, void *dev_id)
{
        struct venus_core *core = dev_id;

        return core->ops->isr_thread(core);
}

irqreturn_t hfi_isr(int irq, void *dev)
{
        struct venus_core *core = dev;

        return core->ops->isr(core);
}

int hfi_create(struct venus_core *core, const struct hfi_core_ops *ops)
{
        if (!ops)
                return -EINVAL;

        atomic_set(&core->insts_count, 0);
        core->core_ops = ops;
        core->state = CORE_UNINIT;
        init_completion(&core->done);
        pkt_set_version(core->res->hfi_version);

        return venus_hfi_create(core);
}

void hfi_destroy(struct venus_core *core)
{
        venus_hfi_destroy(core);
}

void hfi_reinit(struct venus_core *core)
{
        venus_hfi_queues_reinit(core);
}