// 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 "vpu.h"
#include "vpu_core.h"
#include "vpu_rpc.h"
#include "vpu_mbox.h"
#include "vpu_defs.h"
#include "vpu_cmds.h"
#include "vpu_msgs.h"
#include "vpu_v4l2.h"

#define VPU_PKT_HEADER_LENGTH		3

struct vpu_msg_handler {
	u32 id;
	void (*done)(struct vpu_inst *inst, struct vpu_rpc_event *pkt);
};

static void vpu_session_handle_start_done(struct vpu_inst *inst, struct vpu_rpc_event *pkt)
{
	vpu_trace(inst->dev, "[%d]\n", inst->id);
}

static void vpu_session_handle_mem_request(struct vpu_inst *inst, struct vpu_rpc_event *pkt)
{
	struct vpu_pkt_mem_req_data req_data = { 0 };

	vpu_iface_unpack_msg_data(inst->core, pkt, (void *)&req_data);
	vpu_trace(inst->dev, "[%d] %d:%d %d:%d %d:%d\n",
		  inst->id,
		  req_data.enc_frame_size,
		  req_data.enc_frame_num,
		  req_data.ref_frame_size,
		  req_data.ref_frame_num,
		  req_data.act_buf_size,
		  req_data.act_buf_num);
	vpu_inst_lock(inst);
	call_void_vop(inst, mem_request,
		      req_data.enc_frame_size,
		      req_data.enc_frame_num,
		      req_data.ref_frame_size,
		      req_data.ref_frame_num,
		      req_data.act_buf_size,
		      req_data.act_buf_num);
	vpu_inst_unlock(inst);
}

static void vpu_session_handle_stop_done(struct vpu_inst *inst, struct vpu_rpc_event *pkt)
{
	vpu_trace(inst->dev, "[%d]\n", inst->id);

	call_void_vop(inst, stop_done);
}

static void vpu_session_handle_seq_hdr(struct vpu_inst *inst, struct vpu_rpc_event *pkt)
{
	struct vpu_dec_codec_info info;
	const struct vpu_core_resources *res;

	memset(&info, 0, sizeof(info));
	res = vpu_get_resource(inst);
	info.stride = res ? res->stride : 1;
	vpu_iface_unpack_msg_data(inst->core, pkt, (void *)&info);
	call_void_vop(inst, event_notify, VPU_MSG_ID_SEQ_HDR_FOUND, &info);
}

static void vpu_session_handle_resolution_change(struct vpu_inst *inst, struct vpu_rpc_event *pkt)
{
	call_void_vop(inst, event_notify, VPU_MSG_ID_RES_CHANGE, NULL);
}

static void vpu_session_handle_enc_frame_done(struct vpu_inst *inst, struct vpu_rpc_event *pkt)
{
	struct vpu_enc_pic_info info = { 0 };

	vpu_iface_unpack_msg_data(inst->core, pkt, (void *)&info);
	dev_dbg(inst->dev, "[%d] frame id = %d, wptr = 0x%x, size = %d\n",
		inst->id, info.frame_id, info.wptr, info.frame_size);
	call_void_vop(inst, get_one_frame, &info);
}

static void vpu_session_handle_frame_request(struct vpu_inst *inst, struct vpu_rpc_event *pkt)
{
	struct vpu_fs_info fs = { 0 };

	vpu_iface_unpack_msg_data(inst->core, pkt, &fs);
	call_void_vop(inst, event_notify, VPU_MSG_ID_FRAME_REQ, &fs);
}

static void vpu_session_handle_frame_release(struct vpu_inst *inst, struct vpu_rpc_event *pkt)
{
	if (inst->core->type == VPU_CORE_TYPE_ENC) {
		struct vpu_frame_info info;

		memset(&info, 0, sizeof(info));
		vpu_iface_unpack_msg_data(inst->core, pkt, (void *)&info.sequence);
		dev_dbg(inst->dev, "[%d] %d\n", inst->id, info.sequence);
		info.type = inst->out_format.type;
		call_void_vop(inst, buf_done, &info);
	} else if (inst->core->type == VPU_CORE_TYPE_DEC) {
		struct vpu_fs_info fs = { 0 };

		vpu_iface_unpack_msg_data(inst->core, pkt, &fs);
		call_void_vop(inst, event_notify, VPU_MSG_ID_FRAME_RELEASE, &fs);
	}
}

static void vpu_session_handle_input_done(struct vpu_inst *inst, struct vpu_rpc_event *pkt)
{
	dev_dbg(inst->dev, "[%d]\n", inst->id);
	call_void_vop(inst, input_done);
}

static void vpu_session_handle_pic_decoded(struct vpu_inst *inst, struct vpu_rpc_event *pkt)
{
	struct vpu_dec_pic_info info = { 0 };

	vpu_iface_unpack_msg_data(inst->core, pkt, (void *)&info);
	call_void_vop(inst, get_one_frame, &info);
}

static void vpu_session_handle_pic_done(struct vpu_inst *inst, struct vpu_rpc_event *pkt)
{
	struct vpu_dec_pic_info info = { 0 };
	struct vpu_frame_info frame;

	memset(&frame, 0, sizeof(frame));
	vpu_iface_unpack_msg_data(inst->core, pkt, (void *)&info);
	if (inst->core->type == VPU_CORE_TYPE_DEC)
		frame.type = inst->cap_format.type;
	frame.id = info.id;
	frame.luma = info.luma;
	frame.skipped = info.skipped;
	frame.timestamp = info.timestamp;

	call_void_vop(inst, buf_done, &frame);
}

static void vpu_session_handle_eos(struct vpu_inst *inst, struct vpu_rpc_event *pkt)
{
	call_void_vop(inst, event_notify, VPU_MSG_ID_PIC_EOS, NULL);
}

static void vpu_session_handle_error(struct vpu_inst *inst, struct vpu_rpc_event *pkt)
{
	char *str = (char *)pkt->data;

	if (strlen(str))
		dev_err(inst->dev, "instance %d firmware error : %s\n", inst->id, str);
	else
		dev_err(inst->dev, "instance %d is unsupported stream\n", inst->id);
	call_void_vop(inst, event_notify, VPU_MSG_ID_UNSUPPORTED, NULL);
	vpu_v4l2_set_error(inst);
}

static void vpu_session_handle_firmware_xcpt(struct vpu_inst *inst, struct vpu_rpc_event *pkt)
{
	char *str = (char *)pkt->data;

	dev_err(inst->dev, "%s firmware xcpt: %s\n",
		vpu_core_type_desc(inst->core->type), str);
	call_void_vop(inst, event_notify, VPU_MSG_ID_FIRMWARE_XCPT, NULL);
	set_bit(inst->id, &inst->core->hang_mask);
	vpu_v4l2_set_error(inst);
}

static void vpu_session_handle_pic_skipped(struct vpu_inst *inst, struct vpu_rpc_event *pkt)
{
	vpu_inst_lock(inst);
	vpu_skip_frame(inst, 1);
	vpu_inst_unlock(inst);
}

static struct vpu_msg_handler handlers[] = {
	{VPU_MSG_ID_START_DONE, vpu_session_handle_start_done},
	{VPU_MSG_ID_STOP_DONE, vpu_session_handle_stop_done},
	{VPU_MSG_ID_MEM_REQUEST, vpu_session_handle_mem_request},
	{VPU_MSG_ID_SEQ_HDR_FOUND, vpu_session_handle_seq_hdr},
	{VPU_MSG_ID_RES_CHANGE, vpu_session_handle_resolution_change},
	{VPU_MSG_ID_FRAME_INPUT_DONE, vpu_session_handle_input_done},
	{VPU_MSG_ID_FRAME_REQ, vpu_session_handle_frame_request},
	{VPU_MSG_ID_FRAME_RELEASE, vpu_session_handle_frame_release},
	{VPU_MSG_ID_ENC_DONE, vpu_session_handle_enc_frame_done},
	{VPU_MSG_ID_PIC_DECODED, vpu_session_handle_pic_decoded},
	{VPU_MSG_ID_DEC_DONE, vpu_session_handle_pic_done},
	{VPU_MSG_ID_PIC_EOS, vpu_session_handle_eos},
	{VPU_MSG_ID_UNSUPPORTED, vpu_session_handle_error},
	{VPU_MSG_ID_FIRMWARE_XCPT, vpu_session_handle_firmware_xcpt},
	{VPU_MSG_ID_PIC_SKIPPED, vpu_session_handle_pic_skipped},
};

static int vpu_session_handle_msg(struct vpu_inst *inst, struct vpu_rpc_event *msg)
{
	int ret;
	u32 msg_id;
	struct vpu_msg_handler *handler = NULL;
	unsigned int i;

	ret = vpu_iface_convert_msg_id(inst->core, msg->hdr.id);
	if (ret < 0)
		return -EINVAL;

	msg_id = ret;
	dev_dbg(inst->dev, "[%d] receive event(%s)\n", inst->id, vpu_id_name(msg_id));

	for (i = 0; i < ARRAY_SIZE(handlers); i++) {
		if (handlers[i].id == msg_id) {
			handler = &handlers[i];
			break;
		}
	}

	if (handler && handler->done)
		handler->done(inst, msg);

	vpu_response_cmd(inst, msg_id, 1);

	return 0;
}

static bool vpu_inst_receive_msg(struct vpu_inst *inst, struct vpu_rpc_event *pkt)
{
	unsigned long bytes = sizeof(struct vpu_rpc_event_header);
	u32 ret;

	memset(pkt, 0, sizeof(*pkt));
	if (kfifo_len(&inst->msg_fifo) < bytes)
		return false;

	ret = kfifo_out(&inst->msg_fifo, pkt, bytes);
	if (ret != bytes)
		return false;

	if (pkt->hdr.num > 0) {
		bytes = pkt->hdr.num * sizeof(u32);
		ret = kfifo_out(&inst->msg_fifo, pkt->data, bytes);
		if (ret != bytes)
			return false;
	}

	return true;
}

void vpu_inst_run_work(struct work_struct *work)
{
	struct vpu_inst *inst = container_of(work, struct vpu_inst, msg_work);
	struct vpu_rpc_event pkt;

	while (vpu_inst_receive_msg(inst, &pkt))
		vpu_session_handle_msg(inst, &pkt);
}

static void vpu_inst_handle_msg(struct vpu_inst *inst, struct vpu_rpc_event *pkt)
{
	unsigned long bytes;
	u32 id = pkt->hdr.id;
	int ret;

	if (!inst->workqueue)
		return;

	bytes = sizeof(pkt->hdr) + pkt->hdr.num * sizeof(u32);
	ret = kfifo_in(&inst->msg_fifo, pkt, bytes);
	if (ret != bytes)
		dev_err(inst->dev, "[%d:%d]overflow: %d\n", inst->core->id, inst->id, id);
	queue_work(inst->workqueue, &inst->msg_work);
}

static int vpu_handle_msg(struct vpu_core *core)
{
	struct vpu_rpc_event pkt;
	struct vpu_inst *inst;
	int ret;

	memset(&pkt, 0, sizeof(pkt));
	while (!vpu_iface_receive_msg(core, &pkt)) {
		dev_dbg(core->dev, "event index = %d, id = %d, num = %d\n",
			pkt.hdr.index, pkt.hdr.id, pkt.hdr.num);

		ret = vpu_iface_convert_msg_id(core, pkt.hdr.id);
		if (ret < 0)
			continue;

		inst = vpu_core_find_instance(core, pkt.hdr.index);
		if (inst) {
			vpu_response_cmd(inst, ret, 0);
			mutex_lock(&core->cmd_lock);
			vpu_inst_record_flow(inst, ret);
			mutex_unlock(&core->cmd_lock);

			vpu_inst_handle_msg(inst, &pkt);
			vpu_inst_put(inst);
		}
		memset(&pkt, 0, sizeof(pkt));
	}

	return 0;
}

static int vpu_isr_thread(struct vpu_core *core, u32 irq_code)
{
	dev_dbg(core->dev, "irq code = 0x%x\n", irq_code);
	switch (irq_code) {
	case VPU_IRQ_CODE_SYNC:
		vpu_mbox_send_msg(core, PRC_BUF_OFFSET, core->rpc.phys - core->fw.phys);
		vpu_mbox_send_msg(core, BOOT_ADDRESS, core->fw.phys);
		vpu_mbox_send_msg(core, INIT_DONE, 2);
		break;
	case VPU_IRQ_CODE_BOOT_DONE:
		break;
	case VPU_IRQ_CODE_SNAPSHOT_DONE:
		break;
	default:
		vpu_handle_msg(core);
		break;
	}

	return 0;
}

static void vpu_core_run_msg_work(struct vpu_core *core)
{
	const unsigned int SIZE = sizeof(u32);

	while (kfifo_len(&core->msg_fifo) >= SIZE) {
		u32 data = 0;

		if (kfifo_out(&core->msg_fifo, &data, SIZE) == SIZE)
			vpu_isr_thread(core, data);
	}
}

void vpu_msg_run_work(struct work_struct *work)
{
	struct vpu_core *core = container_of(work, struct vpu_core, msg_work);
	unsigned long delay = msecs_to_jiffies(10);

	vpu_core_run_msg_work(core);
	queue_delayed_work(core->workqueue, &core->msg_delayed_work, delay);
}

void vpu_msg_delayed_work(struct work_struct *work)
{
	struct vpu_core *core;
	struct delayed_work *dwork;
	unsigned long bytes = sizeof(u32);
	u32 i;

	if (!work)
		return;

	dwork = to_delayed_work(work);
	core = container_of(dwork, struct vpu_core, msg_delayed_work);
	if (kfifo_len(&core->msg_fifo) >= bytes)
		vpu_core_run_msg_work(core);

	bytes = sizeof(struct vpu_rpc_event_header);
	for (i = 0; i < core->supported_instance_count; i++) {
		struct vpu_inst *inst = vpu_core_find_instance(core, i);

		if (!inst)
			continue;

		if (inst->workqueue && kfifo_len(&inst->msg_fifo) >= bytes)
			queue_work(inst->workqueue, &inst->msg_work);

		vpu_inst_put(inst);
	}
}

int vpu_isr(struct vpu_core *core, u32 irq)
{
	switch (irq) {
	case VPU_IRQ_CODE_SYNC:
		break;
	case VPU_IRQ_CODE_BOOT_DONE:
		complete(&core->cmp);
		break;
	case VPU_IRQ_CODE_SNAPSHOT_DONE:
		complete(&core->cmp);
		break;
	default:
		break;
	}

	if (kfifo_in(&core->msg_fifo, &irq, sizeof(irq)) != sizeof(irq))
		dev_err(core->dev, "[%d]overflow: %d\n", core->id, irq);
	queue_work(core->workqueue, &core->msg_work);

	return 0;
}