/*
 *  CXD Demodulator driver
 *
 *  Copyright 2022 Sony Semiconductor Solutions Corporation
 *
 *  This program is free software; you can redistribute  it and/or modify it
 *  under  the terms of  the GNU General  Public License as published by the
 *  Free Software Foundation;  version 2 of the  License.
 *
 *  THIS  SOFTWARE  IS PROVIDED   ``AS  IS'' AND   ANY  EXPRESS OR IMPLIED
 *  WARRANTIES,   INCLUDING, BUT NOT  LIMITED  TO, THE IMPLIED WARRANTIES OF
 *  MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN
 *  NO  EVENT  SHALL   THE AUTHOR  BE    LIABLE FOR ANY   DIRECT, INDIRECT,
 *  INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
 *  NOT LIMITED   TO, PROCUREMENT OF  SUBSTITUTE GOODS  OR SERVICES; LOSS OF
 *  USE, DATA,  OR PROFITS; OR  BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
 *  ANY THEORY OF LIABILITY, WHETHER IN  CONTRACT, STRICT LIABILITY, OR TORT
 *  (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
 *  THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *
 *  You should have received a copy of the  GNU General Public License along
 *  with this program; if not, write  to the Free Software Foundation, Inc.,
 *  51 Franklin Street, Suite 500, Boston, MA 02110-1335, USA.
 */

#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt

#include <media/dvb_frontend.h>
#include <linux/gpio.h>
#include "sony.h"
#include "sony_dmd_common.h"
#include "sony_priv.h"

#include "cfm.h"
#include "mdrv_adapter.h"

#define SONY_UNIQUE_ID 0x2878
#define SONY_TS_RATE 109330000 //fix me
#define GPIO_A 306

static const char *pinctrl_ts;
static const char *decouple_demod;
static u16 demod_unique_id = SONY_UNIQUE_ID;
static bool lock_ever_flag;
static struct cfm_properties cfm_props_status = { 0 };
static struct cfm_properties cfm_props_configuration = { 0 };
#define MAX_PARAM_NUM    11
static struct cfm_property cfm_prop_status[MAX_PARAM_NUM];
static struct cfm_property cfm_prop_configuration[MAX_PARAM_NUM];
static bool is_demod_inited = false;

static struct sony_config sony_cfg = {
    .xtal_freq_khz = 24000, // Crystal frequency of demodulator
    .i2c_address_slvx = 0xDC, // I2C slave address of system part of demodulator
};

#define PARALLEL_BIT    8
#define SERIAL_BIT      1
#define TS_PACKET_BIT   188
static u8 delivery_sys_rec;

static void (*demod_notify)(int, enum cfm_notify_event_type_t, struct cfm_properties*);
static void demod_status_monitor_n_notify(struct dvb_frontend *fe, enum fe_status *status);


static int sony_get_frontend(struct dvb_frontend *fe, struct dtv_frontend_properties *props);

static int gpio_reset_demod()
{
    int ret, val;

    ret = gpio_request(GPIO_A, "demod_rst");
    if ((ret != -EBUSY) && (ret < 0))
        goto gpio_err;

    ret = gpio_direction_output(GPIO_A, 0);
    if (ret < 0)
        goto gpio_err_release;

    gpio_set_value(GPIO_A, 0);
    msleep(10);
    gpio_set_value(GPIO_A, 1);
    msleep(10);

    val = gpio_get_value(GPIO_A);
    if (val < 0)
        goto gpio_err_release;

    pr_info("gpio_reset_demod gpio %d's value is %d\n", GPIO_A, val);

    gpio_free(GPIO_A);
    return(0);

gpio_err_release:
    gpio_free(GPIO_A);

gpio_err:
    return -1;
}

static int sony_init(struct dvb_frontend *fe)
{
	int ret = 0;

	if (!fe)
		return -EINVAL;

    if( is_demod_inited )
    {
        pr_info("[%s][%d] demod has been init, return!\n", __func__, __LINE__);
        return ret;
    }
    pr_info("[%s][%d]demod reset and init\n", __func__, __LINE__);
    ret = gpio_reset_demod();
    if(ret < 0){
        pr_err("[%s][%d] demod reset failed\n", __func__, __LINE__);
        return ret;
    }

	ret = sony_common_initialize_demod(fe, 0);
	if(ret)
		return ret;

    is_demod_inited = true;

	return 0;
}

static int sony_sleep(struct dvb_frontend *fe)
{
	int ret = 0;

	if(!fe)
		return -EINVAL;

	ret = sony_common_sleep(fe);
	if (ret)
		return ret;

	return 0;
}

static int sony_get_frontend(struct dvb_frontend *fe, struct dtv_frontend_properties *props)
{
	int ret = 0;
    
	if(!fe || !props)
		return -EINVAL;

	pr_info(":---------------------- Monitor (sony_get_frontend) ----------\n");
	pr_info(":---------------------- --------------------- -------------------\n");
	ret = sony_common_monitor_strength(fe, props);
	if (ret) {
		pr_info(": Signal Level (Tuner) | error result        | %d \n",ret);
		pr_info(":---------------------- --------------------- -------------------\n");
	}

	ret = sony_common_monitor_cnr(fe, props);
	if (ret) {
		pr_info(": cnr                  | error result        | %d \n",ret);
		pr_info(":---------------------- --------------------- -------------------\n");
	}

	ret = sony_common_monitor_bit_error(fe, props);
	if (ret) {
		pr_info(": PER                  | error result        | %d \n",ret);
		pr_info(":---------------------- --------------------- -------------------\n");
	}

	ret = sony_common_monitor_IFAGCOut(fe, props);
	if (ret) {
		pr_info(": IFAGC OUT            | error result        | %d \n",ret);
		pr_info(":---------------------- --------------------- -------------------\n");
	}
	
	ret = sony_common_monitor_quality(fe, props);
	if (ret) {
		pr_info(": Signal Quality       | error result        | %d \n",ret);
		pr_info(":---------------------- --------------------- -------------------\n");
	}
	
	ret = sony_common_monitor_plp_info(fe, props);
	if (ret) {
		pr_info(": PLP Info             | error result        | %d \n",ret);
		pr_info(":---------------------- --------------------- -------------------\n");
	}

	return 0;
}

static int sony_read_status(struct dvb_frontend *fe, enum fe_status *status)
{
	int ret = 0;
	u32 pll_lock_status;

	if(!fe || !status)
		return -EINVAL;

	pr_info(":---------------------- Monitor (sony_read_status) -----------\n");
	pr_info(":---------------------- --------------------- -------------------\n");

	if (fe->ops.tuner_ops.get_status) {
		ret = fe->ops.tuner_ops.get_status(fe, &pll_lock_status);
		if (ret) {
			pr_info(": PLL Lock (Tuner)     | error result        | %d \n",ret);
			pr_info(":---------------------- --------------------- -------------------\n");
		}
	}

	ret = sony_common_monitor_sync_stat(fe, status);
	if (ret) {
		pr_info(": Sync State           | error result        | %d \n",ret);
		pr_info(":---------------------- --------------------- -------------------\n");
	}
    demod_status_monitor_n_notify(fe ,status);

	return 0;
}

static void demod_status_monitor_n_notify(struct dvb_frontend *fe, enum fe_status *status)
{
	//int ret;
	u32 ts_rate;

	if ((((*status) & FE_HAS_LOCK) != 0) && (lock_ever_flag == 0)) {
		cfm_prop_status[0].cmd = CFM_STREAMING_STATUS;
		cfm_prop_status[0].u.data = 1;
		cfm_props_status.num = 1;
		cfm_props_status.props = &cfm_prop_status[0];
		demod_notify(demod_unique_id, STREAMING_STATUS_CHANGED, &cfm_props_status);

		//ret = mtk_demod_ts_get_clk_rate(fe, &ts_rate);
		ts_rate = SONY_TS_RATE;
		cfm_prop_configuration[0].cmd = CFM_STREAM_CLOCK_RATE;
		cfm_prop_configuration[0].u.data = ts_rate;
		cfm_props_configuration.num = 1;
		cfm_props_configuration.props = &cfm_prop_configuration[0];
		demod_notify(demod_unique_id,
			STREAM_CONFIGURATION_CHANGED,
			&cfm_props_configuration);

		lock_ever_flag = 1;
		pr_info("[%s][%d] demod lock!, ts clock rate = %d\n", __func__, __LINE__, ts_rate);
	}

	if ((((*status) & FE_HAS_LOCK) == 0) && (lock_ever_flag == 1)) {
		cfm_prop_status[0].cmd = CFM_STREAMING_STATUS;
		cfm_prop_status[0].u.data = 0;
		cfm_props_status.num = 1;
		cfm_props_status.props = &cfm_prop_status[0];
		demod_notify(demod_unique_id,
			STREAMING_STATUS_CHANGED,
			&cfm_props_status);

		cfm_prop_configuration[0].cmd = CFM_STREAM_CLOCK_RATE;
		cfm_prop_configuration[0].u.data = 0;
		cfm_props_configuration.num = 1;
		cfm_props_configuration.props = &cfm_prop_configuration[0];
		demod_notify(demod_unique_id,
			STREAM_CONFIGURATION_CHANGED,
			&cfm_props_configuration);

		lock_ever_flag = 0;
		pr_info("[%s][%d] demod lost lock!\n", __func__, __LINE__);
	}
}

static int sony_tune(struct dvb_frontend *fe, bool re_tune, unsigned int mode_flags,
	unsigned int *delay, enum fe_status *status)
{
	int ret = 0;
	struct dtv_frontend_properties *c;

	if (!fe || !delay || !status)
		return -EINVAL;

	c = &fe->dtv_property_cache;

    if (re_tune) {
    	if (fe->ops.tuner_ops.set_params) {
    		ret = fe->ops.tuner_ops.set_params(fe);
    		if (ret)
    			return ret;
    	}

    	ret = sony_common_tune(fe);
    	if (ret)
    		return ret;

    	msleep(1000);
    	
    	ret = sony_common_monitor_sync_stat(fe, status);
    	if (ret) 
            return ret;
    }

    delivery_sys_rec = c->delivery_system;
	return 0;
}

static enum dvbfe_algo sony_get_frontend_algo(struct dvb_frontend *fe)
{
	return DVBFE_ALGO_HW;
}

static int sony_suspend(struct dvb_frontend *fe)
{
	int ret = 0;

	if(!fe)
		return -EINVAL;

	ret = sony_common_sleep(fe);
	if (ret)
		return ret;

	return 0;
}
static int sony_resume(struct dvb_frontend *fe)
{
	int ret = 0;
	struct sony_state *state;

	if (!fe)
		return -EINVAL;

	state = fe->demodulator_priv;

	ret = sony_common_initialize_demod(fe, 0);
	if(ret)
		return ret;

	return 0;
}

static int cfm_property_process_get(struct cfm_property *tvp)
{
	int ret = 0;

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

	if (!tvp) {
		pr_err("[%s][%d] tvp is null !\n",
		__func__, __LINE__);
		return -EINVAL;
	}

	switch (tvp->cmd) {
	case CFM_STREAM_CLOCK_INV:
		tvp->u.data = 0;
		pr_info("[%s][%d]CFM_TS_CLOCK_INV!, value = %d\n",
			__func__, __LINE__, tvp->u.data);
		break;
	case CFM_STREAM_EXT_SYNC:
		tvp->u.data = 1;
		pr_info("[%s][%d]CFM_TS_EXT_SYNC!, value = %d\n",
			__func__, __LINE__, tvp->u.data);
		break;
	case CFM_STREAM_DATA_BITS_NUM:
		tvp->u.data = SERIAL_BIT;
		pr_info("[%s][%d]CFM_TS_DATA_BITS_NUM!, value = %d\n",
			__func__, __LINE__, tvp->u.data);
		break;
	case CFM_STREAM_BIT_SWAP:
		tvp->u.data = 0;
		pr_info("[%s][%d]CMF_TS_BIT_SWAP!, value = %d\n",
			__func__, __LINE__, tvp->u.data);
		break;
	case CFM_TS_SOURCE_PACKET_SIZE:
		tvp->u.data = TS_PACKET_BIT;
		pr_info("[%s][%d]CMF_TS_SOURCE_PACKET_MODE!, value = %d\n",
			__func__, __LINE__, tvp->u.data);
		break;
	case CFM_DECOUPLE_DMD:
		if (delivery_sys_rec == SYS_ATSC30)
			decouple_demod = "atsc3_sony";
		memcpy(tvp->u.buffer.data, decouple_demod, strlen(decouple_demod) + 1);
		pr_info("[%s][%d]CFM_DECOUPLE_DMD!, value = %s\n",
			__func__, __LINE__, tvp->u.buffer.data);
		break;
	case CFM_STREAM_PIN_MUX_INFO:
		if (delivery_sys_rec == SYS_ATSC30)
			pinctrl_ts = "ext_ts_demod_pad_mux";

		memcpy(tvp->u.buffer.data, pinctrl_ts, strlen(pinctrl_ts) + 1);
		pr_info("[%s][%d]CFM_STREAM_PIN_MUX_INFO!, value = %s\n",
			__func__, __LINE__, tvp->u.buffer.data);
	    break;
	default:
		pr_err("[%s][%d]FE property %d doesn't exist\n",
		 __func__, __LINE__, tvp->cmd);
		pr_info("[%s][%d]FE property %d doesn't exist\n",
			__func__, __LINE__, tvp->cmd);
		return -EINVAL;
	}
	return ret;
}

int sony_get_param(struct dvb_frontend *fe, struct cfm_properties *demodProps)
{
	int ret = 0;
	int i;

	pr_info("[mdbgin_merak_demod_dd][%s][%d] __entry__\n", __func__, __LINE__);

	if (!fe) {
		pr_err("[mdbgerr_merak_demod_dd][%s][%d] fe is null !\n",
		__func__, __LINE__);
		return -EINVAL;
	}

	for (i = 0; i < demodProps->num; i++) {
		ret = cfm_property_process_get((demodProps->props)+i);
		if (ret < 0)
			goto out;
	}
out:
	return (ret > 0) ? 0 : ret;
}

int sony_set_notify(int device_unique_id,
	void (*dd_notify)(int, enum cfm_notify_event_type_t, struct cfm_properties *))
{
	demod_notify = dd_notify;

	return 0;
}

int sony_dvb_frontend_registered(int device_unique_id, struct dvb_frontend *fe)
{
	// save the mapping between device_unique_id and fe to handle multiple frontend cases
	return 0;
}

static int sony_dump_debug_info(char *buffer, int buffer_size, int *eof,
				int offset, struct dvb_frontend *fe)
{
	int ret = 0;
	int char_size = 0;
	struct dtv_frontend_properties *c = NULL;

	pr_info("[sony_dd][%s][%d] __entry__\n", __func__, __LINE__);

	if (!fe) {
		pr_err("[sony_dd][%s][%d] fe is null !\n",
		__func__, __LINE__);
		return -EINVAL;
	}

	ret = sony_common_dump_debug_info(buffer, &char_size, eof, offset, fe);
	if(ret)
		return ret;

	return char_size;
}

static struct dvb_frontend_ops sony_ops = {
	.delsys = {
	    SYS_ATSC30
	},
	.info = {
		.name = "SONY demodulator",
	    .frequency_min_hz =  57 * MHz,
		.frequency_max_hz = 866 * MHz,
		.caps =
			FE_CAN_INVERSION_AUTO |
			FE_CAN_FEC_AUTO |
			FE_CAN_QAM_AUTO |
			FE_CAN_TRANSMISSION_MODE_AUTO |
			FE_CAN_GUARD_INTERVAL_AUTO
	},
	.init = sony_init,
	.sleep = sony_sleep,
	.tune = sony_tune,
	.get_frontend = sony_get_frontend,
	.read_status = sony_read_status,
	.get_frontend_algo = sony_get_frontend_algo,
};

static const struct cfm_demod_ops sony_dmd_ops = {
	.chip_delsys.size = 1,
	.chip_delsys.element = {SYS_ATSC30},
	.info = {
		.name = "CXD demodulator",
	    .frequency_min_hz =  57 * MHz,
		.frequency_max_hz = 866 * MHz,
		.caps =
			FE_CAN_INVERSION_AUTO |
			FE_CAN_FEC_AUTO |
			FE_CAN_QAM_AUTO |
			FE_CAN_TRANSMISSION_MODE_AUTO |
			FE_CAN_GUARD_INTERVAL_AUTO
	},
	.init = sony_init,
	.sleep = sony_sleep,
	.tune = sony_tune,
	.get_demod = sony_get_frontend,
	.read_status = sony_read_status,
	.get_frontend_algo = sony_get_frontend_algo,
	.device_unique_id = SONY_UNIQUE_ID,
    //add cfm api
	.suspend = sony_suspend,
	.resume = sony_resume,
	.get_param = sony_get_param,
	.set_notify = sony_set_notify,
	.dvb_frontend_registered = sony_dvb_frontend_registered,
	.dump_debug_info = sony_dump_debug_info,
};

extern struct dvb_adapter *mdrv_get_dvb_adapter(void);
static int sony_probe(struct i2c_client *client, const struct i2c_device_id *id)
{
    int ret = 0;
	//struct sony_config *config = client->dev.platform_data;
	struct sony_config *config = &sony_cfg;
	struct sony_state *state = NULL;
    struct cfm_demod_ops *cfm_sony_ops = NULL;
    struct dvb_adapter *adapter = NULL;

	if (!client || !id)
		return -EINVAL;

    ret = gpio_reset_demod();
    if(ret < 0){
        pr_err("[%s][%d] demod reset failed\n", __func__, __LINE__);
        return ret;
    }
	state = kzalloc(sizeof(struct sony_state), GFP_KERNEL);
	if (!state)
		return -ENOMEM;

	memcpy(&state->config, config, sizeof(struct sony_config));

    state->i2c = client->adapter;
	state->fe.demodulator_priv = state;

    memcpy(&state->fe.ops, &sony_ops, sizeof(struct dvb_frontend_ops));
	
	i2c_set_clientdata(client, state);
    
    ret = sony_common_initialize_i2c_device(client);
    if (ret < 0) {
        kfree(state);
        return ret;
    }
    
    ret = sony_common_get_dmd_chip_id(client);
    if (ret < 0) {
        kfree(state);
        return ret;
    }

	adapter = mdrv_get_dvb_adapter();
	if (!adapter) {
		pr_err("[%s][%d] get dvb adapter fail\n", __func__, __LINE__);
		return -ENODEV;
	}
    cfm_sony_ops = kzalloc(sizeof(struct cfm_demod_ops), GFP_KERNEL);
    if(!cfm_sony_ops){
		pr_err("[%s][%d] kzalloc cfm_sony_ops failed!\n", __func__, __LINE__);
		return -ENODEV;
    }
    memcpy(cfm_sony_ops, &sony_dmd_ops, sizeof(struct cfm_demod_ops));
    cfm_sony_ops->demodulator_priv = state->fe.demodulator_priv;
    cfm_register_demod_device(adapter, cfm_sony_ops);

	return 0;
}

static int sony_remove(struct i2c_client *client)
{
	struct sony_state *state;

	if (!client)
		return -EINVAL;

    sony_common_finalize_i2c_device(client);

	state = i2c_get_clientdata(client);

    if (state) kfree(state);

	return 0;
}

static const struct i2c_device_id sony_id_table[] = {
	{"sony_dmd", 0},
	{}
};
MODULE_DEVICE_TABLE(i2c, sony_id_table);

static struct i2c_driver sony_driver = {
	.driver = {
		.name = "sony_dmd",
	},
	.probe    = sony_probe,
	.remove   = sony_remove,
	.id_table = sony_id_table,
};
module_i2c_driver(sony_driver);

MODULE_DESCRIPTION("CXD demodulator driver");
MODULE_AUTHOR("Sony Semiconductor Solutions Corporation");
MODULE_LICENSE("GPL v2");
