// 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/delay.h>
#include <linux/version.h>


#include <linux/module.h>
#include <linux/of.h>
#include <linux/err.h>
#include <linux/spi/spi.h>
#include "mtk_msb9121.h"
#include "mtk_msb9121_data_type.h"
#include "mtk_msb9121_core.h"
#include "mtk_msb9121_hal.h"


#include <linux/interrupt.h>
#include <linux/irq.h>
#include <linux/of_irq.h>
#include <linux/of_address.h>
#include <linux/gpio/consumer.h>
#include <linux/pinctrl/consumer.h>


typedef enum {
	OK = 0,
	NOT_OK = -1,
} CI_STATUS_T;

#define MLP_CI_CORE_DBG(fmt, arg...)	//MLP_KLOG_DETAIL(EN_MLP_MODULE_CI, "[CI]" fmt, ##arg)
#define MLP_CI_CORE_INFO(fmt, arg...)	//pr_info("[mdbginfo_CADD_CI]" fmt, ##arg)
#define MLP_CI_CORE_ERR(fmt, arg...)	//pr_info("[mdbgerr_CADD_CI]" fmt, ##arg)
#define MLP_CI_CORE_ENTRY(fmt, arg...)	//pr_info("[mdbgin_CADD_CI_CORE]" fmt, ##arg)
#define MLP_CI_CORE_EXIT()	//MLP_KLOG_TRACE(EN_MLP_MODULE_CI, "[CI Exit]\n")
#define MLP_CI_ENABLE 1


#define DBG_TSD_CORE(fmt, arg...)	//MLP_KLOG_DETAIL(EN_MLP_MODULE_TSD, "[TSD]" fmt, ##arg)
#define DBG_TSD_CORE_ERR(fmt, arg...)	//MLP_KLOG_ERR(EN_MLP_MODULE_TSD, "[TSD Error]" fmt, ##arg)


#define MsOS_DelayTask(x)  msleep_interruptible((unsigned int)x)
#define PCMCIA_WAIT_READY_POLLING_TIME				10
#define PCMCIA_WAIT_READY_POLLING_TIMEOUT_COUNT		300
//#define DRIVER_NAME "msb9121"

int _CardState;

struct spi_device *mtk_msb9121_to_spi;
bool polling_flg = FALSE;


extern void ci_slave_config(struct spi_device *spi, u8 enable);
extern void ci_slave_cs(struct spi_device *spi, u8 enable);
extern int ci_slave_write(struct spi_device *spi, u8 *buffer, u8 len);
extern int ci_slave_read(struct spi_device *spi, u8 *buffer, u8 len);
#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 17, 0)
static void mtk_msb9121_remove(struct spi_device *pdev)
#else
static int mtk_msb9121_remove(struct spi_device *pdev)
#endif
{
	MLP_CI_CORE_ENTRY("[%s][%d]\n", __func__, __LINE__);

	MTK_MSB9121_INFO("\n");
#if LINUX_VERSION_CODE < KERNEL_VERSION(5, 17, 0)
	return 0;
#endif
}

void mtk_msb9121_set_spi(u32	max_speed_hz)
{
	struct spi_device *spi = mtk_msb9121_to_spi;

	MLP_CI_CORE_ENTRY("[%s][%d]max_speed_hz : %d\n", __func__, __LINE__, max_speed_hz);//check

	spi->max_speed_hz = max_speed_hz;
	ci_slave_config(spi, 1);
	//MTK_MSB9121_INFO("max_speed_hz : %d\n", spi->max_speed_hz);
}

void mtk_msb9121_set_spi_dual_mode(void)
{
	struct spi_device *spi = mtk_msb9121_to_spi;
	MLP_CI_CORE_ENTRY("[%s][%d]\n", __func__, __LINE__);//check

	spi->mode |= SPI_TX_DUAL;
	spi->mode |= SPI_RX_DUAL;
	//spi->mode &= ~SPI_MODE_3;
	//spi->mode |= SPI_MODE_1;
	ci_slave_config(spi, 1);
	MTK_MSB9121_INFO("dual mode\n");
}
void mtk_msb9121_set_spi_single_mode(void)
{
	struct spi_device *spi = mtk_msb9121_to_spi;
	MLP_CI_CORE_ENTRY("[%s][%d]\n", __func__, __LINE__);//check

	spi->mode &= ~SPI_TX_DUAL;
	spi->mode &= ~SPI_RX_DUAL;
	//spi->mode &= ~SPI_MODE_3;
	//spi->mode |= SPI_MODE_1;
	ci_slave_config(spi, 1);
	MTK_MSB9121_INFO("signle mode\n");
}
ssize_t mtk_msb9121_to_spi_read(const char *txbuf, size_t txlen, char *buf, size_t len, char *buf1, size_t len1)
{
	struct spi_device *spi = mtk_msb9121_to_spi;
	int ret = 0;

	MLP_CI_CORE_ENTRY("[%s][%d]\n", __func__, __LINE__);//check

	//int len = 16;
	//int i;

	//MTK_MSB9121_INFO("spi device\n");

	// spi bus get data
	//ret = spi_read(spi, buf, len);
	if ((spi->mode)&SPI_MODE_1) {
		//MTK_MSB9121_INFO("\tmax_speed_hz : %d\n", spi->max_speed_hz);
		ci_slave_cs(spi, 1);
		ret = ci_slave_write(spi,(unsigned char*)txbuf, txlen);
		if(ret < 0)
			MTK_MSB9121_ERR("SPI error\n");

		if(len1 > 0) {
			ret = ci_slave_read(spi, buf, len);
			if(ret < 0)
				MTK_MSB9121_ERR("SPI error\n");
			ret = ci_slave_read(spi, buf1, len1);
			if(ret < 0)
				MTK_MSB9121_ERR("SPI error\n");
			ci_slave_cs(spi, 0);
		} else {
			ret = ci_slave_read(spi, buf, len);
			if(ret < 0)
				MTK_MSB9121_ERR("SPI error\n");
			ci_slave_cs(spi, 0);
		}
		if (ret == 0)
			ret = (len);
	}

	return ret;
}

ssize_t mtk_msb9121_to_spi_write(const char *buf1, size_t n1, const char *buf2, size_t n2, char burst_mode)
{
	struct spi_device *spi = mtk_msb9121_to_spi;
	int ret = -EINVAL;

	MLP_CI_CORE_ENTRY("[%s][%d]\n", __func__, __LINE__);//check

	//int i;
	//struct timespec time,time1;
	if ((spi->mode)&SPI_MODE_1) {
		if(burst_mode) {
			if (buf1 != NULL && buf2 != NULL) {
				//getnstimeofday(&time);
				ci_slave_cs(spi, 1);
				ret = ci_slave_write(spi,(unsigned char*)buf1, n1);
				if(ret < 0)
					MTK_MSB9121_ERR("SPI error\n");
				ret = ci_slave_write(spi, (unsigned char*)buf2, n2);
				if(ret < 0)
					MTK_MSB9121_ERR("SPI error\n");

				ci_slave_cs(spi, 0);
				//getnstimeofday(&time1);

				//printk("@@@@@@@@@@@write_time:%lld,\n",(time1.tv_nsec - time.tv_nsec));
				if (ret == 0)
					ret = (n1 + n2);
			}

		} else {
			if (buf1 != NULL) {
				// spi bus send data
			ci_slave_cs(spi, 1);
			ret = ci_slave_write(spi, (unsigned char*)buf1, n1);
			if(ret < 0)
				MTK_MSB9121_ERR("SPI error\n");

			ci_slave_cs(spi, 0);
				if (ret == 0)
				ret = (n1);

			}
		}
	}
	return ret;
}

int mtk_msb9121_reset_companion_chip(void)
{
	struct mtk_dvb_ci_msb9121 *dvb = spi_get_drvdata(mtk_msb9121_to_spi);

	MLP_CI_CORE_ENTRY("[%s][%d]\n", __func__, __LINE__);//check

	mtk_msb9121_reset(dvb->ci_reset);
	return 0;

}
EXPORT_SYMBOL(mtk_msb9121_reset_companion_chip);


int mtk_msb9121_reset(struct gpio_desc *ci_reset)
{
#define RESET_TIME (10)
	MLP_CI_CORE_ENTRY("[%s][%d]\n", __func__, __LINE__);//check
	gpiod_set_value(ci_reset, 1);
	MsOS_DelayTask(RESET_TIME);
	gpiod_set_value(ci_reset, 0);
	MTK_MSB9121_INFO("Reset Companion PASS\n");
	return 0;

#undef RESET_TIME
}

int mtk_msb9121_ci_power_en(int ci_power_en)
{
	struct mtk_dvb_ci_msb9121 *dvb = spi_get_drvdata(mtk_msb9121_to_spi);

	MLP_CI_CORE_ENTRY("[%s][%d]\n", __func__, __LINE__);//check

	gpiod_set_value(dvb->ci_power_en, ci_power_en);
	return 0;

}
EXPORT_SYMBOL(mtk_msb9121_ci_power_en);

int mtk_msb9121_ci_irq(void)
{
	struct mtk_dvb_ci_msb9121 *dvb = spi_get_drvdata(mtk_msb9121_to_spi);

	MLP_CI_CORE_ENTRY("[%s][%d]\n", __func__, __LINE__);//check

	return dvb->ci_irq;

}
EXPORT_SYMBOL(mtk_msb9121_ci_irq);

static int mtk_msb9121_probe(struct spi_device *pdev)
{
	struct mtk_dvb_ci_msb9121 *dvb = NULL;
	int ret = 0;
	struct device_node *dn;

	MTK_MSB9121_INFO("in\n");//check

	//spi_set_drvdata(pdev, pdev);
	mtk_msb9121_to_spi = pdev;

	dvb = kzalloc(sizeof(struct mtk_dvb_ci_msb9121), GFP_KERNEL);
	if (!dvb) {
		MTK_MSB9121_ERR("[%s][%d]kzalloc failed\n", __func__, __LINE__);
		return -ENOMEM;
	}

	dvb->pdev = pdev;

	/* "ci_reset" label is matching the device tree declaration.
	 * OUT_LOW is the value at init
	 */
	dvb->ci_reset = devm_gpiod_get(&pdev->dev, "ci_reset", GPIOD_OUT_HIGH);
	if (IS_ERR(dvb->ci_reset)) {
		MTK_MSB9121_ERR("[%s][%d]Unable to get ci_reset from dtsi\n", __func__, __LINE__);
		ret = -ENONET;
		goto mtk_msb9121_probe_err_exit;
	}

	pdev->mode |= SPI_MODE_1;
	MTK_MSB9121_INFO("spi set mode 1\n");

	/* "ci_power_en" label is matching the device tree declaration.
	 * OUT_LOW is the value at init
	 */
	dvb->ci_power_en = devm_gpiod_get(&pdev->dev, "ci_power_en", GPIOD_OUT_LOW);
	if (IS_ERR(dvb->ci_power_en)) {
		MTK_MSB9121_ERR
			("Unable to get ci_power_en from dtsi\n");
		ret = -ENONET;
		goto mtk_msb9121_probe_err_exit;
	}
	MTK_MSB9121_INFO("[%s][%d] ci_power_en get pass\n", __func__, __LINE__);

	dn = pdev->dev.of_node;
	ret = of_property_read_u32(dn, "reg_ts0_tx_drv", &dvb->reg_ts0_tx_drv);
	if (ret) {
		MTK_MSB9121_ERR("Failed to get reg_ts0_tx_drv\n");
		ret = -ENONET;
		goto mtk_msb9121_probe_err_exit;
	}
	MTK_MSB9121_INFO("reg_ts0_tx_drv=0x%x\n",dvb->reg_ts0_tx_drv);
	mtk_msb9121_set_ts0_tx_drv(dvb->reg_ts0_tx_drv);

	ret = of_property_read_u32(dn, "reg_clk_fromcard_c1_sample", &dvb->reg_clk_fromcard_c1_sample);
	if (ret) {
		MTK_MSB9121_ERR("Failed to get reg_clk_fromcard_c1_sample\n");
		ret = -ENONET;
		goto mtk_msb9121_probe_err_exit;
	}
	MTK_MSB9121_INFO("reg_clk_fromcard_c1_sample=0x%x\n",dvb->reg_clk_fromcard_c1_sample);
	mtk_msb9121_set_reg_clk_fromcard_c1_sample(dvb->reg_clk_fromcard_c1_sample);

	/* PAD_INT for IRQA interrupt gpio*/
	dvb->ci_irq = of_irq_get(dn, 0);
	if (dvb->ci_irq <= 0) {
		MTK_MSB9121_ERR("gpio_irq: no irq, fail");
		ret = -ENONET;
		goto mtk_msb9121_probe_err_exit;

	}
	MTK_MSB9121_INFO("dvb->ci_irq=%d", dvb->ci_irq);

	spi_set_drvdata(pdev, dvb);

	MTK_MSB9121_INFO("succeeded\n");
	return ret;

mtk_msb9121_probe_err_exit:
	if (dvb != NULL)
		kfree(dvb);
	return ret;
}

static int mtk_msb9121_suspend(struct device *pdev)
{
	struct mtk_dvb_ci_msb9121 *dvb = spi_get_drvdata(mtk_msb9121_to_spi);

	MLP_CI_CORE_ENTRY("[%s][%d]\n", __func__, __LINE__);

	//mdrv_pcmcia_set_power_state(E_POWER_SUSPEND);
	dvb->pdev->mode |= SPI_MODE_0;
	MTK_MSB9121_INFO("spi set mode 0\n");
	polling_flg = FALSE; //dvb core stop polling when suspend
	MTK_MSB9121_INFO("polling_flg=%d\n", polling_flg);
	return 0;
}

static int mtk_msb9121_resume(struct device *pdev)
{
	struct mtk_dvb_ci_msb9121 *dvb = spi_get_drvdata(mtk_msb9121_to_spi);;

	MLP_CI_CORE_ENTRY("[%s][%d]\n", __func__, __LINE__);

	dvb->pdev->mode |= SPI_MODE_1;
	MTK_MSB9121_INFO("spi set mode 1\n");
	//mdrv_pcmcia_set_power_state(E_POWER_RESUME);
	polling_flg = TRUE;
	MTK_MSB9121_INFO("polling_flg=%d\n", polling_flg);

	return 0;
}

static const struct dev_pm_ops mtk_msb9121_pm_ops = {
	.suspend = mtk_msb9121_suspend,
	.resume = mtk_msb9121_resume,
};


static const struct of_device_id mtk_msb9121_dt_match[] = {
	{.compatible = "mediatek,msb9121"},
	{},
};

MODULE_DEVICE_TABLE(of, mtk_msb9121_dt_match);

static struct spi_driver mtk_msb9121_driver = {
	.probe = mtk_msb9121_probe,
	.remove = mtk_msb9121_remove,
	.driver = {
		   .name = "mediatek,msb9121",
		   .owner = THIS_MODULE,
		   .of_match_table = mtk_msb9121_dt_match,
		   .pm = &mtk_msb9121_pm_ops
		   },
};

module_spi_driver(mtk_msb9121_driver);

MODULE_DESCRIPTION("Mediatek MSB9121 driver");
MODULE_AUTHOR("Jimmy-CY Chen <Jimmy-CY.Chen@mediatek.com>");
MODULE_LICENSE("GPL");
