// SPDX-License-Identifier: (GPL-2.0+ OR MIT)
/*
 * drivers/amlogic/media/vout/cvbs/cvbs_out.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.
 *
 */

/* Linux Headers */
#include <linux/version.h>
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/errno.h>
#include <linux/string.h>
#include <linux/io.h>
#include <linux/mm.h>
#include <linux/interrupt.h>
#include <linux/mutex.h>
#include <linux/platform_device.h>
#include <linux/of_device.h>
#include <linux/fs.h>
#include <linux/slab.h>
#include <linux/interrupt.h>
#include <linux/ctype.h>
/*#include <linux/major.h>*/
#include <linux/cdev.h>
#include <linux/delay.h>
#include <linux/jiffies.h>
#include <linux/workqueue.h>
#include <linux/uaccess.h>
#include <linux/clk.h>

/* Amlogic Headers */
#include <linux/amlogic/media/registers/cpu_version.h>
#include <linux/amlogic/iomap.h>
#include <linux/amlogic/media/vout/vout_notify.h>
#include <linux/amlogic/media/vout/vinfo.h>
#include <linux/amlogic/media/vout/vdac_dev.h>
#ifdef CONFIG_AMLOGIC_VPU
#include <linux/amlogic/media/vpu/vpu.h>
#endif

/* Local Headers */
#include "cvbsregs.h"
#include "cvbs_out.h"
#include "enc_clk_config.h"
#include "cvbs_out_reg.h"
#include "cvbs_log.h"
#ifdef CONFIG_AMLOGIC_WSS
#include "wss.h"
#endif
#include <linux/component.h>
#include <linux/amlogic/gki_module.h>
#include <drm/amlogic/meson_drm_bind.h>

#ifdef CONFIG_AMLOGIC_VOUT_CC_BYPASS
/* interrupt source */
#define INT_VIU_VSYNC    35
#endif

const char *cvbs_mode_t[] = {
	"480cvbs",
	"576cvbs",
	"pal_m",
	"pal_n",
	"ntsc_m",
	NULL
};

static struct vinfo_s cvbs_info[] = {
	{ /* MODE_480CVBS*/
		.name              = "480cvbs",
		.mode              = VMODE_CVBS,
		.frac              = 0,
		.width             = 720,
		.height            = 480,
		.field_height      = 240,
		.aspect_ratio_num  = 4,
		.aspect_ratio_den  = 3,
		.sync_duration_num = 60,
		.sync_duration_den = 1,
		.video_clk         = 27000000,
		.htotal            = 1716,
		.vtotal            = 525,
		.fr_adj_type       = VOUT_FR_ADJ_NONE,
		.viu_color_fmt     = COLOR_FMT_YUV444,
		.viu_mux           = VIU_MUX_ENCI,
		.vout_device       = NULL,
	},
	{ /* MODE_576CVBS */
		.name              = "576cvbs",
		.mode              = VMODE_CVBS,
		.frac              = 0,
		.width             = 720,
		.height            = 576,
		.field_height      = 288,
		.aspect_ratio_num  = 4,
		.aspect_ratio_den  = 3,
		.sync_duration_num = 50,
		.sync_duration_den = 1,
		.video_clk         = 27000000,
		.htotal            = 1728,
		.vtotal            = 625,
		.fr_adj_type       = VOUT_FR_ADJ_NONE,
		.viu_color_fmt     = COLOR_FMT_YUV444,
		.viu_mux           = VIU_MUX_ENCI,
		.vout_device       = NULL,
	},
	{ /* MODE_PAL_M */
		.name              = "pal_m",
		.mode              = VMODE_CVBS,
		.frac              = 0,
		.width             = 720,
		.height            = 480,
		.field_height      = 240,
		.aspect_ratio_num  = 4,
		.aspect_ratio_den  = 3,
		.sync_duration_num = 60,
		.sync_duration_den = 1,
		.video_clk         = 27000000,
		.htotal            = 1716,
		.vtotal            = 525,
		.viu_color_fmt     = COLOR_FMT_YUV444,
		.viu_mux           = VIU_MUX_ENCI,
		.vout_device       = NULL,
	},
	{ /* MODE_PAL_N */
		.name              = "pal_n",
		.mode              = VMODE_CVBS,
		.frac              = 0,
		.width             = 720,
		.height            = 576,
		.field_height      = 288,
		.aspect_ratio_num  = 4,
		.aspect_ratio_den  = 3,
		.sync_duration_num = 50,
		.sync_duration_den = 1,
		.video_clk         = 27000000,
		.htotal            = 1728,
		.vtotal            = 625,
		.viu_color_fmt     = COLOR_FMT_YUV444,
		.viu_mux           = VIU_MUX_ENCI,
		.vout_device       = NULL,
	},
	{ /* MODE_NTSC_M */
		.name              = "ntsc_m",
		.mode              = VMODE_CVBS,
		.frac              = 0,
		.width             = 720,
		.height            = 480,
		.field_height      = 240,
		.aspect_ratio_num  = 4,
		.aspect_ratio_den  = 3,
		.sync_duration_num = 60,
		.sync_duration_den = 1,
		.video_clk         = 27000000,
		.htotal            = 1716,
		.vtotal            = 525,
		.fr_adj_type       = VOUT_FR_ADJ_NONE,
		.viu_color_fmt     = COLOR_FMT_YUV444,
		.viu_mux           = VIU_MUX_ENCI,
		.vout_device       = NULL,
	},
};

#ifdef CONFIG_AMLOGIC_VOUT_CC_BYPASS
static struct cc_ring_mgr_s cc_ringbuf;
static spinlock_t tvout_clk_lock;
static unsigned int vsync_empty_flag;
static unsigned int vsync_empty_flag_evn;
static unsigned int vsync_empty_flag_odd;
#endif

/*bit[0]: 0=vid_pll, 1=gp0_pll*/
/*bit[1]: 0=vid2_clk, 1=vid1_clk*/
unsigned int cvbs_clk_path;

static struct cvbs_drv_s *cvbs_drv;
static enum cvbs_mode_e local_cvbs_mode;
static DEFINE_MUTEX(setmode_mutex);

static int cvbs_vdac_power_level;
static ssize_t aml_CVBS_attr_vdac_power_show(struct class *class,
					     struct class_attribute *attr,
					     char *buf);
static ssize_t aml_CVBS_attr_vdac_power_store(struct class *class,
					      struct class_attribute *attr,
					      const char *buf, size_t count);
struct class_attribute class_CVBS_attr_vdac_power_level =
	__ATTR(vdac_power_level, 0644, aml_CVBS_attr_vdac_power_show,
	       aml_CVBS_attr_vdac_power_store);

static ssize_t aml_CVBS_attr_debug_show(struct class *class,
					struct class_attribute *attr,
					char *buf);
static ssize_t aml_CVBS_attr_debug_store(struct class *class,
					 struct class_attribute *attr,
					 const char *buf, size_t count);
static int meson_cvbs_bind(struct device *dev,
			      struct device *master, void *data);
static void meson_cvbs_unbind(struct device *dev,
				 struct device *master, void *data);

struct class_attribute class_CVBS_attr_debug =
	__ATTR(debug, 0644, aml_CVBS_attr_debug_show,
	       aml_CVBS_attr_debug_store);

#ifdef CONFIG_AMLOGIC_WSS
struct class_attribute class_CVBS_attr_wss = __ATTR(wss, 0644,
			aml_CVBS_attr_wss_show, aml_CVBS_attr_wss_store);
#endif /*CONFIG_AMLOGIC_WSS*/

static void cvbs_bist_test(unsigned int bist, void *data);

/* static int get_cpu_minor(void)
 * {
 *	return get_meson_cpu_version(MESON_CPU_VERSION_LVL_MINOR);
 * }
 */

int cvbs_cpu_type(void)
{
	if (!cvbs_drv) {
		cvbs_log_err("error: %s: no cvbs drv\n", __func__);
		return -1;
	}
	if (!cvbs_drv->cvbs_data) {
		cvbs_log_err("error: %s: no cvbs data\n", __func__);
		return -1;
	}
	return cvbs_drv->cvbs_data->cpu_id;
}
EXPORT_SYMBOL(cvbs_cpu_type);

struct meson_cvbsout_data *get_cvbs_data(void)
{
	if (!cvbs_drv) {
		cvbs_log_err("error: %s: no cvbs drv\n", __func__);
		return NULL;
	}
	if (!cvbs_drv->cvbs_data) {
		cvbs_log_err("error: %s: no cvbs data\n", __func__);
		return NULL;
	}
	return cvbs_drv->cvbs_data;
}

static void cvbs_vdac_output(unsigned int open)
{
	if (open == 0) { /* close */
		cvbs_drv->flag &= ~CVBS_FLAG_EN_VDAC;
		vdac_enable(0, VDAC_MODULE_CVBS_OUT);
	} else if (open == 1) { /* open */
		vdac_vref_adj(cvbs_drv->cvbs_data->vdac_vref_adj);
		vdac_enable(1, VDAC_MODULE_CVBS_OUT);
		cvbs_drv->flag |= CVBS_FLAG_EN_VDAC;
	}
	cvbs_log_info("%s: %d\n", __func__, open);
}

#ifdef CONFIG_CVBS_PERFORMANCE_COMPATIBILITY_SUPPORT
static void cvbs_performance_enhancement(enum cvbs_mode_e mode)
{
	struct performance_config_s *perfconf = NULL;
	const struct reg_s *s = NULL;
	int i = 0;

	switch (mode) {
	case MODE_576CVBS:
		perfconf = &cvbs_drv->perf_conf_pal;
		break;
	case MODE_480CVBS:
	case MODE_NTSC_M:
		perfconf = &cvbs_drv->perf_conf_ntsc;
		break;
	default:
		break;
	}
	if (!perfconf)
		return;

	if (!perfconf->reg_table) {
		cvbs_log_info("no performance table\n");
		return;
	}

	i = 0;
	s = perfconf->reg_table;
	while (i < perfconf->reg_cnt) {
		cvbs_out_reg_write(s->reg, s->val);
		cvbs_log_info("%s: vcbus reg 0x%04x = 0x%08x\n",
			      __func__, s->reg, s->val);
		s++;
		i++;
	}
	cvbs_log_info("%s\n", __func__);
}
#endif/* end of CVBS_PERFORMANCE_COMPATIBILITY_SUPPORT */

static int cvbs_out_set_venc(enum cvbs_mode_e mode)
{
	const struct reg_s *s;
	int ret;

	/* setup encoding regs */
	s = cvbs_regs_tab[mode].enc_reg_setting;
	if (!s) {
		cvbs_log_info("display mode %d get enc set NULL\n", mode);
		ret = -1;
	} else {
		while (s->reg != MREG_END_MARKER) {
			cvbs_out_reg_write(s->reg, s->val);
			s++;
		}
		ret = 0;
	}
	/* cvbs_log_info("%s[%d]\n", __func__, __LINE__); */
	return ret;
}

static void cvbs_out_disable_clk(void)
{
	disable_vmode_clk();
}

static void cvbs_out_vpu_power_ctrl(int status)
{
	if (!cvbs_drv->vinfo)
		return;
	if (status) {
#ifdef CONFIG_AMLOGIC_VPU
		vpu_dev_clk_request(cvbs_drv->cvbs_vpu_dev, cvbs_drv->vinfo->video_clk);
		vpu_dev_mem_power_on(cvbs_drv->cvbs_vpu_dev);
#endif
	} else {
#ifdef CONFIG_AMLOGIC_VPU
		vpu_dev_mem_power_down(cvbs_drv->cvbs_vpu_dev);
		vpu_dev_clk_release(cvbs_drv->cvbs_vpu_dev);
#endif
	}
}

static void cvbs_out_clk_gate_ctrl(int status)
{
	if (!cvbs_drv->vinfo)
		return;

	if (status) {
		if (cvbs_drv->clk_gate_state) {
			cvbs_log_info("clk_gate is already on\n");
			return;
		}

		if (IS_ERR(cvbs_drv->venci_top_gate))
			cvbs_log_err("error: %s: venci_top_gate\n", __func__);
		else
			clk_prepare_enable(cvbs_drv->venci_top_gate);

		if (IS_ERR(cvbs_drv->venci_0_gate))
			cvbs_log_err("error: %s: venci_0_gate\n", __func__);
		else
			clk_prepare_enable(cvbs_drv->venci_0_gate);

		if (IS_ERR(cvbs_drv->venci_1_gate))
			cvbs_log_err("error: %s: venci_1_gate\n", __func__);
		else
			clk_prepare_enable(cvbs_drv->venci_1_gate);

		if (IS_ERR(cvbs_drv->vdac_clk_gate))
			cvbs_log_err("error: %s: vdac_clk_gate\n", __func__);
		else
			clk_prepare_enable(cvbs_drv->vdac_clk_gate);

#ifdef CONFIG_AMLOGIC_VPU
		vpu_dev_clk_gate_on(cvbs_drv->cvbs_vpu_dev);
#endif

		cvbs_drv->clk_gate_state = 1;
	} else {
		if (cvbs_drv->clk_gate_state == 0) {
			cvbs_log_info("clk_gate is already off\n");
			return;
		}

#ifdef CONFIG_AMLOGIC_VPU
		vpu_dev_clk_gate_off(cvbs_drv->cvbs_vpu_dev);
#endif
		if (IS_ERR(cvbs_drv->vdac_clk_gate))
			cvbs_log_err("error: %s: vdac_clk_gate\n", __func__);
		else
			clk_disable_unprepare(cvbs_drv->vdac_clk_gate);

		if (IS_ERR(cvbs_drv->venci_0_gate))
			cvbs_log_err("error: %s: venci_0_gate\n", __func__);
		else
			clk_disable_unprepare(cvbs_drv->venci_0_gate);

		if (IS_ERR(cvbs_drv->venci_1_gate))
			cvbs_log_err("error: %s: venci_1_gate\n", __func__);
		else
			clk_disable_unprepare(cvbs_drv->venci_1_gate);

		if (IS_ERR(cvbs_drv->venci_top_gate))
			cvbs_log_err("error: %s: venci_top_gate\n", __func__);
		else
			clk_disable_unprepare(cvbs_drv->venci_top_gate);

		cvbs_drv->clk_gate_state = 0;
	}
}

static void cvbs_vdac_dwork(struct work_struct *work)
{
	if ((cvbs_drv->flag & CVBS_FLAG_EN_ENCI) == 0)
		return;
	cvbs_vdac_output(1);
}

static int cvbs_out_setmode(void)
{
	int ret;

	switch (local_cvbs_mode) {
	case MODE_480CVBS:
		cvbs_log_info("SET cvbs mode: 480cvbs\n");
		break;
	case MODE_576CVBS:
		cvbs_log_info("SET cvbs mode: 576cvbs\n");
		break;
	case MODE_PAL_M:
		cvbs_log_info("SET cvbs mode: pal_m\n");
		break;
	case MODE_PAL_N:
		cvbs_log_info("SET cvbs mode: pal_n\n");
		break;
	case MODE_NTSC_M:
		cvbs_log_info("SET cvbs mode: ntsc_m\n");
		break;
	default:
		cvbs_log_err("%s:invalid cvbs mode", __func__);
		break;
	}

	if (local_cvbs_mode >= MODE_MAX) {
		cvbs_log_err("%s:mode error.return", __func__);
		return -1;
	}
	mutex_lock(&setmode_mutex);

	cvbs_out_vpu_power_ctrl(1);
	cvbs_out_clk_gate_ctrl(1);

	cvbs_vdac_output(0);
	cvbs_out_reg_write(VENC_VDAC_SETTING, 0xff);
	/* Before setting clk for CVBS, disable ENCI to avoid hungup */
	cvbs_out_reg_write(ENCI_VIDEO_EN, 0);
	set_vmode_clk();
	ret = cvbs_out_set_venc(local_cvbs_mode);
	if (ret) {
		mutex_lock(&setmode_mutex);
		return -1;
	}
#ifdef CONFIG_CVBS_PERFORMANCE_COMPATIBILITY_SUPPORT
	cvbs_performance_enhancement(local_cvbs_mode);
#endif
	cvbs_drv->flag |= CVBS_FLAG_EN_ENCI;
	schedule_delayed_work(&cvbs_drv->vdac_dwork, msecs_to_jiffies(1000));
	mutex_unlock(&setmode_mutex);
	return 0;
}

static int cvbs_open(struct inode *inode, struct file *file)
{
	struct cvbs_drv_s *cdrv;
	/* Get the per-device structure that contains this cdev */
	cdrv = container_of(&inode->i_cdev, struct cvbs_drv_s, cdev);
	file->private_data = cdrv;
	return 0;
}

static int cvbs_release(struct inode *inode, struct file *file)
{
	file->private_data = NULL;
	return 0;
}

static long cvbs_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
{
	long ret = 0;
#ifdef CONFIG_AMLOGIC_VOUT_CC_BYPASS
	unsigned int CC_2byte_data = 0;
	unsigned long flags = 0;
	void __user *argp = (void __user *)arg;
#endif
	cvbs_log_info("[cvbs..] %s: cmd_nr = 0x%x\n",
			__func__, _IOC_NR(cmd));
	if (_IOC_TYPE(cmd) != _TM_V) {
		cvbs_log_err("%s invalid command: %u\n", __func__, cmd);
		return -ENOTTY;
	}
	switch (cmd) {
#ifdef CONFIG_AMLOGIC_VOUT_CC_BYPASS
	case VOUT_IOC_CC_OPEN:
		spin_lock_irqsave(&tvout_clk_lock, flags);
		memset(&cc_ringbuf, 0, sizeof(struct cc_ring_mgr_s));
		spin_unlock_irqrestore(&tvout_clk_lock, flags);
		cvbs_out_reg_setb(ENCI_VBI_SETTING, 0x3, 0, 2);
		break;
	case VOUT_IOC_CC_CLOSE:
		spin_lock_irqsave(&tvout_clk_lock, flags);
		memset(&cc_ringbuf, 0, sizeof(struct cc_ring_mgr_s));
		spin_unlock_irqrestore(&tvout_clk_lock, flags);
		cvbs_out_reg_setb(ENCI_VBI_SETTING, 0x0, 0, 2);
		break;
	case VOUT_IOC_CC_DATA: {
		struct vout_cc_parm_s parm = {0};

		spin_lock_irqsave(&tvout_clk_lock, flags);
		if (copy_from_user(&parm, argp,
				sizeof(struct vout_cc_parm_s))) {
			cvbs_log_err("VOUT_IOC_CC_DATA invalid parameter\n");
			ret = -EFAULT;
			spin_unlock_irqrestore(&tvout_clk_lock, flags);
			break;
		}

		if (parm.type != 0) {
			spin_unlock_irqrestore(&tvout_clk_lock, flags);
			break;
		}
		/* cc standard:nondisplay control byte + display control byte */
		/* our chip high-low 16bits is opposite */
		CC_2byte_data = parm.data2 << 8 | parm.data1;
		if ((cc_ringbuf.wp + 1) % MAX_RING_BUFF_LEN != cc_ringbuf.rp) {
			cc_ringbuf.cc_data[cc_ringbuf.wp].type = parm.type;
			cc_ringbuf.cc_data[cc_ringbuf.wp].data = CC_2byte_data;
			cc_ringbuf.wp = (cc_ringbuf.wp + 1) %
					MAX_RING_BUFF_LEN;
			/* vout_log_info("CCringbuf Write :0x%x wp:%d\n", */
			/* CC_2byte_data, cc_ringbuf.wp); */
		} else {
			cvbs_log_err("CCringbuf is FULL!! can't write.\n");
		}

		spin_unlock_irqrestore(&tvout_clk_lock, flags);
		break;
	}
#endif
	default:
		ret = -ENOIOCTLCMD;
		cvbs_log_err("%s %d is not supported command\n",
			     __func__, cmd);
		break;
	}
	cvbs_log_info("%s..out.ret=0x%lx\n", __func__, ret);
	return ret;
}

#ifdef CONFIG_COMPAT
static long cvbs_compat_ioctl(struct file *file, unsigned int cmd,
			      unsigned long arg)
{
	unsigned long ret;

	arg = (unsigned long)compat_ptr(arg);
	ret = cvbs_ioctl(file, cmd, arg);
	return ret;
}
#endif

static const struct file_operations am_cvbs_fops = {
	.open	= cvbs_open,
	.read	= NULL,/* am_cvbs_read, */
	.write	= NULL,
	.unlocked_ioctl	= cvbs_ioctl,
#ifdef CONFIG_COMPAT
	.compat_ioctl = cvbs_compat_ioctl,
#endif
	.release	= cvbs_release,
	.poll		= NULL,
};

const struct vinfo_s *get_valid_vinfo(char  *mode)
{
	struct vinfo_s *vinfo = NULL;
	int  i, count = ARRAY_SIZE(cvbs_info);
	int mode_name_len = 0;

	/*cvbs_log_info("get_valid_vinfo..out.mode:%s\n", mode);*/
	for (i = 0; i < count; i++) {
		if (strncmp(cvbs_info[i].name, mode,
			    strlen(cvbs_info[i].name)) == 0) {
			if (strlen(cvbs_info[i].name) > mode_name_len) {
				vinfo = &cvbs_info[i];
				mode_name_len = strlen(cvbs_info[i].name);
				local_cvbs_mode = i;
				break;
			}
		}
	}
	if (vinfo) {
		strncpy(vinfo->ext_name, mode,
			(strlen(mode) < 32) ? strlen(mode) : 31);
		cvbs_drv->vinfo = vinfo;
	} else {
		local_cvbs_mode = MODE_MAX;
	}
	return vinfo;
}
EXPORT_SYMBOL(get_valid_vinfo);

static struct vinfo_s *cvbs_get_current_info(void *data)
{
	return cvbs_drv->vinfo;
}

enum cvbs_mode_e get_local_cvbs_mode(void)
{
	return local_cvbs_mode;
}
EXPORT_SYMBOL(get_local_cvbs_mode);

int cvbs_set_current_vmode(enum vmode_e mode, void *data)
{
	enum vmode_e tvmode;

	tvmode = mode & VMODE_MODE_BIT_MASK;
	if (tvmode != VMODE_CVBS)
		return -EINVAL;
	if (local_cvbs_mode == MODE_MAX) {
		cvbs_log_info("local_cvbs_mode err:%d!\n", local_cvbs_mode);
		return 1;
	}

	cvbs_log_info("mode is %d,sync_duration_den=%d,sync_duration_num=%d\n",
		      tvmode, cvbs_drv->vinfo->sync_duration_den,
		      cvbs_drv->vinfo->sync_duration_num);

	/*set limit range for enci*/
	amvecm_clip_range_limit(1);
	if (mode & VMODE_INIT_BIT_MASK) {
		cvbs_out_vpu_power_ctrl(1);
		cvbs_out_clk_gate_ctrl(1);
		cvbs_vdac_output(1);
		cvbs_drv->flag = (CVBS_FLAG_EN_ENCI | CVBS_FLAG_EN_VDAC);
		cvbs_log_info("already display in uboot\n");
		return 0;
	}
	cvbs_out_setmode();

	return 0;
}
EXPORT_SYMBOL(cvbs_set_current_vmode);

static enum vmode_e cvbs_validate_vmode(char *mode, unsigned int frac,
					void *data)
{
	const struct vinfo_s *info = get_valid_vinfo(mode);

	if (frac)
		return VMODE_MAX;

	if (info)
		return VMODE_CVBS;
	return VMODE_MAX;
}

static int cvbs_check_same_vmodeattr(char *mode, void *data)
{
	return 1;
}

static int cvbs_vmode_is_supported(enum vmode_e mode, void *data)
{
	if ((mode & VMODE_MODE_BIT_MASK) == VMODE_CVBS)
		return true;
	return false;
}

static int cvbs_module_disable(enum vmode_e cur_vmod, void *data)
{
	cvbs_drv->flag &= ~CVBS_FLAG_EN_ENCI;

	cvbs_vdac_output(0);

	/*restore full range for encp/encl*/
	amvecm_clip_range_limit(0);

	cvbs_out_vpu_power_ctrl(0);
	cvbs_out_clk_gate_ctrl(0);

	return 0;
}

static int cvbs_vout_state;
static int cvbs_vout_set_state(int index, void *data)
{
	cvbs_vout_state |= (1 << index);
	return 0;
}

static int cvbs_vout_clr_state(int index, void *data)
{
	cvbs_vout_state &= ~(1 << index);
	return 0;
}

static int cvbs_vout_get_state(void *data)
{
	return cvbs_vout_state;
}

static int cvbs_vout_get_disp_cap(char *buf, void *data)
{
	int ret = 0, i;

	for (i = 0; cvbs_mode_t[i]; i++)
		ret += snprintf(buf + ret, PAGE_SIZE, "%s\n", cvbs_mode_t[i]);
	return ret;
}

#ifdef CONFIG_PM
static int cvbs_suspend(void *data)
{
	/* TODO */
	/* video_dac_disable(); */
	cvbs_drv->flag &= ~CVBS_FLAG_EN_ENCI;
	cvbs_vdac_output(0);
	return 0;
}

static int cvbs_resume(void *data)
{
	/* TODO */
	/* video_dac_enable(0xff); */
	cvbs_set_current_vmode(cvbs_drv->vinfo->mode, NULL);
	return 0;
}
#endif

static struct vout_server_s cvbs_vout_server = {
	.name = "cvbs_vout_server",
	.op = {
		.get_vinfo = cvbs_get_current_info,
		.set_vmode = cvbs_set_current_vmode,
		.validate_vmode = cvbs_validate_vmode,
		.check_same_vmodeattr = cvbs_check_same_vmodeattr,
		.vmode_is_supported = cvbs_vmode_is_supported,
		.disable = cvbs_module_disable,
		.set_state = cvbs_vout_set_state,
		.clr_state = cvbs_vout_clr_state,
		.get_state = cvbs_vout_get_state,
		.get_disp_cap = cvbs_vout_get_disp_cap,
		.set_vframe_rate_hint = NULL,
		.get_vframe_rate_hint = NULL,
		.set_bist = cvbs_bist_test,
#ifdef CONFIG_PM
		.vout_suspend = cvbs_suspend,
		.vout_resume = cvbs_resume,
#endif
	},
	.data = NULL,
};

#ifdef CONFIG_AMLOGIC_VOUT2_SERVE
static struct vout_server_s cvbs_vout2_server = {
	.name = "cvbs_vout2_server",
	.op = {
		.get_vinfo = cvbs_get_current_info,
		.set_vmode = cvbs_set_current_vmode,
		.validate_vmode = cvbs_validate_vmode,
		.vmode_is_supported = cvbs_vmode_is_supported,
		.disable = cvbs_module_disable,
		.set_state = cvbs_vout_set_state,
		.clr_state = cvbs_vout_clr_state,
		.get_state = cvbs_vout_get_state,
		.get_disp_cap = cvbs_vout_get_disp_cap,
		.set_vframe_rate_hint = NULL,
		.get_vframe_rate_hint = NULL,
		.set_bist = cvbs_bist_test,
#ifdef CONFIG_PM
		.vout_suspend = cvbs_suspend,
		.vout_resume = cvbs_resume,
#endif
	},
	.data = NULL,
};
#endif

static void cvbs_init_vout(void)
{
	if (!cvbs_drv->vinfo)
		cvbs_drv->vinfo = &cvbs_info[MODE_480CVBS];

	if (vout_register_server(&cvbs_vout_server))
		cvbs_log_err("register cvbs module server fail\n");
	else
		cvbs_log_info("register cvbs module server ok\n");
#ifdef CONFIG_AMLOGIC_VOUT2_SERVE
	if (vout2_register_server(&cvbs_vout2_server))
		cvbs_log_err("register cvbs module vout2 server fail\n");
	else
		cvbs_log_info("register cvbs module vout2 server ok\n");
#endif
}

static char *cvbs_out_bist_str[] = {
	"OFF",   /* 0 */
	"Color Bar",   /* 1 */
	"Thin Line",   /* 2 */
	"Dot Grid",    /* 3 */
	"White",
	"Red",
	"Green",
	"Blue",
	"Black",
};

static void cvbs_bist_test(unsigned int bist, void *data)
{
	switch (bist) {
	case 1:
	case 2:
	case 3:
		cvbs_out_reg_write(ENCI_TST_CLRBAR_STRT, 0x112);
		cvbs_out_reg_write(ENCI_TST_CLRBAR_WIDTH, 0xb4);
		cvbs_out_reg_write(ENCI_TST_MDSEL, bist);
		cvbs_out_reg_write(ENCI_TST_Y, 0x200);
		cvbs_out_reg_write(ENCI_TST_CB, 0x200);
		cvbs_out_reg_write(ENCI_TST_CR, 0x200);
		cvbs_out_reg_write(ENCI_TST_EN, 1);
		pr_info("show bist pattern %d: %s\n",
			bist, cvbs_out_bist_str[bist]);
		break;
	case 4:
		cvbs_out_reg_write(ENCI_TST_CLRBAR_STRT, 0x112);
		cvbs_out_reg_write(ENCI_TST_CLRBAR_WIDTH, 0xb4);
		cvbs_out_reg_write(ENCI_TST_MDSEL, 0);
		cvbs_out_reg_write(ENCI_TST_Y, 0x3ff);
		cvbs_out_reg_write(ENCI_TST_CB, 0x200);
		cvbs_out_reg_write(ENCI_TST_CR, 0x200);
		cvbs_out_reg_write(ENCI_TST_EN, 1);
		pr_info("show bist pattern %d: %s\n",
			bist, cvbs_out_bist_str[bist]);
		break;
	case 5:
		cvbs_out_reg_write(ENCI_TST_CLRBAR_STRT, 0x112);
		cvbs_out_reg_write(ENCI_TST_CLRBAR_WIDTH, 0xb4);
		cvbs_out_reg_write(ENCI_TST_MDSEL, 0);
		cvbs_out_reg_write(ENCI_TST_Y, 0x200);
		cvbs_out_reg_write(ENCI_TST_CB, 0x0);
		cvbs_out_reg_write(ENCI_TST_CR, 0x3ff);
		cvbs_out_reg_write(ENCI_TST_EN, 1);
		pr_info("show bist pattern %d: %s\n",
			bist, cvbs_out_bist_str[bist]);
		break;
	case 6:
		cvbs_out_reg_write(ENCI_TST_CLRBAR_STRT, 0x112);
		cvbs_out_reg_write(ENCI_TST_CLRBAR_WIDTH, 0xb4);
		cvbs_out_reg_write(ENCI_TST_MDSEL, 0);
		cvbs_out_reg_write(ENCI_TST_Y, 0x200);
		cvbs_out_reg_write(ENCI_TST_CB, 0x0);
		cvbs_out_reg_write(ENCI_TST_CR, 0x0);
		cvbs_out_reg_write(ENCI_TST_EN, 1);
		pr_info("show bist pattern %d: %s\n",
			bist, cvbs_out_bist_str[bist]);
		break;
	case 7:
		cvbs_out_reg_write(ENCI_TST_CLRBAR_STRT, 0x112);
		cvbs_out_reg_write(ENCI_TST_CLRBAR_WIDTH, 0xb4);
		cvbs_out_reg_write(ENCI_TST_MDSEL, 0);
		cvbs_out_reg_write(ENCI_TST_Y, 0x200);
		cvbs_out_reg_write(ENCI_TST_CB, 0x3ff);
		cvbs_out_reg_write(ENCI_TST_CR, 0x0);
		cvbs_out_reg_write(ENCI_TST_EN, 1);
		pr_info("show bist pattern %d: %s\n",
			bist, cvbs_out_bist_str[bist]);
		break;
	case 8:
		cvbs_out_reg_write(ENCI_TST_CLRBAR_STRT, 0x112);
		cvbs_out_reg_write(ENCI_TST_CLRBAR_WIDTH, 0xb4);
		cvbs_out_reg_write(ENCI_TST_MDSEL, 0);
		cvbs_out_reg_write(ENCI_TST_Y, 0x0);
		cvbs_out_reg_write(ENCI_TST_CB, 0x200);
		cvbs_out_reg_write(ENCI_TST_CR, 0x200);
		cvbs_out_reg_write(ENCI_TST_EN, 1);
		pr_info("show bist pattern %d: %s\n",
			bist, cvbs_out_bist_str[bist]);
		break;
	case 0:
	default:
		cvbs_out_reg_write(ENCI_TST_MDSEL, 1);
		cvbs_out_reg_write(ENCI_TST_Y, 0x200);
		cvbs_out_reg_write(ENCI_TST_CB, 0x200);
		cvbs_out_reg_write(ENCI_TST_CR, 0x200);
		cvbs_out_reg_write(ENCI_TST_EN, 0);
		pr_info("show bist pattern %d: %s\n",
			bist, cvbs_out_bist_str[0]);
		break;
	}
}

static ssize_t aml_CVBS_attr_vdac_power_show(struct class *class,
					     struct class_attribute *attr,
					     char *buf)
{
	return snprintf(buf, 40, "%s\n", "vdac_power_level");
}

static void vdac_power_level_store(const char *para)
{
	unsigned long level = 0;
	int ret = 0;

	ret = kstrtoul(para, 10, (unsigned long *)&level);
	cvbs_vdac_power_level = level;
}

static ssize_t aml_CVBS_attr_vdac_power_store(struct class *class,
					      struct class_attribute *attr,
					      const char *buf, size_t count)
{
	vdac_power_level_store(buf);

	return strnlen(buf, count);
}

static void dump_clk_registers(void)
{
	struct meson_cvbsout_data *cvbs_data;
	unsigned int vdac_reg0, vdac_reg1;
		/* hiu 10c8 ~ 10cd */

		cvbs_data = get_cvbs_data();
		if (!cvbs_data)
			return;
		/* hiu 104a, 104b*/
	pr_info("----ana----\n");
	pr_info("vid_pll_div[0x%x]:0x%x", cvbs_data->reg_vid_pll_clk_div,
			cvbs_out_vid_pll_read(cvbs_data->reg_vid_pll_clk_div));
	pr_info("----hiu----\n");
	pr_info("vid_clk_div[0x%x]:0x%x", cvbs_data->reg_vid_clk_div,
			cvbs_out_hiu_read(cvbs_data->reg_vid_clk_div));
	pr_info("vid_clk_ctrl[0x%x]:0x%x", cvbs_data->reg_vid_clk_ctrl,
			cvbs_out_hiu_read(cvbs_data->reg_vid_clk_ctrl));
	pr_info("vid2_clk_div[0x%x]:0x%x", cvbs_data->reg_vid2_clk_div,
			cvbs_out_hiu_read(cvbs_data->reg_vid2_clk_div));
	pr_info("vid2_clk_ctrl[0x%x]:0x%x", cvbs_data->reg_vid2_clk_ctrl,
			cvbs_out_hiu_read(cvbs_data->reg_vid2_clk_ctrl));
	pr_info("vid_clk_ctrl2[0x%x]:0x%x", cvbs_data->reg_vid_clk_ctrl2,
			cvbs_out_hiu_read(cvbs_data->reg_vid_clk_ctrl2));
	pr_info("----hiu----\n");

	pr_info("------------------------\n");
	vdac_reg0 = vdac_get_reg_addr(0);
	vdac_reg1 = vdac_get_reg_addr(1);
	if (vdac_reg0 < 0x1000 && vdac_reg1 < 0x1000) {
		pr_info("hiu [0x%x] = 0x%x\n"
			"hiu [0x%x] = 0x%x\n",
			vdac_reg0, cvbs_out_ana_read(vdac_reg0),
			vdac_reg1, cvbs_out_ana_read(vdac_reg1));
	}
	pr_info("------------------------\n");
}

static void cvbs_performance_regs_dump(void)
{
	unsigned int performance_regs_enci[] = {
			VENC_VDAC_DAC0_GAINCTRL,
			ENCI_SYNC_ADJ,
			ENCI_VIDEO_BRIGHT,
			ENCI_VIDEO_CONT,
			ENCI_VIDEO_SAT,
			ENCI_VIDEO_HUE,
			ENCI_YC_DELAY,
			VENC_VDAC_DAC0_FILT_CTRL0,
			VENC_VDAC_DAC0_FILT_CTRL1
		};
	unsigned int vdac_reg0, vdac_reg1;
	int i, size;

	size = sizeof(performance_regs_enci) / sizeof(unsigned int);
	pr_info("------------------------\n");
	for (i = 0; i < size; i++) {
		pr_info("vcbus [0x%x] = 0x%x\n", performance_regs_enci[i],
			cvbs_out_reg_read(performance_regs_enci[i]));
	}
	pr_info("------------------------\n");
	vdac_reg0 = vdac_get_reg_addr(0);
	vdac_reg1 = vdac_get_reg_addr(1);
	if (vdac_reg0 < 0x1000 && vdac_reg1 < 0x1000) {
		pr_info("vdac cntl0 [0x%x] = 0x%x\n"
			"vdac cntl1 [0x%x] = 0x%x\n",
			vdac_reg0, cvbs_out_ana_read(vdac_reg0),
			vdac_reg1, cvbs_out_ana_read(vdac_reg1));
	}
	pr_info("------------------------\n");
}

static void cvbs_performance_config_dump(void)
{
	struct performance_config_s *perfconf = NULL;
	const struct reg_s *s = NULL;
	int i = 0;

	perfconf = &cvbs_drv->perf_conf_pal;
	if (!perfconf->reg_table) {
		pr_info("no performance_pal table!\n");
	} else {
		pr_info("------------------------\n");
		pr_info("performance_pal config:\n");
		s = perfconf->reg_table;
		while (i < perfconf->reg_cnt) {
			if (s->reg > 0x1000)
				pr_info("0x%04x = 0x%x\n", s->reg, s->val);
			else
				pr_info("0x%02x = 0x%x\n", s->reg, s->val);
			s++;
			i++;
		}
		pr_info("------------------------\n");
	}

	i = 0;
	perfconf = &cvbs_drv->perf_conf_ntsc;
	if (!perfconf->reg_table) {
		pr_info("no performance_ntsc table!\n");
	} else {
		pr_info("------------------------\n");
		pr_info("performance_ntsc config:\n");
		s = perfconf->reg_table;
		while (i < perfconf->reg_cnt) {
			if (s->reg > 0x1000)
				pr_info("0x%04x = 0x%x\n", s->reg, s->val);
			else
				pr_info("0x%02x = 0x%x\n", s->reg, s->val);
			s++;
			i++;
		}
		pr_info("------------------------\n");
	}
}

enum {
	CMD_REG_READ,
	CMD_REG_READ_BITS,
	CMD_REG_DUMP,
	CMD_REG_WRITE,
	CMD_REG_WRITE_BITS,

	CMD_CLK_DUMP,
	CMD_CLK_MSR,

	CMD_BIST,

	/* config a set of performance parameters:
	 *0 for sarft,
	 *1 for telecom,
	 *2 for chinamobile
	 */
	CMD_VP_SET,

	/* get the current performance config */
	CMD_VP_GET,

	/* dump the performance config in dts */
	CMD_VP_CONFIG_DUMP,
	/*set pll path: 3:vid pll  2:gp0 pll path2*/
			/*1:gp0 pll path1*/
	CMD_VP_SET_PLLPATH,

	CMD_VDAC,

	CMD_HELP,

	CMD_DISPLAY_ON_OFF,

	CMD_SVA_VALUE,

	CMD_MAX
} debug_cmd_t;

#define func_type_map(a) {\
	if (!strcmp(a, "h")) {\
		str_type = "hiu";\
		func_read = cvbs_out_hiu_read;\
		func_write = cvbs_out_hiu_write;\
		func_getb = cvbs_out_hiu_getb;\
		func_setb = cvbs_out_hiu_setb;\
	} \
	else if (!strcmp(a, "v")) {\
		str_type = "vcbus";\
		func_read = cvbs_out_reg_read;\
		func_write = cvbs_out_reg_write;\
		func_getb = cvbs_out_reg_getb;\
		func_setb = cvbs_out_reg_setb;\
	} \
	else if (!strcmp(a, "a")) {\
		str_type = "ana";\
		func_read = cvbs_out_ana_read;\
		func_write = cvbs_out_ana_write;\
		func_getb = cvbs_out_ana_getb;\
		func_setb = cvbs_out_ana_setb;\
	} \
}

static ssize_t aml_CVBS_attr_debug_show(struct class *class,
					struct class_attribute *attr,
					char *buf)
{
	return snprintf(buf, 40, "%s\n", "debug");
}

static void cvbs_debug_store(const char *buf)
{
	unsigned int ret = 0;
	unsigned long addr, start, end, value, length, old;
	unsigned int argc, bist;
	char *p = NULL, *para = NULL,
		*argv[6] = {NULL, NULL, NULL, NULL, NULL, NULL};
	char *str_type = NULL;
	unsigned int i, cmd;
	unsigned int (*func_read)(unsigned int)  = NULL;
	void (*func_write)(unsigned int, unsigned int) = NULL;
	unsigned int (*func_getb)(unsigned int,
				  unsigned int, unsigned int) = NULL;
	void (*func_setb)(unsigned int, unsigned int,
			  unsigned int, unsigned int) = NULL;

	p = kstrdup(buf, GFP_KERNEL);
	for (argc = 0; argc < 6; argc++) {
		para = strsep(&p, " ");
		if (!para)
			break;
		argv[argc] = para;
	}

	if (!strcmp(argv[0], "r")) {
		cmd = CMD_REG_READ;
	} else if (!strcmp(argv[0], "rb")) {
		cmd = CMD_REG_READ_BITS;
	} else if (!strcmp(argv[0], "dump")) {
		cmd = CMD_REG_DUMP;
	} else if (!strcmp(argv[0], "w")) {
		cmd = CMD_REG_WRITE;
	} else if (!strcmp(argv[0], "wb")) {
		cmd = CMD_REG_WRITE_BITS;
	} else if (!strncmp(argv[0], "clkdump", strlen("clkdump"))) {
		cmd = CMD_CLK_DUMP;
	} else if (!strncmp(argv[0], "clkmsr", strlen("clkmsr"))) {
		cmd = CMD_CLK_MSR;
	} else if (!strncmp(argv[0], "bist", strlen("bist"))) {
		cmd = CMD_BIST;
	} else if (!strncmp(argv[0], "vpset", strlen("vpset"))) {
		cmd = CMD_VP_SET;
	} else if (!strncmp(argv[0], "vpget", strlen("vpget"))) {
		cmd = CMD_VP_GET;
	} else if (!strncmp(argv[0], "vpconf", strlen("vpconf"))) {
		cmd = CMD_VP_CONFIG_DUMP;
	} else if ((!strncmp(argv[0], "set_clkpath", strlen("set_clkpath"))) ||
		   (!strncmp(argv[0], "clkpath", strlen("clkpath")))) {
		cmd = CMD_VP_SET_PLLPATH;
	} else if (!strncmp(argv[0], "vdac", strlen("vdac"))) {
		cmd = CMD_VDAC;
	} else if (!strncmp(argv[0], "cvbs_sva", strlen("cvbs_sva"))) {
		pr_info("config ccitt033 SVA standard test value\n");
		cmd = CMD_SVA_VALUE;
	} else if (!strncmp(argv[0], "cvbs_display", strlen("cvbs_display"))) {
		cmd = CMD_DISPLAY_ON_OFF;
	} else if (!strncmp(argv[0], "help", strlen("help"))) {
		cmd = CMD_HELP;
	} else if (!strncmp(argv[0], "cvbs_ver", strlen("cvbs_ver"))) {
		pr_info("cvbsout version : %s\n", CVBSOUT_VER);
		goto DEBUG_END;
	} else {
		pr_info("[%s] invalid cmd = %s!\n", __func__, argv[0]);
		goto DEBUG_END;
	}

	switch (cmd) {
	case CMD_REG_READ:
		if (argc != 3) {
			pr_info("[%s] cmd_reg_read format: r c/h/v address_hex\n",
				__func__);
			goto DEBUG_END;
		}

		func_type_map(argv[1]);
		if (!func_read)
			goto DEBUG_END;
		ret = kstrtoul(argv[2], 16, &addr);

		pr_info("read %s[0x%x] = 0x%x\n",
			str_type, (unsigned int)addr, func_read(addr));

		break;

	case CMD_REG_READ_BITS:
		if (argc != 5) {
			pr_info("[%s] cmd_reg_read_bits format:\n"
			"\trb c/h/v address_hex start_dec length_dec\n",
				__func__);
			goto DEBUG_END;
		}

		func_type_map(argv[1]);
		if (!func_read)
			goto DEBUG_END;
		ret = kstrtoul(argv[2], 16, &addr);
		ret = kstrtoul(argv[3], 10, &start);
		ret = kstrtoul(argv[4], 10, &length);

		if (length == 1)
			pr_info("read_bits %s[0x%x] = 0x%x, bit[%d] = 0x%x\n",
				str_type, (unsigned int)addr,
				func_read(addr), (unsigned int)start,
				func_getb(addr, start, length));
		else
			pr_info("read_bits %s[0x%x] = 0x%x, bit[%d-%d] = 0x%x\n",
				str_type, (unsigned int)addr,
				func_read(addr),
				(unsigned int)start + (unsigned int)length - 1,
				(unsigned int)start,
				func_getb(addr, start, length));
		break;

	case CMD_REG_DUMP:
		if (argc != 4) {
			pr_info("[%s] cmd_reg_dump format: dump a/h/v start_dec end_dec\n",
				__func__);
			goto DEBUG_END;
		}

		func_type_map(argv[1]);
		if (!func_read)
			goto DEBUG_END;
		ret = kstrtoul(argv[2], 16, &start);
		ret = kstrtoul(argv[3], 16, &end);

		for (i = start; i <= end; i++)
			pr_info("%s[0x%x] = 0x%x\n",
				str_type, i, func_read(i));

		break;

	case CMD_REG_WRITE:
		if (argc != 4) {
			pr_info("[%s] cmd_reg_write format: w value_hex c/h/v address_hex\n",
				__func__);
			goto DEBUG_END;
		}

		func_type_map(argv[2]);
		if (!func_write)
			goto DEBUG_END;
		ret = kstrtoul(argv[1], 16, &value);
		ret = kstrtoul(argv[3], 16, &addr);

		func_write(addr, value);
		pr_info("write %s[0x%x] = 0x%x\n", str_type,
			(unsigned int)addr, (unsigned int)value);
		break;

	case CMD_REG_WRITE_BITS:
		if (argc != 6) {
			pr_info("[%s] cmd_reg_write_bits format:\n"
			"\twb value_hex c/h/v address_hex start_dec length_dec\n",
				__func__);
			goto DEBUG_END;
		}

		func_type_map(argv[2]);
		if (!func_read)
			goto DEBUG_END;
		ret = kstrtoul(argv[1], 16, &value);
		ret = kstrtoul(argv[3], 16, &addr);
		ret = kstrtoul(argv[4], 10, &start);
		ret = kstrtoul(argv[5], 10, &length);

		old = func_read(addr);
		func_setb(addr, value, start, length);
		pr_info("write_bits %s[0x%x] old = 0x%x, new = 0x%x\n",
			str_type, (unsigned int)addr,
			(unsigned int)old, func_read(addr));
		break;

	case CMD_CLK_DUMP:
		dump_clk_registers();
		break;

	case CMD_CLK_MSR:
		/* todo */
		pr_info("cvbs: debug_store: clk_msr todo!\n");
		break;

	case CMD_BIST:
		if (argc != 2) {
			pr_info("[%s] cmd_bist format:\n"
			"\tbist 1/2/3/4/5/6/7/8/0\n", __func__);
			goto DEBUG_END;
		}
		ret = kstrtouint(argv[1], 10, &bist);
		if (ret) {
			pr_info("cvbs: invalid bist\n");
			goto DEBUG_END;
		}
		cvbs_bist_test(bist, NULL);

		break;

	case CMD_VP_SET:
		if (cvbs_drv->vinfo->mode != VMODE_CVBS) {
			pr_info("NOT VMODE_CVBS,Return\n");
			return;
		}
		cvbs_performance_enhancement(local_cvbs_mode);
		break;

	case CMD_VP_GET:
		cvbs_performance_regs_dump();
		break;

	case CMD_VP_CONFIG_DUMP:
		pr_info("performance config in dts:\n");
		cvbs_performance_config_dump();
		break;
	case CMD_VP_SET_PLLPATH:
		if (cvbs_cpu_type() < CVBS_CPU_TYPE_G12A) {
			pr_info("ERR:Only after g12a/b chip supported\n");
			break;
		}
		if (argc != 2) {
			pr_info("[%s] set clkpath 0x0~0x3\n", __func__);
			goto DEBUG_END;
		}
		ret = kstrtoul(argv[1], 10, &value);
		if (value > 0x3) {
			pr_info("invalid clkpath, only support 0x0~0x3\n");
		} else {
			cvbs_clk_path = value;
			pr_info("set clkpath 0x%x\n", cvbs_clk_path);
		}
		pr_info("bit[0]: 0=vid_pll, 1=gp0_pll\n");
		pr_info("bit[1]: 0=vid2_clk, 1=vid1_clk\n");

		break;
	case CMD_VDAC:
		if (argc != 2) {
			pr_info("[%s] vdac state: %d\n",
				__func__,
				(cvbs_drv->flag & CVBS_FLAG_EN_VDAC) ? 1 : 0);
			goto DEBUG_END;
		}
		ret = kstrtoul(argv[1], 10, &value);
		if (value)
			cvbs_vdac_output(1);
		else
			cvbs_vdac_output(0);

		break;
	case CMD_SVA_VALUE: {
		struct performance_config_s *perfconf = NULL;
		const struct reg_s *s = NULL;
		int i = 0;

		perfconf = &cvbs_drv->perf_conf_pal_sva;
		if (!perfconf)
			return;

		if (!perfconf->reg_table) {
			cvbs_log_info("no performance table\n");
			return;
		}

		i = 0;
		s = perfconf->reg_table;
		while (i < perfconf->reg_cnt) {
			cvbs_out_reg_write(s->reg, s->val);
			cvbs_log_info("%s: vcbus reg 0x%04x = 0x%08x\n",
				      __func__, s->reg, s->val);
			s++;
			i++;
		}
		cvbs_log_info("%s\n", __func__);
		break;
	}
	case CMD_DISPLAY_ON_OFF: {
		if (argc != 2) {
			pr_info("[%s] param not match\n", __func__);
			goto DEBUG_END;
		}
		if (!cvbs_drv || !(cvbs_drv->vinfo)) {
			pr_info("[%s] cvbs_drv or cvbs_drv->vinfo is null\n", __func__);
			goto DEBUG_END;
		}

		ret = kstrtoul(argv[1], 10, &value);
		if (value)
			cvbs_set_current_vmode(cvbs_drv->vinfo->mode, NULL);
		else
			cvbs_module_disable(cvbs_drv->vinfo->mode, NULL);

		break;
		}
	case CMD_HELP:
		pr_info("command format:\n"
		"\tr c/h/v address_hex\n"
		"\trb c/h/v address_hex start_dec length_dec\n"
		"\tdump c/h/v start_dec end_dec\n"
		"\tw value_hex c/h/v address_hex\n"
		"\twb value_hex c/h/v address_hex start_dec length_dec\n"
		"\tbist 0/1/2/3/off\n"
		"\tclkdump\n"
		"\tclkpath 0/1/2/3\n"
		"\tvdac 0/1\n"
		"\tcvbs_ver\n");
		break;
	}

DEBUG_END:
	kfree(p);
}

static ssize_t aml_CVBS_attr_debug_store(struct class *class,
					 struct class_attribute *attr,
					 const char *buf, size_t count)
{
	cvbs_debug_store(buf);

	return strnlen(buf, count);
}

static  struct  class_attribute   *cvbs_attr[] = {
	&class_CVBS_attr_vdac_power_level,
	&class_CVBS_attr_debug,
#ifdef CONFIG_AMLOGIC_WSS
	&class_CVBS_attr_wss,
#endif
};

static int create_cvbs_attr(struct cvbs_drv_s *cdrv)
{
	/* create base class for display */
	int i;
	int ret = 0;

	cdrv->base_class = class_create(THIS_MODULE, CVBS_CLASS_NAME);
	if (IS_ERR(cdrv->base_class)) {
		ret = PTR_ERR(cdrv->base_class);
		goto fail_create_class;
	}
	/* create class attr */
	for (i = 0; i < ARRAY_SIZE(cvbs_attr); i++) {
		if (class_create_file(cdrv->base_class, cvbs_attr[i]) < 0)
			goto fail_class_create_file;
	}
	/*cdev_init(cdrv->cdev, &am_cvbs_fops);*/
	cdrv->cdev = cdev_alloc();
	cdrv->cdev->ops = &am_cvbs_fops;
	cdrv->cdev->owner = THIS_MODULE;
	ret = cdev_add(cdrv->cdev, cdrv->devno, 1);
	if (ret)
		goto fail_add_cdev;
	cdrv->dev = device_create(cdrv->base_class, NULL, cdrv->devno,
				  NULL, CVBS_NAME);
	if (IS_ERR(cdrv->dev)) {
		ret = PTR_ERR(cdrv->dev);
		goto fail_create_device;
	} else {
		cvbs_log_dbg("create cdev %s\n", CVBS_NAME);
	}
	return 0;

fail_create_device:
	cvbs_log_info("[cvbs.] : cvbs device create error.\n");
	cdev_del(cdrv->cdev);
fail_add_cdev:
	cvbs_log_info("[cvbs.] : cvbs add device error.\n");
fail_class_create_file:
	cvbs_log_info("[cvbs.] : cvbs class create file error.\n");
	for (i = 0; i < ARRAY_SIZE(cvbs_attr); i++)
		class_remove_file(cdrv->base_class, cvbs_attr[i]);
	class_destroy(cdrv->base_class);
fail_create_class:
	cvbs_log_info("[cvbs.] : cvbs class create error.\n");
	unregister_chrdev_region(cdrv->devno, 1);
	return ret;
}

/* **************************************************** */
static char *cvbsout_performance_str[] = {
	"performance", /* SVA standard value */
	"performance_pal", /* CTCC standard value */
	"performance_ntsc",
};

static void cvbsout_get_config(struct device *dev)
{
	int ret = 0;
	unsigned int val, cnt, i, j;
	struct reg_s *s = NULL;
	const char *str;

	/*clk path*/
	/*bit[0]: 0=vid_pll, 1=gp0_pll*/
	/*bit[1]: 0=vid2_clk, 1=vid_clk*/
	ret = of_property_read_u32(dev->of_node, "clk_path", &val);
	if (!ret) {
		if (val > 0x3) {
			cvbs_log_err("error: invalid clk_path\n");
		} else {
			cvbs_clk_path = val;
			cvbs_log_dbg("clk path:0x%x\n", cvbs_clk_path);
		}
	}

	/* performance: PAL SVA */
	cvbs_drv->perf_conf_pal_sva.reg_cnt = 0;
	cvbs_drv->perf_conf_pal_sva.reg_table = NULL;
	str = cvbsout_performance_str[0];
	cnt = 0;
	while (cnt < CVBS_PERFORMANCE_CNT_MAX) {
		j = 2 * cnt;
		ret = of_property_read_u32_index(dev->of_node, str, j, &val);
		if (ret) {
			cnt = 0;
			break;
		}
		if (val == MREG_END_MARKER) /* ending */
			break;
		cnt++;
	}
	if (cnt >= CVBS_PERFORMANCE_CNT_MAX)
		cnt = 0;
	if (cnt > 0) {
		cvbs_log_dbg("find performance_pal config\n");
		cvbs_drv->perf_conf_pal_sva.reg_table =
			kcalloc(cnt, sizeof(struct reg_s), GFP_KERNEL);
		if (!cvbs_drv->perf_conf_pal_sva.reg_table) {
			cvbs_log_err("error: failed to alloc %s table\n", str);
			cnt = 0;
		}
		cvbs_drv->perf_conf_pal_sva.reg_cnt = cnt;

		i = 0;
		s = cvbs_drv->perf_conf_pal_sva.reg_table;
		while (i < cvbs_drv->perf_conf_pal_sva.reg_cnt) {
			j = 2 * i;
			ret = of_property_read_u32_index(dev->of_node,
				str, j, &val);
			s->reg = val;
			j = 2 * i + 1;
			ret = of_property_read_u32_index(dev->of_node,
				str, j, &val);
			s->val = val;
			/*pr_info("%p: 0x%04x = 0x%x\n", s, s->reg, s->val);*/

			s++;
			i++;
		}
	}

	/* performance: PAL CTCC */
	cvbs_drv->perf_conf_pal.reg_cnt = 0;
	cvbs_drv->perf_conf_pal.reg_table = NULL;
	str = cvbsout_performance_str[1];
	cnt = 0;
	while (cnt < CVBS_PERFORMANCE_CNT_MAX) {
		j = 2 * cnt;
		ret = of_property_read_u32_index(dev->of_node, str, j, &val);
		if (ret) {
			cnt = 0;
			break;
		}
		if (val == MREG_END_MARKER) /* ending */
			break;
		cnt++;
	}
	if (cnt >= CVBS_PERFORMANCE_CNT_MAX)
		cnt = 0;
	if (cnt > 0) {
		cvbs_log_dbg("find performance_pal config\n");
		cvbs_drv->perf_conf_pal.reg_table =
			kcalloc(cnt, sizeof(struct reg_s), GFP_KERNEL);
		if (!cvbs_drv->perf_conf_pal.reg_table) {
			cvbs_log_err("error: failed to alloc %s table\n", str);
			cnt = 0;
		}
		cvbs_drv->perf_conf_pal.reg_cnt = cnt;

		i = 0;
		s = cvbs_drv->perf_conf_pal.reg_table;
		while (i < cvbs_drv->perf_conf_pal.reg_cnt) {
			j = 2 * i;
			ret = of_property_read_u32_index(dev->of_node,
				str, j, &val);
			s->reg = val;
			j = 2 * i + 1;
			ret = of_property_read_u32_index(dev->of_node,
				str, j, &val);
			s->val = val;
			/*pr_info("%p: 0x%04x = 0x%x\n", s, s->reg, s->val);*/

			s++;
			i++;
		}
	}

	/* performance: NTSC */
	cvbs_drv->perf_conf_ntsc.reg_cnt = 0;
	cvbs_drv->perf_conf_ntsc.reg_table = NULL;
	cnt = 0;
	str = cvbsout_performance_str[2];
	while (cnt < CVBS_PERFORMANCE_CNT_MAX) {
		j = 2 * cnt;
		ret = of_property_read_u32_index(dev->of_node, str, j, &val);
		if (ret) {
			cnt = 0;
			break;
		}
		if (val == MREG_END_MARKER) /* ending */
			break;
		cnt++;
	}
	if (cnt >= CVBS_PERFORMANCE_CNT_MAX)
		cnt = 0;
	if (cnt > 0) {
		cvbs_log_dbg("find performance_ntsc config\n");
		cvbs_drv->perf_conf_ntsc.reg_table =
			kcalloc(cnt, sizeof(struct reg_s), GFP_KERNEL);
		if (!cvbs_drv->perf_conf_ntsc.reg_table) {
			cvbs_log_err("error: failed to alloc %s table\n", str);
			cnt = 0;
		}
		cvbs_drv->perf_conf_ntsc.reg_cnt = cnt;

		i = 0;
		s = cvbs_drv->perf_conf_ntsc.reg_table;
		while (i < cvbs_drv->perf_conf_ntsc.reg_cnt) {
			j = 2 * i;
			ret = of_property_read_u32_index(dev->of_node,
							 str, j, &val);
			s->reg = val;
			j = 2 * i + 1;
			ret = of_property_read_u32_index(dev->of_node,
							 str, j, &val);
			s->val = val;
			/*pr_info("%p: 0x%04x = 0x%x\n", s, s->reg, s->val);*/

			s++;
			i++;
		}
	}
}

static void cvbsout_clktree_probe(struct device *dev)
{
	cvbs_drv->clk_gate_state = 0;
	cvbs_drv->venci_top_gate = devm_clk_get(dev, "venci_top_gate");
	if (IS_ERR(cvbs_drv->venci_top_gate))
		cvbs_log_err("error: %s: clk venci_top_gate\n", __func__);

	cvbs_drv->venci_0_gate = devm_clk_get(dev, "venci_0_gate");
	if (IS_ERR(cvbs_drv->venci_0_gate))
		cvbs_log_err("error: %s: clk venci_0_gate\n", __func__);

	cvbs_drv->venci_1_gate = devm_clk_get(dev, "venci_1_gate");
	if (IS_ERR(cvbs_drv->venci_1_gate))
		cvbs_log_err("error: %s: clk venci_1_gate\n", __func__);

	cvbs_drv->vdac_clk_gate = devm_clk_get(dev, "vdac_clk_gate");
	if (IS_ERR(cvbs_drv->vdac_clk_gate))
		cvbs_log_err("error: %s: clk vdac_clk_gate\n", __func__);
}

static void cvbsout_clktree_remove(struct device *dev)
{
	if (!IS_ERR(cvbs_drv->venci_top_gate))
		devm_clk_put(dev, cvbs_drv->venci_top_gate);
	if (!IS_ERR(cvbs_drv->venci_0_gate))
		devm_clk_put(dev, cvbs_drv->venci_0_gate);
	if (!IS_ERR(cvbs_drv->venci_1_gate))
		devm_clk_put(dev, cvbs_drv->venci_1_gate);
	if (!IS_ERR(cvbs_drv->vdac_clk_gate))
		devm_clk_put(dev, cvbs_drv->vdac_clk_gate);
}

#ifdef CONFIG_OF
struct meson_cvbsout_data meson_g12a_cvbsout_data = {
	.cpu_id = CVBS_CPU_TYPE_G12A,
	.name = "meson-g12a-cvbsout",

	.vdac_vref_adj = 0x10,
	.vdac_gsw = 0x0,

	.reg_vid_pll_clk_div = HHI_VID_PLL_CLK_DIV,
	.reg_vid_clk_div = HHI_VID_CLK_DIV,
	.reg_vid_clk_ctrl = HHI_VID_CLK_CNTL,
	.reg_vid2_clk_div = HHI_VIID_CLK_DIV,
	.reg_vid2_clk_ctrl = HHI_VIID_CLK_CNTL,
	.reg_vid_clk_ctrl2 = HHI_VID_CLK_CNTL2,
};

struct meson_cvbsout_data meson_g12b_cvbsout_data = {
	.cpu_id = CVBS_CPU_TYPE_G12B,
	.name = "meson-g12b-cvbsout",

	.vdac_vref_adj = 0xf,
	.vdac_gsw = 0x0,

	.reg_vid_pll_clk_div = HHI_VID_PLL_CLK_DIV,
	.reg_vid_clk_div = HHI_VID_CLK_DIV,
	.reg_vid_clk_ctrl = HHI_VID_CLK_CNTL,
	.reg_vid2_clk_div = HHI_VIID_CLK_DIV,
	.reg_vid2_clk_ctrl = HHI_VIID_CLK_CNTL,
	.reg_vid_clk_ctrl2 = HHI_VID_CLK_CNTL2,
};

#ifndef CONFIG_AMLOGIC_REMOVE_OLD
struct meson_cvbsout_data meson_tl1_cvbsout_data = {
	.cpu_id = CVBS_CPU_TYPE_TL1,
	.name = "meson-tl1-cvbsout",

	.vdac_vref_adj = 0x10,
	.vdac_gsw = 0x0,

	.reg_vid_pll_clk_div = HHI_VID_PLL_CLK_DIV,
	.reg_vid_clk_div = HHI_VID_CLK_DIV,
	.reg_vid_clk_ctrl = HHI_VID_CLK_CNTL,
	.reg_vid2_clk_div = HHI_VIID_CLK_DIV,
	.reg_vid2_clk_ctrl = HHI_VIID_CLK_CNTL,
	.reg_vid_clk_ctrl2 = HHI_VID_CLK_CNTL2,
};
#endif

struct meson_cvbsout_data meson_sm1_cvbsout_data = {
	.cpu_id = CVBS_CPU_TYPE_SM1,
	.name = "meson-sm1-cvbsout",

	.vdac_vref_adj = 0xf,
	.vdac_gsw = 0x0,

	.reg_vid_pll_clk_div = HHI_VID_PLL_CLK_DIV,
	.reg_vid_clk_div = HHI_VID_CLK_DIV,
	.reg_vid_clk_ctrl = HHI_VID_CLK_CNTL,
	.reg_vid2_clk_div = HHI_VIID_CLK_DIV,
	.reg_vid2_clk_ctrl = HHI_VIID_CLK_CNTL,
	.reg_vid_clk_ctrl2 = HHI_VID_CLK_CNTL2,
};

struct meson_cvbsout_data meson_tm2_cvbsout_data = {
	.cpu_id = CVBS_CPU_TYPE_TM2,
	.name = "meson-tm2-cvbsout",

	.vdac_vref_adj = 0x10,
	.vdac_gsw = 0x0,

	.reg_vid_pll_clk_div = HHI_VID_PLL_CLK_DIV,
	.reg_vid_clk_div = HHI_VID_CLK_DIV,
	.reg_vid_clk_ctrl = HHI_VID_CLK_CNTL,
	.reg_vid2_clk_div = HHI_VIID_CLK_DIV,
	.reg_vid2_clk_ctrl = HHI_VIID_CLK_CNTL,
	.reg_vid_clk_ctrl2 = HHI_VID_CLK_CNTL2,
};

struct meson_cvbsout_data meson_sc2_cvbsout_data = {
	.cpu_id = CVBS_CPU_TYPE_SC2,
	.name = "meson-sc2-cvbsout",

	.vdac_vref_adj = 0x10,
	.vdac_gsw = 0x0,

	.reg_vid_pll_clk_div = CLKCTRL_VID_PLL_CLK_DIV,
	.reg_vid_clk_div = CLKCTRL_VID_CLK_DIV,
	.reg_vid_clk_ctrl = CLKCTRL_VID_CLK_CTRL,
	.reg_vid2_clk_div = CLKCTRL_VIID_CLK_DIV,
	.reg_vid2_clk_ctrl = CLKCTRL_VIID_CLK_CTRL,
	.reg_vid_clk_ctrl2 = CLKCTRL_VID_CLK_CTRL2,
};

struct meson_cvbsout_data meson_t5_cvbsout_data = {
	.cpu_id = CVBS_CPU_TYPE_T5,
	.name = "meson-t5-cvbsout",

	.vdac_vref_adj = 0x10,
	.vdac_gsw = 0x5c,

	.reg_vid_pll_clk_div = HHI_VID_PLL_CLK_DIV,
	.reg_vid_clk_div = HHI_VID_CLK_DIV,
	.reg_vid_clk_ctrl = HHI_VID_CLK_CNTL,
	.reg_vid2_clk_div = HHI_VIID_CLK_DIV,
	.reg_vid2_clk_ctrl = HHI_VIID_CLK_CNTL,
	.reg_vid_clk_ctrl2 = HHI_VID_CLK_CNTL2,
};

struct meson_cvbsout_data meson_t5d_cvbsout_data = {
	.cpu_id = CVBS_CPU_TYPE_T5D,
	.name = "meson-t5d-cvbsout",

	.vdac_vref_adj = 0x10,
	.vdac_gsw = 0x5c,

	.reg_vid_pll_clk_div = HHI_VID_PLL_CLK_DIV,
	.reg_vid_clk_div = HHI_VID_CLK_DIV,
	.reg_vid_clk_ctrl = HHI_VID_CLK_CNTL,
	.reg_vid2_clk_div = HHI_VIID_CLK_DIV,
	.reg_vid2_clk_ctrl = HHI_VIID_CLK_CNTL,
	.reg_vid_clk_ctrl2 = HHI_VID_CLK_CNTL2,
};

struct meson_cvbsout_data meson_s4_cvbsout_data = {
	.cpu_id = CVBS_CPU_TYPE_S4,
	.name = "meson-s4-cvbsout",

	.vdac_vref_adj = 0x10,
	.vdac_gsw = 0x5c,

	.reg_vid_pll_clk_div = CLKCTRL_VID_PLL_CLK_DIV,
	.reg_vid_clk_div = CLKCTRL_VID_CLK_DIV,
	.reg_vid_clk_ctrl = CLKCTRL_VID_CLK_CTRL,
	.reg_vid2_clk_div = CLKCTRL_VIID_CLK_DIV,
	.reg_vid2_clk_ctrl = CLKCTRL_VIID_CLK_CTRL,
	.reg_vid_clk_ctrl2 = CLKCTRL_VID_CLK_CTRL2,
};

struct meson_cvbsout_data meson_t3_cvbsout_data = {
	.cpu_id = CVBS_CPU_TYPE_T3,
	.name = "meson-t3-cvbsout",

	.vdac_vref_adj = 0x10,
	.vdac_gsw = 0x5c,

	.reg_vid_pll_clk_div = HHI_VID_PLL_CLK_DIV_T3,
	.reg_vid_clk_div = CLKCTRL_VID_CLK_DIV,
	.reg_vid_clk_ctrl = CLKCTRL_VID_CLK_CTRL,
	.reg_vid2_clk_div = CLKCTRL_VIID_CLK_DIV,
	.reg_vid2_clk_ctrl = CLKCTRL_VIID_CLK_CTRL,
	.reg_vid_clk_ctrl2 = CLKCTRL_VID_CLK_CTRL2,
};

struct meson_cvbsout_data meson_s4d_cvbsout_data = {
	.cpu_id = CVBS_CPU_TYPE_S4D,
	.name = "meson-s4d-cvbsout",

	.vdac_vref_adj = 0x10,
	.vdac_gsw = 0x5c,

	.reg_vid_pll_clk_div = CLKCTRL_VID_PLL_CLK_DIV,
	.reg_vid_clk_div = CLKCTRL_VID_CLK_DIV,
	.reg_vid_clk_ctrl = CLKCTRL_VID_CLK_CTRL,
	.reg_vid2_clk_div = CLKCTRL_VIID_CLK_DIV,
	.reg_vid2_clk_ctrl = CLKCTRL_VIID_CLK_CTRL,
	.reg_vid_clk_ctrl2 = CLKCTRL_VID_CLK_CTRL2,
};

struct meson_cvbsout_data meson_t5w_cvbsout_data = {
	.cpu_id = CVBS_CPU_TYPE_T5W,
	.name = "meson-t5w-cvbsout",

	.vdac_vref_adj = 0x10,
	.vdac_gsw = 0x5c,

	.reg_vid_pll_clk_div = HHI_VID_PLL_CLK_DIV,
	.reg_vid_clk_div = HHI_VID_CLK0_DIV,
	.reg_vid_clk_ctrl = HHI_VID_CLK0_CTRL,
	.reg_vid2_clk_div = HHI_VIID_CLK0_DIV,
	.reg_vid2_clk_ctrl = HHI_VIID_CLK0_CTRL,
	.reg_vid_clk_ctrl2 = HHI_VID_CLK0_CTRL2,
};

struct meson_cvbsout_data meson_s1a_cvbsout_data = {
	.cpu_id = CVBS_CPU_TYPE_S1A,
	.name = "meson-s1a-cvbsout",

	.vdac_vref_adj = 0x10,
	.vdac_gsw = 0x5c,

	.reg_vid_pll_clk_div = CLKCTRL_VID_PLL_CLK_DIV,
	.reg_vid_clk_div = CLKCTRL_VID_CLK_DIV,
	.reg_vid_clk_ctrl = CLKCTRL_VID_CLK_CTRL,
	.reg_vid2_clk_div = CLKCTRL_VIID_CLK_DIV,
	.reg_vid2_clk_ctrl = CLKCTRL_VIID_CLK_CTRL,
	.reg_vid_clk_ctrl2 = CLKCTRL_VID_CLK_CTRL2,
};

static const struct of_device_id meson_cvbsout_dt_match[] = {
	{
		.compatible = "amlogic, cvbsout-g12a",
		.data		= &meson_g12a_cvbsout_data,
	}, {
		.compatible = "amlogic, cvbsout-g12b",
		.data		= &meson_g12b_cvbsout_data,
	},
#ifndef CONFIG_AMLOGIC_REMOVE_OLD
	{
		.compatible = "amlogic, cvbsout-tl1",
		.data		= &meson_tl1_cvbsout_data,
	},
#endif
	{
		.compatible = "amlogic, cvbsout-sm1",
		.data		= &meson_sm1_cvbsout_data,
	}, {
		.compatible = "amlogic, cvbsout-tm2",
		.data		= &meson_tm2_cvbsout_data,
	}, {
		.compatible = "amlogic, cvbsout-sc2",
		.data		= &meson_sc2_cvbsout_data,
	}, {
		.compatible = "amlogic, cvbsout-t5",
		.data		= &meson_t5_cvbsout_data,
	}, {
		.compatible = "amlogic, cvbsout-t5d",
		.data		= &meson_t5d_cvbsout_data,
	}, {
		.compatible = "amlogic, cvbsout-s4",
		.data		= &meson_s4_cvbsout_data,
	}, {
		.compatible = "amlogic, cvbsout-t3",
		.data		= &meson_t3_cvbsout_data,
	}, {
		.compatible = "amlogic, cvbsout-s4d",
		.data		= &meson_s4d_cvbsout_data,
	}, {
		.compatible = "amlogic, cvbsout-t5w",
		.data		= &meson_t5w_cvbsout_data,
	}, {
		.compatible = "amlogic, cvbsout-s1a",
		.data		= &meson_s1a_cvbsout_data,
	},
	{}
};
#endif

#ifdef CONFIG_AMLOGIC_VOUT_CC_BYPASS
static irqreturn_t tvout_vsync_isr(int irq, void *dev_id)
{
	unsigned int CC_2byte_data;
	unsigned long flags = 0;
	struct vout_cc_parm_s parm = {0};

	spin_lock_irqsave(&tvout_clk_lock, flags);
	if (cc_ringbuf.rp != cc_ringbuf.wp) {
		parm.type = cc_ringbuf.cc_data[cc_ringbuf.rp].type;
		CC_2byte_data = cc_ringbuf.cc_data[cc_ringbuf.rp].data;
		vsync_empty_flag = 0;
		vsync_empty_flag_evn = 0;
		vsync_empty_flag_odd = 0;
	} else {
		if (vsync_empty_flag == 0) {
			if ((cvbs_out_reg_read(ENCI_INFO_READ) &
							0x20000000) == 0x0) {
				cvbs_out_reg_write(ENCI_VBI_CCDT_EVN, 0x8080);
				vsync_empty_flag_evn = 1;
			} else {
				cvbs_out_reg_write(ENCI_VBI_CCDT_ODD, 0x8080);
				vsync_empty_flag_odd = 1;
			}
			vsync_empty_flag = vsync_empty_flag_evn &
					vsync_empty_flag_odd;
		}
		spin_unlock_irqrestore(&tvout_clk_lock, flags);
		return IRQ_HANDLED;
	}

	if (parm.type == 0) {
		if ((((CC_2byte_data >> 8) & 0x7f) >= 0x1) &&
				(((CC_2byte_data >> 8) & 0x7f) < 0x10)) {
			if ((cvbs_out_reg_read(ENCI_INFO_READ) &
							0x20000000) != 0x0) {
				cvbs_out_reg_write(ENCI_VBI_CCDT_ODD,
								CC_2byte_data);
				if (((cvbs_out_reg_read(ENCI_INFO_READ) >> 16) &
							0xff) <= 0x15)
					cc_ringbuf.rp = (cc_ringbuf.rp +
							1) % MAX_RING_BUFF_LEN;
			} else {
				cvbs_out_reg_write(ENCI_VBI_CCDT_ODD, 0x8080);
			}
		} else {
			if ((cvbs_out_reg_read(ENCI_INFO_READ) &
						0x20000000) == 0x0){
				cvbs_out_reg_write(ENCI_VBI_CCDT_EVN,
						CC_2byte_data);
				if (((cvbs_out_reg_read(ENCI_INFO_READ) >>
							16) & 0xff) <= 0x15)
					cc_ringbuf.rp = (cc_ringbuf.rp +
						1) % MAX_RING_BUFF_LEN;
			} else {
				cvbs_out_reg_write(ENCI_VBI_CCDT_EVN, 0x8080);
			}
		}
	} else {
		cvbs_log_err("vsync_isr.type:%d Unknown\n", parm.type);
	}
	spin_unlock_irqrestore(&tvout_clk_lock, flags);
	return IRQ_HANDLED;
}
#endif

static const struct component_ops meson_cvbs_bind_ops = {
	.bind	= meson_cvbs_bind,
	.unbind	= meson_cvbs_unbind,
};

static int am_meson_cvbs_probe(struct platform_device *pdev)
{
	pr_info("[%s:%d] in\n", __func__, __LINE__);
	return component_add(&pdev->dev, &meson_cvbs_bind_ops);
}

static int am_meson_cvbs_remove(struct platform_device *pdev)
{
	pr_info("[%s:%d] in\n", __func__, __LINE__);
	component_del(&pdev->dev, &meson_cvbs_bind_ops);
	return 0;
}

static const struct of_device_id am_meson_cvbs_dt_ids[] = {
	{ .compatible = "amlogic, drm-cvbsout", },
	{}
};
MODULE_DEVICE_TABLE(of, am_meson_cvbs_dt_ids);

static struct platform_driver am_meson_cvbs_pltfm_driver = {
	.probe  = am_meson_cvbs_probe,
	.remove = am_meson_cvbs_remove,
	.driver = {
		.name = "meson-amcvbsout",
#ifdef CONFIG_OF
		.of_match_table = am_meson_cvbs_dt_ids,
#endif
	},
};

static int cvbsout_probe(struct platform_device *pdev)
{
	int  ret;
	const struct of_device_id *match;

	cvbs_clk_path = 0;
	local_cvbs_mode = MODE_MAX;

#ifdef CONFIG_AMLOGIC_VOUT_CC_BYPASS
	memset(&cc_ringbuf, 0, sizeof(struct cc_ring_mgr_s));
	cc_ringbuf.max_len = MAX_RING_BUFF_LEN;
	spin_lock_init(&tvout_clk_lock);
#endif

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

	match = of_match_device(meson_cvbsout_dt_match, &pdev->dev);
	if (!match) {
		cvbs_log_err("%s, no matched table\n", __func__);
		goto cvbsout_probe_err;
	}
	cvbs_drv->cvbs_data = (struct meson_cvbsout_data *)match->data;
	cvbs_log_dbg("%s, cpu_id:%d,name:%s\n", __func__,
		cvbs_drv->cvbs_data->cpu_id, cvbs_drv->cvbs_data->name);

	if (cvbs_drv->cvbs_data->cpu_id != CVBS_CPU_TYPE_SC2 &&
	    cvbs_drv->cvbs_data->cpu_id >= CVBS_CPU_TYPE_S4)
		cvbsout_clktree_probe(&pdev->dev);

	cvbsout_get_config(&pdev->dev);
	cvbs_init_vout();

#ifdef CONFIG_AMLOGIC_VPU
	/*vpu gate register for cvbs*/
	cvbs_drv->cvbs_vpu_dev = vpu_dev_register(VPU_VENCI, CVBS_NAME);
#endif

	ret = alloc_chrdev_region(&cvbs_drv->devno, 0, 1, CVBS_NAME);
	if (ret < 0) {
		cvbs_log_err("alloc_chrdev_region error\n");
		goto cvbsout_probe_err;
	}
	cvbs_log_dbg("chrdev devno %d for disp\n", cvbs_drv->devno);
	ret = create_cvbs_attr(cvbs_drv);
	if (ret < 0) {
		cvbs_log_err("create_cvbs_attr error\n");
		goto cvbsout_probe_err;
	}

#ifdef CONFIG_AMLOGIC_VOUT_CC_BYPASS
	if (request_irq(INT_VIU_VSYNC, &tvout_vsync_isr,
		IRQF_SHARED, "tvout_vsync", (void *)"tvout_vsync")) {
		cvbs_log_err("can't request vsync_irq for tvout\n");
	} else {
		cvbs_log_info("request tvout vsync_irq successful\n");
	}
#endif

	INIT_DELAYED_WORK(&cvbs_drv->vdac_dwork, cvbs_vdac_dwork);
	//component_add(&pdev->dev, &meson_cvbs_bind_ops);
	platform_driver_register(&am_meson_cvbs_pltfm_driver);
	cvbs_log_dbg("%s OK\n", __func__);
	return 0;

cvbsout_probe_err:
	kfree(cvbs_drv);
	cvbs_drv = NULL;
	return -1;
}

static int cvbsout_remove(struct platform_device *pdev)
{
	int i;

#ifdef CONFIG_AMLOGIC_VOUT_CC_BYPASS
	free_irq(INT_VIU_VSYNC, (void *)"tvout_vsync");
#endif
	cvbsout_clktree_remove(&pdev->dev);

	if (cvbs_drv->base_class) {
		for (i = 0; i < ARRAY_SIZE(cvbs_attr); i++)
			class_remove_file(cvbs_drv->base_class, cvbs_attr[i]);
		class_destroy(cvbs_drv->base_class);
	}
	if (cvbs_drv) {
		cdev_del(cvbs_drv->cdev);
#ifdef CONFIG_AMLOGIC_VPU
	/*vpu gate unregister for cvbs*/
	vpu_dev_unregister(cvbs_drv->cvbs_vpu_dev);
#endif
		kfree(cvbs_drv);
	}
	vout_unregister_server(&cvbs_vout_server);
#ifdef CONFIG_AMLOGIC_VOUT2_SERVE
	vout2_unregister_server(&cvbs_vout2_server);
#endif
	//component_del(&pdev->dev, &meson_cvbs_bind_ops);
	platform_driver_unregister(&am_meson_cvbs_pltfm_driver);
	cvbs_log_info("%s\n", __func__);
	return 0;
}

static void cvbsout_shutdown(struct platform_device *pdev)
{
	if ((cvbs_drv->flag & CVBS_FLAG_EN_ENCI) == 0)
		return;

	if (cvbs_drv->flag & CVBS_FLAG_EN_VDAC)
		cvbs_vdac_output(0);

	cvbs_drv->flag &= ~CVBS_FLAG_EN_ENCI;
	cvbs_out_reg_write(ENCI_VIDEO_EN, 0);
	cvbs_out_disable_clk();

	cvbs_out_vpu_power_ctrl(0);
	cvbs_out_clk_gate_ctrl(0);
	cvbs_log_info("%s\n", __func__);
}

static struct meson_cvbs_dev drm_cvbs_instance = {
	.base = {
		.ver = MESON_DRM_CONNECTOR_V10,
	},
};

static int meson_cvbs_bind(struct device *dev,
			      struct device *master, void *data)
{
	struct meson_drm_bound_data *bound_data = data;
	struct cvbs_drv_s *hdev = cvbs_drv;

	if (bound_data->connector_component_bind) {
		hdev->drm_cvbs_id = bound_data->connector_component_bind
			(bound_data->drm,
			DRM_MODE_CONNECTOR_TV,
			&drm_cvbs_instance.base);
		cvbs_log_info("%s cvbs [%d]\n", __func__, hdev->drm_cvbs_id);
	} else {
		cvbs_log_err("no bind func from drm.\n");
	}

	return 0;
}

static void meson_cvbs_unbind(struct device *dev,
				 struct device *master, void *data)
{
	struct meson_drm_bound_data *bound_data = data;
	struct cvbs_drv_s *hdev = cvbs_drv;

	if (bound_data->connector_component_unbind) {
		bound_data->connector_component_unbind(bound_data->drm,
			DRM_MODE_CONNECTOR_TV, hdev->drm_cvbs_id);
		pr_info("%s cvbs [%d]\n", __func__, hdev->drm_cvbs_id);
	} else {
		pr_err("no unbind func from drm.\n");
	}

	hdev->drm_cvbs_id = 0;
}

static struct platform_driver cvbsout_driver = {
	.probe = cvbsout_probe,
	.remove = cvbsout_remove,
	.shutdown = cvbsout_shutdown,
	.driver = {
		.name = "cvbsout",
		.owner = THIS_MODULE,
#ifdef CONFIG_OF
		.of_match_table = meson_cvbsout_dt_match,
#endif
	},
};

int __init cvbs_init_module(void)
{
	/* cvbs_log_info("%s module init\n", __func__); */
	if (platform_driver_register(&cvbsout_driver)) {
		cvbs_log_err("%s failed to register module\n", __func__);
		return -ENODEV;
	}
#ifdef CONFIG_AMLOGIC_VOUT_CC_BYPASS
	memset(&cc_ringbuf, 0, sizeof(struct cc_ring_mgr_s));
	cc_ringbuf.max_len = MAX_RING_BUFF_LEN;
#endif
	return 0;
}

__exit void cvbs_exit_module(void)
{
	/* cvbs_log_info("%s module exit\n", __func__); */
	platform_driver_unregister(&cvbsout_driver);
}

//MODULE_AUTHOR("Platform-BJ <platform.bj@amlogic.com>");
//MODULE_DESCRIPTION("TV Output Module");
//MODULE_LICENSE("GPL");

