Commit 61cbf1c1 authored by Ming Qian's avatar Ming Qian Committed by Hans Verkuil

media: amphion: implement vpu core communication based on mailbox

driver use mailbox to communicate with vpu core.
and there are a command buffer and a message buffer.
driver will write commands to the command buffer,
then trigger a vpu core interrupt
vpu core will write messages to the message buffer,
then trigger a cpu interrupt.
Signed-off-by: default avatarMing Qian <ming.qian@nxp.com>
Signed-off-by: default avatarShijie Qin <shijie.qin@nxp.com>
Signed-off-by: default avatarZhou Peng <eagle.zhou@nxp.com>
Reported-by: default avatarkernel test robot <lkp@intel.com>
Tested-by: default avatarNicolas Dufresne <nicolas.dufresne@collabora.com>
Signed-off-by: default avatarHans Verkuil <hverkuil-cisco@xs4all.nl>
parent 9f599f35
// SPDX-License-Identifier: GPL-2.0
/*
* Copyright 2020-2021 NXP
*/
#include <linux/init.h>
#include <linux/interconnect.h>
#include <linux/ioctl.h>
#include <linux/list.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/of_device.h>
#include <linux/of_address.h>
#include <linux/platform_device.h>
#include <linux/slab.h>
#include <linux/types.h>
#include <linux/delay.h>
#include <linux/vmalloc.h>
#include "vpu.h"
#include "vpu_defs.h"
#include "vpu_cmds.h"
#include "vpu_rpc.h"
#include "vpu_mbox.h"
struct vpu_cmd_request {
u32 request;
u32 response;
u32 handled;
};
struct vpu_cmd_t {
struct list_head list;
u32 id;
struct vpu_cmd_request *request;
struct vpu_rpc_event *pkt;
unsigned long key;
};
static struct vpu_cmd_request vpu_cmd_requests[] = {
{
.request = VPU_CMD_ID_CONFIGURE_CODEC,
.response = VPU_MSG_ID_MEM_REQUEST,
.handled = 1,
},
{
.request = VPU_CMD_ID_START,
.response = VPU_MSG_ID_START_DONE,
.handled = 0,
},
{
.request = VPU_CMD_ID_STOP,
.response = VPU_MSG_ID_STOP_DONE,
.handled = 0,
},
{
.request = VPU_CMD_ID_ABORT,
.response = VPU_MSG_ID_ABORT_DONE,
.handled = 0,
},
{
.request = VPU_CMD_ID_RST_BUF,
.response = VPU_MSG_ID_BUF_RST,
.handled = 1,
},
};
static int vpu_cmd_send(struct vpu_core *core, struct vpu_rpc_event *pkt)
{
int ret = 0;
ret = vpu_iface_send_cmd(core, pkt);
if (ret)
return ret;
/*write cmd data to cmd buffer before trigger a cmd interrupt*/
mb();
vpu_mbox_send_type(core, COMMAND);
return ret;
}
static struct vpu_cmd_t *vpu_alloc_cmd(struct vpu_inst *inst, u32 id, void *data)
{
struct vpu_cmd_t *cmd;
int i;
int ret;
cmd = vzalloc(sizeof(*cmd));
if (!cmd)
return NULL;
cmd->pkt = vzalloc(sizeof(*cmd->pkt));
if (!cmd->pkt) {
vfree(cmd);
return NULL;
}
cmd->id = id;
ret = vpu_iface_pack_cmd(inst->core, cmd->pkt, inst->id, id, data);
if (ret) {
dev_err(inst->dev, "iface pack cmd(%d) fail\n", id);
vfree(cmd->pkt);
vfree(cmd);
return NULL;
}
for (i = 0; i < ARRAY_SIZE(vpu_cmd_requests); i++) {
if (vpu_cmd_requests[i].request == id) {
cmd->request = &vpu_cmd_requests[i];
break;
}
}
return cmd;
}
static void vpu_free_cmd(struct vpu_cmd_t *cmd)
{
if (!cmd)
return;
if (cmd->pkt)
vfree(cmd->pkt);
vfree(cmd);
}
static int vpu_session_process_cmd(struct vpu_inst *inst, struct vpu_cmd_t *cmd)
{
int ret;
dev_dbg(inst->dev, "[%d]send cmd(0x%x)\n", inst->id, cmd->id);
vpu_iface_pre_send_cmd(inst);
ret = vpu_cmd_send(inst->core, cmd->pkt);
if (!ret) {
vpu_iface_post_send_cmd(inst);
vpu_inst_record_flow(inst, cmd->id);
} else {
dev_err(inst->dev, "[%d] iface send cmd(0x%x) fail\n", inst->id, cmd->id);
}
return ret;
}
static void vpu_process_cmd_request(struct vpu_inst *inst)
{
struct vpu_cmd_t *cmd;
struct vpu_cmd_t *tmp;
if (!inst || inst->pending)
return;
list_for_each_entry_safe(cmd, tmp, &inst->cmd_q, list) {
list_del_init(&cmd->list);
if (vpu_session_process_cmd(inst, cmd))
dev_err(inst->dev, "[%d] process cmd(%d) fail\n", inst->id, cmd->id);
if (cmd->request) {
inst->pending = (void *)cmd;
break;
}
vpu_free_cmd(cmd);
}
}
static int vpu_request_cmd(struct vpu_inst *inst, u32 id, void *data,
unsigned long *key, int *sync)
{
struct vpu_core *core;
struct vpu_cmd_t *cmd;
if (!inst || !inst->core)
return -EINVAL;
core = inst->core;
cmd = vpu_alloc_cmd(inst, id, data);
if (!cmd)
return -ENOMEM;
mutex_lock(&core->cmd_lock);
cmd->key = core->cmd_seq++;
if (key)
*key = cmd->key;
if (sync)
*sync = cmd->request ? true : false;
list_add_tail(&cmd->list, &inst->cmd_q);
vpu_process_cmd_request(inst);
mutex_unlock(&core->cmd_lock);
return 0;
}
static void vpu_clear_pending(struct vpu_inst *inst)
{
if (!inst || !inst->pending)
return;
vpu_free_cmd(inst->pending);
wake_up_all(&inst->core->ack_wq);
inst->pending = NULL;
}
static bool vpu_check_response(struct vpu_cmd_t *cmd, u32 response, u32 handled)
{
struct vpu_cmd_request *request;
if (!cmd || !cmd->request)
return false;
request = cmd->request;
if (request->response != response)
return false;
if (request->handled != handled)
return false;
return true;
}
int vpu_response_cmd(struct vpu_inst *inst, u32 response, u32 handled)
{
struct vpu_core *core;
if (!inst || !inst->core)
return -EINVAL;
core = inst->core;
mutex_lock(&core->cmd_lock);
if (vpu_check_response(inst->pending, response, handled))
vpu_clear_pending(inst);
vpu_process_cmd_request(inst);
mutex_unlock(&core->cmd_lock);
return 0;
}
void vpu_clear_request(struct vpu_inst *inst)
{
struct vpu_cmd_t *cmd;
struct vpu_cmd_t *tmp;
mutex_lock(&inst->core->cmd_lock);
if (inst->pending)
vpu_clear_pending(inst);
list_for_each_entry_safe(cmd, tmp, &inst->cmd_q, list) {
list_del_init(&cmd->list);
vpu_free_cmd(cmd);
}
mutex_unlock(&inst->core->cmd_lock);
}
static bool check_is_responsed(struct vpu_inst *inst, unsigned long key)
{
struct vpu_core *core = inst->core;
struct vpu_cmd_t *cmd;
bool flag = true;
mutex_lock(&core->cmd_lock);
cmd = inst->pending;
if (cmd && key == cmd->key) {
flag = false;
goto exit;
}
list_for_each_entry(cmd, &inst->cmd_q, list) {
if (key == cmd->key) {
flag = false;
break;
}
}
exit:
mutex_unlock(&core->cmd_lock);
return flag;
}
static int sync_session_response(struct vpu_inst *inst, unsigned long key)
{
struct vpu_core *core;
if (!inst || !inst->core)
return -EINVAL;
core = inst->core;
call_void_vop(inst, wait_prepare);
wait_event_timeout(core->ack_wq, check_is_responsed(inst, key), VPU_TIMEOUT);
call_void_vop(inst, wait_finish);
if (!check_is_responsed(inst, key)) {
dev_err(inst->dev, "[%d] sync session timeout\n", inst->id);
set_bit(inst->id, &core->hang_mask);
mutex_lock(&inst->core->cmd_lock);
vpu_clear_pending(inst);
mutex_unlock(&inst->core->cmd_lock);
return -EINVAL;
}
return 0;
}
static int vpu_session_send_cmd(struct vpu_inst *inst, u32 id, void *data)
{
unsigned long key;
int sync = false;
int ret = -EINVAL;
if (inst->id < 0)
return -EINVAL;
ret = vpu_request_cmd(inst, id, data, &key, &sync);
if (!ret && sync)
ret = sync_session_response(inst, key);
if (ret)
dev_err(inst->dev, "[%d] send cmd(0x%x) fail\n", inst->id, id);
return ret;
}
int vpu_session_configure_codec(struct vpu_inst *inst)
{
return vpu_session_send_cmd(inst, VPU_CMD_ID_CONFIGURE_CODEC, NULL);
}
int vpu_session_start(struct vpu_inst *inst)
{
vpu_trace(inst->dev, "[%d]\n", inst->id);
return vpu_session_send_cmd(inst, VPU_CMD_ID_START, NULL);
}
int vpu_session_stop(struct vpu_inst *inst)
{
int ret;
vpu_trace(inst->dev, "[%d]\n", inst->id);
ret = vpu_session_send_cmd(inst, VPU_CMD_ID_STOP, NULL);
/* workaround for a firmware bug,
* if the next command is too close after stop cmd,
* the firmware may enter wfi wrongly.
*/
usleep_range(3000, 5000);
return ret;
}
int vpu_session_encode_frame(struct vpu_inst *inst, s64 timestamp)
{
return vpu_session_send_cmd(inst, VPU_CMD_ID_FRAME_ENCODE, &timestamp);
}
int vpu_session_alloc_fs(struct vpu_inst *inst, struct vpu_fs_info *fs)
{
return vpu_session_send_cmd(inst, VPU_CMD_ID_FS_ALLOC, fs);
}
int vpu_session_release_fs(struct vpu_inst *inst, struct vpu_fs_info *fs)
{
return vpu_session_send_cmd(inst, VPU_CMD_ID_FS_RELEASE, fs);
}
int vpu_session_abort(struct vpu_inst *inst)
{
return vpu_session_send_cmd(inst, VPU_CMD_ID_ABORT, NULL);
}
int vpu_session_rst_buf(struct vpu_inst *inst)
{
return vpu_session_send_cmd(inst, VPU_CMD_ID_RST_BUF, NULL);
}
int vpu_session_fill_timestamp(struct vpu_inst *inst, struct vpu_ts_info *info)
{
return vpu_session_send_cmd(inst, VPU_CMD_ID_TIMESTAMP, info);
}
int vpu_session_update_parameters(struct vpu_inst *inst, void *arg)
{
if (inst->type & VPU_CORE_TYPE_DEC)
vpu_iface_set_decode_params(inst, arg, 1);
else
vpu_iface_set_encode_params(inst, arg, 1);
return vpu_session_send_cmd(inst, VPU_CMD_ID_UPDATE_PARAMETER, arg);
}
int vpu_session_debug(struct vpu_inst *inst)
{
return vpu_session_send_cmd(inst, VPU_CMD_ID_DEBUG, NULL);
}
int vpu_core_snapshot(struct vpu_core *core)
{
struct vpu_inst *inst;
int ret;
if (!core || list_empty(&core->instances))
return 0;
inst = list_first_entry(&core->instances, struct vpu_inst, list);
reinit_completion(&core->cmp);
ret = vpu_session_send_cmd(inst, VPU_CMD_ID_SNAPSHOT, NULL);
if (ret)
return ret;
ret = wait_for_completion_timeout(&core->cmp, VPU_TIMEOUT);
if (!ret) {
dev_err(core->dev, "snapshot timeout\n");
return -EINVAL;
}
return 0;
}
int vpu_core_sw_reset(struct vpu_core *core)
{
struct vpu_rpc_event pkt;
int ret;
memset(&pkt, 0, sizeof(pkt));
vpu_iface_pack_cmd(core, &pkt, 0, VPU_CMD_ID_FIRM_RESET, NULL);
reinit_completion(&core->cmp);
mutex_lock(&core->cmd_lock);
ret = vpu_cmd_send(core, &pkt);
mutex_unlock(&core->cmd_lock);
if (ret)
return ret;
ret = wait_for_completion_timeout(&core->cmp, VPU_TIMEOUT);
if (!ret) {
dev_err(core->dev, "sw reset timeout\n");
return -EINVAL;
}
return 0;
}
/* SPDX-License-Identifier: GPL-2.0 */
/*
* Copyright 2020-2021 NXP
*/
#ifndef _AMPHION_VPU_CMDS_H
#define _AMPHION_VPU_CMDS_H
int vpu_session_configure_codec(struct vpu_inst *inst);
int vpu_session_start(struct vpu_inst *inst);
int vpu_session_stop(struct vpu_inst *inst);
int vpu_session_abort(struct vpu_inst *inst);
int vpu_session_rst_buf(struct vpu_inst *inst);
int vpu_session_encode_frame(struct vpu_inst *inst, s64 timestamp);
int vpu_session_alloc_fs(struct vpu_inst *inst, struct vpu_fs_info *fs);
int vpu_session_release_fs(struct vpu_inst *inst, struct vpu_fs_info *fs);
int vpu_session_fill_timestamp(struct vpu_inst *inst, struct vpu_ts_info *info);
int vpu_session_update_parameters(struct vpu_inst *inst, void *arg);
int vpu_core_snapshot(struct vpu_core *core);
int vpu_core_sw_reset(struct vpu_core *core);
int vpu_response_cmd(struct vpu_inst *inst, u32 response, u32 handled);
void vpu_clear_request(struct vpu_inst *inst);
int vpu_session_debug(struct vpu_inst *inst);
#endif
// SPDX-License-Identifier: GPL-2.0
/*
* Copyright 2020-2021 NXP
*/
#include <linux/init.h>
#include <linux/interconnect.h>
#include <linux/ioctl.h>
#include <linux/list.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/of_device.h>
#include <linux/of_address.h>
#include <linux/platform_device.h>
#include "vpu.h"
#include "vpu_mbox.h"
#include "vpu_msgs.h"
static void vpu_mbox_rx_callback(struct mbox_client *cl, void *msg)
{
struct vpu_mbox *rx = container_of(cl, struct vpu_mbox, cl);
struct vpu_core *core = container_of(rx, struct vpu_core, rx);
vpu_isr(core, *(u32 *)msg);
}
static int vpu_mbox_request_channel(struct device *dev, struct vpu_mbox *mbox)
{
struct mbox_chan *ch;
struct mbox_client *cl;
if (!dev || !mbox)
return -EINVAL;
if (mbox->ch)
return 0;
cl = &mbox->cl;
cl->dev = dev;
if (mbox->block) {
cl->tx_block = true;
cl->tx_tout = 1000;
} else {
cl->tx_block = false;
}
cl->knows_txdone = false;
cl->rx_callback = vpu_mbox_rx_callback;
ch = mbox_request_channel_byname(cl, mbox->name);
if (IS_ERR(ch)) {
dev_err(dev, "Failed to request mbox chan %s, ret : %ld\n",
mbox->name, PTR_ERR(ch));
return PTR_ERR(ch);
}
mbox->ch = ch;
return 0;
}
int vpu_mbox_init(struct vpu_core *core)
{
scnprintf(core->tx_type.name, sizeof(core->tx_type.name) - 1, "tx0");
core->tx_type.block = true;
scnprintf(core->tx_data.name, sizeof(core->tx_data.name) - 1, "tx1");
core->tx_data.block = false;
scnprintf(core->rx.name, sizeof(core->rx.name) - 1, "rx");
core->rx.block = true;
return 0;
}
int vpu_mbox_request(struct vpu_core *core)
{
int ret;
ret = vpu_mbox_request_channel(core->dev, &core->tx_type);
if (ret)
goto error;
ret = vpu_mbox_request_channel(core->dev, &core->tx_data);
if (ret)
goto error;
ret = vpu_mbox_request_channel(core->dev, &core->rx);
if (ret)
goto error;
dev_dbg(core->dev, "%s request mbox\n", vpu_core_type_desc(core->type));
return 0;
error:
vpu_mbox_free(core);
return ret;
}
void vpu_mbox_free(struct vpu_core *core)
{
mbox_free_channel(core->tx_type.ch);
mbox_free_channel(core->tx_data.ch);
mbox_free_channel(core->rx.ch);
core->tx_type.ch = NULL;
core->tx_data.ch = NULL;
core->rx.ch = NULL;
dev_dbg(core->dev, "%s free mbox\n", vpu_core_type_desc(core->type));
}
void vpu_mbox_send_type(struct vpu_core *core, u32 type)
{
mbox_send_message(core->tx_type.ch, &type);
}
void vpu_mbox_send_msg(struct vpu_core *core, u32 type, u32 data)
{
mbox_send_message(core->tx_data.ch, &data);
mbox_send_message(core->tx_type.ch, &type);
}
void vpu_mbox_enable_rx(struct vpu_dev *dev)
{
}
/* SPDX-License-Identifier: GPL-2.0 */
/*
* Copyright 2020-2021 NXP
*/
#ifndef _AMPHION_VPU_MBOX_H
#define _AMPHION_VPU_MBOX_H
int vpu_mbox_init(struct vpu_core *core);
int vpu_mbox_request(struct vpu_core *core);
void vpu_mbox_free(struct vpu_core *core);
void vpu_mbox_send_msg(struct vpu_core *core, u32 type, u32 data);
void vpu_mbox_send_type(struct vpu_core *core, u32 type);
void vpu_mbox_enable_rx(struct vpu_dev *dev);
#endif
This diff is collapsed.
/* SPDX-License-Identifier: GPL-2.0 */
/*
* Copyright 2020-2021 NXP
*/
#ifndef _AMPHION_VPU_MSGS_H
#define _AMPHION_VPU_MSGS_H
int vpu_isr(struct vpu_core *core, u32 irq);
void vpu_inst_run_work(struct work_struct *work);
void vpu_msg_run_work(struct work_struct *work);
void vpu_msg_delayed_work(struct work_struct *work);
#endif
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment