// 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/of_device.h>
#include <linux/pm.h>
#include <linux/pm_runtime.h>
#include <linux/kthread.h>
#include <linux/delay.h>

#include "mdrv_demux_common.h"
#include "mdrv_demux_debug.h"
#include "mdrv_demux_dvb.h"
#include "mdrv_mvd_parser.h"
#include "mdrv_demux_ctrl.h"

static struct task_struct *thInfo = {0};
static int mdrv_dvbdmx_rpm_handler(void *data);

static int mdrv_dvbdmx_probe(struct platform_device *pdev)
{
	int ret = 0;
	unsigned long start_time = jiffies;
	struct mdrv_demux_dvb *dvb;
	struct device_node *swcaps_node;
	u32 rpm_en = 0;

	of_node_get(pdev->dev.of_node);
	swcaps_node = of_find_node_by_name(pdev->dev.of_node, "swcaps");
	if (swcaps_node != NULL) {
		ret = of_property_read_u32(swcaps_node, "rpm_en", &rpm_en);
		if (ret)
			DEMUX_DEBUG_ERR("[%s][%d] Failed to get %s\n",
				__func__, __LINE__, "rpm_en");
	}
	of_node_put(swcaps_node);

	dvb = devm_kzalloc(&pdev->dev, sizeof(struct mdrv_demux_dvb), GFP_KERNEL);
	if (!dvb) {
		DEMUX_DEBUG_ERR("[%s][%d] allocate dvb struct fail\n", __FUNCTION__, __LINE__);
		return -ENOMEM;
	}

	dvb->pdev = pdev;
	dvb->tsp.ctrl.bRpmEn = (rpm_en == 1) ? true : false;

	ret = mdrv_demux_debug_init(pdev);
	if (ret < 0) {
		DEMUX_DEBUG_ERR("[%s][%d] debug init fail\n", __FUNCTION__, __LINE__);
		return ret;
	}

	ret = mdrv_demux_res_init(pdev, &dvb->resource);
	if (ret < 0) {
		DEMUX_DEBUG_ERR("[%s][%d] resoucre init fail\n", __FUNCTION__, __LINE__);
		goto res_fail;
	}

	ret = mdrv_demux_tsp_init(pdev, &dvb->tsp);
	if (ret < 0) {
		DEMUX_DEBUG_ERR("[%s][%d] tsp init fail\n", __FUNCTION__, __LINE__);
		goto tsp_fail;
	}

	ret = mdrv_demux_dvb_init(dvb);
	if (ret < 0) {
		DEMUX_DEBUG_ERR("[%s][%d] dvb init fail\n", __FUNCTION__, __LINE__);
		goto dvb_fail;
	}

	ret = mdrv_dscmb_dev_init(pdev, &dvb->dscmb);
	if ((ret) < 0) {
		DEMUX_DEBUG_ERR("[%s] dsc init fail\n", __func__);
		goto dscmb_fail;
	}

	ret = mdrv_scrambler_dev_init(pdev);
	if ((ret) < 0) {
		DEMUX_DEBUG_ERR("[%s] scrambler init fail\n", __func__);
		goto scrambler_fail;
	}

	ret = mtk_dtv_parser_create_sysfs_attr(pdev);
	if ((ret) < 0)
		DEMUX_DEBUG_ERR("[%s] parser sysfs create fail\n", __func__);

	platform_set_drvdata(pdev, &dvb->tsp);

	mutex_init(&dvb->rpm_mutex);
	dvb->rpm_get = false;

	if (rpm_en == 1) {
		pm_runtime_set_active(&pdev->dev);
		pm_runtime_enable(&pdev->dev);
		thInfo = kthread_create(mdrv_dvbdmx_rpm_handler, dvb, "mtkdmx_rpm_handler");
		if (IS_ERR(thInfo))
			DEMUX_DEBUG_ERR("[%s] create runtime handler fail\n", __func__);
		else
			wake_up_process(thInfo);
	}

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

scrambler_fail:
	mdrv_scrambler_dev_exit(pdev);
dscmb_fail:
	mdrv_dscmb_dev_exit(pdev);
dvb_fail:
	mdrv_demux_tsp_exit(pdev, &dvb->tsp);
tsp_fail:
	mdrv_demux_res_exit(pdev, &dvb->resource);
res_fail:
	mdrv_demux_debug_exit(pdev);

	return ret;
}

static int mdrv_dvbdmx_remove(struct platform_device *pdev)
{
	int ret = 0;
	struct mdrv_demux_tsp *tsp = platform_get_drvdata(pdev);
	struct mdrv_demux_dvb *dvb = container_of(tsp, struct mdrv_demux_dvb, tsp);

	pm_runtime_disable(&pdev->dev);

	ret = mdrv_demux_tsp_exit(pdev, tsp);
	if (ret < 0) {
		DEMUX_DEBUG_ERR("[%s][%d] tsp exit fail\n", __FUNCTION__, __LINE__);
		return ret;
	}

	ret = mdrv_demux_dvb_exit(dvb);
	if (ret < 0) {
		DEMUX_DEBUG_ERR("[%s][%d] dvb exit fail\n", __FUNCTION__, __LINE__);
		return ret;
	}

	ret = mdrv_demux_debug_exit(pdev);
	if (ret  < 0) {
		DEMUX_DEBUG_ERR("[%s][%d] debug exit fail\n", __FUNCTION__, __LINE__);
		return ret;
	}

	ret = mdrv_demux_res_exit(pdev, &dvb->resource);
	if (ret < 0) {
		DEMUX_DEBUG_ERR("[%s][%d] resoucre exit fail\n", __FUNCTION__, __LINE__);
		return ret;
	}

	ret = mtk_dtv_parser_remove_sysfs_attr(pdev);
	if (ret < 0)
		DEMUX_DEBUG_ERR("[%s] parser sysfs remove fail\n", __func__);

	ret = mdrv_dscmb_dev_exit(pdev);

	if (ret < 0) {
		DEMUX_DEBUG_ERR("[%s] dscmb exit fail\n", __func__);
		return ret;
	}

	ret = mdrv_scrambler_dev_exit(pdev);
	if (ret < 0) {
		DEMUX_DEBUG_ERR("[%s] scrambler exit fail\n", __func__);
		return ret;
	}

	return 0;
}

static int mdrv_dvbdmx_suspend(struct device *dev)
{
	struct platform_device *pdev = to_platform_device(dev);
	struct mdrv_demux_tsp *tsp = platform_get_drvdata(pdev);

	if (tsp->ctrl.bRpmEn == true)
		kthread_park(thInfo);
	return mdrv_demux_tsp_suspend(pdev, tsp);
}

static int mdrv_dvbdmx_resume(struct device *dev)
{
	struct platform_device *pdev = to_platform_device(dev);
	struct mdrv_demux_tsp *tsp = platform_get_drvdata(pdev);
	int ret = 0;

	ret = mdrv_demux_tsp_resume(pdev, tsp);
	if (tsp->ctrl.bRpmEn == true)
		kthread_unpark(thInfo);
	return ret;
}

static int mdrv_dvbdmx_rpm_handler(void *data)
{
	struct mdrv_demux_dvb *dvb = (struct mdrv_demux_dvb *)data;
	struct dvb_adapter *adap;
	struct dvb_device *dvbdev;
	struct dmxdev *dmxdev;
	int total_users, dev_cnt;
	struct mutex *tmp_mutex = NULL;
	bool rpm_suspend = false;

	adap = mdrv_get_dvb_adapter();
	while (adap != NULL) {
		msleep_interruptible(5*1000);
		total_users = dev_cnt = 0;
		list_for_each_entry(dvbdev, &adap->device_list, list_head)
			if (dvbdev->type == DVB_DEVICE_DEMUX ||
				dvbdev->type == DVB_DEVICE_DVR) {
				dmxdev = dvbdev->priv;
				if (dev_cnt == 0) {
					if (mutex_lock_interruptible(&dmxdev->mutex) < 0) {
						DEMUX_DEBUG_ERR(
							"[%s][%d] Failed to lock dmxdev->mutex\n",
							__func__, __LINE__);
						break;
					}
					tmp_mutex = &dmxdev->mutex;
				}
				dev_cnt++;
				total_users += dvbdev->users;
			}
		if (mutex_lock_interruptible(&dvb->rpm_mutex) < 0) {
			DEMUX_DEBUG_ERR("[%s][%d] Failed to lock dvb->rpm_mutex\n",
				__func__, __LINE__);
			break;
		}
		if (total_users == dev_cnt && dvb->rpm_get == true) {
			pm_runtime_put_sync(&dvb->pdev->dev);
			dvb->rpm_get = false;
			DEMUX_DEBUG_INFO("[%s][%d] RPM PUT !\n", __func__, __LINE__);
		}
		mutex_unlock(&dvb->rpm_mutex);
		if (dev_cnt != 0 && tmp_mutex != NULL) {
			mutex_unlock(tmp_mutex);
			tmp_mutex = NULL;
		}
		if (kthread_should_park()) {
			if (mutex_lock_interruptible(&dvb->rpm_mutex) < 0) {
				DEMUX_DEBUG_ERR("[%s][%d] Failed to lock dvb->rpm_mutex\n",
					__func__, __LINE__);
				break;
			}
			if (dvb->rpm_get == true) {
				rpm_suspend = true;
				pm_runtime_put_sync(&dvb->pdev->dev);
			}
			mutex_unlock(&dvb->rpm_mutex);
			DEMUX_DEBUG_INFO("[%s][%d] RPM Thread STOP !\n", __func__, __LINE__);
			kthread_parkme();
			DEMUX_DEBUG_INFO("[%s][%d] RPM Thread RESTART !\n", __func__, __LINE__);
			if (rpm_suspend == true) {
				pm_runtime_get_sync(&dvb->pdev->dev);
				rpm_suspend = false;
			}
		}
	}
	if (tmp_mutex != NULL)
		mutex_unlock(tmp_mutex);
	DEMUX_DEBUG_INFO("[%s][%d] RPM Thread LEAVING !\n", __func__, __LINE__);
	return 0;
}

static int mdrv_dvbdmx_runtime_suspend(struct device *dev)
{
	struct platform_device *pdev = to_platform_device(dev);
	struct mdrv_demux_tsp *tsp = platform_get_drvdata(pdev);
	if (tsp->ctrl.bRpmEn == true)
		mdrv_demux_ctrl_runtime(&tsp->ctrl, DEMUX_RPM_SUSPEND);
	return 0;
}

static int mdrv_dvbdmx_runtime_resume(struct device *dev)
{
	struct platform_device *pdev = to_platform_device(dev);
	struct mdrv_demux_tsp *tsp = platform_get_drvdata(pdev);
	if (tsp->ctrl.bRpmEn == true)
		mdrv_demux_ctrl_runtime(&tsp->ctrl, DEMUX_RPM_RESUME);
	return 0;
}

static int mdrv_dvbdmx_runtime_idle(struct device *dev)
{
	struct platform_device *pdev = to_platform_device(dev);
	struct mdrv_demux_tsp *tsp = platform_get_drvdata(pdev);
	if (tsp->ctrl.bRpmEn == true)
		mdrv_demux_ctrl_runtime(&tsp->ctrl, DEMUX_RPM_IDLE);
	return 0;
}

static const struct dev_pm_ops mdrv_dvbdmx_pm_ops = {
	.suspend = mdrv_dvbdmx_suspend,
	.resume = mdrv_dvbdmx_resume,
	SET_RUNTIME_PM_OPS(mdrv_dvbdmx_runtime_suspend,
						mdrv_dvbdmx_runtime_resume,
						NULL)
};

static const struct of_device_id mdrv_dvbdmx_dt_match[] = {
	{.compatible = DRV_NAME},
	{},
};

MODULE_DEVICE_TABLE(of, mdrv_dvbdmx_dt_match);

static struct platform_driver mdrv_dvbdmx_driver = {
	.probe = mdrv_dvbdmx_probe,
	.remove = mdrv_dvbdmx_remove,
	.driver = {
		   .name = DRV_NAME,
		   .owner = THIS_MODULE,
		   .of_match_table = mdrv_dvbdmx_dt_match,
		   .pm = &mdrv_dvbdmx_pm_ops}
};

module_platform_driver(mdrv_dvbdmx_driver);

MODULE_DESCRIPTION("Mediatek DVB Demux Driver");
MODULE_AUTHOR("Mediatek Author");
MODULE_LICENSE("GPL");
