// SPDX-License-Identifier: (GPL-2.0+ OR MIT)
/*
 * Copyright (c) 2019 Amlogic, Inc. All rights reserved.
 */

#include <linux/types.h>
#include <uapi/linux/dvb/aml_fe_ext.h>

#include "atvdemod_func.h"
#include "atvauddemod_func.h"
#include "atv_demod_monitor.h"
#include "atv_demod_ops.h"
#include "atv_demod_debug.h"

static DEFINE_MUTEX(monitor_mutex);

bool atvdemod_mixer_tune_en;
bool atvdemod_overmodulated_en;
bool atv_audio_overmodulated_en;
unsigned int atv_audio_overmodulated_cnt = 1;
bool audio_det_en;
bool atvdemod_det_snr_en = true;
bool audio_thd_en;
bool atvdemod_det_nonstd_en;
bool atvaudio_det_outputmode_en = true;
bool audio_carrier_offset_det_en;
bool atvdemod_horiz_freq_det_en = true;

unsigned int atvdemod_timer_delay = 100; /* 1s */
unsigned int atvdemod_timer_delay2 = 10; /* 100ms */

bool atvdemod_timer_en = true;


static void atv_demod_monitor_do_work(struct work_struct *work)
{
	int vpll_lock = 0, line_lock = 0;
	struct atv_demod_monitor *monitor =
			container_of(work, struct atv_demod_monitor, work);
	int adc_status = 0;

	adc_status = atv_demod_get_adc_status();

	if (monitor->state == MONI_DISABLE || !adc_status)
		return;

	retrieve_vpll_carrier_lock(&vpll_lock);
	retrieve_vpll_carrier_line_lock(&line_lock);
	if ((vpll_lock != 0) || (line_lock != 0)) {
		monitor->lock_cnt = 0;
		return;
	}

	monitor->lock_cnt++;

	if (atvdemod_mixer_tune_en)
		atvdemod_mixer_tune();

	if (atvdemod_overmodulated_en)
		atvdemod_video_overmodulated();

	if (atv_audio_overmodulated_en) {
		if (monitor->lock_cnt > atv_audio_overmodulated_cnt)
			aml_audio_overmodulation(1);
	}

	if (atvdemod_det_snr_en)
		atvdemod_det_snr_series();

	if (audio_thd_en)
		audio_thd_det();

	if (atvaudio_det_outputmode_en)
		atvauddemod_set_outputmode();

	if (audio_carrier_offset_det_en)
		audio_carrier_offset_det();

	if (atvdemod_det_nonstd_en)
		atv_dmd_non_std_set(true);

	if (atvdemod_horiz_freq_det_en)
		atvdemod_horiz_freq_detection();
}

static void atv_demod_monitor_timer_handler(struct timer_list *timer)
{
	struct atv_demod_monitor *monitor = container_of(timer,
			struct atv_demod_monitor, timer);

	/* 100ms timer */
	monitor->timer.expires = jiffies +
			ATVDEMOD_INTERVAL * atvdemod_timer_delay2;
	add_timer(&monitor->timer);

	if (atvdemod_timer_en == 0)
		return;

	if (monitor->state == MONI_PAUSE)
		return;

	schedule_work(&monitor->work);
}

static void atv_demod_monitor_enable(struct atv_demod_monitor *monitor)
{
	mutex_lock(&monitor->mtx);

	if (atvdemod_timer_en && monitor->state == MONI_DISABLE) {
		atv_dmd_non_std_set(false);

		timer_setup(&monitor->timer, atv_demod_monitor_timer_handler, 0);
		monitor->timer.function = atv_demod_monitor_timer_handler;
		/* after 1s enable demod auto detect */
		monitor->timer.expires = jiffies +
				ATVDEMOD_INTERVAL * atvdemod_timer_delay;
		add_timer(&monitor->timer);
		monitor->state = MONI_ENABLE;
		monitor->lock_cnt = 0;
	} else if (atvdemod_timer_en && monitor->state == MONI_PAUSE) {
		atv_dmd_non_std_set(false);

		monitor->state = MONI_ENABLE;
		monitor->lock_cnt = 0;
	}

	mutex_unlock(&monitor->mtx);

	pr_dbg("%s: state: %d.\n", __func__, monitor->state);
}

static void atv_demod_monitor_disable(struct atv_demod_monitor *monitor)
{
	mutex_lock(&monitor->mtx);

	if (atvdemod_timer_en && monitor->state != MONI_DISABLE) {
		monitor->state = MONI_DISABLE;
		del_timer_sync(&monitor->timer);
		cancel_work_sync(&monitor->work);
	}

	mutex_unlock(&monitor->mtx);

	pr_dbg("%s: state: %d.\n", __func__, monitor->state);
}

static void atv_demod_monitor_pause(struct atv_demod_monitor *monitor)
{
	mutex_lock(&monitor->mtx);

	if (monitor->state == MONI_ENABLE) {
		monitor->state = MONI_PAUSE;
		atv_dmd_non_std_set(false);
		cancel_work_sync(&monitor->work);
	}

	mutex_unlock(&monitor->mtx);
}

void atv_demod_monitor_init(struct atv_demod_monitor *monitor)
{
	mutex_lock(&monitor_mutex);

	mutex_init(&monitor->mtx);

	monitor->state = MONI_DISABLE;
	monitor->lock = false;
	monitor->lock_cnt = 0;
	monitor->disable = atv_demod_monitor_disable;
	monitor->enable = atv_demod_monitor_enable;
	monitor->pause = atv_demod_monitor_pause;

	INIT_WORK(&monitor->work, atv_demod_monitor_do_work);

	mutex_unlock(&monitor_mutex);
}
