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

#include <linux/version.h>
#include <linux/module.h>
#include <linux/platform_device.h>
#include <media/dvbdev.h>

#include "mdrv_adapter.h"

#include "mtk_dmx_common.h"
#include "mtk_dmx_dev.h"
#include "mtk_dmx_rm.h"
#include "mtk_dmx_pm.h"
#include "mtk_dmx_mediactrl.h"
#include "mtk_dmx_tso.h"
#include "mtk_dmx_io.h"

static int mtk_tso_dvr_open(struct inode *inode, struct file *file);
static int mtk_tso_dvr_release(struct inode *inode, struct file *file);
static ssize_t mtk_tso_dvr_write(struct file *file, const char __user *buf,
	size_t count, loff_t *ppos);

struct tso_private {
	struct mtk_dmx2dev *dmxdev;
	struct dvb_device *dvbdev;
	struct dvb_device *dvr_dvbdev;
	struct dvbdev_notifier *notifier;
	bool streaming_status;
	struct mem_info *svqmap;
	atomic_t refcnt;
	u32 svq_size;

	bool set;
	struct mtk_msource_settings src;
	bool cilink_en;
	bool cifpga_en;
};

static const struct file_operations dvb_tso_fops = {
	.owner = THIS_MODULE,
};

static const struct file_operations dvr_tso_fops = {
	.owner = THIS_MODULE,
	.open = mtk_tso_dvr_open,
	.release = mtk_tso_dvr_release,
	.write = mtk_tso_dvr_write,
};

static const struct dvb_device dvbdev_tso = {
	.priv = NULL,
	.users = 1,
	.readers = 1,
	.writers = 1,
#if defined(CONFIG_MEDIA_CONTROLLER_DVB) || defined(CONFIG_MTK_MEDIA_CONTROLLER_DVB)
	.name = "dvb-mtk-tso",
#endif
	.fops = &dvb_tso_fops,
};


static const struct dvb_device dvr_dvbdev_tso = {
	.priv = NULL,
	.users = 1,
	.readers = 1,
	.writers = 1,
#if defined(CONFIG_MEDIA_CONTROLLER_DVB) || defined(CONFIG_MTK_MEDIA_CONTROLLER_DVB)
	.name = "dvb-dvr-mtk-tso",
#endif
	.fops = &dvr_tso_fops,
};

static struct tso_private *tso_priv;

static int tsout_postlink_notify(struct dvb_device *dev,
	struct dvbdev_link *link)
{
	struct tso_private *priv = ((struct tso_private *)(dev->priv));

	if ((link->state == LINK_STATE_DISCONNECT) && (link->end == LINK_END_SINK)) {
		MTK_DMX_INFO("Clear TSO private settings\n");
		priv->set = false;
	}

	return 0;
}

static int tsout_link_notify(struct dvb_device *dev,
	struct dvbdev_link *link)
{
	struct mtk_dmx2dev *mtkdev = ((struct tso_private *)(dev->priv))->dmxdev;
	struct mtk_dmx_rm *res = &mtkdev->rm;
	u16 ts_num = mtk_dmx_rm_get_hwcap_by_id(res, HWCAPS_TYPE_LIVEIN);
	u16 tlv_num = mtk_dmx_rm_get_hwcap_by_id(res, HWCAPS_TYPE_TLV);
	u16 alp_num = mtk_dmx_rm_get_hwcap_by_id(res, HWCAPS_TYPE_ALP);
	u16 svq_num = mtk_dmx_rm_get_hwcap_by_id(res, HWCAPS_TYPE_SVQ);
	u16 tso_num = mtk_dmx_rm_get_hwcap_by_id(res, HWCAPS_TYPE_TSO);
	u32 size = ((struct tso_private *)(dev->priv))->svq_size;
	u16 i, eng;
	struct tso_private *priv = ((struct tso_private *)(dev->priv));
	int ret = 0;

	eng = (dev->id - (ts_num + tlv_num + alp_num));
	if (link->state == LINK_STATE_CONNECT) {
		mtk_dmx_pm_open(&mtkdev->pm);
		if (size && (atomic_fetch_inc(&priv->refcnt) == 0)) {
			for (i = 0; i < (svq_num / tso_num); i++) {
				priv->svqmap[i].no_mapping = true;
				ret = mtk_dmx_rm_get_buf(res, MMAP_BUF_TYPE_TSO_FILEIN,
					size, &priv->svqmap[i]);
				if (ret < 0) {
					MTK_DMX_ERR("get buf fail\n");
					return -ENOMEM;
				}
				mtk_dmx_io_init_svq(eng, i, priv->svqmap[i].paddr,
					priv->svqmap[i].size, true);
			}
		}
	} else if (link->state == LINK_STATE_DISCONNECT) {
		if (size && atomic_dec_and_test(&priv->refcnt)) {
			for (i = 0; i < (svq_num / tso_num); i++) {
				mtk_dmx_io_init_svq(eng, i, 0, 0, false);
				mtk_dmx_rm_put_buf(res, &priv->svqmap[i]);
			}
		}
		mtk_dmx_pm_close(&mtkdev->pm);
	}
	return 0;
}

static int tsout_query_notify(struct dvb_device *dev,
	struct dvbdev_properties *props)
{
	int i = 0;
	struct dvbdev_property *prop;
	struct dvb_device *tso_dvbdvr;
	bool status = false;

	for (i = 0; i < props->num; i++) {
		prop = &props->props[i];
		switch (prop->cmd) {
		case ADAP_CMD_FILEIN_STREAMING_ON:
			tso_dvbdvr = mdrv_adapter_find_device(DVB_DEVICE_DVR2, dev->id);
			if (tso_dvbdvr)
				status = ((struct tso_private *)(tso_dvbdvr->priv))->streaming_status;
			if (status)
				prop->result = 1;
			else
				prop->result = 0;
			break;
		default:
			break;
		}
	}
	return 0;
}

static bool _is_tso_same_setting(struct tso_private *tso_private,
	struct mtk_msource_settings *src, bool cilink_en, bool cifpga_en)
{
	if (tso_private->set &&
		src->input_type == tso_private->src.input_type &&
		src->port_type == tso_private->src.port_type &&
		src->port_num == tso_private->src.port_num &&
		cilink_en == tso_private->cilink_en &&
		cifpga_en == tso_private->cifpga_en) {
		MTK_DMX_INFO("TSO setting is same as before, skip\n");
		return true;
	}

	tso_private->set = true;
	tso_private->src.input_type = src->input_type;
	tso_private->src.port_type = src->port_type;
	tso_private->src.port_num = src->port_num;
	tso_private->cilink_en = cilink_en;
	tso_private->cifpga_en = cifpga_en;

	return false;
}

static int tsout_streaming_event(struct dvb_device *dev,
	enum cfm_notify_event_type_t ev_type,
	struct cfm_properties *props, void *notify_data)
{
	int i, ret, eng;
	struct dvb_frontend *fe = (struct dvb_frontend *)notify_data;
	u32 data, bits = 0;
	struct cfm_properties p;
	struct mtk_msource_settings src;
	u8 pin[32];
	bool ext_ts = false, file_in = false;
	struct mtk_dmx2dev *mtkdev = NULL;
	u16 ts_num = 0;
	u16 tlv_num = 0;
	u16 alp_num = 0;
	struct media_pad *pad;
	bool cilink_en = false;
	bool cifpga_en = false;
	struct tso_private *tso_private = NULL;
	bool bUseCi2Clk = false;
	u32 rateKHz;

	if (dev == NULL)
		return -EINVAL;
	tso_private = (struct tso_private *)(dev->priv);
	if (tso_private == NULL)
		return -EINVAL;
	mtkdev = tso_private->dmxdev;
	if (mtkdev == NULL)
		return -EINVAL;

	ts_num = mtk_dmx_rm_get_hwcap_by_id(&mtkdev->rm, HWCAPS_TYPE_LIVEIN);
	tlv_num = mtk_dmx_rm_get_hwcap_by_id(&mtkdev->rm, HWCAPS_TYPE_TLV);
	alp_num = mtk_dmx_rm_get_hwcap_by_id(&mtkdev->rm, HWCAPS_TYPE_ALP);

	memset(&src, 0, sizeof(struct mtk_msource_settings));
	for (i = 0; i < props->num; i++)
		switch (props->props[i].cmd) {
		case CFM_STREAM_CLOCK_RATE:
			rateKHz = props->props[i].u.data / 1000UL;
			MTK_DMX_INFO("ts rate: %d KHz\n", rateKHz);

			eng = (dev->id - (ts_num + tlv_num + alp_num));
			pad = mtk_mc_remote_pad(&dev->entity->pads[0]);
			if (pad && is_ts_device(pad->entity)) {
				pad = mtk_mc_remote_pad(&pad->entity->pads[0]);
				if (pad && is_ci2_device(pad->entity)) {
					bUseCi2Clk = true;
					MTK_DMX_INFO("use ci2cam tso clock\n");
				}
			}
			if (pad && is_cifpga_device(pad->entity))
				cifpga_en = true;
			ret = mtk_mc_set_tso_output_source(
				eng,
				rateKHz,
				bUseCi2Clk,
				(cifpga_en ? false : true),
				(cifpga_en ? true : false));
			if (ret < 0) {
				MTK_DMX_ERR("mtk_dmx_tso_out_config fail\n");
			}
			break;
		case CFM_STREAMING_STATUS:
			data = props->props[i].u.data;
			if (data) {
				if (!fe) {
					MTK_DMX_INFO("file-in start\n");
					bits = 8;
					ext_ts = false;
					file_in = true;
				} else {
					MTK_DMX_INFO("demod locked\n");
					p.num = 2;
					p.props = kzalloc(sizeof(struct cfm_property) * p.num, GFP_KERNEL);
					if (p.props) {
						p.props[0].cmd = CFM_STREAM_DATA_BITS_NUM;
						p.props[1].cmd = CFM_STREAM_PIN_MUX_INFO;
						cfm_get_demod_property(fe, &p);
						bits = p.props[0].u.data;
						memset(&pin, 0, sizeof(pin));
						memcpy(&pin, p.props[1].u.buffer.data, (sizeof(pin) - 1));
						kfree(p.props);
						if (!strcmp(pin, "internal_pad")) {
							ext_ts = false;
							src.port_num = 0;
						} else if (!strncmp(pin, "ext_ts_", strlen("ext_ts_"))) {
							ext_ts = true;
							src.port_num = 0;
						} else if (!strncmp(pin, "ext1_ts_", strlen("ext1_ts_"))) {
							ext_ts = true;
							src.port_num = 1;
						} else if (!strncmp(pin, "ext2_ts_", strlen("ext2_ts_"))) {
							ext_ts = true;
							src.port_num = 2;
						} else if (!strncmp(pin, "ext3_ts_", strlen("ext3_ts_"))) {
							ext_ts = true;
							src.port_num = 3;
						} else {
							MTK_DMX_ERR("cannot find pin: %s\n", pin);
							return -EINVAL;
						}
						MTK_DMX_INFO("bits: %d pin: %s\n", bits, pin);
					} else
						return -ENOMEM;
				}
				src.port_type = (bits == 2) ? DMX_MC_PORT_2BITS_SERIAL :
					(bits == 1) ? DMX_MC_PORT_SERIAL : DMX_MC_PORT_PARALLEL;
				src.input_type = (file_in) ? DMX_MC_INPUT_DRAM :
					(ext_ts) ? DMX_MC_INPUT_EXTERNAL_FE :
					DMX_MC_INPUT_INTERNAL_FE;
				eng = (dev->id - (ts_num + tlv_num + alp_num));

				pad = mtk_mc_remote_pad(&dev->entity->pads[0]);
				if (pad && is_ts_device(pad->entity)) {
					pad = mtk_mc_remote_pad(&pad->entity->pads[0]);
					if (pad && is_ci2_device(pad->entity)) {
						MTK_DMX_INFO("use ci2cam tso clock\n");
						bUseCi2Clk = true;
					}
				}

				if (pad) {
					cilink_en = is_ci_device(pad->entity);
					cifpga_en = is_cifpga_device(pad->entity);
				}

				if (_is_tso_same_setting(tso_private, &src, cilink_en, cifpga_en))
					return 0;

				ret = mtk_mc_set_tso_input_source(&mtkdev->rm, eng, &src, pin,
					cilink_en, cifpga_en);
				if (ret < 0)
					MTK_DMX_ERR("set input source fail\n");
				if (file_in) {
					ret = mtk_mc_set_tso_output_source(eng, 0,
						bUseCi2Clk,
						(cifpga_en ? false : true),
						(cifpga_en ? true : false));
				if (ret < 0)
					MTK_DMX_ERR("set tso_out_config fail\n");
				}
			} else {
				MTK_DMX_INFO("demod unlocked\n");
				tso_private->set = false;
			}
			break;
		}

	return 0;
}

static int mtk_tso_dvr_open(struct inode *inode, struct file *file)
{
	struct dvb_device *dvbdev = file->private_data;
	struct mtk_dmx2dev *mtkdev = ((struct tso_private *)(dvbdev->priv))->dmxdev;

	mtk_dmx_pm_open(&mtkdev->pm);
	return 0;
}

static int mtk_tso_dvr_release(struct inode *inode, struct file *file)
{
	struct dvb_device *dvbdev = file->private_data;
	struct mtk_dmx2dev *mtkdev = ((struct tso_private *)(dvbdev->priv))->dmxdev;

	mtk_dmx_pm_close(&mtkdev->pm);
	return 0;
}

static ssize_t mtk_tso_dvr_write(struct file *file, const char __user *buf,
	size_t count, loff_t *ppos)
{
	struct dvb_device *dvbdev = file->private_data;
	struct dvb_device *tso_dvbdev;
	struct mtk_dmx2dev *mtkdev = ((struct tso_private *)(dvbdev->priv))->dmxdev;
	struct mtk_dmx_rm *res = &mtkdev->rm;
	int ret = 0, idx;
	struct mem_info info = { 0 };
	void *parg = NULL;
	u16 ts_num = mtk_dmx_rm_get_hwcap_by_id(res, HWCAPS_TYPE_LIVEIN);
	u16 tlv_num = mtk_dmx_rm_get_hwcap_by_id(res, HWCAPS_TYPE_TLV);
	u16 alp_num = mtk_dmx_rm_get_hwcap_by_id(res, HWCAPS_TYPE_ALP);
	bool *status = &(((struct tso_private *)(dvbdev->priv))->streaming_status);
	struct dvbdev_properties props = { 0 };
	struct dvbdev_property prop = { 0 };

	ret = mtk_dmx_rm_get_buf(res, MMAP_BUF_TYPE_TSO_FILEIN,
		count, &info);
	if (ret < 0) {
		MTK_DMX_ERR("get buf fail\n");
		return -ENOMEM;
	}

	parg = info.vaddr;
	if (!parg)
		goto out;
	if (copy_from_user(parg, (void __user *)buf, count)) {
		MTK_DMX_ERR("fail to copy from user\n");
		ret = -EFAULT;
		goto out;
	}
	// cache flush if cma
	if (info.heaptype == HEAP_CMA)
		mtk_dmx_rm_flush_buf(res, &info);

	*status = true;
	tso_dvbdev = mdrv_adapter_find_device(DVB_DEVICE_DEMUX2, dvbdev->id);
	if (!tso_dvbdev)
		goto out;
	prop.cmd = ADAP_CMD_FILEIN_STREAMING_ON;
	props.num = 1;
	props.props = &prop;
	mdrv_adapter_query_device_params(tso_dvbdev, &props);

	idx = (dvbdev->id - (ts_num + tlv_num + alp_num));
	mtk_dmx_tso_feed(idx, info.paddr, info.size);

out:
	mtk_dmx_rm_put_buf(res, &info);
	*status = false;
	return ret;
}

int mtk_dmx_tsodev_init(struct mtk_dmx2dev *dmxdev)
{
	struct platform_device *pdev = to_platform_device(dmxdev->dev);
	struct dvb_adapter *adapter = mdrv_get_dvb_adapter();
	int i, ret = 0;
	u16 tso_num = mtk_dmx_rm_get_hwcap_by_id(&dmxdev->rm, HWCAPS_TYPE_TSO);
	u16 svq_num = mtk_dmx_rm_get_hwcap_by_id(&dmxdev->rm, HWCAPS_TYPE_SVQ);
	struct device_node *np, *np_tso;
	u32 svq_size = 0;

	if (adapter == NULL)
		return -EFAULT;

	tso_priv = devm_kzalloc(&pdev->dev, sizeof(struct tso_private) * tso_num,
		GFP_KERNEL);
	if (!tso_priv)
		return -ENOMEM;

	np = of_find_node_by_name(pdev->dev.of_node, "ip_configs");
	if (np) {
		np_tso = of_find_node_by_name(pdev->dev.of_node, "tso");
		of_property_read_u32(np_tso, "svq_size", &svq_size);
		of_node_put(np_tso);

		MTK_DMX_DEBUG("svq_size 0x%x\n", svq_size);
	}
	of_node_put(np);

	for (i = 0; i < tso_num; i++, tso_priv++) {
		tso_priv->dmxdev = dmxdev;
		ret = dvb_register_device(adapter, &tso_priv->dvbdev, &dvbdev_tso,
			tso_priv, DVB_DEVICE_DEMUX2, 1);
		if (ret != 0) {
			MTK_DMX_ERR("register dvb device fail\n");
			return -ENODEV;
		}
		ret = dvb_register_device(adapter, &tso_priv->dvr_dvbdev, &dvr_dvbdev_tso,
			tso_priv, DVB_DEVICE_DVR2, 1);
		if (ret != 0) {
			MTK_DMX_ERR("register dvr device fail\n");
			return -ENODEV;
		}
		tso_priv->notifier = devm_kzalloc(&pdev->dev,
			sizeof(struct dvbdev_notifier), GFP_KERNEL);
		if (tso_priv->notifier) {
			tso_priv->notifier->postlink_notify = &tsout_postlink_notify;
			tso_priv->notifier->link_notify = &tsout_link_notify;
			tso_priv->notifier->query_notify = &tsout_query_notify;
			tso_priv->notifier->streaming_event_notify = &tsout_streaming_event;
			ret = mdrv_adapter_register_device_notifier(tso_priv->dvbdev, tso_priv->notifier);
			if (ret < 0)
				MTK_DMX_ERR("register notifier fail\n");
		}
		tso_priv->svqmap = devm_kzalloc(&pdev->dev,
			sizeof(struct mem_info) * (svq_num / tso_num), GFP_KERNEL);
		atomic_set(&tso_priv->refcnt, 0);
		tso_priv->svq_size = svq_size;
	}
	return 0;
}
