// SPDX-License-Identifier: GPL-2.0-only
/* Copyright (c) 2016, 2018, 2020, The Linux Foundation. All rights reserved. */

#include <linux/spinlock.h>
#include <linux/ktime.h>
#include <linux/debugfs.h>
#include <linux/uaccess.h>

#include "mdss.h"
#include "mdss_mdp.h"
#include "mdss_debug.h"

#define FRC_DEFAULT_ENABLE 1
#define FRC_DEFAULT_LOG_ENABLE 0

#define FRC_DEBUG_STAT_MAX_SLOT 1024

DEFINE_SPINLOCK(frc_lock);

struct cadence {
	int repeat;
	int kickoff_idx;
	int vsync_idx;
};

struct vsync_stat {
	int vsync_cnt;
	s64 vsync_ts;
};

struct kick_stat {
	s64 kickoff_ts;
	u32 vsync;
	int remain;
	struct mdss_mdp_frc_info frc_info;
};

struct circ_buf {
	int index;
	int size;
	int cnt;
};

struct vsync_samples {
	struct circ_buf cbuf;
	struct vsync_stat samples[FRC_DEBUG_STAT_MAX_SLOT];
};

struct kickoff_samples {
	struct circ_buf cbuf;
	struct kick_stat samples[FRC_DEBUG_STAT_MAX_SLOT];
};

#define cbuf_init(cbuf, len) { \
	struct circ_buf *cb = (struct circ_buf *)(cbuf); \
	cb->index = 0; \
	cb->cnt = 0; \
	cb->size = (len); }

#define cbuf_begin(cbuf, start) ({ \
	struct circ_buf *cb = (struct circ_buf *)(cbuf); \
	(cb->index > cb->size) ? (cb->index + (start)) % cb->size : (start); })

#define cbuf_end(cbuf, end) ({ \
	struct circ_buf *cb = (struct circ_buf *)(cbuf); \
	((cb->index - 1 - (end)) % cb->size); })

#define cbuf_cur(cbuf) ({ \
	struct circ_buf *cb = (struct circ_buf *)(cbuf); \
	(cb->index % cb->size); })

#define cbuf_next(cbuf, idx) ({ \
	struct circ_buf *cb = (struct circ_buf *)(cbuf); \
	(((idx)+1) % cb->size); })

#define cbuf_prev(cbuf, idx) ({ \
	struct circ_buf *cb = (struct circ_buf *)(cbuf); \
	(((idx)-1) % cb->size); })

#define current_sample(cbuf) ({ \
	struct circ_buf *cb = (struct circ_buf *)(cbuf); \
	int idx = cb->index % cb->size; \
	&((cbuf)->samples[idx]); })

#define insert_sample(cbuf, sample) { \
	struct circ_buf *cb = (struct circ_buf *)(cbuf); \
	int idx = cb->index % cb->size; \
	(cbuf)->samples[idx] = (sample); \
	cb->cnt++; \
	cb->index++; }

#define advance_sample(cbuf) { \
	struct circ_buf *cb = (struct circ_buf *)(cbuf); \
	cb->cnt++; \
	cb->index++; }

#define sample_cnt(cbuf) ({ \
	struct circ_buf *cb = (struct circ_buf *)(cbuf); \
	(cb->cnt % cb->size); })

struct mdss_dbg_frc_stat {
	int cadence_id;
	int display_fp1000s;
	struct vsync_samples vs;
	struct kickoff_samples ks;
	struct cadence cadence_info[FRC_DEBUG_STAT_MAX_SLOT];
};

struct mdss_dbg_frc {
	struct dentry *frc;
	int frc_enable;
	int log_enable;
	struct mdss_dbg_frc_stat frc_stat[2];
	int index;
} mdss_dbg_frc;

static struct mdss_dbg_frc_stat *__current_frc_stat(
	struct mdss_dbg_frc *dbg_frc)
{
	return &dbg_frc->frc_stat[dbg_frc->index];
}

static void __init_frc_stat(struct mdss_dbg_frc *dbg_frc)
{
	struct mdss_dbg_frc_stat *frc_stat = __current_frc_stat(dbg_frc);

	memset(frc_stat, 0, sizeof(struct mdss_dbg_frc_stat));

	/* TODO: increase vsync buffer to avoid wrap around */
	cbuf_init(&frc_stat->ks, FRC_DEBUG_STAT_MAX_SLOT);
	cbuf_init(&frc_stat->vs, FRC_DEBUG_STAT_MAX_SLOT/2);
}

static struct mdss_dbg_frc_stat *__swap_frc_stat(
	struct mdss_dbg_frc *dbg_frc)
{
	int prev_index = dbg_frc->index;

	dbg_frc->index = (dbg_frc->index + 1) % 2;
	__init_frc_stat(dbg_frc);

	return &dbg_frc->frc_stat[prev_index];
}

void mdss_debug_frc_add_vsync_sample(struct mdss_mdp_ctl *ctl,
	ktime_t vsync_time)
{
	if (mdss_dbg_frc.log_enable) {
		unsigned long flags;
		struct mdss_dbg_frc_stat *frc_stat;
		struct vsync_stat vstat;

		spin_lock_irqsave(&frc_lock, flags);
		frc_stat = __current_frc_stat(&mdss_dbg_frc);
		vstat.vsync_cnt = ctl->vsync_cnt;
		vstat.vsync_ts = ktime_to_us(vsync_time);
		insert_sample(&frc_stat->vs, vstat);
		spin_unlock_irqrestore(&frc_lock, flags);
	}
}

/* collect FRC data for debug ahead of repeat */
void mdss_debug_frc_add_kickoff_sample_pre(struct mdss_mdp_ctl *ctl,
	struct mdss_mdp_frc_info *frc_info, int remaining)
{
	if (mdss_dbg_frc.log_enable) {
		unsigned long flags;
		struct mdss_dbg_frc_stat *frc_stat;

		spin_lock_irqsave(&frc_lock, flags);
		frc_stat = __current_frc_stat(&mdss_dbg_frc);

		/* Don't update statistics when video repeats */
		if (frc_info->cur_frc.frame_cnt
				!= frc_info->last_frc.frame_cnt) {
			struct kick_stat *kstat = current_sample(&frc_stat->ks);

			kstat->vsync = ctl->vsync_cnt;
		}

		frc_stat->cadence_id = frc_info->cadence_id;
		frc_stat->display_fp1000s = frc_info->display_fp1000s;
		spin_unlock_irqrestore(&frc_lock, flags);
	}
}

/* collect FRC data for debug later than repeat */
void mdss_debug_frc_add_kickoff_sample_post(struct mdss_mdp_ctl *ctl,
	struct mdss_mdp_frc_info *frc_info, int remaining)
{
	if (mdss_dbg_frc.log_enable) {
		unsigned long flags;

		spin_lock_irqsave(&frc_lock, flags);
		/* Don't update statistics when video repeats */
		if (frc_info->cur_frc.frame_cnt
				!= frc_info->last_frc.frame_cnt) {
			struct mdss_dbg_frc_stat *frc_stat
				= __current_frc_stat(&mdss_dbg_frc);
			struct kick_stat *kstat = current_sample(&frc_stat->ks);
			ktime_t kickoff_time = ktime_get();

			kstat->kickoff_ts = ktime_to_us(kickoff_time);
			kstat->frc_info = *frc_info;
			kstat->remain = remaining;

			advance_sample(&frc_stat->ks);
		}
		spin_unlock_irqrestore(&frc_lock, flags);
	}
}

int mdss_debug_frc_frame_repeat_disabled(void)
{
	return !mdss_dbg_frc.frc_enable;
}

/* find the closest vsync right to this kickoff time */
static int __find_right_vsync(struct vsync_samples *vs, s64 kick)
{
	int idx = cbuf_begin(vs, 0);

	for (; idx != cbuf_end(vs, 0); idx = cbuf_next(vs, idx)) {
		if (vs->samples[idx].vsync_ts >= kick)
			return idx;
	}

	return -EBADSLT;
}

/*
 * These repeat number might start from any position in the sequence. E.g.,
 * given cadence 23223, the first repeat might be 3 and the repeating pattern
 * might be 32232, also, the first repeat could be the 4th 3, so the repeating
 * pattern will be 32322. Below predefined patterns are going to be used to
 * find the position of the first repeat in the full sequence, then we can
 * easily known what the remaining expected repeats.
 */
#define CADENCE_22_LEN 2
static int pattern_22[CADENCE_22_LEN] = {2, 2};

#define CADENCE_23_LEN 2
static int pattern_23[CADENCE_23_LEN][CADENCE_23_LEN] = {
	{2, 3},
	{3, 2}
};

#define CADENCE_23223_LEN 5
static int pattern_23223[CADENCE_23223_LEN][CADENCE_23223_LEN] = {
	{2, 3, 2, 2, 3},
	{3, 2, 2, 3, 2},
	{2, 2, 3, 2, 3},
	{2, 3, 2, 3, 2},
	{3, 2, 3, 2, 2}
};

static int __compare_init_pattern(struct mdss_dbg_frc_stat *frc_stat,
	int *pattern, int s_idx, int e_idx)
{
	int i;

	for (i = 0; i < min(CADENCE_23223_LEN, e_idx-s_idx+1); i++) {
		if (frc_stat->cadence_info[i].repeat != pattern[i])
			break;
	}

	return i == min(CADENCE_23223_LEN, e_idx-s_idx+1);
}

static int __pattern_len(int cadence_id)
{
	switch (cadence_id) {
	case FRC_CADENCE_22:
		return CADENCE_22_LEN;
	case FRC_CADENCE_23:
		return CADENCE_23_LEN;
	case FRC_CADENCE_23223:
		return CADENCE_23223_LEN;
	}

	return 0;
}

static int *__select_pattern(struct mdss_dbg_frc_stat *frc_stat,
	int s_idx, int e_idx)
{
	int i;

	switch (frc_stat->cadence_id) {
	case FRC_CADENCE_22:
		return pattern_22;
	case FRC_CADENCE_23:
		return frc_stat->cadence_info[s_idx].repeat == 2 ?
			pattern_23[0] : pattern_23[1];
	case FRC_CADENCE_23223:
		for (i = 0; i < CADENCE_23223_LEN; i++) {
			if (__compare_init_pattern(frc_stat,
					pattern_23223[i], s_idx, e_idx))
				return pattern_23223[i];
		}
	}

	return NULL;
}

static void __check_cadence_pattern(struct mdss_dbg_frc_stat *frc_stat,
	int s_idx, int e_idx)
{
	if (s_idx < e_idx) {
		int *pattern = __select_pattern(frc_stat, s_idx, e_idx);
		int pattern_len = __pattern_len(frc_stat->cadence_id);
		struct vsync_samples *vs = &frc_stat->vs;
		struct kickoff_samples *ks = &frc_stat->ks;
		int i;

		if (!pattern) {
			pr_info("Can't match pattern in the beginning\n");
			return;
		}

		for (i = s_idx; i < e_idx; i++) {
			if (frc_stat->cadence_info[i].repeat !=
					pattern[i % pattern_len]) {
				int kidx =
					frc_stat->cadence_info[i].kickoff_idx;
				pr_info("\tUnexpected Sample: repeat=%d, kickoff=%lld, vsync=%lld\n",
					frc_stat->cadence_info[i].repeat,
					ks->samples[kidx].kickoff_ts,
					vs->samples[kidx].vsync_ts);
				break;
			}
		}

		/* init check */
		if (i < e_idx)
			__check_cadence_pattern(frc_stat, i+1, e_idx);
	}
}

static int __is_cadence_check_supported(struct mdss_dbg_frc_stat *frc_stat)
{
	int cadence = frc_stat->cadence_id;

	return cadence == FRC_CADENCE_22 ||
		cadence == FRC_CADENCE_23 ||
		cadence == FRC_CADENCE_23223;
}

static int __find_first_valid_sample(struct mdss_dbg_frc_stat *frc_stat)
{
	int i = 0;
	struct kickoff_samples *ks = &frc_stat->ks;
	struct vsync_samples *vs = &frc_stat->vs;
	struct kick_stat *cur_kstat = &ks->samples[cbuf_begin(ks, 0)];
	s64 cur_kick = cur_kstat->kickoff_ts;
	int cur_disp = __find_right_vsync(vs, cur_kick);
	struct vsync_stat *vstat = &vs->samples[cur_disp];

	i = cbuf_begin(ks, 0);
	for (; i != cbuf_end(ks, 1); i = cbuf_next(ks, i)) {
		if (vstat->vsync_ts < ks->samples[i].kickoff_ts)
			break;
	}

	return i;
}

static int __analyze_frc_samples(struct mdss_dbg_frc_stat *frc_stat, int start)
{
	struct kickoff_samples *ks = &frc_stat->ks;
	struct vsync_samples *vs = &frc_stat->vs;
	int i = start;
	int cnt = 0;

	/* analyze kickoff & vsync samples */
	for (; i != cbuf_end(ks, 1); i = cbuf_next(ks, i)) {
		/*
		 * TODO: vsync buffer is not enough so it might
		 * wrap around and drop the samples in the beginning.
		 * skip the first/last sample.
		 */
		s64 cur_kick = ks->samples[i].kickoff_ts;
		s64 right_kick = ks->samples[cbuf_next(ks, i)].kickoff_ts;
		int cur_disp = __find_right_vsync(vs, cur_kick);
		int right_disp = __find_right_vsync(vs, right_kick);

		frc_stat->cadence_info[cnt].repeat =
			right_disp >= cur_disp ? right_disp - cur_disp :
			right_disp - cur_disp + vs->cbuf.size;
		frc_stat->cadence_info[cnt].kickoff_idx = i;
		frc_stat->cadence_info[cnt].vsync_idx = cur_disp;
		cnt++;
	}

	return cnt;
}

static void __dump_frc_samples(struct mdss_dbg_frc_stat *frc_stat, int cnt)
{
	struct kickoff_samples *ks = &frc_stat->ks;
	struct vsync_samples *vs = &frc_stat->vs;
	int i = 0;

	pr_info("===== Collected FRC statistics: Cadence %d, FPS %d =====\n",
		frc_stat->cadence_id, frc_stat->display_fp1000s);
	pr_info("\tKickoff VS. VSYNC:\n");
	for (i = 0; i < cnt; i++) {
		struct cadence *p_info = &frc_stat->cadence_info[i];
		struct kick_stat *kickoff = &ks->samples[p_info->kickoff_idx];
		struct vsync_stat *vsync = &vs->samples[p_info->vsync_idx];

		pr_info("\t[K: %lld V: (%d)%lld R: %d] c_ts: %lld c_cnt: %d b_ts: %lld b_cnt: %d l_ts: %lld l_cnt: %d b_v: %d l_v: %d l_r: %d pos: %d vs: %d remain: %d\n",
			kickoff->kickoff_ts,
			vsync->vsync_cnt,
			vsync->vsync_ts,
			p_info->repeat,
			kickoff->frc_info.cur_frc.timestamp,
			kickoff->frc_info.cur_frc.frame_cnt,
			kickoff->frc_info.base_frc.timestamp,
			kickoff->frc_info.base_frc.frame_cnt,
			kickoff->frc_info.last_frc.timestamp,
			kickoff->frc_info.last_frc.frame_cnt,
			kickoff->frc_info.base_vsync_cnt,
			kickoff->frc_info.last_vsync_cnt,
			kickoff->frc_info.last_repeat,
			kickoff->frc_info.gen.pos,
			kickoff->vsync,
			kickoff->remain);
	}

	pr_info("===== End FRC statistics: =====\n");
}

static bool __is_frc_stat_empty(struct mdss_dbg_frc_stat *frc_stat)
{
	return sample_cnt(&frc_stat->vs) == 0
		|| sample_cnt(&frc_stat->ks) == 0;
}

static void mdss_frc_dump_debug_stat(struct mdss_dbg_frc *frc_debug)
{
	int i = 0;
	int cnt = 0;
	struct mdss_dbg_frc_stat *frc_stat = NULL;
	unsigned long flags;

	/* swap buffer of collect & analyze */
	spin_lock_irqsave(&frc_lock, flags);
	frc_stat = __swap_frc_stat(frc_debug);
	spin_unlock_irqrestore(&frc_lock, flags);

	if (__is_frc_stat_empty(frc_stat))
		return;

	/* find the first valid kickoff sample */
	i = __find_first_valid_sample(frc_stat);

	/* analyze kickoff & vsync samples */
	cnt = __analyze_frc_samples(frc_stat, i);

	/* print collected statistics FRC data */
	__dump_frc_samples(frc_stat, cnt);

	if (__is_cadence_check_supported(frc_stat)) {
		pr_info("===== Check Cadence Pattern: =====\n");
		__check_cadence_pattern(frc_stat, 0, cnt);
		pr_info("===== Check Cadence Pattern End =====\n");
	}
}

static ssize_t mdss_frc_log_read(struct file *file,
			char __user *buff, size_t count, loff_t *ppos)
{
	int len = 0;
	char buf[32] = {'\0'};

	if (*ppos)
		return 0; /* the end */

	len = scnprintf(buf, sizeof(buf), "%d\n", mdss_dbg_frc.log_enable);
	if (len < 0 || len >= sizeof(buf))
		return 0;

	if ((count < sizeof(buf)) || copy_to_user(buff, buf, len))
		return -EFAULT;

	*ppos += len;	/* increase offset */

	return len;
}

static ssize_t mdss_frc_log_write(struct file *file,
		    const char __user *user_buf, size_t count, loff_t *ppos)
{
	char buf[32];
	int enable;
	unsigned long flags;

	if (count >= sizeof(buf))
		return -EFAULT;

	if (copy_from_user(buf, user_buf, count))
		return -EFAULT;

	buf[count] = '\0';
	if (kstrtoint(buf, 0, &enable))
		return -EFAULT;

	if (enable && !mdss_dbg_frc.log_enable) {
		spin_lock_irqsave(&frc_lock, flags);
		__init_frc_stat(&mdss_dbg_frc);
		spin_unlock_irqrestore(&frc_lock, flags);
	}
	mdss_dbg_frc.log_enable = enable;

	pr_info("log_enable = %d\n", mdss_dbg_frc.log_enable);

	return count;
}

static const struct file_operations mdss_dbg_frc_log_fops = {
	.read = mdss_frc_log_read,
	.write = mdss_frc_log_write,
};

static ssize_t mdss_frc_dump_write(struct file *file,
	const char __user *user_buf, size_t count, loff_t *ppos)
{
	mdss_frc_dump_debug_stat(&mdss_dbg_frc);

	return count;
}

static const struct file_operations mdss_dbg_frc_dump_fops = {
	.read = NULL,
	.write = mdss_frc_dump_write,
};

int mdss_create_frc_debug(struct mdss_debug_data *mdd)
{
	mdss_dbg_frc.frc = debugfs_create_dir("frc", mdd->root);
	if (IS_ERR_OR_NULL(mdss_dbg_frc.frc)) {
		pr_err("debugfs_create_dir fail, error %ld\n",
				PTR_ERR(mdss_dbg_frc.frc));
		mdss_dbg_frc.frc = NULL;
		return -ENODEV;
	}

	debugfs_create_u32("enable", 0644, mdss_dbg_frc.frc,
			&mdss_dbg_frc.frc_enable);
	debugfs_create_file("log", 0644, mdss_dbg_frc.frc, NULL,
						&mdss_dbg_frc_log_fops);
	debugfs_create_file("dump", 0644, mdss_dbg_frc.frc, NULL,
						&mdss_dbg_frc_dump_fops);

	mdss_dbg_frc.frc_enable = FRC_DEFAULT_ENABLE;
	mdss_dbg_frc.log_enable = FRC_DEFAULT_LOG_ENABLE;
	mdss_dbg_frc.index = 0;

	pr_debug("frc_dbg: frc_enable:%d log_enable:%d\n",
		mdss_dbg_frc.frc_enable, mdss_dbg_frc.log_enable);

	return 0;
}
