593 lines
		
	
	
		
			11 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
			
		
		
	
	
			593 lines
		
	
	
		
			11 KiB
		
	
	
	
		
			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);
 | |
| }
 | |
| 
 | |
| int hfi_core_ping(struct venus_core *core)
 | |
| {
 | |
| 	int ret;
 | |
| 
 | |
| 	mutex_lock(&core->lock);
 | |
| 
 | |
| 	ret = core->ops->core_ping(core, 0xbeef);
 | |
| 	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 = -ENODEV;
 | |
| unlock:
 | |
| 	mutex_unlock(&core->lock);
 | |
| 	return ret;
 | |
| }
 | |
| 
 | |
| 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);
 | |
| }
 |