// SPDX-License-Identifier: (GPL-2.0+ OR MIT)
/*
 * drivers/amlogic/media/video_processor/videosync/videosync.c
 *
 * Copyright (C) 2017 Amlogic, Inc. All rights reserved.
 *
 * 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; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
 * more details.
 *
 */

#undef DEBUG
#define DEBUG

#include "videosync.h"
#include <linux/amlogic/major.h>
#include <linux/platform_device.h>
#include <linux/uaccess.h>
#include <linux/sched/clock.h>
#ifdef CONFIG_AMLOGIC_MEDIA_FRAME_SYNC
#include <linux/amlogic/media/frame_sync/timestamp.h>
#include <linux/amlogic/media/frame_sync/tsync.h>
#endif

#define VIDEOSYNC_DEVICE_NAME   "videosync"
#define RECEIVER_NAME "videosync"
#define PROVIDER_NAME "videosync"

bool videosync_inited;/*false*/
static bool show_nosync;/*false*/
static bool smooth_sync_enable;
static int enable_video_discontinue_report = 1;
static u32 system_time_scale_base = 1;
static s32 system_time_inc_adj;/*?*/
static u32 vsync_pts_inc;/*?*/
static u32 omx_version = 3;
static u32 vp_debug_flag;
static bool no_render;/* default: false */
static bool async_mode;/* default: false */
/*static u32 video_early_threshold = 900;  default: 900=>10ms */

/* video freerun mode */
#define FREERUN_NONE    0	/* no freerun mode */
#define FREERUN_NODUR   1	/* freerun without duration */
#define FREERUN_DUR     2	/* freerun with duration */
#define M_PTS_SMOOTH_MAX 45000
#define M_PTS_SMOOTH_MIN 2250
#define M_PTS_SMOOTH_ADJUST 900
#define DURATION_GCD 750

static int duration_gcd = DURATION_GCD;

static int omx_pts_interval_upper = 11000;
static int omx_pts_interval_lower = -5500;

#define PRINT_ERROR			0X0
#define PRINT_QUEUE_STATUS	0X0001
#define PRINT_TIMESTAMP		0X0002
#define PRINT_PATTERN		0X0004
#define PRINT_OTHER			0X0008

static struct videosync_dev *vp_dev;
static uint show_first_frame_nosync;
static uint max_delata_time;

static u32 cur_omx_index;

#define PTS_32_PATTERN_DETECT_RANGE 10
#define PTS_22_PATTERN_DETECT_RANGE 10
#define PTS_41_PATTERN_DETECT_RANGE 2
#define PTS_32_PATTERN_DURATION 3750
#define PTS_22_PATTERN_DURATION 3000

enum video_refresh_pattern {
	PTS_32_PATTERN = 0,
	PTS_22_PATTERN,
	PTS_41_PATTERN,
	PTS_MAX_NUM_PATTERNS
};

static int pts_trace;
static int pre_pts_trace;
static bool pts_enforce_pulldown = true;
static int pts_pattern[3] = {0, 0, 0};
static int pts_pattern_enter_cnt[3] = {0, 0, 0};
static int pts_pattern_exit_cnt[3] = {0, 0, 0};
static int pts_log_enable[3] = {0, 0, 0};
static int pts_escape_vsync = -1;
static s32 vsync_pts_align = -DURATION_GCD / 2;
static int pts_pattern_detected = -1;

static int vp_print(char *name, int debug_flag, const char *fmt, ...)
{
	if ((vp_debug_flag & debug_flag) ||
	    debug_flag == PRINT_ERROR) {
		unsigned char buf[512];
		int len = 0;
		va_list args;

		va_start(args, fmt);
		len = sprintf(buf, "[%s]", name);
		vsnprintf(buf + len, 512 - len, fmt, args);
		pr_info("%s", buf);
		va_end(args);
	}
	return 0;
}

static u32 ts_pcrscr_get(struct videosync_s *dev_s)
{
	u32 sys_time = 0;

	sys_time = dev_s->system_time;
	return sys_time;
}

static void ts_pcrscr_set(struct videosync_s *dev_s, u32 pts)
{
	dev_s->system_time = pts;
	vp_print(dev_s->vf_receiver_name, PRINT_TIMESTAMP,
		 "%s sys_time %u\n", __func__, dev_s->system_time);
}

static void ts_pcrscr_enable(struct videosync_s *dev_s, u32 enable)
{
	dev_s->system_time_up = enable;
}

static u32 ts_pcrscr_enable_state(struct videosync_s *dev_s)
{
	return dev_s->system_time_up;
}

static void log_vsync_video_pattern(int pattern)
{
	int factor1 = 0, factor2 = 0, pattern_range = 0;

	if (pattern >= PTS_MAX_NUM_PATTERNS)
		return;

	if (pattern == PTS_32_PATTERN) {
		factor1 = 3;
		factor2 = 2;
		pattern_range =  PTS_32_PATTERN_DETECT_RANGE;
	} else if (pattern == PTS_22_PATTERN) {
		factor1 = 2;
		factor2 = 2;
		pattern_range =  PTS_22_PATTERN_DETECT_RANGE;
	} else if (pattern == PTS_41_PATTERN) {
		pr_info("not support 41 pattern\n");
		return;
	}

	/* update 3:2 or 2:2 mode detection */
	if ((pre_pts_trace == factor1 && pts_trace == factor2) ||
	    (pre_pts_trace == factor2 && pts_trace == factor1)) {
		if (pts_pattern[pattern] < pattern_range) {
			pts_pattern[pattern]++;
			if (pts_pattern[pattern] == pattern_range) {
				pts_pattern_enter_cnt[pattern]++;
				pts_pattern_detected = pattern;
				if (pts_log_enable[pattern])
					pr_info("video %d:%d mode detected\n",
						factor1, factor2);
			}
		}
	} else if (pts_pattern[pattern] == pattern_range) {
		pts_pattern[pattern] = 0;
		pts_pattern_exit_cnt[pattern]++;
		if (pts_log_enable[pattern])
			pr_info("video %d:%d mode broken\n", factor1, factor2);
	} else {
		pts_pattern[pattern] = 0;
	}
}

static void vsync_video_pattern(void)
{
	/*log_vsync_video_pattern(PTS_32_PATTERN);*/
	log_vsync_video_pattern(PTS_22_PATTERN);
	/*log_vsync_video_pattern(PTS_41_PATTERN);*/
}

static inline void vpts_perform_pulldown(struct videosync_s *dev_s,
					 struct vframe_s *next_vf,
					 bool *expired)
{
	int pattern_range, expected_curr_interval;
	int expected_prev_interval;
	int next_vf_nextpts = 0;
	int nextPts;

	/* Dont do anything if we have invalid data */
	if (!next_vf || !next_vf->pts)
		return;
	if (next_vf->next_vf_pts_valid)
		next_vf_nextpts = next_vf->next_vf_pts;

	switch (pts_pattern_detected) {
	case PTS_32_PATTERN:
		pattern_range = PTS_32_PATTERN_DETECT_RANGE;
		switch (pre_pts_trace) {
		case 3:
			expected_prev_interval = 3;
			expected_curr_interval = 2;
			break;
		case 2:
			expected_prev_interval = 2;
			expected_curr_interval = 3;
			break;
		default:
			return;
		}
		if (!next_vf_nextpts)
			next_vf_nextpts = next_vf->pts +
				PTS_32_PATTERN_DURATION;
		break;
	case PTS_22_PATTERN:
		if (pre_pts_trace != 2)
			return;
		pattern_range =  PTS_22_PATTERN_DETECT_RANGE;
		expected_prev_interval = 2;
		expected_curr_interval = 2;
		if (!next_vf_nextpts)
			next_vf_nextpts = next_vf->pts +
				PTS_22_PATTERN_DURATION;
		break;
	case PTS_41_PATTERN:
		/* TODO */
	default:
		return;
	}

	/* We do nothing if  we dont have enough data*/
	if (pts_pattern[pts_pattern_detected] != pattern_range)
		return;

	if (*expired) {
		if (pts_trace < expected_curr_interval) {
			/* 2323232323..2233..2323, prev=2, curr=3,*/
			/* check if next frame will toggle after 3 vsyncs */
			/* 22222...22222 -> 222..2213(2)22...22 */
			/* check if next frame will toggle after 3 vsyncs */
			nextPts = ts_pcrscr_get(dev_s) + vsync_pts_align;

			if (((int)(nextPts + (expected_prev_interval + 1) *
				vsync_pts_inc - next_vf_nextpts) >= 0)) {
				*expired = false;
				if (pts_log_enable[PTS_32_PATTERN] ||
				    pts_log_enable[PTS_22_PATTERN])
					pr_info("hold frame for pattern: %d",
						pts_pattern_detected);
			}

			/* here need to escape a vsync */
			if (ts_pcrscr_get(dev_s) >
				(next_vf->pts + vsync_pts_inc)) {
				*expired = true;
				pts_escape_vsync = 1;
				if (pts_log_enable[PTS_32_PATTERN] ||
				    pts_log_enable[PTS_22_PATTERN])
					pr_info("escape a vsync pattern: %d",
						pts_pattern_detected);
			}
		}
	} else {
		if (pts_trace == expected_curr_interval) {
			/* 23232323..233223...2323 curr=2, prev=3 */
			/* check if this frame will expire next vsyncs and */
			/* next frame will expire after 3 vsyncs */
			/* 22222...22222 -> 222..223122...22 */
			/* check if this frame will expire next vsyncs and */
			/* next frame will expire after 2 vsyncs */
			int nextPts = ts_pcrscr_get(dev_s) + vsync_pts_align;

			if (((int)(nextPts + vsync_pts_inc - next_vf->pts)
				>= 0) &&
			    ((int)(nextPts +
			    vsync_pts_inc * (expected_prev_interval - 1)
			    - next_vf_nextpts) < 0) &&
			    ((int)(nextPts + expected_prev_interval *
				vsync_pts_inc - next_vf_nextpts) >= 0)) {
				*expired = true;
				if (pts_log_enable[PTS_32_PATTERN] ||
				    pts_log_enable[PTS_22_PATTERN])
					pr_info("pull frame for pattern: %d",
						pts_pattern_detected);
			}
		}
	}
}

void videosync_pcrscr_update(s32 inc, u32 base)
{
	int i = 0;
	u32 r;
	/*unsigned long flags;*/
	struct videosync_s *dev_s;
	u32 current_omx_pts;
	int diff;

	if (!videosync_inited)
		return;
	for (i = 0; i < VIDEOSYNC_S_COUNT; i++) {
		dev_s = &vp_dev->video_prov[i];
		if (dev_s && dev_s->active_state == VIDEOSYNC_ACTIVE) {
			if (system_time_scale_base != base) {
				dev_s->system_time_scale_remainder =
					dev_s->system_time_scale_remainder *
						base / system_time_scale_base;
				system_time_scale_base = base;
			}
			if (dev_s->system_time_up) {
				dev_s->time_update = sched_clock();
				dev_s->system_time +=
					div_u64_rem(90000ULL * inc, base, &r)
					+ system_time_inc_adj;
				vsync_pts_inc = 90000 * inc / base;
				dev_s->system_time_scale_remainder += r;
				if (dev_s->system_time_scale_remainder
					>= system_time_scale_base) {
					dev_s->system_time++;
					dev_s->system_time_scale_remainder -=
						system_time_scale_base;
				}
				vp_print(dev_s->vf_receiver_name, PRINT_OTHER,
					 "update sys_time %u, system_time_scale_base %d, inc %d\n",
					 dev_s->system_time,
					 system_time_scale_base, inc);
			}

			/*check if need to correct pcr by omx_pts*/
			current_omx_pts = dev_s->omx_pts;
			diff = dev_s->system_time - current_omx_pts;

			if ((diff - omx_pts_interval_upper) > 0 ||
			    (diff - omx_pts_interval_lower) < 0) {
				vp_print(dev_s->vf_receiver_name,
					 PRINT_TIMESTAMP,
					 "sys_time=%d, omx_pts=%d, diff=%d\n",
					 dev_s->system_time,
					 current_omx_pts,
					 diff);
				ts_pcrscr_set(dev_s,
					      current_omx_pts + duration_gcd);
			}
		}
	}
}

void videosync_pcrscr_inc(s32 inc)
{
	int i = 0;
	/*unsigned long flags;*/
	struct videosync_s *dev_s;
	u32 current_omx_pts;
	int diff;

	if (!videosync_inited)
		return;
	for (i = 0; i < VIDEOSYNC_S_COUNT; i++) {
		dev_s = &vp_dev->video_prov[i];
		if (dev_s && dev_s->active_state == VIDEOSYNC_ACTIVE) {
			if (dev_s->system_time_up) {
				dev_s->system_time += inc + system_time_inc_adj;

				vp_print
				(dev_s->vf_receiver_name, PRINT_OTHER,
				"update sys_time %u, system_time_inc_adj %d, inc %d\n",
				dev_s->system_time,
				system_time_inc_adj,
				inc);
			}

			/*check if need to correct pcr by omx_pts*/
			if (!dev_s->vmaster_mode) {
				current_omx_pts = dev_s->omx_pts;
				diff = dev_s->system_time - current_omx_pts;

				if ((diff - omx_pts_interval_upper) > 0 ||
				    (diff - omx_pts_interval_lower)
				    < 0) {
					vp_print(dev_s->vf_receiver_name,
						 PRINT_TIMESTAMP,
						 "sys_time=%u, omx_pts=%u, diff=%d\n",
						 dev_s->system_time,
						 current_omx_pts,
						 diff);
					ts_pcrscr_set
						(dev_s,
						current_omx_pts + duration_gcd);
				}
			}
		}
	}
}

/* -----------------------------------------------------------------
 *           videosync operations
 * -----------------------------------------------------------------
 */
static struct vframe_s *videosync_vf_peek(void *op_arg)
{
	struct vframe_s *vf = NULL;
	struct videosync_s *dev_s = (struct videosync_s *)op_arg;

	vf = vfq_peek(&dev_s->ready_q);
	return vf;
}

static struct vframe_s *videosync_vf_get(void *op_arg)
{
	struct vframe_s *vf = NULL;
	struct videosync_s *dev_s = (struct videosync_s *)op_arg;

	vf = vfq_pop(&dev_s->ready_q);
	if (vf) {
		dev_s->cur_dispbuf = vf;
		dev_s->first_frame_toggled = 1;
	}
	return vf;
}

static void videosync_vf_put(struct vframe_s *vf, void *op_arg)
{
	struct videosync_s *dev_s = (struct videosync_s *)op_arg;

	if (!IS_ERR_OR_NULL(vf)) {
#ifdef CONFIG_AMLOGIC_MEDIA_VFM
		vf_put(vf, dev_s->vf_receiver_name);
#endif
		dev_s->put_frame_count++;
	} else {
		vp_print(dev_s->vf_receiver_name, 0,
			 "videosync vf put: NULL!\n");
	}
}

void vsync_notify_videosync(void)
{
	int i = 0;
	struct videosync_s *dev_s = NULL;
	bool has_active = false;

	if (!videosync_inited)
		return;

	for (i = 0; i < VIDEOSYNC_S_COUNT; i++) {
		dev_s = &vp_dev->video_prov[i];
		if (dev_s && dev_s->active_state == VIDEOSYNC_ACTIVE) {
			has_active = true;
			break;
		}
	}
	if (has_active) {
		pts_trace++;
		vp_dev->wakeup = 1;
		wake_up_interruptible(&vp_dev->videosync_wait);
	}
}

static int videosync_event_cb(int type, void *data, void *private_data)
{
	return 0;
}

static int videosync_buffer_states(struct videosync_buffer_states *states,
				   void *op_arg)
{
	struct videosync_s *dev_s = (struct videosync_s *)op_arg;

	states->buf_ready_num = vfq_level(&dev_s->ready_q);
	states->buf_queued_num = vfq_level(&dev_s->queued_q);
	states->total_num = dev_s->get_frame_count;
	return 0;
}

static int videosync_buffer_states_vf(struct vframe_states *states,
				      void *op_arg)
{
	struct videosync_s *dev_s = (struct videosync_s *)op_arg;

	states->vf_pool_size = VIDEOSYNC_S_POOL_SIZE;
	states->buf_recycle_num = 0;
	states->buf_free_num = VIDEOSYNC_S_POOL_SIZE
		- vfq_level(&dev_s->ready_q)
		- vfq_level(&dev_s->queued_q);
	states->buf_avail_num = vfq_level(&dev_s->ready_q)
		+ vfq_level(&dev_s->queued_q);

	return 0;
}

static const struct vframe_operations_s v4lvideo_vf_provider = {
	.peek = videosync_vf_peek,
	.get = videosync_vf_get,
	.put = videosync_vf_put,
	.event_cb = videosync_event_cb,
	.vf_states = videosync_buffer_states_vf,
};

static void videosync_register(struct videosync_s *dev_s)
{
#ifdef CONFIG_AMLOGIC_MEDIA_VFM
	vf_provider_init(&dev_s->video_vf_prov,
			 dev_s->vf_provider_name,
			 &v4lvideo_vf_provider, dev_s);
	vf_reg_provider(&dev_s->video_vf_prov);
	vf_notify_receiver(dev_s->vf_provider_name,
			   VFRAME_EVENT_PROVIDER_START,
			   NULL);
#endif
}

static void videosync_unregister(struct videosync_s *dev_s)
{
#ifdef CONFIG_AMLOGIC_MEDIA_VFM
	vf_unreg_provider(&dev_s->video_vf_prov);
#endif
}

static ssize_t dump_queue_state_show(struct class *class,
				     struct class_attribute *attr, char *buf)
{
	int ret = 0;
	int i = 0;
	struct videosync_buffer_states states;
	struct videosync_s *dev_s = NULL;

	if (IS_ERR_OR_NULL(vp_dev))
		return -1;

	if (vp_dev->active_dev_s_num == 0) {
		ret += sprintf(buf, "no active videosync\n");
	} else {
		for (i = 0; i < VIDEOSYNC_S_COUNT; i++) {
			dev_s = &vp_dev->video_prov[i];
			if (dev_s->active_state == VIDEOSYNC_INACTIVE)
				continue;

			if (videosync_buffer_states(&states, dev_s) == 0) {
				ret += sprintf(buf + ret,
					     "\n#------ %s state ------#\n",
						dev_s->vf_receiver_name);
				ret += sprintf(buf + ret,
					       "queued_q_size=%d\n",
						states.buf_queued_num);
				ret += sprintf(buf + ret,
					       "ready_q_size=%d\n",
					       states.buf_ready_num);
				ret += sprintf(buf + ret,
					       "total frame count=%d\n",
					       states.total_num);
			}
		}
	}
	return ret;
}

static ssize_t dump_pts_show(struct class *class,
			     struct class_attribute *attr, char *buf)
{
	int ret = 0;
	int i = 0;
	struct videosync_s *dev_s = NULL;

	if (IS_ERR_OR_NULL(vp_dev))
		return -1;

	if (vp_dev->active_dev_s_num == 0) {
		ret += sprintf(buf, "no active videosync\n");
	} else {
		for (i = 0; i < VIDEOSYNC_S_COUNT; i++) {
			dev_s = &vp_dev->video_prov[i];
			if (dev_s->active_state == VIDEOSYNC_INACTIVE)
				continue;

			ret += sprintf(buf + ret,
				       "%s: ", dev_s->vf_receiver_name);
			ret += sprintf(buf + ret,
				       "omx_pts=%d, ", dev_s->omx_pts);
			ret += sprintf(buf + ret,
				       "system_time=%d\n", dev_s->system_time);
		}
	}
	return ret;
}

static ssize_t dump_get_put_framecount_show(struct class *class,
					    struct class_attribute *attr,
					    char *buf)
{
	int ret = 0;
	int i = 0;
	struct videosync_s *dev_s = NULL;

	if (IS_ERR_OR_NULL(vp_dev))
		return -1;

	if (vp_dev->active_dev_s_num == 0) {
		ret += sprintf(buf, "no active videosync\n");
	} else {
		for (i = 0; i < VIDEOSYNC_S_COUNT; i++) {
			dev_s = &vp_dev->video_prov[i];
			if (dev_s->active_state == VIDEOSYNC_INACTIVE)
				continue;

			ret += sprintf(buf + ret,
				       "%s: ", dev_s->vf_receiver_name);
			ret += sprintf(buf + ret,
				       "get_frame_count=%d, put_frame_count=%d\n",
				       dev_s->get_frame_count,
				       dev_s->put_frame_count);
		}
	}
	return ret;
}

static ssize_t dump_rect_show(struct class *class,
			      struct class_attribute *attr, char *buf)
{
	int ret = 0;
	int i = 0;
	struct videosync_s *dev_s = NULL;

	if (IS_ERR_OR_NULL(vp_dev))
		return -1;

	if (vp_dev->active_dev_s_num == 0) {
		ret += sprintf(buf, "no active videosync\n");
	} else {
		for (i = 0; i < VIDEOSYNC_S_COUNT; i++) {
			dev_s = &vp_dev->video_prov[i];
			if (dev_s->active_state == VIDEOSYNC_INACTIVE)
				continue;

			ret += sprintf(buf + ret,
				       "%s: ", dev_s->vf_receiver_name);
			ret += sprintf(buf + ret,
				       "rect = [%d,%d,%d,%d], ",
				       dev_s->rect.left,
				       dev_s->rect.top,
				       dev_s->rect.width,
				       dev_s->rect.height);
			ret += sprintf(buf + ret,
				       "zorder = %d\n", dev_s->zorder);
		}
	}
	return ret;
}

static ssize_t audio_mode_show(struct class *cla,
			       struct class_attribute *attr, char *buf)
{
	return snprintf(buf, 80,
			"current async_mode is %d\n",
			async_mode);
}

static ssize_t audio_mode_store(struct class *cla,
				struct class_attribute *attr,
				const char *buf, size_t count)
{
	long tmp;
	int ret;

	ret = kstrtol(buf, 0, &tmp);
	if (ret != 0) {
		pr_info("ERROR converting %s to long int!\n", buf);
		return ret;
	}
	async_mode = tmp;
	return count;
}

static ssize_t not_rendor_show(struct class *cla,
			       struct class_attribute *attr, char *buf)
{
	return snprintf(buf, 80,
			"current no_render is %d\n",
			no_render);
}

static ssize_t not_rendor_store(struct class *cla,
				struct class_attribute *attr,
				const char *buf, size_t count)
{
	long tmp;
	int ret;

	ret = kstrtol(buf, 0, &tmp);
	if (ret != 0) {
		pr_info("ERROR converting %s to long int!\n", buf);
		return ret;
	}
	no_render = tmp;
	return count;
}

static ssize_t current_omx_index_show(struct class *cla,
				      struct class_attribute *attr, char *buf)
{
	return snprintf(buf, 80,
			"current cur_omx_index is %d\n",
			cur_omx_index);
}

static ssize_t current_omx_index_store(struct class *cla,
				       struct class_attribute *attr,
				       const char *buf, size_t count)
{
	long tmp;
	int ret;

	ret = kstrtol(buf, 0, &tmp);
	if (ret != 0) {
		pr_info("ERROR converting %s to long int!\n", buf);
		return ret;
	}
	cur_omx_index = tmp;
	return count;
}

static ssize_t vpts_align_show(struct class *cla,
			       struct class_attribute *attr, char *buf)
{
	return snprintf(buf, 80,
			"current vsync_pts_align is %d\n",
			vsync_pts_align);
}

static ssize_t vpts_align_store(struct class *cla,
				struct class_attribute *attr,
				const char *buf, size_t count)
{
	long tmp;
	int ret;

	ret = kstrtol(buf, 0, &tmp);
	if (ret != 0) {
		pr_info("ERROR converting %s to long int!\n", buf);
		return ret;
	}
	vsync_pts_align = tmp;
	return count;
}

static ssize_t first_frame_nosync_show(struct class *cla,
				       struct class_attribute *attr, char *buf)
{
	return snprintf(buf, 80,
			"current show_first_frame_nosync is %d\n",
			show_first_frame_nosync);
}

static ssize_t first_frame_nosync_store(struct class *cla,
					struct class_attribute *attr,
					const char *buf, size_t count)
{
	long tmp;
	int ret;

	ret = kstrtol(buf, 0, &tmp);
	if (ret != 0) {
		pr_info("ERROR converting %s to long int!\n", buf);
		return ret;
	}
	show_first_frame_nosync = tmp;
	return count;
}

static ssize_t delata_time_show(struct class *cla,
				struct class_attribute *attr, char *buf)
{
	return snprintf(buf, 80,
			"current max_delata_time is %d\n",
			max_delata_time);
}

static ssize_t delata_time_store(struct class *cla,
				 struct class_attribute *attr,
				 const char *buf, size_t count)
{
	long tmp;
	int ret;

	ret = kstrtol(buf, 0, &tmp);
	if (ret != 0) {
		pr_info("ERROR converting %s to long int!\n", buf);
		return ret;
	}
	max_delata_time = tmp;
	return count;
}

static ssize_t no_sync_show(struct class *cla,
			    struct class_attribute *attr, char *buf)
{
	return snprintf(buf, 80,
			"current show_nosync is %d\n",
			show_nosync);
}

static ssize_t no_sync_store(struct class *cla,
			     struct class_attribute *attr,
			     const char *buf, size_t count)
{
	long tmp;
	int ret;

	ret = kstrtol(buf, 0, &tmp);
	if (ret != 0) {
		pr_info("ERROR converting %s to long int!\n", buf);
		return ret;
	}
	show_nosync = tmp;
	return count;
}

static ssize_t is_smooth_sync_enable_show(struct class *cla,
					  struct class_attribute *attr,
					  char *buf)
{
	return snprintf(buf, 80,
			"current smooth_sync_enable is %d\n",
			smooth_sync_enable);
}

static ssize_t is_smooth_sync_enable_store(struct class *cla,
					   struct class_attribute *attr,
					   const char *buf, size_t count)
{
	long tmp;
	int ret;

	ret = kstrtol(buf, 0, &tmp);
	if (ret != 0) {
		pr_info("ERROR converting %s to long int!\n", buf);
		return ret;
	}
	smooth_sync_enable = tmp;
	return count;
}

static ssize_t vp_debug_show(struct class *cla,
			     struct class_attribute *attr,
			     char *buf)
{
	return snprintf(buf, 80,
			"current vp_debug_flag is %d\n",
			vp_debug_flag);
}

static ssize_t vp_debug_store(struct class *cla,
			      struct class_attribute *attr,
			      const char *buf, size_t count)
{
	long tmp;
	int ret;

	ret = kstrtol(buf, 0, &tmp);
	if (ret != 0) {
		pr_info("ERROR converting %s to long int!\n", buf);
		return ret;
	}
	vp_debug_flag = tmp;
	return count;
}

static ssize_t gcd_duration_show(struct class *cla,
				 struct class_attribute *attr,
				 char *buf)
{
	return snprintf(buf, 80,
			"current duration_gcd is %d\n",
			duration_gcd);
}

static ssize_t gcd_duration_store(struct class *cla,
				  struct class_attribute *attr,
				  const char *buf, size_t count)
{
	long tmp;
	int ret;

	ret = kstrtol(buf, 0, &tmp);
	if (ret != 0) {
		pr_info("ERROR converting %s to long int!\n", buf);
		return ret;
	}
	duration_gcd = tmp;
	return count;
}

static ssize_t pts_enforce_pull_down_show(struct class *cla,
					  struct class_attribute *attr,
					  char *buf)
{
	return snprintf(buf, 80,
			"current pts_enforce_pulldown is %d\n",
			pts_enforce_pulldown);
}

static ssize_t pts_enforce_pull_down_store(struct class *cla,
					   struct class_attribute *attr,
					   const char *buf, size_t count)
{
	long tmp;
	int ret;

	ret = kstrtol(buf, 0, &tmp);
	if (ret != 0) {
		pr_info("ERROR converting %s to long int!\n", buf);
		return ret;
	}
	pts_enforce_pulldown = tmp;
	return count;
}

static CLASS_ATTR_RO(dump_queue_state);
static CLASS_ATTR_RO(dump_pts);
static CLASS_ATTR_RO(dump_get_put_framecount);
static CLASS_ATTR_RO(dump_rect);
static CLASS_ATTR_RW(audio_mode);
static CLASS_ATTR_RW(not_rendor);
static CLASS_ATTR_RW(current_omx_index);
static CLASS_ATTR_RW(vpts_align);
static CLASS_ATTR_RW(first_frame_nosync);
static CLASS_ATTR_RW(delata_time);
static CLASS_ATTR_RW(no_sync);
static CLASS_ATTR_RW(is_smooth_sync_enable);
static CLASS_ATTR_RW(vp_debug);
static CLASS_ATTR_RW(gcd_duration);
static CLASS_ATTR_RW(pts_enforce_pull_down);

static struct attribute *videosync_class_attrs[] = {
	&class_attr_dump_queue_state.attr,
	&class_attr_dump_pts.attr,
	&class_attr_dump_get_put_framecount.attr,
	&class_attr_dump_rect.attr,
	&class_attr_audio_mode.attr,
	&class_attr_not_rendor.attr,
	&class_attr_current_omx_index.attr,
	&class_attr_vpts_align.attr,
	&class_attr_first_frame_nosync.attr,
	&class_attr_delata_time.attr,
	&class_attr_no_sync.attr,
	&class_attr_is_smooth_sync_enable.attr,
	&class_attr_vp_debug.attr,
	&class_attr_gcd_duration.attr,
	&class_attr_pts_enforce_pull_down.attr,
	NULL
};

ATTRIBUTE_GROUPS(videosync_class);

static struct class videosync_class = {
	.name = "videosync",
	.class_groups = videosync_class_groups,
};

int videosync_assign_map(char **receiver_name, int *inst)
{
	int i = 0;
	struct videosync_s *dev_s  = NULL;

	mutex_lock(&vp_dev->vp_mutex);
	for (i = 0; i < VIDEOSYNC_S_COUNT; i++) {
		dev_s = &vp_dev->video_prov[i];
		if (dev_s->inst == *inst) {
			*receiver_name = dev_s->vf_receiver_name;
			pr_info("videosync assign map %s %p\n",
				dev_s->vf_receiver_name, dev_s);
			mutex_unlock(&vp_dev->vp_mutex);
			return 0;
		}
	}
	mutex_unlock(&vp_dev->vp_mutex);
	return -ENODEV;
}

int videosync_alloc_map(int *inst)
{
	int i = 0;
	struct videosync_s *dev_s  = NULL;

	mutex_lock(&vp_dev->vp_mutex);
	for (i = 0; i < VIDEOSYNC_S_COUNT; i++) {
		dev_s = &vp_dev->video_prov[i];
		if (dev_s->inst >= 0  && !dev_s->mapped) {
			dev_s->mapped = true;
			*inst = dev_s->inst;
			pr_info("%s %d OK\n", __func__, dev_s->inst);
			mutex_unlock(&vp_dev->vp_mutex);
			return 0;
		}
	}
	mutex_unlock(&vp_dev->vp_mutex);
	return -ENODEV;
}

void videosync_release_map(int inst)
{
	int i = 0;
	struct videosync_s *dev_s  = NULL;

	mutex_lock(&vp_dev->vp_mutex);
	for (i = 0; i < VIDEOSYNC_S_COUNT; i++) {
		dev_s = &vp_dev->video_prov[i];
		if (dev_s->inst == inst  && dev_s->mapped) {
			dev_s->mapped = false;
			pr_info("videosync release map %d OK\n", inst);
			break;
		}
	}
	mutex_unlock(&vp_dev->vp_mutex);
}

void videosync_release_map_force(struct videosync_priv_s *priv)
{
	int i = 0;
	struct videosync_s *dev_s  = NULL;

	mutex_lock(&vp_dev->vp_mutex);
	for (i = 0; i < VIDEOSYNC_S_COUNT; i++) {
		dev_s = &vp_dev->video_prov[i];
		if (dev_s->inst == priv->vp_id && dev_s->mapped) {
			dev_s->mapped = false;
			pr_info("videosync release map force %d OK\n",
				priv->vp_id);
			break;
		}
	}
	mutex_unlock(&vp_dev->vp_mutex);
}

static int videosync_open(struct inode *inode, struct file *file)
{
	struct videosync_priv_s *priv = NULL;

	pr_info("videosync open\n");
	priv = kzalloc(sizeof(*priv), GFP_KERNEL);
	if (!priv)
		return -ENOMEM;

	priv->vp_id = -1;
	priv->dev_s = NULL;
	file->private_data = priv;

	return 0;
}

static int videosync_release(struct inode *inode, struct file *file)
{
	struct videosync_priv_s *priv = NULL;

	pr_info("videosync release\n");
	if (file && file->private_data) {
		priv = file->private_data;

		/*check if map released.*/
		videosync_release_map_force(priv);

		kfree(priv);
		priv = NULL;
		file->private_data = NULL;
	}
	return 0;
}

static int set_omx_pts(u32 *p)
{
	struct videosync_s *dev_s = NULL;
	int ret = 0;
	u32 tmp_pts = p[0];
	/*u32 vision = p[1];*/
	u32 set_from_hwc = p[2];
	u32 frame_num = p[3];
	u32 not_reset = p[4];
	u32 session = p[5];
	u32 dev_id = p[6];

	cur_omx_index = frame_num;

	if (dev_id < VIDEOSYNC_S_COUNT)
		dev_s = &vp_dev->video_prov[dev_id];

	if (!IS_ERR_OR_NULL(dev_s) && dev_s->mapped) {
		mutex_lock(&dev_s->omx_mutex);
		vp_print(dev_s->vf_receiver_name, PRINT_TIMESTAMP,
			 "set omx_pts %d, hwc %d, not_reset %d, vf_num %d\n",
			 tmp_pts, set_from_hwc, not_reset, frame_num);
		if (dev_s->omx_check_previous_session) {
			if (session != dev_s->omx_cur_session) {
				dev_s->omx_cur_session = session;
				dev_s->omx_check_previous_session = false;
			} else {
				pr_info("videosync: tmp_pts %d, session=0x%x\n",
					tmp_pts, dev_s->omx_cur_session);
				mutex_unlock(&dev_s->omx_mutex);
				return ret;
			}
		}

		if (dev_s->omx_pts_set_index < frame_num)
			dev_s->omx_pts_set_index = frame_num;

		if (not_reset == 0)
			dev_s->omx_pts = tmp_pts;

		mutex_unlock(&dev_s->omx_mutex);

		ts_pcrscr_enable(dev_s, 1);
	} else {
		ret = -EFAULT;
		pr_err("[%s]cannot find dev_id %d device\n",
		       dev_s->vf_receiver_name, dev_id);
	}
	return ret;
}

static int set_omx_zorder(u32 *p)
{
	struct videosync_s *dev_s = NULL;
	int ret = 0;
	u32 dev_id = p[5];
	/*32 tmp_pts = p[6];*/

	if (dev_id < VIDEOSYNC_S_COUNT)
		dev_s = &vp_dev->video_prov[dev_id];

	if (!IS_ERR_OR_NULL(dev_s) && dev_s->mapped) {
		dev_s->zorder = p[0];
		dev_s->rect.left = p[1];
		dev_s->rect.top = p[2];
		dev_s->rect.width = p[3];
		dev_s->rect.height = p[4];

		vp_print(dev_s->vf_receiver_name, PRINT_TIMESTAMP,
			 "set zorder %d: %d %d %d %d\n",
			 p[0], p[1], p[2], p[3], p[4]);
	} else {
		ret = -EFAULT;
		pr_err("[%s]cannot find dev_id %d device\n",
		       dev_s->vf_receiver_name, dev_id);
	}
	return ret;
}

static long videosync_ioctl(struct file *file,
			    unsigned int cmd,
			    ulong arg)
{
	long ret = 0;
	void __user *argp = (void __user *)arg;
	struct videosync_priv_s *priv = NULL;

	switch (cmd) {
	case VIDEOSYNC_IOC_ALLOC_ID:{
		u32 videosync_id = 0;

		ret = videosync_alloc_map(&videosync_id);
		if (ret != 0)
			break;
		put_user(videosync_id, (u32 __user *)argp);
		pr_info("alloc-id %d\n", videosync_id);

		if (file && file->private_data) {
			priv = file->private_data;
			priv->vp_id = videosync_id;
			priv->dev_s = &vp_dev->video_prov[videosync_id];
			pr_info("get dev_s %p\n", priv->dev_s);
		}
	}
	break;
	case VIDEOSYNC_IOC_FREE_ID:{
		u32 videosync_id;

		get_user(videosync_id, (u32 __user *)argp);
		pr_info("free-id %d\n", videosync_id);
		videosync_release_map(videosync_id);
	}
	break;

	case VIDEOSYNC_IOC_SET_FREERUN_MODE:
		if (arg > FREERUN_DUR) {
			ret = -EFAULT;
		} else {
			if (file && file->private_data) {
				priv = file->private_data;
				if (priv && priv->dev_s)
					priv->dev_s->freerun_mode = arg;
				else
					ret = -EFAULT;
			} else {
				ret = -EFAULT;
			}
		}
	break;

	case VIDEOSYNC_IOC_GET_FREERUN_MODE:
		if (file && file->private_data) {
			priv = file->private_data;
			if (priv && priv->dev_s)
				put_user(priv->dev_s->freerun_mode,
					 (u32 __user *)argp);
			else
				ret = -EFAULT;
		} else {
			ret = -EFAULT;
		}
	break;
	case VIDEOSYNC_IOC_SET_OMX_VPTS:{
		u32 pts[7];

		if (copy_from_user(pts, argp, sizeof(pts)) == 0)
			ret = set_omx_pts(pts);
	}
	break;
	case VIDEOSYNC_IOC_SET_OMX_ZORDER:{
		u32 zorder[7];

		if (copy_from_user(zorder, argp, sizeof(zorder)) == 0)
			ret = set_omx_zorder(zorder);
	}
	break;
	case VIDEOSYNC_IOC_GET_OMX_VPTS:
		if (file && file->private_data) {
			priv = file->private_data;
			if (priv && priv->dev_s)
				put_user(priv->dev_s->omx_pts,
					 (u32 __user *)argp);
			else
				ret = -EFAULT;
		} else {
			ret = -EFAULT;
		}
	break;
	case VIDEOSYNC_IOC_GET_OMX_VERSION:
		put_user(omx_version, (u32 __user *)argp);
		pr_info("get omx_version %d\n", omx_version);
	break;
	case VIDEOSYNC_IOC_SET_FIRST_FRAME_NOSYNC: {
		u32 info[5];
		struct videosync_s *dev_s = NULL;
		u32 dev_id;

		if (copy_from_user(info, argp, sizeof(info)) == 0) {
			dev_id = info[0];
			if (dev_id < VIDEOSYNC_S_COUNT)
				dev_s = &vp_dev->video_prov[dev_id];

			if (dev_s && dev_s->mapped) {
				dev_s->show_first_frame_nosync = info[1];
				pr_info("show_first_frame_nosync =%d\n",
					dev_s->show_first_frame_nosync);
			}
		}
	}
	break;
	default:
		pr_info("ioctl invalid cmd 0x%x\n", cmd);
		return -EINVAL;
	}
	return ret;
}

#ifdef CONFIG_COMPAT
static long videosync_compat_ioctl(struct file *file,
				   unsigned int cmd,
				   ulong arg)
{
	long ret = 0;

	ret = videosync_ioctl(file, cmd, (ulong)compat_ptr(arg));
	return ret;
}
#endif

static void clear_queued_queue(struct videosync_s *dev_s)
{
	struct vframe_s *vf = NULL;

	vp_print(dev_s->vf_receiver_name, PRINT_QUEUE_STATUS,
		 "clear queued queue: size %d!\n",
		 vfq_level(&dev_s->queued_q));

	vf = vfq_pop(&dev_s->queued_q);
	while (vf) {
#ifdef CONFIG_AMLOGIC_MEDIA_VFM
		vf_put(vf, dev_s->vf_receiver_name);
#endif
		vf = vfq_pop(&dev_s->queued_q);
	}
}

static void clear_ready_queue(struct videosync_s *dev_s)
{
	struct vframe_s *vf = NULL;

	vp_print(dev_s->vf_receiver_name, PRINT_QUEUE_STATUS,
		 "clear ready queue: size %d!\n",
		 vfq_level(&dev_s->ready_q));
	vf = vfq_pop(&dev_s->ready_q);
	while (vf) {
#ifdef CONFIG_AMLOGIC_MEDIA_VFM
		vf_put(vf, dev_s->vf_receiver_name);
#endif
		vf = vfq_pop(&dev_s->ready_q);
	}
}

static u64 func_div(u64 number, u32 divid)
{
	u64 tmp = number;

	do_div(tmp, divid);
	return tmp;
}

static inline bool omx_vpts_expire(struct vframe_s *cur_vf,
				   struct vframe_s *next_vf,
				   struct videosync_s *dev_s,
				   int toggled_cnt)
{
	u32 pts;
#ifdef VIDEO_PTS_CHASE
	u32 vid_pts, scr_pts;
#endif
	u32 systime;
#ifdef DDD
	u32 adjust_pts, org_vpts;
#endif
	unsigned long delta = 0;
	int delta_32 = 0;
	/*u32 dur_pts = 0;*/
	bool expired;

	if (!next_vf)
		return false;

	pts = next_vf->pts;

	if (dev_s->freerun_mode == FREERUN_NODUR)
		return true;

	if (next_vf->duration == 0)
		return true;

	if (dev_s->show_first_frame_nosync || show_first_frame_nosync) {
		if (next_vf->omx_index == 0)
			return true;
	}
	systime = ts_pcrscr_get(dev_s);

	if (no_render)
		dev_s->first_frame_toggled = 1; /*just for debug, not render*/

	vp_print(dev_s->vf_receiver_name, PRINT_TIMESTAMP,
		"sys_time=%u, vf->pts=%u, diff=%d, index=%d\n",
		systime, pts, (int)(systime - pts), next_vf->omx_index);
	/* check video PTS discontinuity */
	if (ts_pcrscr_enable_state(dev_s) > 0 &&
	    enable_video_discontinue_report &&
	    dev_s->first_frame_toggled &&
#ifdef CONFIG_AMLOGIC_MEDIA_FRAME_SYNC
	    (abs(systime - pts) > tsync_vpts_discontinuity_margin()) &&
#endif
	    ((next_vf->flag & VFRAME_FLAG_NO_DISCONTINUE) == 0 ||
#ifdef CONFIG_AMLOGIC_MEDIA_FRAME_SYNC
	    tsync_vpts_discontinuity_margin() <= 90000)) {
#else
	    0)) {
#endif
		vp_print(dev_s->vf_receiver_name, PRINT_TIMESTAMP,
			 "discontinue, systime = %d, next_vf->pts = %d\n",
			 systime, next_vf->pts);
		return true;
	} else if ((dev_s->omx_pts + omx_pts_interval_upper < next_vf->pts) &&
		   dev_s->omx_pts_set_index >= next_vf->omx_index) {
		pr_info("videosync, omx_pts=%d omx_pts_set_index=%d pts=%d omx_index=%d\n",
			dev_s->omx_pts,
			dev_s->omx_pts_set_index,
			next_vf->pts,
			next_vf->omx_index);
		return true;
	}
#ifdef DDD
	if (0/*smooth_sync_enable*/) {
		org_vpts = timestamp_vpts_get();
		if (abs(org_vpts + vsync_pts_inc - systime) <
		    M_PTS_SMOOTH_MAX &&
		    abs(org_vpts + vsync_pts_inc - systime) >
			M_PTS_SMOOTH_MIN) {
			if (!dev_s->video_frame_repeat_count) {
				dev_s->vpts_ref = org_vpts;
				dev_s->video_frame_repeat_count++;
			}

			if ((int)(org_vpts + vsync_pts_inc - systime) > 0) {
				adjust_pts =
				    dev_s->vpts_ref + (vsync_pts_inc -
						M_PTS_SMOOTH_ADJUST) *
				    dev_s->video_frame_repeat_count;
			} else {
				adjust_pts =
					dev_s->vpts_ref
					+ (vsync_pts_inc + M_PTS_SMOOTH_ADJUST)
					* dev_s->video_frame_repeat_count;
			}
			return (int)(adjust_pts - pts) >= 0;
		}

		if (dev_s->video_frame_repeat_count) {
			dev_s->vpts_ref = 0;
			dev_s->video_frame_repeat_count = 0;
		}
	}
#endif
	delta = func_div(sched_clock() - dev_s->time_update, 1000);
	delta_32 = delta * 90 / 1000;
	if (delta_32 > max_delata_time)
		max_delata_time = delta_32;

	expired = (systime + vsync_pts_align) >= pts;
	vp_print(dev_s->vf_receiver_name, PRINT_PATTERN,
		 "expired=%d, valid=%d, next_pts=%d, cnt=%d, systime=%d, inc=%d\n",
		 expired,
		 next_vf->next_vf_pts_valid,
		 next_vf->next_vf_pts,
		 toggled_cnt,
		 systime,
		 vsync_pts_inc);

	if (expired && next_vf->next_vf_pts_valid &&
	    pts_enforce_pulldown &&
	    next_vf->next_vf_pts &&
	    toggled_cnt > 0 &&
	    ((int)(systime + vsync_pts_inc +
	    vsync_pts_align - next_vf->next_vf_pts) < 0)) {
		expired = false;
		vp_print(dev_s->vf_receiver_name, PRINT_PATTERN,
			 "force expired false\n");
	} else if (!expired && next_vf->next_vf_pts_valid &&
		pts_enforce_pulldown &&
		next_vf->next_vf_pts &&
		(toggled_cnt == 0) &&
		((int)(systime + vsync_pts_inc +
		vsync_pts_align - next_vf->next_vf_pts) >= 0)) {
		expired = true;
		vp_print(dev_s->vf_receiver_name, PRINT_PATTERN,
			 "force expired true\n");
	}

	if (pts_enforce_pulldown)
		vpts_perform_pulldown(dev_s, next_vf, &expired);
	return expired;
}

void videosync_sync(struct videosync_s *dev_s)
{
	int ready_q_size = 0;
	struct vframe_s *vf;
	int expire_count = 0;

	if (smooth_sync_enable) {
		if (dev_s->video_frame_repeat_count)
			dev_s->video_frame_repeat_count++;
	}

	vf = vfq_peek(&dev_s->queued_q);

	while (vf) {
		if (omx_vpts_expire(dev_s->cur_dispbuf,
				    vf, dev_s, expire_count) || show_nosync) {
			vf = vfq_pop(&dev_s->queued_q);
			if (vf) {
				if (async_mode)	{
					if (vfq_level(&dev_s->ready_q) > 0)
						clear_ready_queue(dev_s);
				}

				if (pts_escape_vsync == 1) {
					pts_trace++;
					pts_escape_vsync = 0;
				}
				vsync_video_pattern();
				pre_pts_trace = pts_trace;
				pts_trace = 0;
				expire_count++;

				vfq_push(&dev_s->ready_q, vf);
				ready_q_size = vfq_level(&dev_s->ready_q);
				vp_print(dev_s->vf_receiver_name,
					 PRINT_QUEUE_STATUS,
					 "add pts %u index 0x%x to ready_q, size %d\n",
					 vf->pts, vf->index, ready_q_size);
#ifdef CONFIG_AMLOGIC_MEDIA_VFM
			vf_notify_receiver(dev_s->vf_provider_name,
					   VFRAME_EVENT_PROVIDER_VFRAME_READY,
					   NULL);
#endif
				if (ready_q_size > VIDEOSYNC_S_POOL_SIZE - 1) {
					vp_print(dev_s->vf_receiver_name,
						 PRINT_QUEUE_STATUS,
						 "ready_q full!\n");
					break;
				}
			}

			vf = vfq_peek(&dev_s->queued_q);
			if (!vf)
				break;
		} else {
			break;
		}
	}
}

static void prepare_queued_queue(struct videosync_dev *dev)
{
	int i = 0;
	struct videosync_s *dev_s;
#ifdef CONFIG_AMLOGIC_MEDIA_VFM
	struct vframe_s *vf;
#endif

	for (i = 0; i < VIDEOSYNC_S_COUNT; i++) {
		dev_s = &dev->video_prov[i];
		if (dev_s->active_state == VIDEOSYNC_INACTIVE)
			continue;

		if (vfq_full(&dev_s->queued_q)) {
			pr_info("[%s]queued_q full!\n",
				dev_s->vf_receiver_name);
			continue;
		}
#ifdef CONFIG_AMLOGIC_MEDIA_VFM
		while (vf_peek(dev_s->vf_receiver_name)) {
			vf = vf_get(dev_s->vf_receiver_name);
			if (vf) {
				vfq_push(&dev_s->queued_q, vf);
				dev_s->get_frame_count++;
				vp_print(dev_s->vf_receiver_name,
					 PRINT_QUEUE_STATUS,
					 "add pts %u index 0x%x to queued_q, size %d\n",
					 vf->pts, vf->index,
					 vfq_level(&dev_s->queued_q));
			}
		}
#endif
	}
}

static void prepare_ready_queue(struct videosync_dev *dev)
{
	int i = 0;
	struct videosync_s *dev_s;

	for (i = 0; i < VIDEOSYNC_S_COUNT; i++) {
		dev_s = &dev->video_prov[i];
		if (dev_s->active_state == VIDEOSYNC_INACTIVE)
			continue;

		if (vfq_full(&dev_s->ready_q)) {
			pr_info("[%s]ready_q full!\n", dev_s->vf_receiver_name);
			continue;
		}

		videosync_sync(dev_s);

		if (no_render)
			clear_ready_queue(dev_s);/*just for debug, not render*/
	}
}

static void reinit_dev_s(struct videosync_s *dev_s)
{
	dev_s->get_frame_count = 0;
	dev_s->put_frame_count = 0;
	dev_s->system_time_up = 0;
	dev_s->system_time = 0;
	dev_s->first_frame_toggled = 0;
	dev_s->freerun_mode = 0;
	dev_s->system_time_scale_remainder = 0;
	dev_s->omx_pts = 0;
	dev_s->vpts_ref = 0;
	dev_s->video_frame_repeat_count = 0;
	dev_s->rect.top = 0;
	dev_s->rect.left = 0;
	dev_s->rect.width = 0;
	dev_s->rect.height = 0;
	dev_s->zorder = 0;
}

static int videosync_receiver_event_fun(int type, void *data,
					void *private_data)
{
	struct videosync_s *dev_s = (struct videosync_s *)private_data;
	unsigned long flags = 0;
	struct videosync_dev *dev;
	unsigned long time_left;

	if (type == VFRAME_EVENT_PROVIDER_UNREG) {
		pr_info("videosync: try to unreg %p, %s\n",
			dev_s, dev_s->vf_receiver_name);
		dev = (struct videosync_dev *)dev_s->dev;

		spin_lock_irqsave(&dev->dev_s_num_slock, flags);
		--dev->active_dev_s_num;
		spin_unlock_irqrestore(&dev->dev_s_num_slock, flags);

		videosync_unregister(dev_s);
		dev_s->receiver_register = false;

		if (dev_s->active_state == VIDEOSYNC_ACTIVE) {
			dev_s->active_state = VIDEOSYNC_INACTIVE_REQ;
			dev->wakeup = 1;
			wake_up_interruptible(&dev->videosync_wait);
			time_left =
			wait_for_completion_timeout(&dev_s->inactive_done,
						    msecs_to_jiffies(100));
			if (time_left == 0)
				vp_print(RECEIVER_NAME, PRINT_OTHER,
					 "videosync: unreg timeout\n");
		}
		clear_ready_queue(dev_s);
		clear_queued_queue(dev_s);

		/*tsync_avevent(VIDEO_STOP, 0);*/
		pr_info("videosync: unreg %p, %s\n",
			dev_s, dev_s->vf_receiver_name);
	} else if (type == VFRAME_EVENT_PROVIDER_REG) {
		//omx_secret_mode = false;
		struct videosync_dev *dev = (struct videosync_dev *)dev_s->dev;

		reinit_dev_s(dev_s);
		//videosync_register(dev_s);
		dev_s->receiver_register = true;
		dev_s->active_state = VIDEOSYNC_ACTIVE;
		dev_s->omx_check_previous_session = true;
		dev_s->omx_pts_set_index = 0;

		spin_lock_irqsave(&dev->dev_s_num_slock, flags);
		++dev->active_dev_s_num;
		spin_unlock_irqrestore(&dev->dev_s_num_slock, flags);

		vfq_init(&dev_s->queued_q, VIDEOSYNC_S_POOL_SIZE + 1,
			 &dev_s->videosync_pool_queued[0]);
		vfq_init(&dev_s->ready_q, VIDEOSYNC_S_POOL_SIZE + 1,
			 &dev_s->videosync_pool_ready[0]);

		init_completion(&dev_s->inactive_done);
		complete(&dev->thread_active);
		pr_info("videosync: reg %p, %s\n",
			dev_s, dev_s->vf_receiver_name);
		pts_trace = 0;
		pts_pattern_detected = -1;
		pre_pts_trace = 0;
		pts_escape_vsync = 0;
	} else if (type == VFRAME_EVENT_PROVIDER_VFRAME_READY) {
	} else if (type == VFRAME_EVENT_PROVIDER_START) {
		videosync_register(dev_s);
		pr_info("videosync: start %p, %s\n",
			dev_s, dev_s->vf_receiver_name);
	} else if (type == VFRAME_EVENT_PROVIDER_QUREY_STATE) {
		struct videosync_buffer_states states;

		videosync_buffer_states(&states, dev_s);
		if (states.buf_queued_num + states.buf_ready_num > 0)
			return RECEIVER_ACTIVE;
		vp_print(dev_s->vf_receiver_name, 0,
			 "buf queue empty!!\n");
		if (vf_notify_receiver(dev_s->vf_provider_name,
				       VFRAME_EVENT_PROVIDER_QUREY_STATE,
				       NULL) == RECEIVER_ACTIVE)
			return RECEIVER_ACTIVE;
		return RECEIVER_INACTIVE;
	}
	return 0;
}

static const struct file_operations videosync_fops = {
	.owner = THIS_MODULE,
	.open = videosync_open,
	.release = videosync_release,
	.unlocked_ioctl = videosync_ioctl,
#ifdef CONFIG_COMPAT
	.compat_ioctl = videosync_compat_ioctl,
#endif
	.poll = NULL,
};

static const struct vframe_receiver_op_s videosync_vf_receiver = {
	.event_cb = videosync_receiver_event_fun,
};

static const struct videosync_operations_s videosync_vf_provider = {
	.peek = videosync_vf_peek,
	.get = videosync_vf_get,
	.put = videosync_vf_put,
	.event_cb = videosync_event_cb,
	.buffer_states = videosync_buffer_states,
};

static int __init videosync_create_instance(int inst)
{
	struct videosync_s *dev_s;

	dev_s = &vp_dev->video_prov[inst];
	dev_s->dev = vp_dev;
	dev_s->fd_num = 0;
	dev_s->ops = &videosync_vf_provider;
	dev_s->active_state = VIDEOSYNC_INACTIVE;
	dev_s->omx_cur_session = 0xffffffff;
	pr_info("%s dev_s %p,dev_s->dev %p\n", __func__, dev_s, dev_s->dev);

	/* initialize locks */
	mutex_init(&dev_s->omx_mutex);
	spin_lock_init(&dev_s->timestamp_lock);

	dev_s->inst = inst;
	dev_s->index = inst;
	dev_s->mapped = true;
	snprintf(dev_s->vf_receiver_name, VIDEOSYNC_S_VF_RECEIVER_NAME_SIZE,
		 RECEIVER_NAME ".%x", inst & 0xff);
	snprintf(dev_s->vf_provider_name, VIDEOSYNC_S_VF_RECEIVER_NAME_SIZE,
		 PROVIDER_NAME ".%x", inst & 0xff);

	pr_info("videosync create instance reg %s\n",
		dev_s->vf_receiver_name);
#ifdef CONFIG_AMLOGIC_MEDIA_VFM
	vf_receiver_init(&dev_s->vp_vf_receiver,
			 dev_s->vf_receiver_name,
			 &videosync_vf_receiver, dev_s);

	vf_reg_receiver(&dev_s->vp_vf_receiver);
#endif
	return 0;
}

static void videosync_destroy_instance(int inst)
{
	pr_info("videosync destroy instance %s\n",
		vp_dev->video_prov[inst].vf_receiver_name);
#ifdef CONFIG_AMLOGIC_MEDIA_VFM
	vf_unreg_receiver(&vp_dev->video_prov[inst].vp_vf_receiver);
#endif
}

static void videosync_thread_tick(struct videosync_dev *dev)
{
	int i = 0;
	struct videosync_s *dev_s;
	unsigned long flags = 0;
	unsigned long time_left;

	if (!dev)
		return;

	wait_event_interruptible_timeout(dev->videosync_wait, dev->wakeup,
					 msecs_to_jiffies(500));
	dev->wakeup = 0;

	for (i = 0; i < VIDEOSYNC_S_COUNT; i++) {
		dev_s = &dev->video_prov[i];
		if (dev_s->active_state == VIDEOSYNC_INACTIVE_REQ) {
			dev_s->active_state = VIDEOSYNC_INACTIVE;
			complete(&dev_s->inactive_done);
		}
	}

	vp_print(RECEIVER_NAME, PRINT_OTHER,
		 "active num %d\n", dev->active_dev_s_num);

	spin_lock_irqsave(&dev->dev_s_num_slock, flags);
	if (dev->active_dev_s_num > 0) {
		spin_unlock_irqrestore(&dev->dev_s_num_slock, flags);
		prepare_queued_queue(dev);
		prepare_ready_queue(dev);
	} else {
		spin_unlock_irqrestore(&dev->dev_s_num_slock, flags);
		vp_print(RECEIVER_NAME, PRINT_OTHER,
			 "videosync: no active dev, thread go to sleep\n");
		time_left =
			wait_for_completion_timeout(&dev->thread_active,
						    msecs_to_jiffies(500));
		if (time_left == 0)
			vp_print(RECEIVER_NAME, PRINT_OTHER,
				 "videosync: thread tick timeout\n");
	}
}

static void videosync_sleep(struct videosync_dev *dev)
{
	videosync_thread_tick(dev);
	try_to_freeze();
}

static int videosync_thread(void *data)
{
	struct videosync_dev *dev = (struct videosync_dev *)data;

	pr_info("videosync thread started\n");

	set_freezable();

	while (!kthread_should_stop())
		videosync_sleep(dev);

	pr_info("videosync thread exit\n");
	return 0;
}

int __init videosync_init(void)
{
	int ret = -1, i;
	struct device *devp;

	ret = class_register(&videosync_class);
	if (ret < 0)
		return ret;
	ret = register_chrdev(VIDEOSYNC_MAJOR, "videosync", &videosync_fops);
	if (ret < 0) {
		pr_err("Can't allocate major for videosync device\n");
		goto error1;
	}

	devp = device_create(&videosync_class,
			     NULL,
			     MKDEV(VIDEOSYNC_MAJOR, 0),
			     NULL,
			     VIDEOSYNC_DEVICE_NAME);
	if (IS_ERR(devp)) {
		pr_err("failed to create videosync device node\n");
		ret = PTR_ERR(devp);
		return ret;
	}

	vp_dev = kzalloc(sizeof(*vp_dev), GFP_KERNEL);
	if (!vp_dev)
		return -ENOMEM;

	vp_dev->video_prov = kcalloc(VIDEOSYNC_S_COUNT,
				     sizeof(struct videosync_s),
				     GFP_KERNEL);
	if (!vp_dev->video_prov)
		return -ENOMEM;

	mutex_init(&vp_dev->vp_mutex);
	spin_lock_init(&vp_dev->dev_s_num_slock);

	for (i = 0; i < VIDEOSYNC_S_COUNT; i++) {
		ret = videosync_create_instance(i);
		if (ret) {
			pr_err("videosync: error %d while create instance\n",
			       ret);
			goto error1;
		}
	}

	init_completion(&vp_dev->thread_active);
	vp_dev->wakeup = 0;
	init_waitqueue_head(&vp_dev->videosync_wait);
	vp_dev->kthread = kthread_run(videosync_thread, vp_dev, "videosync");
	videosync_inited = true;
	return ret;
error1:
	kfree(vp_dev);
	vp_dev = NULL;
	unregister_chrdev(VIDEOSYNC_MAJOR, "videosync");
	class_unregister(&videosync_class);
	return ret;
}

void __exit videosync_exit(void)
{
	int i, ret;

	complete(&vp_dev->thread_active);

	if (vp_dev->kthread) {
		ret = kthread_stop(vp_dev->kthread);
		if (ret < 0)
			pr_info("%s, kthread_stop return %d.\n", __func__, ret);
		vp_dev->kthread = NULL;
	}
	videosync_inited = false;

	for (i = 0; i < VIDEOSYNC_S_COUNT; i++)
		videosync_destroy_instance(i);

	kfree(vp_dev->video_prov);
	vp_dev->video_prov = NULL;

	kfree(vp_dev);
	vp_dev = NULL;
	device_destroy(&videosync_class, MKDEV(VIDEOSYNC_MAJOR, 0));
	unregister_chrdev(VIDEOSYNC_MAJOR, VIDEOSYNC_DEVICE_NAME);
	class_unregister(&videosync_class);
}

