| /* |
| * Copyright (c) 2012-2016, The Linux Foundation. All rights reserved. |
| * Copyright (C) 2017 Linaro Ltd. |
| * |
| * This program is free software; you can redistribute it and/or modify |
| * it under the terms of the GNU General Public License version 2 and |
| * only version 2 as published by the Free Software Foundation. |
| * |
| * This program is distributed in the hope that it will be useful, |
| * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| * GNU General Public License for more details. |
| * |
| */ |
| #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; |
| 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; |
| } |
| |
| static int core_deinit_wait_atomic_t(atomic_t *p) |
| { |
| schedule(); |
| return 0; |
| } |
| |
| 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_on_atomic_t(&core->insts_count, core_deinit_wait_atomic_t, |
| TASK_UNINTERRUPTIBLE); |
| mutex_lock(&core->lock); |
| } |
| |
| 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; |
| |
| if (!ops) |
| return -EINVAL; |
| |
| inst->state = INST_UNINIT; |
| init_completion(&inst->done); |
| inst->ops = ops; |
| |
| mutex_lock(&core->lock); |
| list_add_tail(&inst->list, &core->instances); |
| atomic_inc(&core->insts_count); |
| mutex_unlock(&core->lock); |
| |
| return 0; |
| } |
| 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; |
| u32 codec; |
| int ret; |
| |
| codec = to_codec_type(pixfmt); |
| reinit_completion(&inst->done); |
| |
| ret = ops->session_init(inst, inst->session_type, 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); |
| atomic_dec(&core->insts_count); |
| wake_up_atomic_t(&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; |
| |
| reinit_completion(&inst->done); |
| |
| ret = ops->session_end(inst); |
| if (ret) |
| return ret; |
| |
| ret = wait_session_msg(inst); |
| if (ret) |
| return ret; |
| |
| 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 (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; |
| } |
| |
| int hfi_session_stop(struct venus_inst *inst) |
| { |
| const struct hfi_ops *ops = inst->core->ops; |
| int ret; |
| |
| 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; |
| } |
| |
| int hfi_session_continue(struct venus_inst *inst) |
| { |
| struct venus_core *core = inst->core; |
| |
| if (core->res->hfi_version != HFI_VERSION_3XX) |
| 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; |
| |
| reinit_completion(&inst->done); |
| |
| ret = ops->session_abort(inst); |
| if (ret) |
| return ret; |
| |
| ret = wait_session_msg(inst); |
| if (ret) |
| return ret; |
| |
| return 0; |
| } |
| |
| int hfi_session_load_res(struct venus_inst *inst) |
| { |
| const struct hfi_ops *ops = inst->core->ops; |
| int ret; |
| |
| 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 (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; |
| } |
| |
| int hfi_session_flush(struct venus_inst *inst) |
| { |
| const struct hfi_ops *ops = inst->core->ops; |
| int ret; |
| |
| reinit_completion(&inst->done); |
| |
| ret = ops->session_flush(inst, HFI_FLUSH_ALL); |
| if (ret) |
| return ret; |
| |
| 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; |
| |
| 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; |
| |
| 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 (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 (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 (fd->buffer_type == HFI_BUFFER_INPUT) |
| return ops->session_etb(inst, fd); |
| else if (fd->buffer_type == HFI_BUFFER_OUTPUT) |
| 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) |
| { |
| int ret; |
| |
| 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); |
| ret = venus_hfi_create(core); |
| |
| return ret; |
| } |
| |
| void hfi_destroy(struct venus_core *core) |
| { |
| venus_hfi_destroy(core); |
| } |