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

#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/interrupt.h>

#include "mdrv_demux_common.h"
#include "mdrv_demux_debug.h"
#include "mdrv_demux_dvb.h"
#include "mdrv_demux_tsp.h"
#include "mdrv_demux_resource.h"
#include "mdrv_mvd_parser.h"


typedef enum {
	RESTORE_TSP_BANKS,
	RESTORE_TSO_BANKS,
	RESTORE_TS_SAMPLE_BANK,
	RESTORE_ALP_BANKS,
	RESTORE_TLV_BANKS,
	RESTORE_CILINK_BANKS,
} restore_regs_t;

static u16 *tsp_banks_reg_array;
static u16 *tso_banks_reg_array;
static u16 *ts_sample_reg_array;
static u16 *alp_banks_reg_array;
static u16 *tlv_banks_reg_array;
static u16 *cilink_reg_array;

static irqreturn_t pas_irq_handler(int irq, void *dev_id)
{
	if (video_parser_isr(irq, dev_id))
		return IRQ_HANDLED;
	else
		return IRQ_NONE;
}

static irqreturn_t tsp_irq_handler(int irq, void *dev_id)
{
	struct mdrv_demux_tsp *ptsp = dev_id;
	struct mdrv_demux_ctrl *pctrl = &ptsp->ctrl;
	struct mdrv_demux_filter *pfilter = &ptsp->filter;
	enum sw_interrupt_event sw_event = SW_INT_EVENT_NULL;
	u16 pcr_filter_num = ptsp->res->hwcaps->pcr_filter_num;
	u16 ntp_filter_num = ptsp->res->hwcaps->tlv_num;
	u16 i, sec_filter_id = 0;
	bool ret;
	int fail = 0;

	//#### STC synth adjustment ####
	for (i = 0; i < pcr_filter_num; i++) {
		if (mdrv_demux_ctrl_get_pcr_int_status(pctrl, i)) {
			ret = mdrv_demux_filter_handle_pcr_event(pfilter, i);
			if (ret)
				mdrv_demux_ctrl_clear_pcr_int_status(pctrl, i);
			else
				fail++;
		}
	}

	//#### STC synth adjustment ####
	for (i = 0; i < ntp_filter_num; i++) {
		if (mdrv_demux_ctrl_get_ntp_int_status(pctrl, i)) {
			ret = mdrv_demux_filter_handle_ntp_event(pfilter, i);
			if (ret)
				mdrv_demux_ctrl_clear_ntp_int_status(pctrl, i);
			else
				fail++;
		}
	}

	//#### section SW interrupt ####
	if (mdrv_demux_ctrl_get_sw_int_status(pctrl, &sec_filter_id, &sw_event)) {
		ret = mdrv_demux_filter_handle_aeon_event(pfilter, sec_filter_id, sw_event);
		if (ret)
			mdrv_demux_ctrl_clear_sw_int_status(pctrl);
		else
			fail++;
	}

	return IRQ_RETVAL(fail == 0);
}

int mdrv_demux_tsp_init(struct platform_device *pdev, struct mdrv_demux_tsp *ptsp)
{
	int ret = 0;
	struct mdrv_demux_dvb *dvb;

    ptsp->irq = platform_get_irq(pdev, 0);
	if (ptsp->irq < 0) {
		DEMUX_DEBUG_ERR("[%s][%d] failed to get irq\n", __FUNCTION__, __LINE__);
		return -ENOENT;
	}

	DEMUX_DEBUG_INFO("demux irq = %x\n", ptsp->irq);

	dvb = (struct mdrv_demux_dvb *)container_of(ptsp, struct mdrv_demux_dvb, tsp);
	ptsp->res = &dvb->resource;
	ptsp->ctrl.res = ptsp->res;
	ptsp->filter.res = ptsp->res;
	ptsp->tsio.res = ptsp->res;
	ptsp->pvr.res = ptsp->res;

	ret = devm_request_irq(&pdev->dev, ptsp->irq, tsp_irq_handler, 0, DRV_NAME, ptsp);
	if (ret < 0) {
		DEMUX_DEBUG_ERR("[%s][%d] failed to request irq\n", __FUNCTION__, __LINE__);
		return -ENODEV;
	}

	ptsp->pas_irq = platform_get_irq(pdev, 1);
	if (ptsp->pas_irq < 0)
		DEMUX_DEBUG_ERR("[%s][%d] fail to get pas_irq\n", __func__,
				__LINE__);
	else {
		DEMUX_DEBUG_INFO("[%s][%d] ptsp->pas_irq = %x\n", __func__,
				__LINE__, ptsp->pas_irq);
		ret =
			devm_request_irq(&pdev->dev, ptsp->pas_irq, pas_irq_handler, IRQF_SHARED,
					 DRV_NAME, ptsp);
		if (ret < 0)
			DEMUX_DEBUG_ERR("[%s][%d] failed to request pas_irq\n",
					__func__, __LINE__);
	}

	ret = mdrv_demux_ctrl_init(pdev, &ptsp->ctrl, true);
	if (ret < 0) {
		DEMUX_DEBUG_ERR("[%s][%d] failed to init ctrl\n", __FUNCTION__, __LINE__);
		return -EFAULT;
	}

	ret = mdrv_demux_filter_init(pdev, &ptsp->filter);
	if (ret < 0) {
		DEMUX_DEBUG_ERR("[%s][%d] failed to init filter\n", __FUNCTION__, __LINE__);
		return -EFAULT;
	}

	ret = mdrv_demux_tsio_init(pdev, &ptsp->tsio);
	if (ret < 0) {
		DEMUX_DEBUG_ERR("[%s][%d] failed to init tsio\n", __FUNCTION__, __LINE__);
		return -EFAULT;
	}

	ret = mdrv_demux_pvr_init(pdev, &ptsp->pvr);
	if (ret < 0) {
		DEMUX_DEBUG_ERR("[%s][%d] failed to init pvr\n", __FUNCTION__, __LINE__);
		return -EFAULT;
	}

	return 0;
}

int mdrv_demux_tsp_exit(struct platform_device *pdev, struct mdrv_demux_tsp *ptsp)
{
	int ret = 0;

	ret = mdrv_demux_pvr_exit(pdev, &ptsp->pvr);
	if (ret < 0) {
		DEMUX_DEBUG_ERR("[%s][%d] failed to exit pvr\n", __FUNCTION__, __LINE__);
		return -EFAULT;
	}

	ret = mdrv_demux_tsio_exit(pdev, &ptsp->tsio);
	if (ret < 0) {
		DEMUX_DEBUG_ERR("[%s][%d] failed to exit tsio\n", __FUNCTION__, __LINE__);
		return -EFAULT;
	}

	ret = mdrv_demux_filter_exit(pdev, &ptsp->filter);
	if (ret < 0) {
		DEMUX_DEBUG_ERR("[%s][%d] failed to exit filter\n", __FUNCTION__, __LINE__);
		return -EFAULT;
	}

	ret = mdrv_demux_ctrl_exit(pdev, &ptsp->ctrl);
	if (ret < 0) {
		DEMUX_DEBUG_ERR("[%s][%d] failed to exit ctrl\n", __FUNCTION__, __LINE__);
		return -EFAULT;
	}

	return 0;
}

static int tsp_save_regs(mtk_demux_riu_banks *riu_banks, restore_regs_t type)
{
	u32 i, j;
	u32 bank_num;
	REG16 __iomem *offset;

	switch (type) {
	case RESTORE_TSP_BANKS:
		bank_num = riu_banks->tsp_bank_num;
		tsp_banks_reg_array = vmalloc(bank_num * RIU_BANK_SIZE * sizeof(u16));
		if (!tsp_banks_reg_array) {
			DEMUX_DEBUG_ERR("[%s][%d] vmalloc(tsp_banks_reg_array) fail\n", __func__,
					__LINE__);
			return -ENOMEM;
		}
		for (i = 0; i < bank_num; i++) {
			offset = riu_banks->bank_tsp[i];
			for (j = 0; j < RIU_BANK_SIZE; j++, offset += sizeof(u32))
				tsp_banks_reg_array[i * RIU_BANK_SIZE + j] = reg16_read(offset);
		}
		break;
	case RESTORE_TSO_BANKS:
		bank_num = riu_banks->tso_bank_num;
		tso_banks_reg_array = vmalloc(bank_num * RIU_BANK_SIZE * sizeof(u16));
		if (!tso_banks_reg_array) {
			DEMUX_DEBUG_ERR("[%s][%d] vmalloc(tso_banks_reg_array) fail\n", __func__,
					__LINE__);
			return -ENOMEM;
		}
		for (i = 0; i < bank_num; i++) {
			offset = riu_banks->bank_tso[i];
			for (j = 0; j < RIU_BANK_SIZE; j++, offset += sizeof(u32))
				tso_banks_reg_array[i * RIU_BANK_SIZE + j] = reg16_read(offset);
		}
		break;
	case RESTORE_TS_SAMPLE_BANK:
		ts_sample_reg_array = vmalloc(RIU_BANK_SIZE * sizeof(u16));
		if (!ts_sample_reg_array) {
			DEMUX_DEBUG_ERR("[%s][%d] vmalloc(ts_sample_reg_array) fail\n", __func__,
					__LINE__);
			return -ENOMEM;
		}
		offset = riu_banks->bank_ts_sample;
		for (i = 0; i < RIU_BANK_SIZE; i++, offset += sizeof(u32))
			ts_sample_reg_array[i] = reg16_read(offset);
		break;
	case RESTORE_ALP_BANKS:
		bank_num = riu_banks->alp_bank_num;
		alp_banks_reg_array = vmalloc(bank_num * RIU_BANK_SIZE * sizeof(u16));
		if (!alp_banks_reg_array) {
			DEMUX_DEBUG_ERR("[%s][%d] vmalloc(alp_banks_reg_array) fail\n", __func__,
					__LINE__);
			return -ENOMEM;
		}
		for (i = 0; i < bank_num; i++) {
			offset = riu_banks->bank_alp[i];
			for (j = 0; j < RIU_BANK_SIZE; j++, offset += sizeof(u32))
				alp_banks_reg_array[i * RIU_BANK_SIZE + j] = reg16_read(offset);
		}
		break;
	case RESTORE_TLV_BANKS:
		bank_num = riu_banks->tlv_bank_num;
		tlv_banks_reg_array = vmalloc(bank_num * RIU_BANK_SIZE * sizeof(u16));
		if (!tlv_banks_reg_array) {
			DEMUX_DEBUG_ERR("[%s][%d] vmalloc(tlv_banks_reg_array) fail\n", __func__,
					__LINE__);
			return -ENOMEM;
		}
		for (i = 0; i < bank_num; i++) {
			offset = riu_banks->bank_tlv[i];
			for (j = 0; j < RIU_BANK_SIZE; j++, offset += sizeof(u32))
				tlv_banks_reg_array[i * RIU_BANK_SIZE + j] = reg16_read(offset);
		}
		break;
	case RESTORE_CILINK_BANKS:
		cilink_reg_array = vmalloc(RIU_BANK_SIZE * sizeof(u16));
		if (!cilink_reg_array) {
			DEMUX_DEBUG_ERR("[%s][%d] vmalloc(cilink_reg_array) fail\n", __func__,
				__LINE__);
			return -ENOMEM;
		}
		offset = riu_banks->bank_cilink;
		for (i = 0; i < RIU_BANK_SIZE; i++, offset += sizeof(u32))
			cilink_reg_array[i] = reg16_read(offset);
		break;
	default:
		DEMUX_DEBUG_ERR("[%s][%d] invalid type(%x)\n", __func__, __LINE__, type);
		return -EFAULT;
	}
	return 0;
}

static int tsp_restore_regs(mtk_demux_riu_banks *riu_banks, restore_regs_t type)
{
	u32 i, j;
	u32 bank_num;
	REG16 __iomem *offset;

	switch (type) {
	case RESTORE_TSP_BANKS:
		bank_num = riu_banks->tsp_bank_num;
		for (i = 0; i < bank_num; i++) {
			offset = riu_banks->bank_tsp[i];
			for (j = 0; j < RIU_BANK_SIZE; j++, offset += sizeof(u32))
				reg16_write(offset, tsp_banks_reg_array[i * RIU_BANK_SIZE + j]);
		}
		vfree(tsp_banks_reg_array);
		tsp_banks_reg_array = NULL;
		break;
	case RESTORE_TSO_BANKS:
		bank_num = riu_banks->tso_bank_num;
		for (i = 0; i < bank_num; i++) {
			offset = riu_banks->bank_tso[i];
			for (j = 0; j < RIU_BANK_SIZE; j++, offset += sizeof(u32))
				reg16_write(offset, tso_banks_reg_array[i * RIU_BANK_SIZE + j]);
		}
		vfree(tso_banks_reg_array);
		tso_banks_reg_array = NULL;
		break;
	case RESTORE_TS_SAMPLE_BANK:
		offset = riu_banks->bank_ts_sample;
		for (i = 0; i < RIU_BANK_SIZE; i++, offset += sizeof(u32))
			reg16_write(offset, ts_sample_reg_array[i]);
		vfree(ts_sample_reg_array);
		ts_sample_reg_array = NULL;
		break;
	case RESTORE_ALP_BANKS:
		bank_num = riu_banks->alp_bank_num;
		for (i = 0; i < bank_num; i++) {
			offset = riu_banks->bank_alp[i];
			for (j = 0; j < RIU_BANK_SIZE; j++, offset += sizeof(u32))
				reg16_write(offset, alp_banks_reg_array[i * RIU_BANK_SIZE + j]);
		}
		vfree(alp_banks_reg_array);
		alp_banks_reg_array = NULL;
		break;
	case RESTORE_TLV_BANKS:
		bank_num = riu_banks->tlv_bank_num;
		for (i = 0; i < bank_num; i++) {
			offset = riu_banks->bank_tlv[i];
			for (j = 0; j < RIU_BANK_SIZE; j++, offset += sizeof(u32))
				reg16_write(offset, tlv_banks_reg_array[i * RIU_BANK_SIZE + j]);
		}
		vfree(tlv_banks_reg_array);
		tlv_banks_reg_array = NULL;
		break;
	case RESTORE_CILINK_BANKS:
		offset = riu_banks->bank_cilink;
		for (i = 0; i < RIU_BANK_SIZE; i++, offset += sizeof(u32))
			reg16_write(offset, cilink_reg_array[i]);
		vfree(cilink_reg_array);
		cilink_reg_array = NULL;
		break;
	default:
		DEMUX_DEBUG_ERR("[%s][%d] invalid type(%x)\n", __FUNCTION__, __LINE__, type);
		return -EFAULT;
	}

	return 0;
}

int mdrv_demux_tsp_suspend(struct platform_device *pdev, struct mdrv_demux_tsp *ptsp)
{
	int ret = 0;

	disable_irq((unsigned int)(ptsp->irq));
	disable_irq((unsigned int)(ptsp->pas_irq));

	ret |= tsp_save_regs(&ptsp->res->regmaps->riu_banks, RESTORE_TSP_BANKS);
	ret |= tsp_save_regs(&ptsp->res->regmaps->riu_banks, RESTORE_TSO_BANKS);
	ret |= tsp_save_regs(&ptsp->res->regmaps->riu_banks, RESTORE_ALP_BANKS);
	ret |= tsp_save_regs(&ptsp->res->regmaps->riu_banks, RESTORE_TLV_BANKS);
	ret |= tsp_save_regs(&ptsp->res->regmaps->riu_banks, RESTORE_CILINK_BANKS);
	ret |= tsp_save_regs(&ptsp->res->regmaps->riu_banks, RESTORE_TS_SAMPLE_BANK);
	if (ret != 0)
		goto err;

	ret = mdrv_demux_tsio_suspend(&ptsp->tsio);
	if (ret < 0) {
		DEMUX_DEBUG_ERR("[%s][%d] failed to suspend tsio\n", __FUNCTION__, __LINE__);
		return -EFAULT;
	}

	ret = mdrv_demux_filter_suspend(&ptsp->filter);
	if (ret < 0) {
		DEMUX_DEBUG_ERR("[%s][%d] failed to suspend filter\n", __FUNCTION__, __LINE__);
		return -EFAULT;
	}

	ret = mdrv_demux_pvr_suspend(&ptsp->pvr);
	if (ret < 0) {
		DEMUX_DEBUG_ERR("[%s][%d] failed to suspend pvr\n", __FUNCTION__, __LINE__);
		return -EFAULT;
	}

	ptsp->ctrl.bIsSuspend = true;
	ret = mdrv_demux_ctrl_suspend(&ptsp->ctrl);
	if (ret < 0) {
		DEMUX_DEBUG_ERR("[%s][%d] failed to suspend ctrl\n", __FUNCTION__, __LINE__);
		return -EFAULT;
	}

	return 0;
err:
	if (tsp_banks_reg_array) {
		vfree(tsp_banks_reg_array);
		tsp_banks_reg_array = NULL;
	}
	if (tso_banks_reg_array) {
		vfree(tso_banks_reg_array);
		tso_banks_reg_array = NULL;
	}
	if (ts_sample_reg_array) {
		vfree(ts_sample_reg_array);
		ts_sample_reg_array = NULL;
	}
	if (alp_banks_reg_array) {
		vfree(alp_banks_reg_array);
		alp_banks_reg_array = NULL;
	}
	if (tlv_banks_reg_array) {
		vfree(tlv_banks_reg_array);
		tlv_banks_reg_array = NULL;
	}
	if (cilink_reg_array) {
		vfree(cilink_reg_array);
		cilink_reg_array = NULL;
	}
	return -EFAULT;
}
EXPORT_SYMBOL(mdrv_demux_tsp_suspend);

int mdrv_demux_tsp_resume(struct platform_device *pdev, struct mdrv_demux_tsp *ptsp)
{
	int i, ret = 0;
	unsigned long start_time = jiffies;

	ret = mdrv_demux_ctrl_init(pdev, &ptsp->ctrl, false);
	if (ret < 0) {
		DEMUX_DEBUG_ERR("[%s][%d] failed to init ctrl\n", __FUNCTION__, __LINE__);
		return -EFAULT;
	}

	tsp_restore_regs(&ptsp->res->regmaps->riu_banks, RESTORE_TSP_BANKS);
	tsp_restore_regs(&ptsp->res->regmaps->riu_banks, RESTORE_TSO_BANKS);
	tsp_restore_regs(&ptsp->res->regmaps->riu_banks, RESTORE_ALP_BANKS);
	tsp_restore_regs(&ptsp->res->regmaps->riu_banks, RESTORE_TLV_BANKS);
	tsp_restore_regs(&ptsp->res->regmaps->riu_banks, RESTORE_CILINK_BANKS);

	ret = mdrv_demux_ctrl_resume(&ptsp->ctrl);
	if (ret < 0) {
		DEMUX_DEBUG_ERR("[%s][%d] failed to resume ctrl\n", __FUNCTION__, __LINE__);
		return -EFAULT;
	}
	ptsp->ctrl.bIsSuspend = false;

	ret = mdrv_demux_pvr_resume(&ptsp->pvr);
	if (ret < 0) {
		DEMUX_DEBUG_ERR("[%s][%d] failed to resume pvr\n", __FUNCTION__, __LINE__);
		return -EFAULT;
	}

	ret = mdrv_demux_filter_resume(&ptsp->filter);
	if (ret < 0) {
		DEMUX_DEBUG_ERR("[%s][%d] failed to resume filter\n", __FUNCTION__, __LINE__);
		return -EFAULT;
	}

	ret = mdrv_demux_tsio_resume(&ptsp->tsio);
	if (ret < 0) {
		DEMUX_DEBUG_ERR("[%s][%d] failed to resume tsio\n", __FUNCTION__, __LINE__);
		return -EFAULT;
	}

	tsp_restore_regs(&ptsp->res->regmaps->riu_banks, RESTORE_TS_SAMPLE_BANK);

	enable_irq((unsigned int)(ptsp->irq));
	enable_irq((unsigned int)(ptsp->pas_irq));

	DEMUX_DEBUG_INFO("[%s][%d] cost %d msec\n", __func__, __LINE__,
		jiffies_to_msecs(jiffies - start_time));
	return 0;
}
EXPORT_SYMBOL(mdrv_demux_tsp_resume);
