// SPDX-License-Identifier: (GPL-2.0 OR BSD-3-Clause)
/*
 * Copyright (c) 2020 MediaTek Inc.
 */

#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/mutex.h>
#include <linux/dma-buf.h>

#include <media/dvb_vb2.h>

#include "mtk_iommu_dtv_api.h"

#include "mdrv_demux_common.h"
#include "mdrv_demux_resource.h"
#include "mdrv_demux_debug.h"
#include "mdrv_demux_dvb.h"
#include "mdrv_demux_pvr.h"
#include "mdrv_demux_tee_if.h"

static int _pvr_ringbuffer_write(struct dvb_ringbuffer *buf, const u8 *src, size_t len)
{
	ssize_t free;

	if (!len)
		return 0;
	if (!buf->data)
		return 0;

	free = dvb_ringbuffer_free(buf);
	if (len > free) {
		DEMUX_DEBUG_DEBUG("[%s][%d] buffer overflow\n", __func__, __LINE__);
		return -EOVERFLOW;
	}

	return dvb_ringbuffer_write(buf, src, len);
}

static int _pvr_trans_ree_ringbuf(int pvr_eng_id, struct pvr_data_trans_param *param)
{
	struct mdrv_demux_path *path = param->pvr_slot->dvbdemux->priv;
	struct dmxdev *dmxdev = &path->dmxdev.parent;
	struct dvb_ringbuffer *buffer;
	int ret;
	unsigned long flags;

	if (!dmxdev) {
		DEMUX_DEBUG_ERR("[%s][%d] dmxdev is NULL\n", __func__, __LINE__);
		return -EINVAL;
	}

	spin_lock_irqsave(&dmxdev->lock, flags);

	buffer = &dmxdev->dvr_buffer;
	if (buffer->error) {
		spin_unlock_irqrestore(&dmxdev->lock, flags);
		wake_up(&buffer->queue);
		return 0;
	}

	ret = _pvr_ringbuffer_write(buffer, param->va[0], param->len[0]);
	if (ret == param->len[0])
		ret = _pvr_ringbuffer_write(buffer, param->va[1], param->len[1]);

	if (ret < 0)
		buffer->error = ret;

	spin_unlock_irqrestore(&dmxdev->lock, flags);
	wake_up(&buffer->queue);
	return 0;
}

#ifdef CONFIG_DVB_MMAP
static int _pvr_trans_ree_vb2_splane(int pvr_eng_id, struct pvr_data_trans_param *param)
{
	struct mdrv_demux_path *path = param->pvr_slot->dvbdemux->priv;
	struct dmxdev *dmxdev = &path->dmxdev.parent;
	struct dvb_vb2_ctx *ctx;
	int ret;

	if (!dmxdev) {
		DEMUX_DEBUG_ERR("[%s][%d] dmxdev is NULL\n", __func__, __LINE__);
		return -EINVAL;
	}

	spin_lock(&dmxdev->lock);

	ctx = &dmxdev->dvr_vb2_ctx;
	if ((!dvb_vb2_is_streaming(ctx)) || (ctx->type != DVB_BUF_TYPE_CAPTURE)) {
		DEMUX_DEBUG_ERR("[%s][%d] ctx state is wrong!\n", __func__, __LINE__);
		spin_unlock(&dmxdev->lock);
		return -EFAULT;
	}

	ret = dvb_vb2_fill_buffer(ctx, param->va[0], param->len[0], NULL);
	if (ret == param->len[0])
		ret = dvb_vb2_fill_buffer(ctx, param->va[1], param->len[1], NULL);

	spin_unlock(&dmxdev->lock);
	return ret;
}

static int _pvr_trans_ree_vb2_mplane(int pvr_eng_id, struct pvr_data_trans_param *param)
{
	struct mdrv_demux_path *path = param->pvr_slot->dvbdemux->priv;
	struct dmxdev *dmxdev = &path->dmxdev.parent;
	struct dvb_vb2_ctx *ctx;
	int ret;
	static u8 CC = 1;
	u8 temp[32];

	if (!dmxdev) {
		DEMUX_DEBUG_ERR("[%s][%d] dmxdev is NULL\n", __func__, __LINE__);
		return -EINVAL;
	}

	spin_lock(&dmxdev->lock);

	ctx = &dmxdev->dvr_vb2_ctx;
	if ((!dvb_vb2_is_streaming(ctx)) || (ctx->type != DVB_BUF_TYPE_CAPTURE_MPLANE)) {
		DEMUX_DEBUG_ERR("[%s][%d] ctx state is wrong!\n", __func__, __LINE__);
		spin_unlock(&dmxdev->lock);
		return -EFAULT;
	}

	// @NOTE: currently 2nd fd is only for test
	ret = dvb_vb2_fill_plane(ctx, param->va[0], param->len[0], 0, NULL);
	memset(temp, CC, sizeof(temp));
	ret = dvb_vb2_fill_plane(ctx, temp, sizeof(temp), 1, NULL);
	CC++;

	if (param->len[1] > 0) {
		ret = dvb_vb2_fill_plane(ctx, param->va[1], param->len[1], 0, NULL);
		memset(temp, CC, sizeof(temp));
		ret = dvb_vb2_fill_plane(ctx, temp, sizeof(temp), 1, NULL);
		CC++;
	}

	spin_unlock(&dmxdev->lock);
	return ret;
}

static bool _get_vb2_queued_buf(struct dvb_vb2_ctx *ctx)
{
	if (ctx->buf) {
		DEMUX_DEBUG_INFO("[%s] use existing buffer!!!\n", ctx->name);
		return true;
	}

	if (list_empty(&ctx->dvb_q)) {
		DEMUX_DEBUG_INFO("[%s] Buffer overflow!!!\n", ctx->name);
		return false;
	}

	ctx->buf = list_entry(ctx->dvb_q.next, struct dvb_buffer, list);
	ctx->offset[0] = 0;
	ctx->remain[0] = vb2_plane_size(&ctx->buf->vb, 0);

	if (ctx->buf_siz[0] <= ctx->remain[0]) {
		ctx->remain[0] = ctx->buf_siz[0];
		vb2_set_plane_payload(&ctx->buf->vb, 0, ctx->buf_siz[0]);
	}

	return true;
}

static int _map_dmabuf_va(struct dma_buf *db, void **va, u64 *size)
{
	int ret = 0;

	if ((db == NULL) || (va == NULL) || (size == NULL)) {
		DEMUX_DEBUG_ERR("[%s][%d] invalid parameters\n", __func__, __LINE__);
		return -EINVAL;
	}

	*size = db->size;

	ret = dma_buf_begin_cpu_access(db, DMA_BIDIRECTIONAL);
	if (ret < 0) {
		DEMUX_DEBUG_ERR("[%s][%d] dma_buf_begin_cpu_access fail\n", __func__, __LINE__);
		return ret;
	}

	*va = dma_buf_vmap(db);
	if ((*va == NULL) || (*va == -1)) {
		DEMUX_DEBUG_ERR("[%s][%d] dma_buf_kmap fail\n", __func__, __LINE__);
		dma_buf_end_cpu_access(db, DMA_BIDIRECTIONAL);
		return -EFAULT;
	}

	return 0;
}

static int _unmap_dmabuf_va(struct dma_buf *db, void *va)
{
	if ((db == NULL) || (va == NULL)) {
		DEMUX_DEBUG_ERR("[%s][%d] invalid parameters\n", __func__, __LINE__);
		return -EINVAL;
	}

	dma_buf_vunmap(db, va);
	dma_buf_end_cpu_access(db, DMA_BIDIRECTIONAL);

	return 0;
}

static int _get_dmabuf_iova(struct dma_buf *db, struct device *dev, u64 *iova, u32 *size)
{
	struct dma_buf_attachment *buf_att;
	struct sg_table *sgt;

	buf_att = dma_buf_attach(db, dev);
	sgt = dma_buf_map_attachment(buf_att, DMA_BIDIRECTIONAL);

	if (!sgt) {
		DEMUX_DEBUG_ERR("[%s][%d] dma_buf_map_attachment fail\n", __func__, __LINE__);
		return -EFAULT;
	}

	*iova = sg_dma_address(sgt->sgl);
	*size = sg_dma_len(sgt->sgl);

	dma_buf_unmap_attachment(buf_att, sgt, DMA_BIDIRECTIONAL);
	dma_buf_detach(db, buf_att);

	DEMUX_DEBUG_DEBUG("[%s][%d] iova = %llx\n", __func__, __LINE__, *iova);
	return 0;
}

static void _queue_pvr_metadata_event(struct dmx_fh_mgr *mgr,
	pvr_stream_type_t vcodec, struct pvr_metadata *meta, u32 num, bool end)
{
	int i;
	struct dmx_event ev = { 0 };
	struct dmx_event_sc_info info = { 0 };

	ev.type = (vcodec == PVR_STREAM_TYPE_HEVC_VIDEO) ?
		DMX_EVENT_SC_HEVC_INDEX : DMX_EVENT_SC_INDEX;
	ev.mask = U32_MAX;

	for (i = 0; i < num; i++) {
		if (meta[i].pid == 0)
			break;

		// @TODO: need to modify
		info.sc_index = (meta[i].mask & PVR_TAG_MASK_I_FRAME) ? DMX_I_FRAME : 0;
		info.byte_number = meta[i].offset;
		info.pts = meta[i].pts;

		if ((i < (num - 1)) && (meta[i + 1].pid == 0) && end) // last one
			info.flags = DMX_EVENT_SC_INFO_END;
		else
			info.flags = 0;

		memcpy(ev.u.data, &info, sizeof(info));
		mdrv_demux_event_queue(mgr, &ev);
	}
}

static int _pvr_trans_tee_vb2_splane(int pvr_eng_id, struct pvr_data_trans_param *param)
{
	static struct pvr_metadata metadata[PVR_METADATA_MAX_NUM];
	int i;
	struct mdrv_demux_path *path = param->pvr_slot->dvbdemux->priv;
	struct dmxdev *dmxdev = &path->dmxdev.parent;
	struct dvb_vb2_ctx *ctx;
	struct pvr_encrypt_param encrypt_param;
	struct dmx_fh_mgr *mgr = &param->pvr_slot->fh_mgr;
	pvr_stream_type_t vcodec = param->pvr_slot->vcodec_type;
	struct dma_buf *db = NULL;
	void *va = NULL;
	u64 iova;
	u32 size;
	u64 src[2];
	int todo[2], ll = 0;
	int ll_align = 0;

	if (!dmxdev) {
		DEMUX_DEBUG_ERR("[%s][%d] dmxdev is NULL\n", __func__, __LINE__);
		return -EINVAL;
	}

	spin_lock(&dmxdev->lock);

	ctx = &dmxdev->dvr_vb2_ctx;
	if ((!dvb_vb2_is_streaming(ctx)) || (ctx->type != DVB_BUF_TYPE_CAPTURE)) {
		DEMUX_DEBUG_ERR("[%s][%d] ctx state is wrong!\n", __func__, __LINE__);
		spin_unlock(&dmxdev->lock);
		return -EFAULT;
	}

	spin_lock(&ctx->slock); // @NOTE: hang, when use spin_lock_irqsave() ???

	src[0] = param->pa[0];
	src[1] = param->pa[1];
	todo[0] = param->len[0];
	todo[1] = param->len[1];

	i = 0;
	while (i < 2) {
		if (!_get_vb2_queued_buf(ctx))
			break;

		if (!dvb_vb2_is_streaming(ctx)) {
			DEMUX_DEBUG_ERR("[%s][%d] in stream off state\n", __func__, __LINE__);
			vb2_buffer_done(&ctx->buf->vb, VB2_BUF_STATE_ERROR);
			list_del(&ctx->buf->list);
			ctx->buf = NULL;
			break;
		}

		db = ctx->buf->vb.planes[0].dbuf;
		if (_get_dmabuf_iova(db, ctx->vb_q.dev, &iova, &size) < 0) {
			DEMUX_DEBUG_ERR("[%s][%d] get iova fail\n", __func__, __LINE__);
			break;
		}

		ll = min(todo[i], ctx->remain[0]);
		ll_align = rounddown(ll, 192);
		if (ll != ll_align)
			DEMUX_DEBUG_ERR("[%s][%d] size(%d) not alignment !!!\n",
				__func__, __LINE__, ll);

		if (ll > 0) {
			memset(&encrypt_param, 0, sizeof(struct pvr_encrypt_param));
			encrypt_param.src_addr = src[i];
			encrypt_param.dst_addr =
				iova + (ctx->buf->vb.index * ctx->buf_siz[0]) + ctx->offset[0];
			encrypt_param.size = ll;
			encrypt_param.index_num = PVR_METADATA_MAX_NUM;
			encrypt_param.metadata = metadata;

			spin_unlock(&ctx->slock);
			spin_unlock(&dmxdev->lock);
			if (mdrv_demux_tee_pvr_encrypt_buffer(pvr_eng_id, &encrypt_param) < 0) {
				DEMUX_DEBUG_ERR("[%s][%d] encrypt buffer fail !!!\n",
					__func__, __LINE__);
				spin_lock(&dmxdev->lock);
				spin_lock(&ctx->slock);
				break;
			}
			spin_lock(&dmxdev->lock);
			spin_lock(&ctx->slock);

			todo[i] -= ll;
			src[i] += ll;

			ctx->offset[0] += ll;
			ctx->remain[0] -= ll;

			_queue_pvr_metadata_event(
				mgr,
				vcodec,
				metadata,
				PVR_METADATA_MAX_NUM,
				!ctx->remain[0]);
		}

		if (ctx->remain[0] == 0) {
			vb2_buffer_done(&ctx->buf->vb, VB2_BUF_STATE_DONE);
			list_del(&ctx->buf->list);
			ctx->buf = NULL;
		}

		if (todo[i] == 0)
			i++;
	}

	if (ctx->nonblocking && ctx->buf) {
		vb2_set_plane_payload(&ctx->buf->vb, 0, ll);
		vb2_buffer_done(&ctx->buf->vb, VB2_BUF_STATE_DONE);
		list_del(&ctx->buf->list);
		ctx->buf = NULL;
	}

	spin_unlock(&ctx->slock); // @NOTE: hang, when use spin_lock_irqsave() ???
	spin_unlock(&dmxdev->lock);

	return (param->len[0] - todo[0]) + (param->len[1] - todo[1]);
}

static int _pvr_trans_tee_vb2_mplane(int pvr_eng_id, struct pvr_data_trans_param *param)
{
	struct mdrv_demux_path *path = param->pvr_slot->dvbdemux->priv;
	struct dmxdev *dmxdev = &path->dmxdev.parent;
	struct dvb_vb2_ctx *ctx;
	int ret = 0;

	if (!dmxdev) {
		DEMUX_DEBUG_ERR("[%s][%d] dmxdev is NULL\n", __func__, __LINE__);
		return -EINVAL;
	}

	spin_lock(&dmxdev->lock);

	ctx = &dmxdev->dvr_vb2_ctx;
	if ((!dvb_vb2_is_streaming(ctx)) || (ctx->type != DVB_BUF_TYPE_CAPTURE)) {
		DEMUX_DEBUG_ERR("[%s][%d] ctx state is wrong!\n", __func__, __LINE__);
		spin_unlock(&dmxdev->lock);
		return -EFAULT;
	}

	// -> REC TA

	dvb_vb2_fill_plane(ctx, NULL, 0, 1, NULL); // need to modify

	spin_unlock(&dmxdev->lock);
	return ret;
}
#endif

int _pvr_select_strategy_if(struct mdrv_demux_pvr *pvr, u16 path_id)
{
	int ret = 0, pvr_eng_id;
	struct mdrv_demux_tsp *tsp = container_of(pvr, struct mdrv_demux_tsp, pvr);
	struct mdrv_demux_dvb *dvb = container_of(tsp, struct mdrv_demux_dvb, tsp);
	struct dvb_vb2_ctx *ctx = &dvb->paths[path_id].dmxdev.parent.dvr_vb2_ctx;

	pvr_eng_id = mdrv_demux_pvr_alloc_engine(pvr, path_id);
	if (pvr_eng_id < 0) {
		DEMUX_DEBUG_ERR("[%s][%d] get pvr engine fail\n", __func__, __LINE__);
		return -EINVAL;
	}

	mutex_lock(&pvr->pvr_mutex);

	if (pvr->pvr_slot_map[pvr_eng_id].trans_mode == PVR_DATA_TRANS_REE_MODE) {
		switch (pvr->pvr_slot_map[pvr_eng_id].trans_type) {
		case PVR_DATA_TRANS_RINGBUF_TYPE:
			pvr->pvr_slot_map[pvr_eng_id].fn = &_pvr_trans_ree_ringbuf;
			break;
	#ifdef CONFIG_DVB_MMAP
		case PVR_DATA_TRANS_VB2_SPLANE_TYPE:
			pvr->pvr_slot_map[pvr_eng_id].fn = &_pvr_trans_ree_vb2_splane;
			break;
		case PVR_DATA_TRANS_VB2_MPLANE_TYPE:
			// only for test, no real use case
			dvb_vb2_set_plane_attr(ctx, 0, DVB_VB2_PLANE_ATTR_NONE);
			dvb_vb2_set_plane_attr(ctx, 1, DVB_VB2_PLANE_ATTR_NONBLOCK);
			pvr->pvr_slot_map[pvr_eng_id].fn = &_pvr_trans_ree_vb2_mplane;
			break;
	#endif
		default:
			DEMUX_DEBUG_ERR("[%s][%d] not support!\n", __func__, __LINE__);
			ret = -EINVAL;
			break;
		}
	} else {
		switch (pvr->pvr_slot_map[pvr_eng_id].trans_type) {
	#ifdef CONFIG_DVB_MMAP
		case PVR_DATA_TRANS_VB2_SPLANE_TYPE:
			pvr->pvr_slot_map[pvr_eng_id].fn = &_pvr_trans_tee_vb2_splane;
			break;
		case PVR_DATA_TRANS_VB2_MPLANE_TYPE:
			pvr->pvr_slot_map[pvr_eng_id].fn = &_pvr_trans_tee_vb2_mplane;
			break;
	#endif
		default:
			DEMUX_DEBUG_ERR("[%s][%d] not support!\n", __func__, __LINE__);
			ret = -EINVAL;
			break;
		}
	}

	mutex_unlock(&pvr->pvr_mutex);
	return ret;
}

int mdrv_demux_pvr_set_strategy_mode(
	struct mdrv_demux_pvr *pvr,
	u16 path_id,
	enum pvr_data_trans_mode mode)
{
	int pvr_eng_id;
	struct mdrv_demux_tsp *tsp = container_of(pvr, struct mdrv_demux_tsp, pvr);
	struct mdrv_demux_dvb *dvb = container_of(tsp, struct mdrv_demux_dvb, tsp);
	struct dvb_vb2_ctx *ctx = &dvb->paths[path_id].dmxdev.parent.dvr_vb2_ctx;

	if (mode >= PVR_DATA_TRANS_MAX_MODE) {
		DEMUX_DEBUG_ERR("[%s][%d] mode out-of-range\n", __func__, __LINE__);
		return -EINVAL;
	}

	pvr_eng_id = mdrv_demux_pvr_alloc_engine(pvr, path_id);
	if (pvr_eng_id < 0) {
		DEMUX_DEBUG_ERR("[%s][%d] get pvr engine fail\n", __func__, __LINE__);
		return -EINVAL;
	}

	mutex_lock(&pvr->pvr_mutex);
	pvr->pvr_slot_map[pvr_eng_id].trans_mode = mode;
	mutex_unlock(&pvr->pvr_mutex);

	if (_pvr_select_strategy_if(pvr, path_id) < 0) {
		DEMUX_DEBUG_ERR("[%s][%d] select strategy mode fail\n", __func__, __LINE__);
		return -EFAULT;
	}

	return 0;
}

int mdrv_demux_pvr_set_strategy_type(
	struct mdrv_demux_pvr *pvr,
	u16 path_id,
	enum pvr_data_trans_type type)
{
	int pvr_eng_id;
	struct mdrv_demux_tsp *tsp = container_of(pvr, struct mdrv_demux_tsp, pvr);
	struct mdrv_demux_dvb *dvb = container_of(tsp, struct mdrv_demux_dvb, tsp);
	struct dvb_vb2_ctx *ctx = &dvb->paths[path_id].dmxdev.parent.dvr_vb2_ctx;

	if (type >= PVR_DATA_TRANS_MAX_TYPE) {
		DEMUX_DEBUG_ERR("[%s][%d] type out-of-range\n", __func__, __LINE__);
		return -EINVAL;
	}

	pvr_eng_id = mdrv_demux_pvr_alloc_engine(pvr, path_id);
	if (pvr_eng_id < 0) {
		DEMUX_DEBUG_ERR("[%s][%d] get pvr engine fail\n", __func__, __LINE__);
		return -EINVAL;
	}

	mutex_lock(&pvr->pvr_mutex);
	pvr->pvr_slot_map[pvr_eng_id].trans_type = type;
	mutex_unlock(&pvr->pvr_mutex);

	if (_pvr_select_strategy_if(pvr, path_id) < 0) {
		DEMUX_DEBUG_ERR("[%s][%d] select strategy type fail\n", __func__, __LINE__);
		return -EFAULT;
	}

	return 0;
}
