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

#include <linux/module.h>
#include <linux/poll.h>

#include "mtk_iommu_dtv_api.h"

#include "mtk_dmx_common.h"
#include "mtk_dmx_log.h"
#include "mtk_dmx_riu.h"
#include "mtk_dmx_rm.h"
#include "mtk_dmx_pm.h"
#include "mtk_dmx_bufctrl.h"
#include "mtk_dmx_drv.h"
#include "mtk_dmx_dvrdrv.h"
#include "mtk_dmx_dev.h"

#define MTK_BC_RINGBUF_PEAK_USAGE_LOG_STEP (5)

static u32 pvr_pause_threshold = MTK_BC_PVR_WATER_LEVEL_THRESHOLD;

int mtk_bc_get_data_buf_type(struct mtk_bc_ctx *ctx, enum mtk_bc_memtype *type)
{
	enum dmx2_memory_type mem_type;

	mem_type = ctx->d_ctx.buf.type;

	*type = (mem_type == DMX2_MEMORY_RINGBUF) ?
		MTK_BC_MEMTYPE_RINGBUF : MTK_BC_MEMTYPE_VB2;

	return 0;
}

int mtk_bc_ringbuf_init(struct mtk_bc_ctx *ctx, void *data, size_t len)
{
	struct dvb_ringbuffer *ringbuf = &ctx->d_ctx.buf.ring;

	dvb_ringbuffer_init(ringbuf, data, len);
	ctx->d_ctx.buf.type = DMX2_MEMORY_RINGBUF;

	return 0;
}

int mtk_bc_secure_ringbuf_init(struct mtk_bc_ctx *ctx, void *data, size_t len)
{
	struct dvb_ringbuffer *ringbuf = &ctx->d_ctx.secure_buf.ring;

	dvb_ringbuffer_init(ringbuf, data, len);
	ctx->d_ctx.buf.type = DMX2_MEMORY_RINGBUF;

	return 0;
}

int mtk_bc_ringbuf_free(struct mtk_bc_ctx *ctx)
{
	struct dvb_ringbuffer *ringbuf = &ctx->d_ctx.buf.ring;

	dvb_ringbuffer_init(ringbuf, NULL, 0);

	return 0;
}

int mtk_bc_secure_ringbuf_free(struct mtk_bc_ctx *ctx)
{
	struct dvb_ringbuffer *ringbuf = &ctx->d_ctx.secure_buf.ring;

	dvb_ringbuffer_init(ringbuf, NULL, 0);

	return 0;
}

int mtk_bc_ringbuf_read_user(
	struct mtk_bc_ctx *ctx,
	char __user *buf, size_t len,
	int non_blocking)
{
	int ret;
	size_t todo, consume = 0;
	ssize_t avail;
	struct dvb_ringbuffer *ringbuf = &ctx->d_ctx.buf.ring;

	if (!ringbuf->data)
		return 0;

	if (ringbuf->error) {
		ret = ringbuf->error;
		dvb_ringbuffer_flush(ringbuf);
		return ret;
	}

	for (todo = len; todo > 0; todo -= ret) {
		if (non_blocking && dvb_ringbuffer_empty(ringbuf)) {
			ret = -EWOULDBLOCK;
			break;
		}

		ret = wait_event_interruptible(ringbuf->queue,
					       !dvb_ringbuffer_empty(ringbuf) ||
					       (ringbuf->error != 0));
		if (ret < 0)
			break;

		if (ringbuf->error) {
			ret = ringbuf->error;
			dvb_ringbuffer_flush(ringbuf);
			break;
		}

		avail = dvb_ringbuffer_avail(ringbuf);
		if (avail > todo)
			avail = todo;

		ret = dvb_ringbuffer_read_user(ringbuf, buf, avail);
		if (ret < 0)
			break;

		buf += ret;
		consume += ret;
	}

	if (consume && ctx->d_ctx.cb)
		ctx->d_ctx.cb(ctx, ringbuf->pread);

	return (consume) ? consume : ret;
}

int mtk_bc_ringbuf_write_user(struct mtk_bc_ctx *ctx, const char __user *buf, size_t len)
{
	int ret;
	struct dvb_ringbuffer *ringbuf = &ctx->d_ctx.buf.ring;

	ret = dvb_ringbuffer_write_user(ringbuf, buf, len);

	return ret;
}

static int update_ringbuf_peak_usage(struct mtk_bc_ctx *ctx)
{
	struct dvb_ringbuffer *ringbuf = NULL;
	struct data_buf_ctx *d_ctx = NULL;
	u8 peak_usage = 0;

	if (ctx == NULL)
		return -EINVAL;
	d_ctx = &ctx->d_ctx;
	ringbuf = &d_ctx->buf.ring;

	if (ringbuf->size == 0)
		peak_usage = 0;
	else
		peak_usage =
			(ringbuf->size - dvb_ringbuffer_free(ringbuf)) *
			100 /
			ringbuf->size;

	if (peak_usage > d_ctx->peak_usage) {
		d_ctx->peak_usage = peak_usage;

		if ((d_ctx->peak_usage % MTK_BC_RINGBUF_PEAK_USAGE_LOG_STEP) == 0)
			MTK_DMX_INFO(
				"peak %u%%\n",
				d_ctx->peak_usage);
	}

	return 0;
}

int mtk_bc_ringbuf_write(struct mtk_bc_ctx *ctx, const u8 *buf, size_t len)
{
	ssize_t ret;
	struct dvb_ringbuffer *ringbuf = &ctx->d_ctx.buf.ring;

	if (!len)
		return 0;

	if (!ringbuf->data)
		return 0;

	if (len > dvb_ringbuffer_free(ringbuf)) {
		ringbuf->error = -EOVERFLOW;
		return -EOVERFLOW;
	}



	ret = dvb_ringbuffer_write(ringbuf, buf, len);
	if (update_ringbuf_peak_usage(ctx)) {
		MTK_DMX_DEBUG("fail to update peak usage\n");

		return -EFAULT;
	}
	wake_up(&ringbuf->queue);

	return ret;
}

int mtk_bc_ringbuf_flush(struct mtk_bc_ctx *ctx)
{
	struct dvb_ringbuffer *ringbuf = &ctx->d_ctx.buf.ring;

	dvb_ringbuffer_flush(ringbuf);

	return 0;
}

int mtk_bc_secure_ringbuf_flush(struct mtk_bc_ctx *ctx)
{
	struct dvb_ringbuffer *ringbuf = &ctx->d_ctx.secure_buf.ring;

	dvb_ringbuffer_flush(ringbuf);

	return 0;
}

int mtk_bc_ringbuf_reset(struct mtk_bc_ctx *ctx, bool clearErr)
{
	struct dvb_ringbuffer *ringbuf = &ctx->d_ctx.buf.ring;

	smp_store_release(&ringbuf->pread, 0);
	/* smp_store_release() to update write pointer */
	smp_store_release(&ringbuf->pwrite, 0);
	if (clearErr)
		ringbuf->error = 0;

	return 0;
}

int mtk_bc_secure_ringbuf_reset(struct mtk_bc_ctx *ctx, bool clearErr)
{
	struct dvb_ringbuffer *ringbuf = &ctx->d_ctx.secure_buf.ring;

	smp_store_release(&ringbuf->pread, 0);
	/* smp_store_release() to update write pointer */
	smp_store_release(&ringbuf->pwrite, 0);
	if (clearErr)
		ringbuf->error = 0;

	return 0;
}

__poll_t mtk_bc_ringbuf_poll(struct mtk_bc_ctx *ctx, struct file *file, poll_table *wait)
{
	struct dvb_ringbuffer *ringbuf = &ctx->d_ctx.buf.ring;
	__poll_t mask = 0;

	poll_wait(file, &ringbuf->queue, wait);

	if (ringbuf->error == -ESHUTDOWN)
		mask |= EPOLLERR | EPOLLHUP;
	else if (ringbuf->error)
		mask |= (EPOLLIN | EPOLLRDNORM | EPOLLPRI | EPOLLERR);

	if (!dvb_ringbuffer_empty(ringbuf))
		mask |= (EPOLLIN | EPOLLRDNORM | EPOLLPRI);

	return mask;
}

int mtk_bc_ringbuf_set_rptr_cb(struct mtk_bc_ctx *ctx, bc_set_rptr_cb cb)
{
	ctx->d_ctx.cb = cb;

	return 0;
}

static int check_ringbuf_overflow(struct dvb_ringbuffer *ringbuf, u64 wptr)
{
	ssize_t rptr, prev_wptr;

	/* smp_load_acquire() to load read pointer on writer side */
	rptr = smp_load_acquire(&ringbuf->pread);
	prev_wptr = ringbuf->pwrite;

	// out-of-range
	if (wptr < 0 || wptr >= ringbuf->size)
		goto FAIL;

	if ((prev_wptr >= rptr) && (wptr > rptr && wptr < prev_wptr))
		goto FAIL;

	if ((prev_wptr < rptr) && !(wptr >= prev_wptr && wptr < rptr))
		goto FAIL;

	return 0;

FAIL:
	ringbuf->error = -EOVERFLOW;
	return -EOVERFLOW;
}

int mtk_bc_ringbuf_set_water_level(u32 level)
{
	pvr_pause_threshold = level;

	return 0;
}

int mtk_bc_ringbuf_check_water_level(struct mtk_bc_ctx *ctx)
{
	struct dvb_ringbuffer *ringbuf = &ctx->d_ctx.buf.ring;

	ssize_t freespace = dvb_ringbuffer_free(ringbuf);

	if (freespace < pvr_pause_threshold)
	{
		MTK_DMX_ERR("pvr buf free space:%lld\n", (s64)freespace);
		goto FAIL;
	}

	return 0;

FAIL:
	ringbuf->error = -EOVERFLOW;
	return -EOVERFLOW;
}

int mtk_bc_ringbuf_set_wptr(struct mtk_bc_ctx *ctx, u64 wptr)
{
	struct dvb_ringbuffer *ringbuf = &ctx->d_ctx.buf.ring;

	if (check_ringbuf_overflow(ringbuf, wptr) < 0) {
		MTK_DMX_WARN("overflow\n");
		return -EFAULT;
	}

	spin_lock(&ringbuf->lock);
	/* smp_store_release() for write pointer update to ensure that
	 * written data is visible on other cpu cores before the pointer
	 * update
	 */
	smp_store_release(&ringbuf->pwrite, wptr);
	spin_unlock(&ringbuf->lock);

	wake_up(&ringbuf->queue);

	return 0;
}

int mtk_bc_secure_ringbuf_set_wptr(struct mtk_bc_ctx *ctx, ssize_t wptr)
{
	struct dvb_ringbuffer *ringbuf = &ctx->d_ctx.secure_buf.ring;

	if (check_ringbuf_overflow(ringbuf, wptr) < 0) {
		MTK_DMX_WARN("overflow\n");
		return -EFAULT;
	}

	spin_lock(&ringbuf->lock);
	/* smp_store_release() for write pointer update to ensure that
	 * written data is visible on other cpu cores before the pointer
	 * update
	 */
	smp_store_release(&ringbuf->pwrite, wptr);
	spin_unlock(&ringbuf->lock);

	wake_up(&ringbuf->queue);

	return 0;
}

int mtk_bc_ringbuf_set_rptr(struct mtk_bc_ctx *ctx, ssize_t rptr)
{
	struct dvb_ringbuffer *ringbuf = &ctx->d_ctx.buf.ring;

	if (rptr > ringbuf->size) {
		return -EFAULT;
	}

	spin_lock(&ringbuf->lock);
	/* smp_store_release() for write pointer update to ensure that
	 * written data is visible on other cpu cores before the pointer
	 * update
	 */
	smp_store_release(&ringbuf->pread, rptr);
	spin_unlock(&ringbuf->lock);

	wake_up(&ringbuf->queue);

	return 0;
}

int mtk_bc_secure_ringbuf_set_rptr(struct mtk_bc_ctx *ctx, ssize_t rptr)
{
	struct dvb_ringbuffer *ringbuf = &ctx->d_ctx.secure_buf.ring;

	if (rptr > ringbuf->size) {
		return -EFAULT;
	}

	spin_lock(&ringbuf->lock);
	/* smp_store_release() for write pointer update to ensure that
	 * written data is visible on other cpu cores before the pointer
	 * update
	 */
	smp_store_release(&ringbuf->pread, rptr);
	spin_unlock(&ringbuf->lock);

	wake_up(&ringbuf->queue);

	return 0;
}

u64 mtk_bc_ringbuf_get_wptr(struct mtk_bc_ctx *ctx)
{
	struct dvb_ringbuffer *ringbuf = &ctx->d_ctx.buf.ring;

	return ringbuf->pwrite;
}

int mtk_bc_ringbuf_is_initialized(struct mtk_bc_ctx *ctx)
{
	struct dvb_ringbuffer *ringbuf = &ctx->d_ctx.buf.ring;

	return ((ringbuf->data != NULL) && (ringbuf->size != 0));
}

int mtk_bc_secure_ringbuf_is_initialized(struct mtk_bc_ctx *ctx)
{
	struct dvb_ringbuffer *ringbuf = &ctx->d_ctx.secure_buf.ring;

	return ((ringbuf->data != NULL) && (ringbuf->size != 0));
}

int mtk_bc_ringbuf_available_to_read(struct mtk_bc_ctx *ctx)
{
	int buf_available_size = 0;

	if (mtk_bc_ringbuf_is_initialized(ctx)) {
		ssize_t buf_size = ctx->d_ctx.buf.ring.size;
		ssize_t buf_read_offset = ctx->d_ctx.buf.ring.pread;
		ssize_t buf_write_offset = ctx->d_ctx.buf.ring.pwrite;

		if (buf_write_offset >= buf_read_offset)
			buf_available_size = buf_write_offset - buf_read_offset;
		else
			buf_available_size = buf_size - (buf_read_offset - buf_write_offset);
	}

	return buf_available_size;
}

int mtk_bc_ringbuf_available_to_write(struct mtk_bc_ctx *ctx)
{
	int buf_available_size = 0;

	if (mtk_bc_ringbuf_is_initialized(ctx)) {
		ssize_t buf_size = ctx->d_ctx.buf.ring.size;
		ssize_t avail_read = mtk_bc_ringbuf_available_to_read(ctx);

		buf_available_size = buf_size - avail_read;
	}

	return buf_available_size;
}

int mtk_bc_secure_ringbuf_available_to_read(struct mtk_bc_ctx *ctx)
{
	int secure_buf_available_size = 0;

	if (mtk_bc_secure_ringbuf_is_initialized(ctx)) {
		ssize_t secure_buf_size = ctx->d_ctx.secure_buf.ring.size;
		ssize_t secure_buf_read_offset = ctx->d_ctx.secure_buf.ring.pread;
		ssize_t secure_buf_write_offset = ctx->d_ctx.secure_buf.ring.pwrite;

		if (secure_buf_write_offset >= secure_buf_read_offset)
			secure_buf_available_size =
				secure_buf_write_offset - secure_buf_read_offset;
		else
			secure_buf_available_size = secure_buf_size -
				(secure_buf_read_offset - secure_buf_write_offset);
	}

	return secure_buf_available_size;
}

int mtk_bc_vb2_init(struct mtk_bc_ctx *ctx, const char *name, int non_blocking, int wo_memcpy)
{
	int ret;
	struct dvb_fh *fh;
	struct dmx2dev *dmxdev = NULL;
	struct mtk_dmx2dev *mtkdev = NULL;
	struct dvb2_vb2_ctx *vb2_ctx = &ctx->d_ctx.buf.vb2;

	if (!strncmp(name, "demux2_filter", DVB2_VB2_NAME_MAX)) {
		struct mtk_demux2_fh_ctx *fh_dmxctx;
		struct mtk_demux2 *dmx2;

		fh_dmxctx = container_of(ctx, struct mtk_demux2_fh_ctx, bc_ctx);
		fh = fh_dmxctx_get_fh(fh_dmxctx);
		dmxdev = fh_to_dmx2dev(fh);
		vb2_ctx->mutex = &dmxdev->mutex;

		dmx2 = fh_to_demux(fh);
		mtkdev = container_of(dmx2->rm, struct mtk_dmx2dev, rm);
	} else if (!strncmp(name, "dvr2", DVB2_VB2_NAME_MAX)) {
		struct mtk_dvr2_fh_ctx *fh_dvrctx;
		struct mtk_dvr2 *dvr2;

		fh_dvrctx = container_of(ctx, struct mtk_dvr2_fh_ctx, bc_ctx);
		fh = fh_dvrctx_get_fh(fh_dvrctx);
		dmxdev = fh_to_dmx2dev(fh);
		vb2_ctx->mutex = &dmxdev->mutex;

		dvr2 = fh_to_dvr(fh);
		mtkdev = container_of(dvr2->rm, struct mtk_dmx2dev, rm);
	}

	ret = dvb2_vb2_init(vb2_ctx, name, non_blocking, wo_memcpy);
	if (ret < 0) {
		MTK_DMX_ERR("init fail\n");
		return -EFAULT;
	}

	if (!mtkdev) {
		MTK_DMX_ERR("mtkdev NULL, init fail\n");
		return -EFAULT;
	}

	ctx->d_ctx.buf.vb2.vb_q.dev = mtkdev->dev;
	ctx->d_ctx.buf.type = DMX2_MEMORY_VB2;
	return 0;
}

int mtk_bc_vb2_release(struct mtk_bc_ctx *ctx)
{
	int ret;
	struct dvb2_vb2_ctx *vb2_ctx = &ctx->d_ctx.buf.vb2;

	ret = dvb2_vb2_release(vb2_ctx);
	if (ret < 0) {
		MTK_DMX_ERR("release fail\n");
		return -EFAULT;
	}

	return 0;
}

int mtk_bc_vb2_reqbufs(struct mtk_bc_ctx *ctx, struct dmx2_requestbuffers *req)
{
	int ret;
	struct dvb2_vb2_ctx *vb2_ctx = &ctx->d_ctx.buf.vb2;

	ret = dvb2_vb2_reqbufs(vb2_ctx, req);
	if (ret < 0) {
		MTK_DMX_ERR("request buffers fail\n");
		return -EFAULT;
	}

	return 0;
}

int mtk_bc_vb2_querybuf(struct mtk_bc_ctx *ctx, struct dmx2_buffer *b)
{
	int ret;
	struct dvb2_vb2_ctx *vb2_ctx = &ctx->d_ctx.buf.vb2;

	ret = dvb2_vb2_querybuf(vb2_ctx, b);
	if (ret < 0) {
		MTK_DMX_ERR("query buffer fail\n");
		return -EFAULT;
	}

	return 0;
}

int mtk_bc_vb2_expbuf(struct mtk_bc_ctx *ctx, struct dmx2_exportbuffer *exp)
{
	int ret;
	struct dvb2_vb2_ctx *vb2_ctx = &ctx->d_ctx.buf.vb2;

	ret = dvb2_vb2_expbuf(vb2_ctx, exp);
	if (ret < 0) {
		MTK_DMX_ERR("export buffer fail\n");
		return -EFAULT;
	}

	return 0;
}

int mtk_bc_vb2_qbuf(struct mtk_bc_ctx *ctx, struct dmx2_buffer *b)
{
	int ret;
	struct dvb2_vb2_ctx *vb2_ctx = &ctx->d_ctx.buf.vb2;

	ret = dvb2_vb2_qbuf(vb2_ctx, b);
	if (ret < 0) {
		MTK_DMX_DEBUG("queue buffer fail\n");
		return ret;
	}

	// rec dma
	ctx->d_ctx.vb2_fd = b->m.fd;
	MTK_DMX_DEBUG("qbuf vb2_fd (%d)buffer\n", ctx->d_ctx.vb2_fd);

	/* count > 0: to determine whether we ever invoked dqbuf */
	if (vb2_ctx->count && ctx->d_ctx.cb) {
		u64 rptr;

		if (b->index == (vb2_ctx->buf_cnt - 1)) // wrap around
			rptr = 0;
		else
			rptr = ((u64)b->index + 1) * vb2_ctx->buf_siz;

		ret = ctx->d_ctx.cb(ctx, rptr);
	}

	return ret;
}

int mtk_bc_vb2_dqbuf(struct mtk_bc_ctx *ctx, struct dmx2_buffer *b)
{
	int ret;
	struct dvb2_vb2_ctx *vb2_ctx = &ctx->d_ctx.buf.vb2;

	ret = dvb2_vb2_dqbuf(vb2_ctx, b);
	if (ret < 0) {
		MTK_DMX_DEBUG("dequeue buffer fail\n");
		return ret;
	}

	return 0;
}

__poll_t mtk_bc_vb2_poll(struct mtk_bc_ctx *ctx, struct file *file, poll_table *wait)
{
	struct dvb2_vb2_ctx *vb2_ctx = &ctx->d_ctx.buf.vb2;

	return dvb2_vb2_poll(vb2_ctx, file, wait);
}

int mtk_bc_vb2_mmap(struct mtk_bc_ctx *ctx, struct vm_area_struct *vma)
{
	int ret;
	struct dvb2_vb2_ctx *vb2_ctx = &ctx->d_ctx.buf.vb2;

	ret = dvb2_vb2_mmap(vb2_ctx, vma);
	if (ret < 0) {
		MTK_DMX_ERR("mmap fail\n");
		return -EFAULT;
	}

	return 0;
}

int mtk_bc_vb2_is_streaming(struct mtk_bc_ctx *ctx)
{
	struct dvb2_vb2_ctx *vb2_ctx = &ctx->d_ctx.buf.vb2;

	return dvb2_vb2_is_streaming(vb2_ctx);
}

int mtk_bc_vb2_stream_on(struct mtk_bc_ctx *ctx)
{
	int ret;
	struct dvb2_vb2_ctx *vb2_ctx = &ctx->d_ctx.buf.vb2;

	ret = dvb2_vb2_stream_on(vb2_ctx);
	if (ret < 0) {
		MTK_DMX_ERR("stream on fail\n");
		return -EFAULT;
	}

	return 0;
}

int mtk_bc_vb2_stream_off(struct mtk_bc_ctx *ctx)
{
	int ret;
	struct dvb2_vb2_ctx *vb2_ctx = &ctx->d_ctx.buf.vb2;

	ret = dvb2_vb2_stream_off(vb2_ctx);
	if (ret < 0) {
		MTK_DMX_ERR("stream off fail\n");
		return -EFAULT;
	}

	return 0;
}

int mtk_bc_vb2_fill_buffer(struct mtk_bc_ctx *ctx, const u8 *src, int len)
{
	int ret;
	struct dvb2_vb2_ctx *vb2_ctx = &ctx->d_ctx.buf.vb2;

	ret = dvb2_vb2_fill_buffer(vb2_ctx, src, len, NULL);
	if (ret < 0) {
		MTK_DMX_ERR("fill buffer fail\n");
		return -EFAULT;
	}

	return 0;
}

bool mtk_bc_vb2_is_ringbuf_mode(struct mtk_bc_ctx *ctx)
{
	enum dmx2_memory_type mem_type = ctx->d_ctx.buf.type;
	struct dvb2_vb2_ctx *vb2_ctx = &ctx->d_ctx.buf.vb2;
	struct dvb2_buffer *buf;
	int prev_fd = -1;

	if (mem_type == DMX2_MEMORY_RINGBUF)
		return false;

	if (vb2_ctx->memory != VB2_MEMORY_DMABUF)
		return false;

	spin_lock(&vb2_ctx->slock);

	/* compare already queued buffers */
	list_for_each_entry(buf, &vb2_ctx->dvb_q, list) {
		if (prev_fd < 0) {
			prev_fd = buf->plane.m.fd;
			continue;
		}

		if (buf->plane.m.fd != prev_fd) {
			spin_unlock(&vb2_ctx->slock);
			return false;
		}

		prev_fd = buf->plane.m.fd;
	}

	spin_unlock(&vb2_ctx->slock);

	return true;
}

int mtk_bc_vb2_query_dmabuf(struct mtk_bc_ctx *ctx, struct mem_info *mem_info)
{
#define IOVA_BASE 0x200000000
	struct dvb2_vb2_ctx *vb2_ctx = &ctx->d_ctx.buf.vb2;
	struct dvb2_buffer *buf = NULL;
	int dmabuf_fd;
	unsigned long long pa;
	unsigned int size;
	struct dma_buf_attachment *db_att;
	struct sg_table *sgt;
	struct dma_buf *db;

	if (!mtk_bc_vb2_is_ringbuf_mode(ctx)) {
		MTK_DMX_ERR("Only support vb2 ringbuffer mode\n");
		return -EINVAL;
	}

	if (list_empty(&vb2_ctx->dvb_q)) {
		MTK_DMX_ERR("dvb_q list is empty\n");
		return -EFAULT;
	}

	buf = list_first_entry(&vb2_ctx->dvb_q, struct dvb2_buffer, list);
	if (!buf) {
		MTK_DMX_ERR("Can't find 1st entry of dvb_q\n");
		return -EFAULT;
	}

	dmabuf_fd = buf->plane.m.fd;

	db = dma_buf_get(dmabuf_fd);
	db_att = dma_buf_attach(db, vb2_ctx->vb_q.dev);
	sgt = dma_buf_map_attachment(db_att, DMA_BIDIRECTIONAL);

	if (!sgt) {
		MTK_DMX_ERR("dma_buf_map_attachment failed\n");
		return -EFAULT;
	}
	pa = sg_dma_address(sgt->sgl);
	size = sg_dma_len(sgt->sgl);

	dma_buf_unmap_attachment(db_att, sgt, DMA_BIDIRECTIONAL);
	dma_buf_detach(db, db_att);
	dma_buf_put(db);

	memset(mem_info, 0, sizeof(*mem_info));
	mem_info->paddr = (pa >= IOVA_BASE) ? pa : BA_to_PA(pa);
	mem_info->size = size;
	mem_info->heaptype = (pa >= IOVA_BASE) ? HEAP_IOMMU : HEAP_CMA;
	mem_info->dmabuf_fd = dmabuf_fd;

	MTK_DMX_INFO("Buffer PA: %llX, size: %X\n", mem_info->paddr, mem_info->size);

	return 0;
#undef IOVA_BASE
}

int mtk_bc_vb2_set_rptr_cb(struct mtk_bc_ctx *ctx, bc_set_rptr_cb cb)
{
	ctx->d_ctx.cb = cb;

	return 0;
}

int mtk_bc_vb2_set_wptr(struct mtk_bc_ctx *ctx, u64 wptr)
{
	int ret;
	struct dvb2_vb2_ctx *vb2_ctx = &ctx->d_ctx.buf.vb2;
	int offset;
	u64 prev_wptr, end, index;
	int len;

	spin_lock(&vb2_ctx->slock);

	if (vb2_ctx->buf) {
		index = vb2_ctx->buf->vb.index;
		offset = vb2_ctx->offset;
	} else if (!list_empty(&vb2_ctx->dvb_q)) {
		struct dvb2_buffer *buf =
			list_entry(vb2_ctx->dvb_q.next, struct dvb2_buffer, list);

		index = buf->vb.index;
		offset = 0;
	} else {
		MTK_DMX_ERR("dvb_q is empty\n");
		spin_unlock(&vb2_ctx->slock);
		return -EFAULT;
	}

	prev_wptr = (index * vb2_ctx->buf_siz) + offset;
	end = (u64)(vb2_ctx->buf_cnt) * vb2_ctx->buf_siz;

	spin_unlock(&vb2_ctx->slock);

	if (wptr >= prev_wptr)
		len = wptr - prev_wptr;
	else
		len = (end - prev_wptr) + wptr;

	ret = dvb2_vb2_fill_buffer(vb2_ctx, NULL, len, NULL);

	return ret;
}

int mtk_bc_metabuf_init(struct mtk_bc_ctx *ctx, u32 size, u32 num)
{
	int ret;
	struct metadata_buf_ctx *m_ctx = &ctx->m_ctx;

	ret = kfifo_alloc(&m_ctx->kfifo, (size * num), GFP_KERNEL);
	if (ret < 0) {
		MTK_DMX_ERR("kfifo_alloc() fail\n");
		return -EFAULT;
	}

	m_ctx->size = size;
	m_ctx->num = num;
	m_ctx->navailable = 0;
	m_ctx->inited = true;
	mutex_init(&m_ctx->mutex);
	init_waitqueue_head(&m_ctx->wait);

	return 0;
}

int mtk_bc_metabuf_release(struct mtk_bc_ctx *ctx)
{
	struct metadata_buf_ctx *m_ctx = &ctx->m_ctx;

	if (m_ctx == NULL) {
		MTK_DMX_ERR("m_ctx is NULL\n");

		return -EINVAL;
	}
	// TODO: Mutex cannot be used here if ctx is not inited.
	if (!m_ctx->inited)
		return 0;

	// TODO: race condition for m_ctx->inited
	mutex_lock(&m_ctx->mutex);

	kfifo_free(&m_ctx->kfifo);

	m_ctx->size = 0;
	m_ctx->num = 0;
	m_ctx->inited = false;

	mutex_unlock(&m_ctx->mutex);
	return 0;
}

static int _is_metadata_available(struct metadata_buf_ctx *m_ctx)
{
	int ret = 0;

	if (kfifo_avail(&m_ctx->kfifo) < m_ctx->size) {
		MTK_DMX_INFO("no free memory\n");

		ret = -ENOMEM;
	}

	return ret;
}

int mtk_bc_metabuf_avail(struct mtk_bc_ctx *ctx)
{
	int ret = 0;
	struct metadata_buf_ctx *m_ctx = &ctx->m_ctx;

	if (m_ctx == NULL) {
		MTK_DMX_ERR("m_ctx is NULL\n");

		return -EINVAL;
	}
	// TODO: Mutex cannot be used here if ctx is not inited.
	if (!m_ctx->inited) {
		MTK_DMX_ERR("not yet inited\n");

		return -EINVAL;
	}

	// TODO: race condition for m_ctx->inited
	mutex_lock(&m_ctx->mutex);

	ret = _is_metadata_available(m_ctx);

	mutex_unlock(&m_ctx->mutex);

	return ret;
}

int mtk_bc_metabuf_queue(struct mtk_bc_ctx *ctx, void *meta)
{
	int ret = 0;
	struct metadata_buf_ctx *m_ctx = &ctx->m_ctx;

	if (m_ctx == NULL) {
		MTK_DMX_ERR("m_ctx is NULL\n");

		return -EINVAL;
	}
	// TODO: Mutex cannot be used here if ctx is not inited.
	if (!m_ctx->inited) {
		MTK_DMX_ERR("not yet inited\n");

		return -EINVAL;
	}
	if (meta == NULL) {
		MTK_DMX_ERR("meta is NULL\n");

		return -EINVAL;
	}

	// TODO: race condition for m_ctx->inited
	mutex_lock(&m_ctx->mutex);

	ret = _is_metadata_available(m_ctx);
	if (ret != 0)  {
		MTK_DMX_ERR("no available memory\n");
		ret = -ENOMEM;
		goto exit;
	}

	ret = kfifo_in(&m_ctx->kfifo, meta, m_ctx->size);
	if (ret != m_ctx->size)  {
		MTK_DMX_ERR("fail to queue\n");
		ret = -ENOMEM;
		goto exit;
	}

	m_ctx->navailable++;
	wake_up_all(&m_ctx->wait);

exit:
	mutex_unlock(&m_ctx->mutex);
	return ret;
}

static int _dequeue_metadata(struct metadata_buf_ctx *m_ctx, void *meta)
{
	int ret = 0;

	// TODO: Mutex cannot be used here if ctx is not inited.
	if (!m_ctx->inited) {
		MTK_DMX_ERR("not yet inited\n");
		return -EINVAL;
	}

	// TODO: race condition for m_ctx->inited
	mutex_lock(&m_ctx->mutex);

	if (kfifo_len(&m_ctx->kfifo) < m_ctx->size) {
		MTK_DMX_DEBUG("no available memory\n");
		ret = -ENOMEM;
		goto exit;
	}

	ret = kfifo_out(&m_ctx->kfifo, meta, m_ctx->size);
	m_ctx->navailable--;
exit:
	mutex_unlock(&m_ctx->mutex);
	return ret;
}

int mtk_bc_metabuf_dequeue(struct mtk_bc_ctx *ctx, void *meta, int non_blocking)
{
	int ret = 0;
	struct metadata_buf_ctx *m_ctx = &ctx->m_ctx;

	if (m_ctx == NULL) {
		MTK_DMX_ERR("m_ctx is NULL\n");

		return -EINVAL;
	}
	// TODO: Mutex cannot be used here if ctx is not inited.
	if (!m_ctx->inited) {
		MTK_DMX_ERR("not yet inited\n");
		return -EINVAL;
	}
	if (meta == NULL) {
		MTK_DMX_ERR("meta is NULL\n");

		return -EINVAL;
	}

	if (non_blocking)
		return _dequeue_metadata(m_ctx, meta);
	do {
		// TODO: Mutex cannot be used here if ctx is not inited.
		if (!m_ctx->inited) {
			MTK_DMX_ERR("not yet inited\n");
			ret = -EINVAL;
			break;
		}

		ret = wait_event_interruptible(
			m_ctx->wait,
			m_ctx->navailable != 0 || !m_ctx->inited);
		if (ret < 0)
			break;

		ret = _dequeue_metadata(m_ctx, meta);
	} while (ret == -ENOMEM);

	return ret;
}

int mtk_bc_metabuf_reset(struct mtk_bc_ctx *ctx)
{
	struct metadata_buf_ctx *m_ctx = &ctx->m_ctx;

	if (m_ctx == NULL) {
		MTK_DMX_ERR("m_ctx is NULL\n");

		return -EINVAL;
	}
	// TODO: Mutex cannot be used here if ctx is not inited.
	if (!m_ctx->inited) {
		MTK_DMX_ERR("not yet inited\n");

		return -EINVAL;
	}

	// TODO: race condition for m_ctx->inited
	mutex_lock(&m_ctx->mutex);

	kfifo_reset(&m_ctx->kfifo);
	m_ctx->navailable = 0;

	mutex_unlock(&m_ctx->mutex);

	return 0;
}

int mtk_bc_set_buf_size(struct mtk_bc_ctx *ctx, u32 buf_size)
{
	struct data_buf_ctx *d_ctx = NULL;

	if (MTK_DMX_VERIFY(ctx == NULL))
		return -EINVAL;

	d_ctx = &ctx->d_ctx;
	if (buf_size == d_ctx->buf_size) {
		MTK_DMX_DEBUG("buf size is not changed\n");

		return 0;
	}

	d_ctx->buf_size = buf_size;

	return 0;
}

int mtk_bc_get_buf_size(struct mtk_bc_ctx *ctx, u32 *buf_size)
{
	struct data_buf_ctx *d_ctx = NULL;

	if (MTK_DMX_VERIFY(ctx == NULL))
		return -EINVAL;

	d_ctx = &ctx->d_ctx;
	*buf_size = d_ctx->buf_size;

	return 0;
}

int mtk_bc_reset_mem_info(struct mtk_bc_ctx *ctx)
{
	struct data_buf_ctx *d_ctx = NULL;
	static struct mem_info default_mem_info = {
		.vaddr = NULL,
		.paddr = 0,
		.size = 0,
		.heaptype = HEAP_NONE,
		.id = -1,
		.dmabuf_fd = -1,
		.db = NULL,
		.buf_type = MMAP_BUF_TYPE_MAX,
	};
	int i = 0;

	if (MTK_DMX_VERIFY(ctx == NULL))
		return -EINVAL;

	d_ctx = &ctx->d_ctx;
	for (i = MEM_INFO_TYPE_MIN; i < MEM_INFO_TYPE_MAX; i++)
		memcpy(&d_ctx->mem_info[i],
			&default_mem_info,
			sizeof(default_mem_info));

	return 0;
}

int mtk_bc_set_mem_info(
		struct mtk_bc_ctx *ctx,
		enum mem_info_type mem_info_type,
		struct mem_info *mem_info)
{
	struct data_buf_ctx *d_ctx = NULL;

	if (MTK_DMX_VERIFY(ctx == NULL))
		return -EINVAL;
	if (MTK_DMX_VERIFY(mem_info_type < MEM_INFO_TYPE_MIN))
		return -EINVAL;
	if (MTK_DMX_VERIFY(mem_info_type >= MEM_INFO_TYPE_MAX))
		return -EINVAL;
	if (MTK_DMX_VERIFY(mem_info == NULL))
		return -EINVAL;

	d_ctx = &ctx->d_ctx;
	memcpy(&d_ctx->mem_info[mem_info_type],
		mem_info,
		sizeof(*mem_info));

	return 0;
}

int mtk_bc_get_mem_info(
		struct mtk_bc_ctx *ctx,
		enum mem_info_type mem_info_type,
		struct mem_info *mem_info)
{
	struct data_buf_ctx *d_ctx = NULL;

	if (MTK_DMX_VERIFY(ctx == NULL))
		return -EINVAL;
	if (MTK_DMX_VERIFY(mem_info_type < MEM_INFO_TYPE_MIN))
		return -EINVAL;
	if (MTK_DMX_VERIFY(mem_info_type >= MEM_INFO_TYPE_MAX))
		return -EINVAL;
	if (MTK_DMX_VERIFY(mem_info == NULL))
		return -EINVAL;

	d_ctx = &ctx->d_ctx;
	memcpy(mem_info,
		&d_ctx->mem_info[mem_info_type],
		sizeof(*mem_info));

	return 0;
}

int mtk_bc_get_ctx(struct dvb_fh *fh, struct mtk_bc_ctx **bc_ctx)
{
	struct mtk_demux2_fh_ctx *mtk_demux2_fh_ctx = NULL;
	struct mtk_dvr2_fh_ctx *mtk_dvr2_fh_ctx = NULL;

	if (MTK_DMX_VERIFY(fh == NULL))
		return -EINVAL;
	if (MTK_DMX_VERIFY(bc_ctx == NULL))
		return -EINVAL;

	switch (fh->type) {
	case DVB_DEVICE_DEMUX2:
		mtk_demux2_fh_ctx = fh_to_dmxctx(fh);
		*bc_ctx = fh_dmxctx_get_bc_ctx(mtk_demux2_fh_ctx);
		break;

	case DVB_DEVICE_DVR2:
		mtk_dvr2_fh_ctx = fh_to_dvrctx(fh);
		*bc_ctx = fh_dvrctx_get_bc_ctx(mtk_dvr2_fh_ctx);
		break;

	default:
		MTK_DMX_ERR(
			"fh->type 0x%x is not supported\n",
			fh->type);

		return -EINVAL;
	}

	return 0;
}
