// SPDX-License-Identifier: GPL-2.0
/*
 * Copyright (C) 2016 MediaTek Inc.
 */

#include "ccci_fsm_internal.h"
#include "modem_sys.h"

#define POLLING_INTERVAL_TIME 15000
#define POLLING_TIMEOUT 15
#define FORCE_ASSERT_TIMEOUT 15000

static int fsm_get_no_response_assert_type(struct ccci_fsm_poller *poller_ctl)
{
	unsigned long long traffic_info[3] = {0};
	u64 latest_isr_time = 0;
	u64 latest_q0_isr_time = 0;
	u64 latest_q0_rx_time = 0;
	unsigned long long last_poll_time = 0;
	int ret;
	unsigned long rem_nsec0, rem_nsec1, rem_nsec2, rem_nsec3;

	if (!poller_ctl)
		return MD_FORCE_ASSERT_BY_MD_NO_RESPONSE;

	ret = ccci_hif_dump_status(1 << MD1_NORMAL_HIF, DUMP_FLAG_GET_TRAFFIC,
			traffic_info, sizeof(traffic_info));
	if (!ret) {
		latest_isr_time = traffic_info[0];
		latest_q0_isr_time = traffic_info[1];
		latest_q0_rx_time = traffic_info[2];
	}
	last_poll_time = poller_ctl->latest_poll_start_time;

	rem_nsec0 = (last_poll_time == 0 ?
		0 : do_div(last_poll_time, NSEC_PER_SEC));
	rem_nsec1 = (latest_isr_time == 0 ?
		0 : do_div(latest_isr_time, NSEC_PER_SEC));
	rem_nsec2 = (latest_q0_isr_time == 0 ?
		0 : do_div(latest_q0_isr_time, NSEC_PER_SEC));
	rem_nsec3 = (latest_q0_rx_time == 0 ?
		0 : do_div(latest_q0_rx_time, NSEC_PER_SEC));

	CCCI_ERROR_LOG(0, FSM,
		"polling: start=%llu.%06lu, isr=%llu.%06lu,q0_isr=%llu.%06lu, q0_rx=%llu.%06lu\n",
		last_poll_time, rem_nsec0 / 1000,
		latest_isr_time, rem_nsec1 / 1000,
		latest_q0_isr_time, rem_nsec2 / 1000,
		latest_q0_rx_time, rem_nsec3 / 1000);
	/* Check whether ap received polling queue irq, after polling start */
	/* send status > last q0 isr time */
	if (poller_ctl->latest_poll_start_time > traffic_info[1]) {
		/* send status < last isr time */
		if (poller_ctl->latest_poll_start_time < traffic_info[0])
			CCCI_ERROR_LOG(0, FSM,
				"After polling start, have isr but no polling isr, maybe md no response\n");
		else {
			CCCI_ERROR_LOG(0, FSM,
				"After polling start, no any irq, check ap irq status and md side send or no\n");
		}
		return MD_FORCE_ASSERT_BY_MD_NO_RESPONSE;
	}
	 /* send status > last wq time, < last isr time */
	if (poller_ctl->latest_poll_start_time > traffic_info[2]) {
		CCCI_ERROR_LOG(0, FSM,
		"no work after poll but isr, rx queue maybe blocked\n");
		return MD_FORCE_ASSERT_BY_AP_Q0_BLOCKED;
	}

	CCCI_ERROR_LOG(0, FSM,
	"AP polling isr & rx queue & kthread normally after polling start, MD may not response\n");
	return MD_FORCE_ASSERT_BY_MD_NO_RESPONSE;
}

static int fsm_poll_main(void *data)
{
	struct ccci_fsm_poller *poller_ctl = (struct ccci_fsm_poller *)data;
	struct ccci_fsm_ctl *ctl = container_of(poller_ctl,
		struct ccci_fsm_ctl, poller_ctl);
	int ret, assert_md_type, count;
	enum MD_STATE md_state;

	while (1) {
		md_state = ccci_fsm_get_md_state();
		if (md_state != READY
			|| ccci_port_get_critical_user(CRIT_USR_MDLOG) != 1)
			goto next;
		poller_ctl->poller_state = FSM_POLLER_WAITING_RESPONSE;
		poller_ctl->latest_poll_start_time = local_clock();
		ret = ccci_port_send_msg_to_md(CCCI_STATUS_TX, 0, 0, 1);
		CCCI_NORMAL_LOG(0, FSM,
				"poll MD status send msg %d\n", ret);
		ret = wait_event_timeout(poller_ctl->status_rx_wq,
		poller_ctl->poller_state == FSM_POLLER_RECEIVED_RESPONSE,
		POLLING_TIMEOUT * HZ);
		CCCI_NORMAL_LOG(0, FSM,
			"poll MD status wait done %d\n", ret);
		if (!ret) { /* timeout */
			md_state = ccci_fsm_get_md_state();
			if (md_state == READY) {
				CCCI_ERROR_LOG(0, FSM,
					"poll MD status timeout, force assert\n");
				/*
				 * mtk09077: q0.mp2 ccci_hif_ccif.c:
				 * dump_ctrl_path_hif_status();
				 */
				assert_md_type =
				fsm_get_no_response_assert_type(poller_ctl);
				if (assert_md_type
					== MD_FORCE_ASSERT_BY_MD_NO_RESPONSE)
					ccci_md_dump_info(DUMP_FLAG_IRQ_STATUS, NULL, 0);
				ccci_md_dump_info(DUMP_FLAG_QUEUE_0, NULL, 0);
				ccci_md_force_assert(assert_md_type, NULL, 0);

				count = 0;
				while (count < FORCE_ASSERT_TIMEOUT / 200) {
					if (ccci_fsm_get_md_state() == EXCEPTION) {
						count = 0;
						break;
					}
					count++;
					msleep(200);
				}
				if (count) {
					CCCI_ERROR_LOG(0, FSM,
						"MD long time no response\n");
					ccci_md_dump_info(DUMP_FLAG_QUEUE_0, NULL, 0);
					fsm_append_command(ctl,
						CCCI_COMMAND_MD_HANG, 0);
				}
			}
		}
next:
		msleep(POLLING_INTERVAL_TIME);
	}
	return 0;
}


int fsm_poller_init(struct ccci_fsm_poller *poller_ctl)
{
	init_waitqueue_head(&poller_ctl->status_rx_wq);

	poller_ctl->poll_thread = kthread_run(fsm_poll_main, poller_ctl,
		"ccci_poll");
	return 0;
}


int ccci_fsm_recv_status_packet(struct sk_buff *skb)
{
	struct ccci_fsm_ctl *ctl = ccci_fsm_entries;
	struct ccci_fsm_poller *poller_ctl = NULL;

	if (!ctl)
		return -CCCI_ERR_INVALID_PARAM;
	poller_ctl = &ctl->poller_ctl;

	CCCI_NORMAL_LOG(0, FSM,
		"received MD status response %x\n", *(((u32 *)skb->data) + 2));
	/*
	 * ccci_util_cmpt_mem_dump(CCCI_DUMP_REPEAT, skb->data, skb->len);
	 */
	poller_ctl->poller_state = FSM_POLLER_RECEIVED_RESPONSE;
	wake_up(&poller_ctl->status_rx_wq);
	ccci_free_skb(skb);
	return 0;
}

