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

#include <linux/module.h>

#include <linux/dvb/dmx2_mtk.h>

#include "mtk_dmx_mediactrl.h"
#include "mtk_dmx_io.h"
#include "mtk_dmx_rm.h"
#include "mtk_dmx_tso.h"

int mtk_mc_set_tsp_input_source(struct mtk_dmx_rm *rm,
	u16 path, struct mtk_msource_settings *src, u8 *pin)
{
	u16 cilink_num = mtk_dmx_rm_get_hwcap_by_id(rm, HWCAPS_TYPE_CILINK);
	struct demux_io_cfg cfg = { 0 };
	struct demux_filein_engine filein_eng = {0};
	u16 path_id = path;
	int ret, tsif_port;

	switch (src->port_type) {
	case DMX_MC_PORT_PARALLEL:
		cfg.port_type = DEMUX_PORT_TYPE_PARALLEL;
		break;
	case DMX_MC_PORT_2BITS_SERIAL:
		cfg.port_type = DEMUX_PORT_TYPE_SERIAL_2BITS;
		break;
	case DMX_MC_PORT_SERIAL:
		cfg.port_type = DEMUX_PORT_TYPE_SERIAL;
		break;
	default:
		MTK_DMX_ERR("port type (%x) not support\n", src->port_type);
		return -EINVAL;
	}
	if (src->cicam) {
		if (cilink_num != 0)
			cfg.type = DEMUX_IO_TYPE_CILINK;
		else {
			cfg.type = DEMUX_IO_TYPE_EXT_DEMOD;
			ret = mtk_dmx_rm_set_padcfg(rm, "ext_ts_para_demod_pad_mux");
			if (ret < 0)
				return -EFAULT;
		}
	} else if (src->cifpga) {
		cfg.type = DEMUX_IO_TYPE_EXT_DEMOD;
		ret = mtk_dmx_rm_set_padcfg(rm, "ext2_ts_demod_pad_mux");
		if (ret < 0)
			return -EFAULT;
	} else if (!strcmp(pin, "tso_pad")) {
		cfg.type = DEMUX_IO_TYPE_TSO;
	} else {
		switch (src->input_type) {
		case DMX_MC_INPUT_INTERNAL_FE:
			cfg.type = DEMUX_IO_TYPE_IN_DEMOD;
			break;
		case DMX_MC_INPUT_EXTERNAL_FE:
			cfg.type = DEMUX_IO_TYPE_EXT_DEMOD;

			ret = mtk_dmx_rm_set_padcfg(rm, pin);
			if (ret < 0) {
				MTK_DMX_ERR("_setup_input_pad_config fail\n");
				return -EFAULT;
			}
			break;
		case DMX_MC_INPUT_DRAM:
			cfg.type = DEMUX_IO_TYPE_MEM;
			filein_eng.engine_id = mtk_dmx_rm_filein_find_engine_by_path(rm, path_id);
			if (filein_eng.engine_id < 0)
				return -ENODEV;
			filein_eng.tlv_mux = mtk_dmx_rm_tlv_filein_find(rm, path_id);
			break;
		default:
			MTK_DMX_ERR("input type (%x) not support\n", src->input_type);
			return -EINVAL;
		}
	}
	tsif_port = src->port_num;
	cfg.port_num = tsif_port;

	mtk_dmx_io_set_input_src(path_id, &cfg, (filein_eng.engine_id < 0) ? NULL : &filein_eng);
	return 0;
}

int mtk_mc_set_tso_input_source(struct mtk_dmx_rm *rm,
	u16 eng, struct mtk_msource_settings *src, u8 *pin, bool cilink_en, bool cifpga_en)
{
	u16 cilink_num = mtk_dmx_rm_get_hwcap_by_id(rm, HWCAPS_TYPE_CILINK);
	int ret;
	bool pad_ok = true;

	// configure input/output of tso
	ret = mtk_dmx_tso_in_config(src, eng);
	if (ret < 0) {
		MTK_DMX_ERR("mtk_dmx_tso_in_config fail\n");
		return -EFAULT;
	}

	if (cilink_en) {
		if (cilink_num != 0) {
		// cilink pin control set
			ret = mtk_dmx_tso_cilink_config(rm, eng);
			if (ret < 0) {
				MTK_DMX_ERR("mtk_dmx_tso_cilink_config fail\n");
				return -EFAULT;
			}
			// cilink tx pad mux
			ret = mtk_dmx_rm_set_padcfg(rm, "cilink_pad_clk");
			if (ret < 0)
				pad_ok = false;
			mtk_dmx_rm_set_padcfg(rm, "cilink_drive_clk");
			switch (eng) {
			case 0:
				ret = mtk_dmx_rm_set_padcfg(rm, "cilink_pad_data0");
				if (ret < 0)
					pad_ok = false;
				mtk_dmx_rm_set_padcfg(rm, "cilink_drive_data0");
				break;
			case 1:
				ret = mtk_dmx_rm_set_padcfg(rm, "cilink_pad_data1");
				if (ret < 0)
					pad_ok = false;
				mtk_dmx_rm_set_padcfg(rm, "cilink_drive_data1");
				break;
			default:
				MTK_DMX_ERR("index %d is invalid\n", eng);
				pad_ok = false;
			}
		} else {
			// tso pin control set
			ret = mtk_dmx_rm_set_padcfg(rm, "ts1_para_out_pad_mux");
			if (ret < 0)
				pad_ok = false;
		}
	} else if (cifpga_en) {
		ret = mtk_dmx_rm_set_padcfg(rm, "ts1_out_pad_mux");
		if (ret < 0)
			pad_ok = false;
	}
	return (pad_ok) ? 0 : -EFAULT;
}

int mtk_mc_set_tso_output_source(
		int tso_index,
		u32 rateKHz,
		bool bUseCi2Clk,
		bool isParallel,
		bool bClk_sample_inv)
{
	int ret;
	ret = mtk_dmx_tso_out_config(
			tso_index,
			rateKHz,
			bUseCi2Clk,
			isParallel,
			bClk_sample_inv);
	if (ret < 0) {
		MTK_DMX_ERR("mtk_dmx_tso_out_config fail\n");
		return -EFAULT;
	}

	return 0;
}

int mtk_mc_dvr2_ext_set_input_source(struct mtk_dmx_rm *rm, u16 path,
	struct dmx2_source_settings *src, u8 *pin_name)
{
	struct demux_io_cfg cfg = { 0 };
	struct demux_filein_engine filein_eng = {0};
	u16 path_id = path;
	u16 cilink_num = mtk_dmx_rm_get_hwcap_by_id(rm, HWCAPS_TYPE_CILINK);
	int ret, idx, tsif_port;
	u8 *pin = pin_name;
	struct mtk_msource_settings mc_src = { 0 };
	bool isParallel = true;
	bool bClk_sample_inv = false;

	switch (src->port_type) {
	case DMX2_PORT_TYPE_PARALLEL:
		cfg.port_type = DEMUX_PORT_TYPE_PARALLEL;
		break;
	case DMX2_PORT_TYPE_SERIAL_2BITS:
		cfg.port_type = DEMUX_PORT_TYPE_SERIAL_2BITS;
		break;
	case DMX2_PORT_TYPE_SERIAL:
		cfg.port_type = DEMUX_PORT_TYPE_SERIAL;
		break;
	default:
		MTK_DMX_ERR("port type (%x) not support\n", src->port_type);
		return -EINVAL;
	}

	if (src->with_CICAM) {
		if (cilink_num > 0) {
			idx = mtk_dmx_rm_alloc_tso_idx(rm, path_id);
			if (idx < 0) {
				MTK_DMX_ERR("mtk_dmx_rm_alloc_tso_idx fail\n");
				return -EINVAL;
			}

			// configure input/output of tso
			switch (src->input_type) {
			default:
				return -EINVAL;
			case DMX2_INPUT_TYPE_DRAM:
				mc_src.input_type = DMX_MC_INPUT_DRAM;
				break;
			case DMX2_INPUT_TYPE_INTERNAL_FRONTEND:
				mc_src.input_type = DMX_MC_INPUT_INTERNAL_FE;
				break;
			case DMX2_INPUT_TYPE_EXTERNAL_FRONTEND:
				mc_src.input_type = DMX_MC_INPUT_EXTERNAL_FE;
				break;
			}
			switch (src->port_type) {
			default:
				return -EINVAL;
			case DMX2_PORT_TYPE_PARALLEL:
				mc_src.port_type = DMX_MC_PORT_PARALLEL;
				break;
			case DMX2_PORT_TYPE_SERIAL:
				mc_src.port_type = DMX_MC_PORT_SERIAL;
				isParallel = false;
				break;
			case DMX2_PORT_TYPE_SERIAL_2BITS:
				mc_src.port_type = DMX_MC_PORT_2BITS_SERIAL;
				break;
			}
			mc_src.port_num = src->port_num;
			mc_src.cicam = src->with_CICAM;
			ret = mtk_dmx_tso_in_config(&mc_src, idx);
			if (ret < 0) {
				MTK_DMX_ERR("mtk_dmx_tso_in_config fail\n");
				return -EFAULT;
			}
			ret = mtk_dmx_tso_out_config(
					idx,
					0,
					0,
					isParallel,
					bClk_sample_inv);
			if (ret < 0) {
				MTK_DMX_ERR("mtk_dmx_tso_out_config fail\n");
				return -EFAULT;
			}

			// cilink pin control set
			ret = mtk_dmx_tso_cilink_config(rm, idx);
			if (ret < 0) {
				MTK_DMX_ERR("mtk_dmx_tso_cilink_config fail\n");
				return -EFAULT;
			}

			cfg.type = DEMUX_IO_TYPE_CILINK;
			tsif_port = idx;
		} else { // TSO -> CICAM -> TSP scenario
			MTK_DMX_ERR("platform not support cilink hardward\n");
			return -EINVAL;
		}
	} else { //non-CICAM case
		mtk_dmx_rm_free_tso_idx(rm, path_id);

		switch (src->input_type) {
		case DMX2_INPUT_TYPE_INTERNAL_FRONTEND:
			cfg.type = DEMUX_IO_TYPE_IN_DEMOD;
			break;
		case DMX2_INPUT_TYPE_EXTERNAL_FRONTEND:
			cfg.type = DEMUX_IO_TYPE_EXT_DEMOD;

			// @TODO: remove if DMX2_CID_SET_SOURCE is phased out
			if (pin == NULL) {
				switch (src->port_num) {
				case 0:
					pin = (src->port_type == DMX2_PORT_TYPE_PARALLEL) ? "ext_ts_para_demod_pad_mux" :
						"ext_ts_demod_pad_mux";
					break;
				case 1:
					pin = (src->port_type == DMX2_PORT_TYPE_PARALLEL) ? "ext1_ts_para_demod_pad_mux" :
						"ext1_ts_demod_pad_mux";
					break;
				case 2:
					pin = (src->port_type == DMX2_PORT_TYPE_PARALLEL) ? "ext2_ts_para_demod_pad_mux" :
						"ext2_ts_demod_pad_mux";
					break;
				case 3:
					pin = (src->port_type == DMX2_PORT_TYPE_PARALLEL) ? "ext3_ts_para_demod_pad_mux" :
						"ext3_ts_demod_pad_mux";
					break;
				default:
					return -EINVAL;
				}
			}

			ret = mtk_dmx_rm_set_padcfg(rm, pin);
			if (ret < 0) {
				MTK_DMX_ERR("_setup_input_pad_config fail\n");
				return -EFAULT;
			}
			break;
		case DMX2_INPUT_TYPE_DRAM:
			cfg.type = DEMUX_IO_TYPE_MEM;
			filein_eng.engine_id = mtk_dmx_rm_filein_find_engine_by_path(rm, path_id);
			if (filein_eng.engine_id < 0)
				return -ENODEV;
			filein_eng.tlv_mux = mtk_dmx_rm_tlv_filein_find(rm, path_id);
			break;
		default:
			MTK_DMX_ERR("input type (%x) not support\n", src->input_type);
			return -EINVAL;
		}
		tsif_port = src->port_num;
	}

	cfg.port_num = tsif_port;
	mtk_dmx_io_set_input_src(path_id, &cfg, (filein_eng.engine_id < 0) ? NULL : &filein_eng);

	return 0;
}
