/*
 * Copyright (C) 2015 MediaTek Inc.
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License version 2 as
 * published by the Free Software Foundation.
 *
 * 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.
 */

#include <generated/autoconf.h>
#include <linux/module.h>
#include <linux/mm.h>
#include <linux/init.h>
#include <linux/fb.h>
#include <linux/delay.h>
#include <linux/device.h>
#include <linux/platform_device.h>
#include <linux/dma-mapping.h>
#include <linux/kthread.h>
#include <linux/vmalloc.h>
#include "disp_assert_layer.h"
#include <linux/semaphore.h>
#include <linux/mutex.h>
/* #include <linux/leds-mt65xx.h> */
#include <linux/suspend.h>
#include <linux/of_fdt.h>
#include <linux/of.h>
#include <linux/of_address.h>
#include <linux/dma-buf.h>
#include <linux/uaccess.h>
#include <linux/atomic.h>
/* #include <asm/mach-types.h> */
#include <asm/cacheflush.h>
#include <linux/io.h>
#include "ion_drv.h"
#include "mt-plat/dma.h"
/* #include <mach/irqs.h> */
#include <linux/dma-mapping.h>
#include <linux/compat.h>
#include "mt-plat/aee.h"

#include "mt-plat/mtk_boot.h"
#include "debug.h"
#include "ddp_hal.h"
#include "disp_drv_log.h"
#include "disp_lcm.h"
#include "mtkfb.h"
#include "mtkfb_console.h"
#include "mtkfb_fence.h"
#include "mtkfb_info.h"
#include "ddp_ovl.h"
#include "disp_drv_platform.h"
#include "primary_display.h"
#include "ddp_dump.h"
#include "display_recorder.h"
#include "fbconfig_kdebug_x.h"
#include "mtk_ovl.h"
#include "mtk_boot.h"
#include "disp_helper.h"
#include "compat_mtkfb.h"
#include "disp_dts_gpio.h"
#include "disp_recovery.h"
#include "ddp_clkmgr.h"
#include "ddp_log.h"

/* static variable */
static u32 MTK_FB_XRES;
static u32 MTK_FB_YRES;
static u32 MTK_FB_BPP;
static u32 MTK_FB_PAGES;
static u32 fb_xres_update;
static u32 fb_yres_update;
static size_t mtkfb_log_on = true;

static int sem_flipping_cnt = 1;
static int sem_early_suspend_cnt = 1;
#if 0 /* defined but not used */
static int sem_overlay_buffer_cnt = 1;
#endif
static int vsync_cnt;
static const struct timeval FRAME_INTERVAL = { 0, 30000 };	/* 33ms */

#if 0 /* defined but not used */
static UINT32 mtkfb_using_layer_type = LAYER_2D;
static bool hwc_force_fb_enabled = true;
static unsigned int BL_level;
static BOOL BL_set_level_resume = FALSE;
static struct fb_var_screeninfo fbi_var_backup;
static struct fb_fix_screeninfo fbi_fix_backup;
static BOOL need_restore = FALSE;
#endif
static bool no_update;
#if 0 /* defined but not used */
static bool first_enable_esd = true;
static struct task_struct *screen_update_task;
static struct task_struct *esd_recovery_task;
#endif
static struct disp_session_input_config session_input;
static int slt_test;

/* macro definiton */
#define ALIGN_TO(x, n)  (((x) + ((n) - 1)) & ~((n) - 1))
#define MTK_FB_XRESV (ALIGN_TO(MTK_FB_XRES, MTK_FB_ALIGNMENT))
#define MTK_FB_YRESV (ALIGN_TO(MTK_FB_YRES, MTK_FB_ALIGNMENT) * MTK_FB_PAGES)	/* For page flipping */
#define MTK_FB_BYPP  ((MTK_FB_BPP + 7) >> 3)
#define MTK_FB_LINE  (ALIGN_TO(MTK_FB_XRES, MTK_FB_ALIGNMENT) * MTK_FB_BYPP)
#define MTK_FB_SIZE  (MTK_FB_LINE * ALIGN_TO(MTK_FB_YRES, MTK_FB_ALIGNMENT))
#define MTK_FB_SIZEV (MTK_FB_LINE * ALIGN_TO(MTK_FB_YRES, MTK_FB_ALIGNMENT) * MTK_FB_PAGES)
#define ASSERT_LAYER    (DDP_OVL_LAYER_MUN-1)
#define DISP_DEFAULT_UI_LAYER_ID (DDP_OVL_LAYER_MUN-1)
#define DISP_CHANGED_UI_LAYER_ID (DDP_OVL_LAYER_MUN-2)
#define NOT_REFERENCED(x)   { (x) = (x); }
#define CHECK_RET(expr)    \
do {                   \
	int ret = (expr);  \
	aee_kernel_exception("mtkfb", "[DISP]error:%s,%d", __FILE__, __LINE__);  \
} while (0)

#define MTKFB_LOG(fmt, arg...) \
	do { \
		if (mtkfb_log_on) \
			DISP_LOG_PRINT(ANDROID_LOG_WARN, "MTKFB", fmt, ##arg); \
	} while (0)
/* always show this debug info while the global debug log is off */
#define MTKFB_LOG_DBG(fmt, arg...) \
	do { \
		if (!mtkfb_log_on) \
			DISP_LOG_PRINT(ANDROID_LOG_WARN, "MTKFB", fmt, ##arg); \
	} while (0)

#define MTKFB_FUNC()	\
	do { \
		if (mtkfb_log_on) \
			DISP_LOG_PRINT(ANDROID_LOG_INFO, "MTKFB", "[Func]%s\n", __func__); \
	} while (0)

#define PRNERR(fmt, args...)   DISP_LOG_PRINT(ANDROID_LOG_INFO, "MTKFB", fmt, ## args)

/* --------------------------------------------------------------------------- */
/* local variables */
/* --------------------------------------------------------------------------- */
struct notifier_block pm_nb;
unsigned int EnableVSyncLog;
unsigned long fb_pa;
atomic_t has_pending_update = ATOMIC_INIT(0);
struct fb_overlay_layer video_layerInfo;
UINT32 dbr_backup;
UINT32 dbg_backup;
UINT32 dbb_backup;
bool fblayer_dither_needed;
bool is_ipoh_bootup;
struct fb_info *mtkfb_fbi;
struct fb_overlay_layer fb_layer_context;
struct mtk_dispif_info dispif_info[MTKFB_MAX_DISPLAY_COUNT];
unsigned int FB_LAYER = 2;
bool is_early_suspended = FALSE;
atomic_t OverlaySettingDirtyFlag = ATOMIC_INIT(0);
atomic_t OverlaySettingApplied = ATOMIC_INIT(0);
unsigned int PanDispSettingPending;
unsigned int PanDispSettingDirty;
unsigned int PanDispSettingApplied;
unsigned int need_esd_check;
unsigned int lcd_fps = 6000;
wait_queue_head_t screen_update_wq;
char mtkfb_lcm_name[256] = { 0 };


DEFINE_SEMAPHORE(sem_flipping);
DEFINE_SEMAPHORE(sem_early_suspend);
DEFINE_SEMAPHORE(sem_overlay_buffer);

/* --------------------------------------------------------------------------- */
/* local function declarations */
/* --------------------------------------------------------------------------- */
static int mtkfb_set_par(struct fb_info *fbi);
static int init_framebuffer(struct fb_info *info);
static int mtkfb_get_overlay_layer_info(struct fb_overlay_layer_info *layerInfo);

#ifdef CONFIG_OF
static int _parse_tag_videolfb(void);
#endif
static void mtkfb_late_resume(void);
static void mtkfb_early_suspend(void);


void mtkfb_log_enable(int enable)
{
	mtkfb_log_on = enable;
	MTKFB_LOG("mtkfb log %s\n", enable ? "enabled" : "disabled");
}

/*
 * ---------------------------------------------------------------------------
 * fbdev framework callbacks and the ioctl interface
 * ---------------------------------------------------------------------------
 */
/* Called each time the mtkfb device is opened */
static int mtkfb_open(struct fb_info *info, int user)
{
	NOT_REFERENCED(info);
	NOT_REFERENCED(user);
	DISPFUNC();
	MSG_FUNC_ENTER();
	MSG_FUNC_LEAVE();
	return 0;
}

/* Called when the mtkfb device is closed. We make sure that any pending*/
/* gfx DMA operations are ended, before we return. */
static int mtkfb_release(struct fb_info *info, int user)
{

	NOT_REFERENCED(info);
	NOT_REFERENCED(user);
	DISPFUNC();

	MSG_FUNC_ENTER();
	MSG_FUNC_LEAVE();
	return 0;
}

/* Store a single color palette entry into a pseudo palette or the hardware
 * palette if one is available. For now we support only 16bpp and thus store
 * the entry only to the pseudo palette.
 */
static int mtkfb_setcolreg(u_int regno, u_int red, u_int green,
			   u_int blue, u_int transp, struct fb_info *info)
{
	int r = 0;
	unsigned bpp, m;

	NOT_REFERENCED(transp);

	MSG_FUNC_ENTER();

	bpp = info->var.bits_per_pixel;
	m = 1 << bpp;
	if (regno >= m) {
		r = -EINVAL;
		goto exit;
	}

	switch (bpp) {
	case 16:
		/* RGB 565 */
		((u32 *) (info->pseudo_palette))[regno] =
		    ((red & 0xF800) | ((green & 0xFC00) >> 5) | ((blue & 0xF800) >> 11));
		break;
	case 32:
		/* ARGB8888 */
		((u32 *) (info->pseudo_palette))[regno] =
		    (0xff000000) |
		    ((red & 0xFF00) << 8) | ((green & 0xFF00)) | ((blue & 0xFF00) >> 8);
		break;

		/* TODO: RGB888, BGR888, ABGR8888 */

	default:
		DISPERR("set color info fail,bpp=%d\n", bpp);
		ASSERT(0);
		r = -EINVAL;
	}

exit:
	MSG_FUNC_LEAVE();
	return r;
}

#if defined(CONFIG_PM_AUTOSLEEP)
static int mtkfb_blank(int blank_mode, struct fb_info *info)
{
	enum mtkfb_power_mode prev_pm = primary_display_get_power_mode();

	switch (blank_mode) {
	case FB_BLANK_UNBLANK:
	case FB_BLANK_NORMAL:
		DISPDBG("mtkfb_blank mtkfb_late_resume\n");
		if (bypass_blank) {
			DISPERR("FB_BLANK_UNBLANK bypass_blank %d\n", bypass_blank);
			break;
		}

		primary_display_set_power_mode(FB_RESUME);
		mtkfb_late_resume();

		debug_print_power_mode_check(prev_pm, FB_RESUME);

		if (!lcd_fps)
			msleep(30);
		else
			msleep(2 * 100000 / lcd_fps);	/* Delay 2 frames. */
		break;
	case FB_BLANK_VSYNC_SUSPEND:
	case FB_BLANK_HSYNC_SUSPEND:
		break;
	case FB_BLANK_POWERDOWN:
		DISPDBG("mtkfb_blank mtkfb_early_suspend\n");
		if (bypass_blank) {
			DISPERR("FB_BLANK_POWERDOWN bypass_blank %d\n", bypass_blank);
			break;
		}

		primary_display_set_power_mode(FB_SUSPEND);
		mtkfb_early_suspend();

		debug_print_power_mode_check(prev_pm, FB_SUSPEND);

		break;
	default:
		return -EINVAL;
	}

	return 0;
}
#endif

void set_slt_test(int enable)
{
	slt_test = enable;
}

int is_slt_test(void)
{
	return slt_test;
}

int mtkfb_set_backlight_level(unsigned int level)
{
	MTKFB_FUNC();
	DISPDBG("mtkfb_set_backlight_level:%d Start\n", level);
	primary_display_setbacklight(level);
	DISPDBG("mtkfb_set_backlight_level End\n");
	return 0;
}
EXPORT_SYMBOL(mtkfb_set_backlight_level);

int mtkfb_set_backlight_mode(unsigned int mode)
{
	MTKFB_FUNC();
	if (down_interruptible(&sem_flipping)) {
		DISPERR("[FB Driver] can't get semaphore:%d\n", __LINE__);
		return -ERESTARTSYS;
	}
	sem_flipping_cnt--;
	if (down_interruptible(&sem_early_suspend)) {
		DISPERR("[FB Driver] can't get semaphore:%d\n", __LINE__);
		sem_flipping_cnt++;
		up(&sem_flipping);
		return -ERESTARTSYS;
	}

	sem_early_suspend_cnt--;
	if (primary_display_is_sleepd())
		goto End;

	/* DISP_SetBacklight_mode(mode); */
End:
	sem_flipping_cnt++;
	sem_early_suspend_cnt++;
	up(&sem_early_suspend);
	up(&sem_flipping);
	return 0;
}
EXPORT_SYMBOL(mtkfb_set_backlight_mode);

int mtkfb_set_backlight_pwm(int div)
{
	MTKFB_FUNC();
	if (down_interruptible(&sem_flipping)) {
		DISPERR("[FB Driver] can't get semaphore:%d\n", __LINE__);
		return -ERESTARTSYS;
	}
	sem_flipping_cnt--;
	if (down_interruptible(&sem_early_suspend)) {
		DISPERR("[FB Driver] can't get semaphore:%d\n", __LINE__);
		sem_flipping_cnt++;
		up(&sem_flipping);
		return -ERESTARTSYS;
	}
	sem_early_suspend_cnt--;
	if (primary_display_is_sleepd())
		goto End;
	/* DISP_SetPWM(div); */
End:
	sem_flipping_cnt++;
	sem_early_suspend_cnt++;
	up(&sem_early_suspend);
	up(&sem_flipping);
	return 0;
}
EXPORT_SYMBOL(mtkfb_set_backlight_pwm);

int mtkfb_get_backlight_pwm(int div, unsigned int *freq)
{
	/* DISP_GetPWM(div, freq); */
	return 0;
}
EXPORT_SYMBOL(mtkfb_get_backlight_pwm);

void mtkfb_waitVsync(void)
{
	if (primary_display_is_sleepd()) {
		DISPMSG("[MTKFB_VSYNC]:mtkfb has suspend, return directly\n");
		msleep(20);
		return;
	}
	vsync_cnt++;
#ifdef CONFIG_FPGA_EARLY_PORTING
	msleep(20);
#else
	primary_display_wait_for_vsync(NULL);
#endif
	vsync_cnt--;

}
EXPORT_SYMBOL(mtkfb_waitVsync);

static int _convert_fb_layer_to_disp_input(struct fb_overlay_layer *src, struct disp_input_config *dst)
{

	dst->layer_id = src->layer_id;
	dst->dirty_roi_num = 0;
	if (!src->layer_enable) {
		dst->layer_enable = 0;
		return 0;
	}

	switch (src->src_fmt) {
	case MTK_FB_FORMAT_YUV422:
		dst->src_fmt = DISP_FORMAT_YUV422;
		break;

	case MTK_FB_FORMAT_RGB565:
		dst->src_fmt = DISP_FORMAT_RGB565;
		break;

	case MTK_FB_FORMAT_RGB888:
		dst->src_fmt = DISP_FORMAT_RGB888;
		break;

	case MTK_FB_FORMAT_BGR888:
		dst->src_fmt = DISP_FORMAT_BGR888;
		break;

	case MTK_FB_FORMAT_ARGB8888:
		dst->src_fmt = DISP_FORMAT_ARGB8888;
		break;

	case MTK_FB_FORMAT_ABGR8888:
		dst->src_fmt = DISP_FORMAT_ABGR8888;
		break;

	case MTK_FB_FORMAT_XRGB8888:
		dst->src_fmt = DISP_FORMAT_XRGB8888;
		break;

	case MTK_FB_FORMAT_XBGR8888:
		dst->src_fmt = DISP_FORMAT_XBGR8888;
		break;

	case MTK_FB_FORMAT_UYVY:
		dst->src_fmt = DISP_FORMAT_UYVY;
		break;

	default:
		DISPERR("Invalid color format: 0x%x\n", src->src_fmt);
		return -1;
	}

	dst->src_base_addr = src->src_base_addr;
	dst->security = src->security;
	dst->src_phy_addr = src->src_phy_addr;
	DISPDBG("_convert_fb_layer_to_disp_input, dst->addr=0x%p\n", dst->src_phy_addr);

	dst->isTdshp = src->isTdshp;
	dst->next_buff_idx = src->next_buff_idx;
	dst->identity = src->identity;
	dst->connected_type = src->connected_type;

	/* set Alpha blending */
	dst->alpha = src->alpha;
	if (MTK_FB_FORMAT_ARGB8888 == src->src_fmt || MTK_FB_FORMAT_ABGR8888 == src->src_fmt)
		dst->alpha_enable = TRUE;
	else
		dst->alpha_enable = FALSE;


	/* set src width, src height */
	dst->src_offset_x = src->src_offset_x;
	dst->src_offset_y = src->src_offset_y;
	dst->src_width = src->src_width;
	dst->src_height = src->src_height;
	dst->tgt_offset_x = src->tgt_offset_x;
	dst->tgt_offset_y = src->tgt_offset_y;
	dst->tgt_width = src->tgt_width;
	dst->tgt_height = src->tgt_height;
	if (dst->tgt_width > dst->src_width)
		dst->tgt_width = dst->src_width;
	if (dst->tgt_height > dst->src_height)
		dst->tgt_height = dst->src_height;

	dst->src_pitch = src->src_pitch;

	/* set color key */
	dst->src_color_key = src->src_color_key;
	dst->src_use_color_key = src->src_use_color_key;

	/* data transferring is triggerred in MTKFB_TRIG_OVERLAY_OUT */
	dst->layer_enable = src->layer_enable;
	dst->ext_sel_layer = -1;
#if 1
	DISPDBG("%s:id=%u,en=%u,next_idx=%u,vaddr=%p,pa=%p,srcfmt=%u,dstfmt=%u,pitch=%u,x=%u,y=%u,w=%u,h=%u\n",
	     __func__, dst->layer_id, dst->layer_enable, dst->next_buff_idx, dst->src_base_addr,
	     dst->src_phy_addr, src->src_fmt, dst->src_fmt, dst->src_pitch, dst->src_offset_x,
	     dst->src_offset_y, dst->src_width, dst->src_height);
	DISPDBG("%s:target xoff=%u, target yoff=%u, target w=%u, target h=%u, aen=%u\n",
	     __func__, dst->tgt_offset_x, dst->tgt_offset_y, dst->tgt_width, dst->tgt_height,
	     dst->alpha_enable);
#endif

	return 0;
}

#if 0 /* defined but not used */
static int _overlay_info_convert(struct fb_overlay_layer *src, struct OVL_CONFIG_STRUCT *dst)
{
	unsigned int layerpitch = 0;
	unsigned int layerbpp = 0;

	dst->layer = src->layer_id;

	if (!src->layer_enable) {
		dst->layer_en = 0;
		dst->isDirty = true;
		return 0;
	}

	switch (src->src_fmt) {
	case MTK_FB_FORMAT_YUV422:
		dst->fmt = UFMT_YUYV;
		layerpitch = 2;
		layerbpp = 16;
		break;

	case MTK_FB_FORMAT_RGB565:
		dst->fmt = UFMT_RGB565;
		layerpitch = 2;
		layerbpp = 16;
		break;

	case MTK_FB_FORMAT_RGB888:
		dst->fmt = UFMT_RGB888;
		layerpitch = 3;
		layerbpp = 24;
		break;

	case MTK_FB_FORMAT_BGR888:
		dst->fmt = UFMT_BGR888;
		layerpitch = 3;
		layerbpp = 24;
		break;

	case MTK_FB_FORMAT_ARGB8888:
		dst->fmt = UFMT_ARGB8888;
		layerpitch = 4;
		layerbpp = 32;
		break;

	case MTK_FB_FORMAT_ABGR8888:
		/* dst->fmt = eABGR8888; */
		dst->fmt = UFMT_ABGR8888;
		layerpitch = 4;
		layerbpp = 32;
		break;
	case MTK_FB_FORMAT_XRGB8888:
		dst->fmt = UFMT_XRGB8888;
		layerpitch = 4;
		layerbpp = 32;
		break;

	case MTK_FB_FORMAT_XBGR8888:
		dst->fmt = UFMT_XBGR8888;
		layerpitch = 4;
		layerbpp = 32;
		break;

	case MTK_FB_FORMAT_UYVY:
		dst->fmt = UFMT_UYVY;
		layerpitch = 2;
		layerbpp = 16;
		break;

	default:
		DISPERR("Invalid color format: 0x%x\n", src->src_fmt);
		return -1;
	}

	dst->vaddr = (unsigned long)src->src_base_addr;
	dst->security = src->security;
/* set overlay will not use fence+ion handle */
#if 0
/* #if defined (MTK_FB_ION_SUPPORT) */
	if (src->src_phy_addr != NULL)
		dst->addr = (unsigned int)src->src_phy_addr;
	else
		dst->addr = mtkfb_query_buf_mva(src->layer_id, (unsigned int)src->next_buff_idx);

#else
	dst->addr = (unsigned long)src->src_phy_addr;
#endif
	DISPMSG("_overlay_info_convert, dst->addr=0x%lx\n", dst->addr);
	dst->isTdshp = src->isTdshp;
	dst->buff_idx = src->next_buff_idx;
	dst->identity = src->identity;
	dst->connected_type = src->connected_type;

	/* set Alpha blending */
	dst->alpha = src->alpha;
	if (MTK_FB_FORMAT_ARGB8888 == src->src_fmt || MTK_FB_FORMAT_ABGR8888 == src->src_fmt)
		dst->aen = TRUE;
	else
		dst->aen = FALSE;


	/* set src width, src height */
	dst->src_x = src->src_offset_x;
	dst->src_y = src->src_offset_y;
	dst->src_w = src->src_width;
	dst->src_h = src->src_height;
	dst->dst_x = src->tgt_offset_x;
	dst->dst_y = src->tgt_offset_y;
	dst->dst_w = src->tgt_width;
	dst->dst_h = src->tgt_height;
	if (dst->dst_w > dst->src_w)
		dst->dst_w = dst->src_w;
	if (dst->dst_h > dst->src_h)
		dst->dst_h = dst->src_h;

	dst->src_pitch = src->src_pitch * layerpitch;
	/* set color key */
	dst->key = src->src_color_key;
	dst->keyEn = src->src_use_color_key;
	/* data transferring is triggerred in MTKFB_TRIG_OVERLAY_OUT */
	dst->layer_en = src->layer_enable;
	dst->isDirty = true;

#if 1
	DISPMSG("%s:id=%u,en=%u,next_idx=%u,vaddr=0x%lx,paddr=0x%lx,fmt=%u,pitch=%u,xoff=%u,yoff=%u,w=%u,h=%u\n",
		__func__, dst->layer, dst->layer_en, dst->buff_idx, dst->addr, dst->vaddr, dst->fmt,
		dst->src_pitch, dst->src_x, dst->src_y, dst->src_w, dst->src_h);
	DISPMSG("%s:target xoff=%u, target yoff=%u, target w=%u, target h=%u, aen=%u\n",
	     __func__, dst->dst_x, dst->dst_y, dst->dst_w, dst->dst_h, dst->aen);
#endif

	return 0;
}
#endif

static int mtkfb_pan_display_impl(struct fb_var_screeninfo *var, struct fb_info *info)
{
	UINT32 offset = 0;
	UINT32 paStart = 0;
	char *vaStart = NULL, *vaEnd = NULL;
	int ret = 0;
	/* int wait_ret = 0; */
	/* unsigned int layerpitch = 0; */
	unsigned int src_pitch = 0;
	struct disp_session_input_config *session_input;
	struct disp_input_config *input;
	unsigned int session_id = MAKE_DISP_SESSION(DISP_SESSION_PRIMARY, 0);
	/* DISPFUNC(); */

	if (no_update) {
		DISPMSG("the first time of mtkfb_pan_display_impl will be ignored\n");
		return ret;
	}
#if 0
	DISPCHECK("pan_display: offset(%u,%u), res(%u,%u), resv(%u,%u)\n",
		  var->xoffset, var->yoffset, info->var.xres, info->var.yres,
		  info->var.xres_virtual, info->var.yres_virtual);
#endif
	info->var.yoffset = var->yoffset;
	offset = var->yoffset * info->fix.line_length;
	paStart = fb_pa + offset;
	vaStart = info->screen_base + offset;
	vaEnd = vaStart + info->var.yres * info->fix.line_length;
#if 0
	DISPCHECK("fb dump: 0x%08x, 0x%08x, 0x%08x, 0x%08x\n",
				*(unsigned int *)vaStart,
				*(unsigned int *)(vaStart+4),
				*(unsigned int *)(vaStart+8),
				*(unsigned int *)(vaStart+0xC));
#endif
	session_input = kzalloc(sizeof(*session_input), GFP_KERNEL);
	if (!session_input) {
		DISPERR("session input allocat fail\n");
		ASSERT(0);
		return -1;
	}

	/* pan display use layer 0 */
	input = &session_input->config[0];
	input->layer_id = 0;
	input->src_phy_addr = (void *)((unsigned long)paStart);
	input->src_base_addr = (void *)((unsigned long)vaStart);
	input->layer_id = primary_display_get_option("FB_LAYER");
	input->layer_enable = 1;
	input->src_offset_x = 0;
	input->src_offset_y = 0;
	input->src_width = var->xres;
	input->src_height = var->yres;
	input->tgt_offset_x = 0;
	input->tgt_offset_y = 0;
	input->tgt_width = var->xres;
	input->tgt_height = var->yres;
	input->ext_sel_layer = -1;
	switch (var->bits_per_pixel) {
	case 16:
		input->src_fmt = DISP_FORMAT_RGB565;
		break;
	case 24:
		input->src_fmt = DISP_FORMAT_RGB888;
		break;
	case 32:
		input->src_fmt =
		    (var->blue.offset == 0) ? DISP_FORMAT_BGRA8888 : DISP_FORMAT_RGBX8888;

		break;
	default:
		DISPERR("Invalid color format bpp: %d\n", var->bits_per_pixel);
		kfree(session_input);
		return -1;
	}
	input->alpha_enable = FALSE;

	input->alpha = 0xFF;
	input->next_buff_idx = -1;
	src_pitch = ALIGN_TO(var->xres, MTK_FB_ALIGNMENT);
	input->src_pitch = src_pitch;
	input->ext_sel_layer = -1;

	session_input->config_layer_num++;
	session_input->session_id = session_id;

	if (!is_DAL_Enabled()) {
		/* disable font layer(layer3) drawed in lk */
		session_input->config[1].layer_id = primary_display_get_option("ASSERT_LAYER");
		session_input->config[1].next_buff_idx = -1;
		session_input->config[1].layer_enable = 0;
		session_input->config_layer_num++;
	}
	ret = primary_display_config_input_multiple(session_input);
	ret = primary_display_trigger(TRUE, NULL, 0);

	kfree(session_input);
	return ret;
}


/* Set fb_info.fix fields and also updates fbdev.
 * When calling this fb_info.var must be set up already.
 */
static void set_fb_fix(struct mtkfb_device *fbdev)
{
	struct fb_info *fbi = fbdev->fb_info;
	struct fb_fix_screeninfo *fix = &fbi->fix;
	struct fb_var_screeninfo *var = &fbi->var;
	struct fb_ops *fbops = fbi->fbops;

	strncpy(fix->id, MTKFB_DRIVER, sizeof(fix->id));
	fix->type = FB_TYPE_PACKED_PIXELS;

	switch (var->bits_per_pixel) {
	case 16:
	case 24:
	case 32:
		fix->visual = FB_VISUAL_TRUECOLOR;
		break;
	case 1:
	case 2:
	case 4:
	case 8:
		fix->visual = FB_VISUAL_PSEUDOCOLOR;
		break;
	default:
		DISPERR("set fb fix fail,bits per pixel=%d\n", var->bits_per_pixel);
		return;
	}

	fix->accel = FB_ACCEL_NONE;
	fix->line_length = ALIGN_TO(var->xres_virtual, MTK_FB_ALIGNMENT) * var->bits_per_pixel / 8;
	fix->smem_len = fbdev->fb_size_in_byte;
	fix->smem_start = fbdev->fb_pa_base;

	fix->xpanstep = 0;
	fix->ypanstep = 1;

	fbops->fb_fillrect = cfb_fillrect;
	fbops->fb_copyarea = cfb_copyarea;
	fbops->fb_imageblit = cfb_imageblit;
}


/* Check values in var, try to adjust them in case of out of bound values if
 * possible, or return error.
 */
static int mtkfb_check_var(struct fb_var_screeninfo *var, struct fb_info *fbi)
{
	unsigned int bpp;
	unsigned long max_frame_size;
	unsigned long line_size;

	struct mtkfb_device *fbdev = (struct mtkfb_device *)fbi->par;

	/* DISPFUNC(); */
#if 0
	DISPCHECK("mtkfb_check_var,xres=%u,yres=%u,x_virt=%u,y_virt=%u,xoffset=%u,yoffset=%u,bits_per_pixel=%u)\n",
		  var->xres, var->yres, var->xres_virtual, var->yres_virtual,
		  var->xoffset, var->yoffset, var->bits_per_pixel);
#endif
	bpp = var->bits_per_pixel;

	if (bpp != 16 && bpp != 24 && bpp != 32) {
		MTKFB_LOG("[%s]unsupported bpp: %d", __func__, bpp);
		return -1;
	}

	switch (var->rotate) {
	case 0:
	case 180:
		var->xres = MTK_FB_XRES;
		var->yres = MTK_FB_YRES;
		break;
	case 90:
	case 270:
		var->xres = MTK_FB_YRES;
		var->yres = MTK_FB_XRES;
		break;
	default:
		return -1;
	}

	if (var->xres_virtual < var->xres)
		var->xres_virtual = var->xres;
	if (var->yres_virtual < var->yres)
		var->yres_virtual = var->yres;

	max_frame_size = fbdev->fb_size_in_byte;
	DISPDBG("fbdev->fb_size_in_byte=0x%08lx\n", fbdev->fb_size_in_byte);
	line_size = var->xres_virtual * bpp / 8;

	if (line_size * var->yres_virtual > max_frame_size) {
		/* Try to keep yres_virtual first */
		line_size = max_frame_size / var->yres_virtual;
		var->xres_virtual = line_size * 8 / bpp;
		if (var->xres_virtual < var->xres) {
			/* Still doesn't fit. Shrink yres_virtual too */
			var->xres_virtual = var->xres;
			line_size = var->xres * bpp / 8;
			var->yres_virtual = max_frame_size / line_size;
		}
	}
	DISPDBG("mtkfb_check_var,xres=%u,yres=%u,x_virt=%u,y_virl=%u,xoffset=%u,yoffset=%u,bits_per_pixel=%u)\n",
		var->xres, var->yres, var->xres_virtual, var->yres_virtual,
		var->xoffset, var->yoffset, var->bits_per_pixel);
	if (var->xres + var->xoffset > var->xres_virtual)
		var->xoffset = var->xres_virtual - var->xres;
	if (var->yres + var->yoffset > var->yres_virtual)
		var->yoffset = var->yres_virtual - var->yres;
#if 0
	DISPMSG("mtkfb_check_var,xres=%u,yres=%u,x_virt=%u,y_virt=%u,xoffset=%u,yoffset=%u,bits_per_pixel=%u)\n",
		var->xres, var->yres, var->xres_virtual, var->yres_virtual,
		var->xoffset, var->yoffset, var->bits_per_pixel);
#endif
	if (bpp == 16) {
		var->red.offset = 11;
		var->red.length = 5;
		var->green.offset = 5;
		var->green.length = 6;
		var->blue.offset = 0;
		var->blue.length = 5;
		var->transp.offset = 0;
		var->transp.length = 0;
	} else if (bpp == 24) {
		var->red.length = var->green.length = var->blue.length = 8;
		var->transp.length = 0;

		ASSERT(var->green.offset == 8);
		ASSERT((var->red.offset + var->blue.offset) == 16);
		ASSERT((var->red.offset == 16 || var->red.offset == 0));
	} else if (bpp == 32) {
		var->red.length = var->green.length = var->blue.length = var->transp.length = 8;

		/*ASSERT((var->green.offset == 8 && var->transp.offset == 24));*/
		ASSERT(var->red.offset + var->blue.offset == 16);
		ASSERT((var->red.offset == 16 || var->red.offset == 0));
	}

	var->red.msb_right = var->green.msb_right = var->blue.msb_right = var->transp.msb_right = 0;

	if (var->activate & FB_ACTIVATE_NO_UPDATE)
		no_update = true;
	else
		no_update = false;

	var->activate = FB_ACTIVATE_NOW;

	var->height = UINT_MAX;
	var->width = UINT_MAX;
	var->grayscale = 0;
	var->nonstd = 0;

	var->pixclock = UINT_MAX;
	var->left_margin = UINT_MAX;
	var->right_margin = UINT_MAX;
	var->upper_margin = UINT_MAX;
	var->lower_margin = UINT_MAX;
	var->hsync_len = UINT_MAX;
	var->vsync_len = UINT_MAX;

	var->vmode = FB_VMODE_NONINTERLACED;
	var->sync = 0;

	MSG_FUNC_LEAVE();
	return 0;
}



/* Switch to a new mode. The parameters for it has been check already by
 * mtkfb_check_var.
 */
static int mtkfb_set_par(struct fb_info *fbi)
{
	struct fb_var_screeninfo *var = &fbi->var;
	struct mtkfb_device *fbdev = (struct mtkfb_device *)fbi->par;
	struct fb_overlay_layer fb_layer;
	u32 bpp = var->bits_per_pixel;
	struct disp_session_input_config *session_input;
	struct disp_input_config *input;

	unsigned int session_id = MAKE_DISP_SESSION(DISP_SESSION_PRIMARY, 0);

	/* DISPFUNC(); */
	memset(&fb_layer, 0, sizeof(struct fb_overlay_layer));
	switch (bpp) {
	case 16:
		fb_layer.src_fmt = MTK_FB_FORMAT_RGB565;
		fb_layer.src_use_color_key = 1;
		fb_layer.src_color_key = 0xFF000000;
		break;

	case 24:
		fb_layer.src_use_color_key = 1;
		fb_layer.src_fmt = (var->blue.offset == 0) ?
		    MTK_FB_FORMAT_RGB888 : MTK_FB_FORMAT_BGR888;
		fb_layer.src_color_key = 0xFF000000;
		break;

	case 32:
		fb_layer.src_use_color_key = 0;
		DISPDBG("set_par,var->blue.offset=%d\n", var->blue.offset);
		fb_layer.src_fmt = (var->blue.offset == 0) ?
		    MTK_FB_FORMAT_ARGB8888 : MTK_FB_FORMAT_ABGR8888;
		fb_layer.src_color_key = 0;
		break;

	default:
		fb_layer.src_fmt = MTK_FB_FORMAT_UNKNOWN;
		DISPERR("[%s]unsupported bpp: %d", __func__, bpp);
		return -1;
	}

	set_fb_fix(fbdev);

	fb_layer.layer_id = primary_display_get_option("FB_LAYER");
	fb_layer.layer_enable = 1;
	fb_layer.src_base_addr =
	    (void *)((unsigned long)fbdev->fb_va_base + var->yoffset * fbi->fix.line_length);
	DISPDBG("fb_pa=0x%08lx, var->yoffset=0x%08x,fbi->fix.line_length=0x%08x\n",
		fb_pa, var->yoffset, fbi->fix.line_length);
	fb_layer.src_phy_addr = (void *)(fb_pa + var->yoffset * fbi->fix.line_length);
	fb_layer.src_direct_link = 0;
	fb_layer.src_offset_x = fb_layer.src_offset_y = 0;
	fb_layer.src_pitch = ALIGN_TO(var->xres, MTK_FB_ALIGNMENT);
	fb_layer.src_width = fb_layer.tgt_width = var->xres;
	fb_layer.src_height = fb_layer.tgt_height = var->yres;
	fb_layer.tgt_offset_x = fb_layer.tgt_offset_y = 0;
	fb_layer.alpha = 0xff;
	/* fb_layer.src_color_key = 0; */
	fb_layer.layer_rotation = MTK_FB_ORIENTATION_0;
	fb_layer.layer_type = LAYER_2D;
	DISPDBG("mtkfb_set_par, fb_layer.src_fmt=%x\n", fb_layer.src_fmt);

	session_input = kzalloc(sizeof(*session_input), GFP_KERNEL);
	if (!session_input)
		goto out;

	session_input->config_layer_num = 0;
	session_input->session_id = session_id;

	if (!is_DAL_Enabled()) {
		DISPCHECK("AEE is not enabled, will disable layer 3\n");
		input = &session_input->config[session_input->config_layer_num++];
		input->layer_id = primary_display_get_option("ASSERT_LAYER");
		input->layer_enable = 0;
	} else {
		DISPCHECK("AEE is enabled, should not disable layer 3\n");
	}

	input = &session_input->config[session_input->config_layer_num++];
	_convert_fb_layer_to_disp_input(&fb_layer, input);
	primary_display_config_input_multiple(session_input);
	kfree(session_input);

out:
	/* backup fb_layer information. */
	memcpy(&fb_layer_context, &fb_layer, sizeof(fb_layer));

	MSG_FUNC_LEAVE();
	return 0;
}


static int mtkfb_soft_cursor(struct fb_info *info, struct fb_cursor *cursor)
{
	NOT_REFERENCED(info);
	NOT_REFERENCED(cursor);

	return 0;
}

static int mtkfb_get_overlay_layer_info(struct fb_overlay_layer_info *layerInfo)
{
	return 0;
}

void mtkfb_dump_layer_info(void)
{
}


UINT32 color;
unsigned int mtkfb_fm_auto_test(void)
{
	unsigned int result = 0;
	unsigned int i = 0;
	unsigned long fbVirAddr;
	UINT32 fbsize;
	int r = 0;
	unsigned int *fb_buffer;
	struct mtkfb_device *fbdev = (struct mtkfb_device *)mtkfb_fbi->par;
	struct fb_var_screeninfo var;

	fbVirAddr = (unsigned long)fbdev->fb_va_base;
	fb_buffer = (unsigned int *)fbVirAddr;

	memcpy(&var, &(mtkfb_fbi->var), sizeof(var));
	var.activate = FB_ACTIVATE_NOW;
	var.bits_per_pixel = 32;
	var.transp.offset = 24;
	var.transp.length = 8;
	var.red.offset = 16;
	var.red.length = 8;
	var.green.offset = 8;
	var.green.length = 8;
	var.blue.offset = 0;
	var.blue.length = 8;

	r = mtkfb_check_var(&var, mtkfb_fbi);
	if (r != 0)
		PRNERR("failed to mtkfb_check_var\n");

	mtkfb_fbi->var = var;

#if 0
	r = mtkfb_set_par(mtkfb_fbi);

	if (r != 0)
		PRNERR("failed to mtkfb_set_par\n");
#endif
	if (color == 0)
		color = 0xFF00FF00;
	fbsize =
	    ALIGN_TO(DISP_GetScreenWidth(),
		     MTK_FB_ALIGNMENT) * DISP_GetScreenHeight() * MTK_FB_PAGES;
	for (i = 0; i < fbsize; i++)
		*fb_buffer++ = color;
#if 0
	if (!primary_display_is_video_mode())
		primary_display_trigger(1, NULL, 0);
#endif
	mtkfb_pan_display_impl(&mtkfb_fbi->var, mtkfb_fbi);
	msleep(100);

	result = primary_display_lcm_ATA();
	//result = 1;

	if (result == 0)
		DISPERR("ATA LCM failed\n");
	else
		DISPMSG("ATA LCM passed\n");


	return result;
}



static int mtkfb_ioctl(struct fb_info *info, unsigned int cmd, unsigned long arg)
{
	void __user *argp = (void __user *)arg;
	enum DISP_STATUS ret = 0;
	int r = 0;

	DISPFUNC();
	/* / M: dump debug mmprofile log info */
	DISPDBG("mtkfb_ioctl, info=%p, cmd nr=0x%08x, cmd size=0x%08x\n", info,
		(unsigned int)_IOC_NR(cmd), (unsigned int)_IOC_SIZE(cmd));

	switch (cmd) {

	case MTKFB_GET_FRAMEBUFFER_MVA:
		return copy_to_user(argp, &fb_pa, sizeof(fb_pa)) ? -EFAULT : 0;
		/* remain this for engineer mode dfo multiple resolution */

	case MTKFB_GET_DISPLAY_IF_INFORMATION:
		{
			int displayid = 0;

			if (copy_from_user(&displayid, (void __user *)arg, sizeof(displayid))) {
				MTKFB_LOG("[FB]: copy_from_user failed! line:%d\n", __LINE__);
				return -EFAULT;
			}

			if (displayid >= MTKFB_MAX_DISPLAY_COUNT) {
				DISPERR("[FB]: invalid display id:%d\n", displayid);
				return -EFAULT;
			}

			if (displayid == 0) {
				dispif_info[displayid].displayWidth = primary_display_get_width();
				dispif_info[displayid].displayHeight = primary_display_get_height();

				dispif_info[displayid].lcmOriginalWidth =
				    primary_display_get_original_width();
				dispif_info[displayid].lcmOriginalHeight =
				    primary_display_get_original_height();
				dispif_info[displayid].displayMode =
				    primary_display_is_video_mode() ? 0 : 1;
			} else {
				DISPERR("information for displayid: %d is not available now\n",
					displayid);
					return -EFAULT;
			}

			if (copy_to_user((void __user *)arg, &(dispif_info[displayid]),
				sizeof(struct mtk_dispif_info))) {
				MTKFB_LOG("[FB]: copy_to_user failed! line:%d\n", __LINE__);
				r = -EFAULT;
			}

			return r;
		}
	case MTKFB_SET_AOD_POWER_MODE:
	{
		enum mtkfb_aod_power_mode aod_pm = MTKFB_AOD_POWER_MODE_ERROR;
		enum mtkfb_power_mode prev_pm = primary_display_get_power_mode();

		aod_pm = (enum mtkfb_aod_power_mode)arg;
		DISPCHECK("AOD: ioctl: %s\n", aod_pm ? "AOD_DOZE_SUSPEND" : "AOD_DOZE");

		if (!primary_is_aod_supported()) {
			DISPCHECK("AOD: feature not support\n");
			return r;
		}

		if (aod_pm == MTKFB_AOD_DOZE_SUSPEND) {
			/*
			 * First DOZE to power on dispsys and LCM(low power mode);
			 * then DOZE_SUSPEND to power off dispsys.
			 */
			if (primary_display_is_sleepd() && primary_display_get_lcm_power_state()) {
				primary_display_set_power_mode(DOZE);
				primary_display_resume();

				debug_print_power_mode_check(prev_pm, DOZE);
			}

			primary_display_set_power_mode(DOZE_SUSPEND);
			ret = primary_display_suspend();

			debug_print_power_mode_check(prev_pm, DOZE_SUSPEND);
		} else if (aod_pm == MTKFB_AOD_DOZE) {
			primary_display_set_power_mode(DOZE);
			ret = primary_display_resume();

			debug_print_power_mode_check(prev_pm, DOZE);
		} else {
			DISPERR("AOD: error: unknown AOD power mode %d\n", aod_pm);
		}
		if (ret < 0)
			DISPERR("AOD: set %s failed\n", aod_pm ? "AOD_SUSPEND" : "AOD_RESUME");

		break;
	}
	case MTKFB_POWEROFF:
		{
			MTKFB_FUNC();
			if (primary_display_is_sleepd()) {
				DISPERR("[FB Driver] is still in MTKFB_POWEROFF!!!\n");
				return r;
			}

			DISPDBG("[FB Driver] enter MTKFB_POWEROFF\n");
			ret = primary_display_suspend();
			if (ret < 0)
				DISPERR("primary display suspend failed\n");
			DISPDBG("[FB Driver] leave MTKFB_POWEROFF\n");

			is_early_suspended = TRUE;	/* no care */
			return r;
		}

	case MTKFB_POWERON:
		{
			MTKFB_FUNC();
			if (primary_display_is_alive()) {
				DISPERR("[FB Driver] is still in MTKFB_POWERON!!!\n");
				return r;
			}
			DISPDBG("[FB Driver] enter MTKFB_POWERON\n");
			primary_display_resume();
			DISPDBG("[FB Driver] leave MTKFB_POWERON\n");
			is_early_suspended = FALSE;	/* no care */
			return r;
		}
	case MTKFB_GET_POWERSTATE:
		{
			int power_state;

			if (primary_display_is_sleepd())
				power_state = 0;
			else
				power_state = 1;

			if (copy_to_user(argp, &power_state, sizeof(power_state))) {
				DISPERR("MTKFB_GET_POWERSTATE failed\n");
				return -EFAULT;
			}

			return 0;
		}

	case MTKFB_CONFIG_IMMEDIATE_UPDATE:
		{
			MTKFB_LOG("[%s] MTKFB_CONFIG_IMMEDIATE_UPDATE, enable = %lu\n", __func__,
				  arg);
			if (down_interruptible(&sem_early_suspend)) {
				MTKFB_LOG("[mtkfb_ioctl] can't get semaphore:%d\n", __LINE__);
				return -ERESTARTSYS;
			}
			sem_early_suspend_cnt--;
			/* DISP_WaitForLCDNotBusy(); */
			/* ret = DISP_ConfigImmediateUpdate((BOOL)arg); */
			/* sem_early_suspend_cnt++; */
			up(&sem_early_suspend);
			return r;
		}

	case MTKFB_CAPTURE_FRAMEBUFFER:
		{
			unsigned long dst_pbuf = 0;
			unsigned long *src_pbuf = 0;
			unsigned int pixel_bpp = primary_display_get_bpp() / 8;
			unsigned int fbsize = DISP_GetScreenHeight() * DISP_GetScreenWidth() * pixel_bpp;

			if (copy_from_user(&dst_pbuf, (void __user *)arg, sizeof(dst_pbuf))) {
				MTKFB_LOG("[FB]: copy_from_user failed! line:%d\n", __LINE__);
				r = -EFAULT;
			} else {
				src_pbuf = vmalloc(fbsize);
				if (!src_pbuf) {
					MTKFB_LOG("[FB]: vmalloc capture src_pbuf failed! line:%d\n", __LINE__);
					r = -EFAULT;
				} else {
					dprec_logger_start(DPREC_LOGGER_WDMA_DUMP, 0, 0);
					r = primary_display_capture_framebuffer_ovl((unsigned long)src_pbuf,
						UFMT_BGRA8888);
					if (r < 0)
						DISPERR("primary display capture framebuffer failed!\n");
					dprec_logger_done(DPREC_LOGGER_WDMA_DUMP, 0, 0);
					if (copy_to_user((unsigned long *)dst_pbuf, src_pbuf, fbsize)) {
						MTKFB_LOG("[FB]: copy_to_user failed! line:%d\n", __LINE__);
						r = -EFAULT;
					}
					vfree(src_pbuf);
				}
			}

			return r;
		}


	case MTKFB_SLT_AUTO_CAPTURE:
		{
			struct fb_slt_catpure capConfig;
			unsigned long *src_pbuf = 0;
			unsigned int pixel_bpp = primary_display_get_bpp() / 8;
			unsigned int fbsize = DISP_GetScreenHeight() * DISP_GetScreenWidth() * pixel_bpp;

			if (copy_from_user(&capConfig, (void __user *)arg, sizeof(capConfig))) {
				MTKFB_LOG("[FB]: copy_from_user failed! line:%d\n", __LINE__);
				r = -EFAULT;
			} else {
				src_pbuf = vmalloc(fbsize);
				if (!src_pbuf) {
					MTKFB_LOG("[FB]: vmalloc capture src_pbuf failed! line:%d\n", __LINE__);
					r = -EFAULT;
				} else {
					unsigned int format;

					switch (capConfig.format) {
					case MTK_FB_FORMAT_RGB888:
						format = UFMT_RGB888;
						break;
					case MTK_FB_FORMAT_BGR888:
						format = UFMT_BGR888;
						break;
					case MTK_FB_FORMAT_ARGB8888:
						format = UFMT_ARGB8888;
						break;
					case MTK_FB_FORMAT_RGB565:
						format = UFMT_RGB565;
						break;
					case MTK_FB_FORMAT_UYVY:
						format = UFMT_UYVY;
						break;
					case MTK_FB_FORMAT_ABGR8888:
					default:
						format = UFMT_ABGR8888;
						break;
					}
					set_slt_test(1);
					stop_smart_ovl_nolock();
					do_primary_display_switch_mode(DISP_SESSION_DIRECT_LINK_MODE,
						primary_get_sess_id(), 1, NULL, 1);
					primary_display_capture_framebuffer_ovl((unsigned long)
										src_pbuf,
										format);
					if (copy_to_user((unsigned long *)capConfig.outputBuffer, src_pbuf, fbsize)) {
						MTKFB_LOG("[FB]: copy_to_user failed! line:%d\n", __LINE__);
						r = -EFAULT;
						}
					vfree(src_pbuf);
					restart_smart_ovl_nolock();
					set_slt_test(0);
					}
				}
			return r;
		}
	case MTKFB_GET_OVERLAY_LAYER_INFO:
		{
			struct fb_overlay_layer_info layerInfo;

			MTKFB_LOG(" mtkfb_ioctl():MTKFB_GET_OVERLAY_LAYER_INFO\n");

			if (copy_from_user(&layerInfo, (void __user *)arg, sizeof(layerInfo))) {
				MTKFB_LOG("[FB]: copy_from_user failed! line:%d\n", __LINE__);
				return -EFAULT;
			}
			if (mtkfb_get_overlay_layer_info(&layerInfo) < 0) {
				MTKFB_LOG("[FB]: Failed to get overlay layer info\n");
				return -EFAULT;
			}
			if (copy_to_user((void __user *)arg, &layerInfo, sizeof(layerInfo))) {
				MTKFB_LOG("[FB]: copy_to_user failed! line:%d\n", __LINE__);
				r = -EFAULT;
			}
			return r;
		}
	case MTKFB_SET_OVERLAY_LAYER:
		{		/* no function */
			struct fb_overlay_layer *layerInfo;
			unsigned int session_id = MAKE_DISP_SESSION(DISP_SESSION_PRIMARY, 0);
			DISPMSG(" mtkfb_ioctl():MTKFB_SET_OVERLAY_LAYER\n");

			layerInfo = kmalloc(sizeof(*layerInfo), GFP_KERNEL);
			if (!layerInfo)
				return -ENOMEM;

			if (copy_from_user(layerInfo, (void __user *)arg, sizeof(*layerInfo))) {
				MTKFB_LOG("[FB]: copy_from_user failed! line:%d\n", __LINE__);
				r = -EFAULT;
			} else {
				struct disp_input_config *input;

				/* in early suspend mode ,will not update buffer index, info SF by return value */
				if (primary_display_is_sleepd()) {
					DISPERR("[FB] error, set overlay in early suspend ,skip!\n");
					kfree(layerInfo);
					return MTKFB_ERROR_IS_EARLY_SUSPEND;
				}

				memset((void *)&session_input, 0, sizeof(session_input));
				input = &session_input.config[session_input.config_layer_num++];
				_convert_fb_layer_to_disp_input(layerInfo, input);
				session_input.session_id = session_id;
				primary_display_config_input_multiple(&session_input);
				primary_display_trigger(1, NULL, 0);
			}

			kfree(layerInfo);
			return r;
		}

	case MTKFB_ERROR_INDEX_UPDATE_TIMEOUT:
		{
			DISPERR("[DDP] mtkfb_ioctl():MTKFB_ERROR_INDEX_UPDATE_TIMEOUT\n");
			/* call info dump function here */
			/* mtkfb_dump_layer_info(); */
			return r;
		}

	case MTKFB_ERROR_INDEX_UPDATE_TIMEOUT_AEE:
		{
			DISPERR("[DDP] mtkfb_ioctl():MTKFB_ERROR_INDEX_UPDATE_TIMEOUT\n");
			/* call info dump function here */
			/* mtkfb_dump_layer_info(); */
			return r;
		}

	case MTKFB_SET_VIDEO_LAYERS:
		{
			struct mmp_fb_overlay_layers {
				struct fb_overlay_layer Layer0;
				struct fb_overlay_layer Layer1;
				struct fb_overlay_layer Layer2;
				struct fb_overlay_layer Layer3;
			};

			struct fb_overlay_layer *layerInfo;
			int layerInfo_size = sizeof(struct fb_overlay_layer) * VIDEO_LAYER_COUNT;
			unsigned int session_id = MAKE_DISP_SESSION(DISP_SESSION_PRIMARY, 0);
			DISPMSG(" mtkfb_ioctl():MTKFB_SET_VIDEO_LAYERS\n");

			layerInfo = kmalloc(layerInfo_size, GFP_KERNEL);
			if (!layerInfo)
				return -ENOMEM;

			if (copy_from_user(layerInfo, (void __user *)arg, layerInfo_size)) {
				MTKFB_LOG("[FB]: copy_from_user failed! line:%d\n", __LINE__);
				r = -EFAULT;
			} else {
				int32_t i;
				struct disp_input_config *input;

				memset((void *)&session_input, 0, sizeof(session_input));

				for (i = 0; i < VIDEO_LAYER_COUNT; ++i) {
					if (layerInfo[i].layer_id >= TOTAL_OVL_LAYER_NUM) {
						disp_aee_print
						    ("MTKFB_SET_VIDEO_LAYERS, layer_id invalid=%d\n",
						     layerInfo[i].layer_id);
						continue;
					}

					input = &session_input.config[session_input.config_layer_num++];
					_convert_fb_layer_to_disp_input(&layerInfo[i], input);
				}
				session_input.session_id = session_id;
				primary_display_config_input_multiple(&session_input);
				primary_display_trigger(1, NULL, 0);
			}
			kfree(layerInfo);

			return r;
		}

	case MTKFB_TRIG_OVERLAY_OUT:
		{
			DISPMSG(" mtkfb_ioctl():MTKFB_TRIG_OVERLAY_OUT\n");
			primary_display_trigger(1, NULL, 0);
			return 0;
		}

	case MTKFB_META_RESTORE_SCREEN:
		{
			struct fb_var_screeninfo var;

			if (copy_from_user(&var, argp, sizeof(var)))
				return -EFAULT;

			info->var.yoffset = var.yoffset;
			/* check var.yoffset passed by user space */
			if (info->var.yres + info->var.yoffset > info->var.yres_virtual)
				info->var.yoffset = info->var.yres_virtual - info->var.yres;

			init_framebuffer(info);

			return mtkfb_pan_display_impl(&var, info);
		}


	case MTKFB_GET_DEFAULT_UPDATESPEED:
		{
			unsigned int speed = 0;

			MTKFB_LOG("[MTKFB] get default update speed\n");
			/* DISP_Get_Default_UpdateSpeed(&speed); */

			DISPMSG("[MTKFB EM]MTKFB_GET_DEFAULT_UPDATESPEED is %d\n", speed);
			return copy_to_user(argp, &speed, sizeof(speed)) ? -EFAULT : 0;
		}

	case MTKFB_GET_CURR_UPDATESPEED:
		{
			unsigned int speed = 0;

			MTKFB_LOG("[MTKFB] get current update speed\n");
			/* DISP_Get_Current_UpdateSpeed(&speed); */

			DISPMSG("[MTKFB EM]MTKFB_GET_CURR_UPDATESPEED is %d\n", speed);
			return copy_to_user(argp, &speed, sizeof(speed)) ? -EFAULT : 0;
		}

	case MTKFB_CHANGE_UPDATESPEED:
		{
			unsigned int speed = 0;

			MTKFB_LOG("[MTKFB] change update speed\n");

			if (copy_from_user(&speed, (void __user *)arg, sizeof(speed))) {
				MTKFB_LOG("[FB]: copy_from_user failed! line:%d\n", __LINE__);
				r = -EFAULT;
			} else {
				/* DISP_Change_Update(speed); */

				DISPMSG("[MTKFB EM]MTKFB_CHANGE_UPDATESPEED is %d\n", speed);

			}
			return r;
		}

	case MTKFB_AEE_LAYER_EXIST:
		{
			int dal_en = is_DAL_Enabled();

			/* DISPMSG("[MTKFB] isAEEEnabled=%d\n", isAEEEnabled); */
			return copy_to_user(argp, &dal_en, sizeof(dal_en)) ? -EFAULT : 0;
		}
	case MTKFB_LOCK_FRONT_BUFFER:
		return 0;
	case MTKFB_UNLOCK_FRONT_BUFFER:
		return 0;

	case MTKFB_FACTORY_AUTO_TEST:
		{
			unsigned int result = 0;

			DISPMSG("factory mode: lcm auto test\n");
			result = mtkfb_fm_auto_test();
			return copy_to_user(argp, &result, sizeof(result)) ? -EFAULT : 0;
		}
	case MTKFB_META_SHOW_BOOTLOGO:
		{
			struct mtkfb_device *fbdev = (struct mtkfb_device *)mtkfb_fbi->par;
			int i;
			struct disp_input_config *input;
			unsigned int session_id = MAKE_DISP_SESSION(DISP_SESSION_PRIMARY, 0);
			DISPMSG("MTKFB_META_SHOW_BOOTLOGO\n");
			memset((void *)&session_input, 0, sizeof(session_input));

			for (i = 0; i < 2; i++) {

				input = &session_input.config[session_input.config_layer_num++];

				input->layer_enable = 1;
				input->src_fmt = DISP_FORMAT_RGBA8888;
				input->src_offset_x = 0;
				input->src_offset_y = 0;
				input->src_width = MTK_FB_XRES;
				input->src_height = MTK_FB_YRES;
				input->tgt_offset_x = 0;
				input->tgt_offset_y = 0;
				input->tgt_width = MTK_FB_XRES;
				input->tgt_height = MTK_FB_YRES;

				input->src_pitch = ALIGN_TO(MTK_FB_XRES, MTK_FB_ALIGNMENT) * 4;
				input->alpha_enable = 1;
				input->alpha = 0xff;
				input->next_buff_idx = -1;
			}

			input = &session_input.config[0];
			input->layer_id = 0;
			input->src_phy_addr = (void *)(unsigned long)(fbdev->fb_pa_base);

			input = &session_input.config[1];
			input->layer_id = 3;
			input->src_phy_addr = (void *)(unsigned long)(fbdev->fb_pa_base +
						       (ALIGN_TO(MTK_FB_XRES, MTK_FB_ALIGNMENT) *
							ALIGN_TO(MTK_FB_YRES, MTK_FB_ALIGNMENT) * 4));
			session_input.session_id = session_id;
			primary_display_config_input_multiple(&session_input);
			primary_display_trigger(1, NULL, 0);

			return 0;
		}

	default:
		DISPERR("mtkfb_ioctl Not support, info=0x%p, cmd=0x%08x, arg=0x%08lx\n", info,
		       (unsigned int)cmd, arg);
		return -EINVAL;
	}

	return r;
}

#ifdef CONFIG_COMPAT

static void compat_convert(struct compat_fb_overlay_layer *compat_info,
			   struct fb_overlay_layer *info)
{
	info->layer_id = compat_info->layer_id;
	info->layer_enable = compat_info->layer_enable;
	info->src_base_addr = (void *)((unsigned long)(compat_info->src_base_addr));
	info->src_phy_addr = (void *)((unsigned long)(compat_info->src_phy_addr));
	info->src_direct_link = compat_info->src_direct_link;
	info->src_fmt = compat_info->src_fmt;
	info->src_use_color_key = compat_info->src_use_color_key;
	info->src_color_key = compat_info->src_color_key;
	info->src_pitch = compat_info->src_pitch;
	info->src_offset_x = compat_info->src_offset_x;
	info->src_offset_y = compat_info->src_offset_y;
	info->src_width = compat_info->src_width;
	info->src_height = compat_info->src_height;
	info->tgt_offset_x = compat_info->tgt_offset_x;
	info->tgt_offset_y = compat_info->tgt_offset_y;
	info->tgt_width = compat_info->tgt_width;
	info->tgt_height = compat_info->tgt_height;
	info->layer_rotation = compat_info->layer_rotation;
	info->layer_type = compat_info->layer_type;
	info->video_rotation = compat_info->video_rotation;

	info->isTdshp = compat_info->isTdshp;
	info->next_buff_idx = compat_info->next_buff_idx;
	info->identity = compat_info->identity;
	info->connected_type = compat_info->connected_type;

	info->security = compat_info->security;
	info->alpha_enable = compat_info->alpha_enable;
	info->alpha = compat_info->alpha;
	info->fence_fd = compat_info->fence_fd;
	info->ion_fd = compat_info->ion_fd;
}


static int mtkfb_compat_ioctl(struct fb_info *info, unsigned int cmd, unsigned long arg)
{
	struct fb_overlay_layer layerInfo;
	long ret = 0;

	DISPDBG("[FB Driver] mtkfb_compat_ioctl, cmd=0x%08x, cmd nr=0x%08x, cmd size=0x%08x\n", cmd,
	       (unsigned int)_IOC_NR(cmd), (unsigned int)_IOC_SIZE(cmd));

	switch (cmd) {
	case COMPAT_MTKFB_GET_FRAMEBUFFER_MVA:
		{
			compat_uint_t __user *data32;
			__u32 data;

			data32 = compat_ptr(arg);
			data = (__u32) fb_pa;
			if (put_user(data, data32)) {
				DISPERR("MTKFB_FRAMEBUFFER_MVA failed\n");
				ret = -EFAULT;
			}
			DISPDBG("MTKFB_FRAMEBUFFER_MVA success 0x%lx\n", fb_pa);
			return ret;
		}
	case COMPAT_MTKFB_GET_DISPLAY_IF_INFORMATION:
		{
			compat_uint_t __user *data32;
			compat_uint_t displayid = 0;

			data32 = compat_ptr(arg);
			if (get_user(displayid, data32)) {
				DISPERR("COMPAT_MTKFB_GET_DISPLAY_IF_INFORMATION failed\n");
				return -EFAULT;
			}
			if (displayid >= MTKFB_MAX_DISPLAY_COUNT) {
				DISPERR("[FB]: invalid display id:%d\n", displayid);
				return -EFAULT;
			}
			if (displayid == 0) {
				dispif_info[displayid].displayWidth = primary_display_get_width();
				dispif_info[displayid].displayHeight = primary_display_get_height();

				dispif_info[displayid].lcmOriginalWidth =
					primary_display_get_original_width();
				dispif_info[displayid].lcmOriginalHeight =
					primary_display_get_original_height();
				dispif_info[displayid].displayMode =
					primary_display_is_video_mode() ? 0 : 1;
			} else {
				DISPERR("information for displayid: %d is not available now\n",
				displayid);
			}

			if (copy_to_user((void __user *)arg,
				&(dispif_info[displayid]), sizeof(struct compat_mtk_dispif_info))) {
				DISPERR("[FB]: copy_to_user failed! line:%d\n", __LINE__);
				return -EFAULT;
			}
			break;
		}
	case COMPAT_MTKFB_POWEROFF:
		{
			ret = mtkfb_ioctl(info, MTKFB_POWEROFF, arg);
			break;
		}

	case COMPAT_MTKFB_POWERON:
		{
			ret = mtkfb_ioctl(info, MTKFB_POWERON, arg);
			break;
		}
	case COMPAT_MTKFB_GET_POWERSTATE:
		{
			compat_uint_t __user *data32;
			int power_state = 0;

			data32 = compat_ptr(arg);
			if (primary_display_is_sleepd())
				power_state = 0;
			else
				power_state = 1;
			if (put_user(power_state, data32)) {
				DISPERR("MTKFB_GET_POWERSTATE failed\n");
				ret = -EFAULT;
			}
			DISPDBG("MTKFB_GET_POWERSTATE success %d\n", power_state);
			break;
		}
	case COMPAT_MTKFB_CAPTURE_FRAMEBUFFER:
		{
			compat_ulong_t __user *data32;
			unsigned long __user *data;
			compat_ulong_t ptr = 0;

			data = compat_alloc_user_space(sizeof(unsigned long));
			if (!data) {
				DISPERR("[FB]: vmalloc capture src_pbuf failed! line:%d\n", __LINE__);
				ret  = -EFAULT;
			} else {
				data32 = compat_ptr(arg);
				get_user(ptr, data32);
				put_user(ptr, data);
				ret = mtkfb_ioctl(info, MTKFB_CAPTURE_FRAMEBUFFER, (unsigned long)data);
			}
			break;
		}
	case COMPAT_MTKFB_TRIG_OVERLAY_OUT:
		{
			arg = (unsigned long)compat_ptr(arg);
			ret = mtkfb_ioctl(info, MTKFB_TRIG_OVERLAY_OUT, arg);
			break;
		}
	case COMPAT_MTKFB_META_RESTORE_SCREEN:
		{
			arg = (unsigned long)compat_ptr(arg);
			ret = mtkfb_ioctl(info, MTKFB_META_RESTORE_SCREEN, arg);
			break;
		}
	case COMPAT_MTKFB_SET_OVERLAY_LAYER:
	{
		struct compat_fb_overlay_layer *compat_layerInfo;
		unsigned int session_id = MAKE_DISP_SESSION(DISP_SESSION_PRIMARY, 0);
		compat_layerInfo = kmalloc(sizeof(*compat_layerInfo), GFP_KERNEL);
		if (!compat_layerInfo)
			return -ENOMEM;

		MTKFB_LOG(" mtkfb_compat_ioctl():MTKFB_SET_OVERLAY_LAYER\n");

		arg = (unsigned long)compat_ptr(arg);
		if (copy_from_user(compat_layerInfo, (void __user *)arg, sizeof(*compat_layerInfo))) {
			MTKFB_LOG("[FB Driver]: copy_from_user failed! line:%d\n", __LINE__);
			ret = -EFAULT;
		} else {
			struct disp_input_config *input;

			compat_convert(compat_layerInfo, &layerInfo);

			/* in early suspend mode ,will not update buffer index, info SF by return value */
			if (primary_display_is_sleepd()) {
				DISPERR("[FB Driver] error, set overlay in early suspend ,skip!\n");
				kfree(compat_layerInfo);
				return MTKFB_ERROR_IS_EARLY_SUSPEND;
			}
			memset((void *)&session_input, 0, sizeof(session_input));
			input = &session_input.config[session_input.config_layer_num++];
			session_input.session_id = session_id;
			_convert_fb_layer_to_disp_input(&layerInfo, input);
			primary_display_config_input_multiple(&session_input);
			/* primary_display_trigger(1, NULL, 0); */
		}
		kfree(compat_layerInfo);
	}
		break;

	case COMPAT_MTKFB_SET_VIDEO_LAYERS:
	{
		struct compat_fb_overlay_layer *compat_layerInfo;
		int compat_layerInfo_size = sizeof(struct compat_fb_overlay_layer) * VIDEO_LAYER_COUNT;

		compat_layerInfo = kmalloc(compat_layerInfo_size, GFP_KERNEL);
		if (!compat_layerInfo)
			return -ENOMEM;

		MTKFB_LOG(" mtkfb_compat_ioctl():MTKFB_SET_VIDEO_LAYERS\n");

		if (copy_from_user(compat_layerInfo, (void __user *)arg, compat_layerInfo_size)) {
			MTKFB_LOG("[FB Driver]: copy_from_user failed! line:%d\n", __LINE__);
			ret = -EFAULT;
		} else {
			int32_t i;
			/* mutex_lock(&OverlaySettingMutex); */
			struct disp_input_config *input;

			memset((void *)&session_input, 0, sizeof(session_input));

			for (i = 0; i < VIDEO_LAYER_COUNT; ++i) {
				compat_convert(&compat_layerInfo[i], &layerInfo);
				input =
				    &session_input.config[session_input.config_layer_num++];
				_convert_fb_layer_to_disp_input(&layerInfo, input);
			}
			/* is_ipoh_bootup = false; */
			/* atomic_set(&OverlaySettingDirtyFlag, 1); */
			/* atomic_set(&OverlaySettingApplied, 0); */
			/* mutex_unlock(&OverlaySettingMutex); */
			/* mmprofile_logStructure(MTKFB_MMP_Events.SetOverlayLayers, MMPROFILE_FLAG_END,*/
						 /*layerInfo, struct mmp_fb_overlay_layers); */
			primary_display_config_input_multiple(&session_input);
			/* primary_display_trigger(1, NULL, 0); */
		}
		kfree(compat_layerInfo);
	}
		break;
	case COMPAT_MTKFB_AEE_LAYER_EXIST:
		{
			int dal_en = is_DAL_Enabled();
			compat_ulong_t __user *data32;

			data32 = compat_ptr(arg);
			if (put_user(dal_en, data32)) {
				DISPERR("MTKFB_GET_POWERSTATE failed\n");
				ret = -EFAULT;
			}
			break;
		}
	case COMPAT_MTKFB_FACTORY_AUTO_TEST:
		{
			unsigned long result = 0;
			compat_ulong_t __user *data32;

			DISPMSG("factory mode: lcm auto test\n");
			result = mtkfb_fm_auto_test();
			data32 = compat_ptr(arg);
			if (put_user(result, data32)) {
				DISPERR("MTKFB_GET_POWERSTATE failed\n");
				ret = -EFAULT;
			}
			break;
			/*return copy_to_user(argp, &result, sizeof(result)) ? -EFAULT : 0;*/
		}
	case COMPAT_MTKFB_META_SHOW_BOOTLOGO:
		{
			arg = (unsigned long)compat_ptr(arg);
			ret = mtkfb_ioctl(info, MTKFB_META_SHOW_BOOTLOGO, arg);
			break;
		}
	default:
		/* NOTHING DIFFERENCE with standard ioctl calling */
		arg = (unsigned long)compat_ptr(arg);
		ret = mtkfb_ioctl(info, cmd, arg);
		break;
	}

	return ret;
}
#endif

static int mtkfb_pan_display_proxy(struct fb_var_screeninfo *var, struct fb_info *info)
{
#ifdef CONFIG_MTPROF_APPLAUNCH	/* eng enable, user disable */
	LOG_PRINT(ANDROID_LOG_INFO, "AppLaunch", "mtkfb_pan_display_proxy.\n");
#endif
	return mtkfb_pan_display_impl(var, info);
}

/* Callback table for the frame buffer framework. Some of these pointers
 * will be changed according to the current setting of fb_info->accel_flags.
 */
static struct fb_ops mtkfb_ops = {
	.owner = THIS_MODULE,
	.fb_open = mtkfb_open,
	.fb_release = mtkfb_release,
	.fb_setcolreg = mtkfb_setcolreg,
	.fb_pan_display = mtkfb_pan_display_proxy,
	.fb_fillrect = cfb_fillrect,
	.fb_copyarea = cfb_copyarea,
	.fb_imageblit = cfb_imageblit,
	.fb_cursor = mtkfb_soft_cursor,
	.fb_check_var = mtkfb_check_var,
	.fb_set_par = mtkfb_set_par,
	.fb_ioctl = mtkfb_ioctl,
#ifdef CONFIG_COMPAT
	.fb_compat_ioctl = mtkfb_compat_ioctl,
#endif
#if defined(CONFIG_PM_AUTOSLEEP)
	.fb_blank = mtkfb_blank,
#endif
};

/*
 * ---------------------------------------------------------------------------
 * Sysfs interface
 * ---------------------------------------------------------------------------
 */

static int mtkfb_register_sysfs(struct mtkfb_device *fbdev)
{
	NOT_REFERENCED(fbdev);

	return 0;
}

static void mtkfb_unregister_sysfs(struct mtkfb_device *fbdev)
{
	NOT_REFERENCED(fbdev);
}

/*
 * ---------------------------------------------------------------------------
 * LDM callbacks
 * ---------------------------------------------------------------------------
 */
/* Initialize system fb_info object and set the default video mode.
 * The frame buffer memory already allocated by lcddma_init
 */
static int mtkfb_fbinfo_init(struct fb_info *info)
{
	struct mtkfb_device *fbdev = (struct mtkfb_device *)info->par;
	struct fb_var_screeninfo var;
	int r = 0;

	DISPFUNC();

	ASSERT(fbdev->fb_va_base);
	info->fbops = &mtkfb_ops;
	info->flags = FBINFO_FLAG_DEFAULT;
	info->screen_base = (char *)fbdev->fb_va_base;
	info->screen_size = fbdev->fb_size_in_byte;
	info->pseudo_palette = fbdev->pseudo_palette;

	r = fb_alloc_cmap(&info->cmap, 32, 0);
	if (r != 0)
		DISPERR("unable to allocate color map memory\n");

	/* setup the initial video mode (RGB565) */

	memset(&var, 0, sizeof(var));

	var.xres = MTK_FB_XRES;
	var.yres = MTK_FB_YRES;
	var.xres_virtual = MTK_FB_XRESV;
	var.yres_virtual = MTK_FB_YRESV;
	DISPMSG("mtkfb_fbinfo_init var.xres=%d,var.yres=%d,var.xres_virtual=%d,var.yres_virtual=%d\n",
	     var.xres, var.yres, var.xres_virtual, var.yres_virtual);
	/* use 32 bit framebuffer as default */
	var.bits_per_pixel = 32;

	var.transp.offset = 24;
	var.red.length = 8;
#if 0
	var.red.offset = 16;
	var.red.length = 8;
	var.green.offset = 8;
	var.green.length = 8;
	var.blue.offset = 0;
	var.blue.length = 8;
#else
	var.red.offset = 0;
	var.red.length = 8;
	var.green.offset = 8;
	var.green.length = 8;
	var.blue.offset = 16;
	var.blue.length = 8;
#endif

	var.width = DISP_GetActiveWidth();
	var.height = DISP_GetActiveHeight();

	var.activate = FB_ACTIVATE_NOW;

	r = mtkfb_check_var(&var, info);
	if (r != 0)
		DISPERR("failed to mtkfb_check_var\n");

	info->var = var;

	r = mtkfb_set_par(info);
	if (r != 0)
		DISPERR("failed to mtkfb_set_par\n");

	MSG_FUNC_LEAVE();
	return r;
}

/* Release the fb_info object */
static void mtkfb_fbinfo_cleanup(struct mtkfb_device *fbdev)
{
	MSG_FUNC_ENTER();

	fb_dealloc_cmap(&fbdev->fb_info->cmap);

	MSG_FUNC_LEAVE();
}

/* Init frame buffer content as 3 R/G/B color bars for debug */
static int init_framebuffer(struct fb_info *info)
{
	void *buffer = info->screen_base + info->var.yoffset * info->fix.line_length;
	int size = info->var.xres_virtual * info->var.yres * info->var.bits_per_pixel/8;

	memset_io(buffer, 0, size);
	return 0;
}


/* Free driver resources. Can be called to rollback an aborted initialization
 * sequence.
 */
static void mtkfb_free_resources(struct mtkfb_device *fbdev, int state)
{
	int r = 0;

	switch (state) {
	case MTKFB_ACTIVE:
		r = unregister_framebuffer(fbdev->fb_info);
		ASSERT(r == 0);
		/* lint -fallthrough */
	case 5:
		mtkfb_unregister_sysfs(fbdev);
		/* lint -fallthrough */
	case 4:
		mtkfb_fbinfo_cleanup(fbdev);
		/* lint -fallthrough */
	case 3:
		/* DISP_CHECK_RET(DISP_Deinit()); */
		/* lint -fallthrough */
	case 2:
#ifndef CONFIG_FPGA_EARLY_PORTING
		dma_free_coherent(0, fbdev->fb_size_in_byte, fbdev->fb_va_base, fbdev->fb_pa_base);
#endif
		/* lint -fallthrough */
	case 1:
		dev_set_drvdata(fbdev->dev, NULL);
		framebuffer_release(fbdev->fb_info);
		/* lint -fallthrough */
	case 0:
		/* nothing to free */
		break;
	default:
		DISPERR("free resources fail, state=%d\n", state);
	}
}

void disp_get_fb_address(unsigned long *fbVirAddr, unsigned long *fbPhysAddr)
{
	struct mtkfb_device *fbdev = (struct mtkfb_device *)mtkfb_fbi->par;

	*fbVirAddr =
	    (unsigned long)fbdev->fb_va_base + mtkfb_fbi->var.yoffset * mtkfb_fbi->fix.line_length;
	*fbPhysAddr =
	    (unsigned long)fbdev->fb_pa_base + mtkfb_fbi->var.yoffset * mtkfb_fbi->fix.line_length;
}

#if 0 /* defined but not used */
static int mtkfb_fbinfo_modify(struct fb_info *info)
{
	struct fb_var_screeninfo var;
	int r = 0;

	memcpy(&var, &(info->var), sizeof(var));
	var.activate = FB_ACTIVATE_NOW;
	var.bits_per_pixel = 32;
	var.transp.offset = 24;
	var.transp.length = 8;
	var.red.offset = 16;
	var.red.length = 8;
	var.green.offset = 8;
	var.green.length = 8;
	var.blue.offset = 0;
	var.blue.length = 8;
	var.yoffset = var.yres;

	r = mtkfb_check_var(&var, info);
	if (r != 0)
		PRNERR("failed to mtkfb_check_var\n");

	info->var = var;

	r = mtkfb_set_par(info);
	if (r != 0)
		PRNERR("failed to mtkfb_set_par\n");

	return r;
}

static void _mtkfb_draw_point(unsigned int addr, unsigned int x, unsigned int y, unsigned int color)
{

}
#endif

static void _mtkfb_draw_block(unsigned long addr, unsigned int x, unsigned int y, unsigned int w,
			      unsigned int h, unsigned int color)
{
	int i = 0;
	int j = 0;
	unsigned long start_addr = addr + MTK_FB_XRESV * 4 * y + x * 4;

	for (j = 0; j < h; j++) {
		for (i = 0; i < w; i++)
			mt_reg_sync_writel(color, (start_addr + i * 4 + j * MTK_FB_XRESV * 4));
	}
}

char *mtkfb_find_lcm_driver(void)
{
	_parse_tag_videolfb();
	DISPMSG("%s, %s\n", __func__, mtkfb_lcm_name);
	return mtkfb_lcm_name;
}

int _mtkfb_internal_test(unsigned long va, unsigned int w, unsigned int h)
{
	/* this is for debug, used in bring up day */
	unsigned int i = 0;
	unsigned int color = 0;
	int _internal_test_block_size = 120;

	DISPFUNC();

	for (i = 0; i < w * h / _internal_test_block_size / _internal_test_block_size; i++) {
		color = (i & 0x1) * 0xffff;
		color += 0xff000000U;
		_mtkfb_draw_block(va,
				  i % (w / _internal_test_block_size) * _internal_test_block_size,
				  i / (w / _internal_test_block_size) * _internal_test_block_size,
				  _internal_test_block_size, _internal_test_block_size, color);
	}

	primary_display_trigger(1, NULL, 0);

	return 0;
}

#ifdef CONFIG_OF
struct tag_videolfb {
	u64 fb_base;
	u32 islcmfound;
	u32 fps;
	u32 vram;
	char lcmname[1];	/* this is the minimum size */
};
unsigned int islcmconnected;
unsigned int is_lcm_inited;
unsigned int vramsize;
phys_addr_t fb_base;
static int is_videofb_parse_done;

static int __parse_tag_videolfb_extra(struct device_node *node)
{
	void *prop;
	unsigned long size = 0;
	u32 fb_base_h, fb_base_l;

	prop = (void *)of_get_property(node, "atag,videolfb-fb_base_h", NULL);
	if (!prop)
		return -1;
	fb_base_h = of_read_number(prop, 1);

	prop = (void *)of_get_property(node, "atag,videolfb-fb_base_l", NULL);
	if (!prop)
		return -1;
	fb_base_l = of_read_number(prop, 1);

	fb_base = ((u64) fb_base_h << 32) | (u64) fb_base_l;

	prop = (void *)of_get_property(node, "atag,videolfb-islcmfound", NULL);
	if (!prop)
		return -1;
	islcmconnected = of_read_number(prop, 1);

	prop = (void *)of_get_property(node, "atag,videolfb-islcm_inited", NULL);
	if (!prop)
		is_lcm_inited = 1;
	else
		is_lcm_inited = of_read_number(prop, 1);

	prop = (void *)of_get_property(node, "atag,videolfb-fps", NULL);
	if (!prop)
		return -1;
	lcd_fps = of_read_number(prop, 1);
	if (lcd_fps == 0)
		lcd_fps = 6000;

	prop = (void *)of_get_property(node, "atag,videolfb-vramSize", NULL);
	if (!prop)
		return -1;
	vramsize = of_read_number(prop, 1);

	prop = (void *)of_get_property(node, "atag,videolfb-fb_base_l", NULL);
	if (!prop)
		return -1;
	fb_base_l = of_read_number(prop, 1);

	prop = (void *)of_get_property(node, "atag,videolfb-lcmname", (int *)&size);
	if (!prop)
		return -1;
	if (size >= sizeof(mtkfb_lcm_name)) {
		DISPCHECK("%s: error to get lcmname size=%ld\n", __func__, size);
		return -1;
	}
	memset((void *)mtkfb_lcm_name, 0, sizeof(mtkfb_lcm_name));
	strncpy((char *)mtkfb_lcm_name, prop, sizeof(mtkfb_lcm_name));
	mtkfb_lcm_name[size] = '\0';
	DISPMSG("__parse_tag_videolfb_extra done\n");
	return 0;
}

static int __parse_tag_videolfb(struct device_node *node)
{
	struct tag_videolfb *videolfb_tag = NULL;
	unsigned long size = 0;

	videolfb_tag = (struct tag_videolfb *)of_get_property(node, "atag,videolfb", (int *)&size);
	if (videolfb_tag) {
		memset((void *)mtkfb_lcm_name, 0, sizeof(mtkfb_lcm_name));
		strncpy((char *)mtkfb_lcm_name, videolfb_tag->lcmname, sizeof(mtkfb_lcm_name));
		mtkfb_lcm_name[strlen(videolfb_tag->lcmname)] = '\0';

		lcd_fps = videolfb_tag->fps;
		if (lcd_fps == 0)
			lcd_fps = 6000;

		islcmconnected = videolfb_tag->islcmfound;
		vramsize = videolfb_tag->vram;
		fb_base = videolfb_tag->fb_base;
		is_lcm_inited = 1;

		return 0;
	}

	DISPCHECK("[DT][videolfb] videolfb_tag not found\n");
	return -1;
}


static int _parse_tag_videolfb(void)
{
	int ret;
	struct device_node *chosen_node;

	DISPCHECK("[DT][videolfb]isvideofb_parse_done = %d\n", is_videofb_parse_done);

	if (is_videofb_parse_done)
		return 0;

	chosen_node = of_find_node_by_path("/chosen");
	if (!chosen_node)
		chosen_node = of_find_node_by_path("/chosen@0");

	if (chosen_node) {
		ret = __parse_tag_videolfb(chosen_node);
		if (!ret)
			goto found;
		ret = __parse_tag_videolfb_extra(chosen_node);
		if (!ret)
			goto found;
	} else {
		DISPCHECK("[DT][videolfb] of_chosen not found\n");
	}
	return -1;

found:
	is_videofb_parse_done = 1;
	DISPCHECK("[DT][videolfb] islcmfound = %d\n", islcmconnected);
	DISPCHECK("[DT][videolfb] is_lcm_inited = %d\n", is_lcm_inited);
	DISPCHECK("[DT][videolfb] fps        = %d\n", lcd_fps);
	DISPCHECK("[DT][videolfb] fb_base    = 0x%lx\n", (unsigned long)fb_base);
	DISPCHECK("[DT][videolfb] vram       = 0x%x (%d)\n", vramsize, vramsize);
	DISPCHECK("[DT][videolfb] lcmname    = %s\n", mtkfb_lcm_name);
	return 0;
}

phys_addr_t mtkfb_get_fb_base(void)
{
	_parse_tag_videolfb();
	return fb_base;
}
EXPORT_SYMBOL(mtkfb_get_fb_base);

size_t mtkfb_get_fb_size(void)
{
	_parse_tag_videolfb();
	return vramsize;
}
EXPORT_SYMBOL(mtkfb_get_fb_size);
#endif

/* used when early porting, test pan display*/
int pan_display_test(int frame_num, int bpp)
{
	int i, j;
	int Bpp = bpp / 8;
	unsigned char *fb_va;
	unsigned long fb_pa;
	unsigned int fb_size;
	int w, h, fb_h;
	int yoffset_max;
	int yoffset;

	if (frame_num < -1) {
		DISPERR("%s: frame_num should be an unsigned integer:frame_num = %d",
			__func__, frame_num);
		return -1;
	}
	mtkfb_fbi->var.yoffset = 0;
	disp_get_fb_address((unsigned long *)&fb_va, &fb_pa);
	fb_size = mtkfb_fbi->fix.smem_len;
	w = mtkfb_fbi->var.xres;
	h = mtkfb_fbi->var.yres;
	fb_h = fb_size / (w * Bpp) - 10;

	DISPMSG("%s: frame_num=%d,bpp=%d, w=%d,h=%d,fb_h=%d\n",
		__func__, frame_num, bpp, w, h, fb_h);

	for (i = 0; i < fb_h; i++)
		for (j = 0; j < w; j++) {
			int x = (i * w + j) * Bpp;

			fb_va[x++] = (i + j) % 256;
			fb_va[x++] = (i + j) % 256;
			fb_va[x++] = (i + j) % 256;
			if (Bpp == 4)
				fb_va[x++] = 255;
		}

	mtkfb_fbi->var.bits_per_pixel = bpp;

	yoffset_max = fb_h - h;
	yoffset = 0;
	for (i = 0; i < frame_num; i++, yoffset += 10) {

		if (yoffset >= yoffset_max)
			yoffset = 0;

		mtkfb_fbi->var.xoffset = 0;
		mtkfb_fbi->var.yoffset = yoffset;
		mtkfb_pan_display_impl(&mtkfb_fbi->var, mtkfb_fbi);
	}

	return 0;
}

/*#define FPGA_DEBUG_PAN */
#ifdef FPGA_DEBUG_PAN
static struct task_struct *test_task;
static int update_test_kthread(void *data)
{
	/* struct sched_param param = { .sched_priority = RTPM_PRIO_SCRN_UPDATE }; */
	/* sched_setscheduler(current, SCHED_RR, &param); */
	unsigned int i = 0, j = 0;
	unsigned long fb_va;
	unsigned long fb_pa;
	unsigned int *fb_start;
	unsigned int fbsize = primary_display_get_height() * primary_display_get_width();

	mtkfb_fbi->var.yoffset = 0;
	disp_get_fb_address(&fb_va, &fb_pa);

	for (;;) {

		if (kthread_should_stop())
			break;
		msleep(1000);	/* 2s */
		DISPMSG("update test thread work,offset = %d\n", i);

		mtkfb_fbi->var.yoffset = 0;
		disp_get_fb_address(&fb_va, &fb_pa);
		fb_start = (unsigned int *)fb_va;
		for (j = 0; j < fbsize; j++) {
			*fb_start = (0x55) << ((i % 4) * 8);
			fb_start++;
		}
		mtkfb_pan_display_impl(&mtkfb_fbi->var, mtkfb_fbi);
		i++;
	}

	DISPMSG("exit update_test_kthread()\n");
	return 0;
}
#endif

/* static int mtkfb_probe(struct device *dev) */
static int mtkfb_probe(struct platform_device *pdev)
{
	struct mtkfb_device *fbdev = NULL;
	struct fb_info *fbi;
	int init_state;
	int r = 0;

	/* struct platform_device *pdev; */
	long dts_gpio_state = 0;

	DISPMSG("mtkfb_probe name [%s]  = [%s][%p]\n", pdev->name, pdev->dev.init_name, (void *)&pdev->dev);

	_parse_tag_videolfb();

	init_state = 0;

	/* pdev = to_platform_device(dev); */
	/* repo call DTS gpio module, if not necessary, invoke nothing */
	dts_gpio_state = disp_dts_gpio_init_repo(pdev);
	if (dts_gpio_state != 0)
		dev_err(&pdev->dev, "retrieve GPIO DTS failed.");

	fbi = framebuffer_alloc(sizeof(struct mtkfb_device), &(pdev->dev));
	if (!fbi) {
		DISPERR("unable to allocate memory for device info\n");
		r = -ENOMEM;
		goto cleanup;
	}
	mtkfb_fbi = fbi;

	fbdev = (struct mtkfb_device *)fbi->par;
	fbdev->fb_info = fbi;
	fbdev->dev = &(pdev->dev);
	dev_set_drvdata(&(pdev->dev), fbdev);

	DISPMSG("mtkfb_probe: fb_pa = %pa\n", &fb_base);

	disp_hal_allocate_framebuffer(fb_base, (fb_base + vramsize - 1),
				      (unsigned long *)(&fbdev->fb_va_base), &fb_pa);
	fbdev->fb_pa_base = fb_base;

	primary_display_set_frame_buffer_address((unsigned long)(fbdev->fb_va_base), fb_pa);
	primary_display_init(mtkfb_find_lcm_driver(), lcd_fps, is_lcm_inited);

	init_state++;		/* 1 */
	MTK_FB_XRES = DISP_GetScreenWidth();
	MTK_FB_YRES = DISP_GetScreenHeight();
	fb_xres_update = MTK_FB_XRES;
	fb_yres_update = MTK_FB_YRES;

	MTK_FB_BPP = DISP_GetScreenBpp();
	MTK_FB_PAGES = DISP_GetPages();
	DISPCHECK("MTK_FB_XRES=%d, MTKFB_YRES=%d, MTKFB_BPP=%d, MTK_FB_PAGES=%d, MTKFB_LINE=%d, MTKFB_SIZEV=%d\n",
		  MTK_FB_XRES, MTK_FB_YRES, MTK_FB_BPP, MTK_FB_PAGES, MTK_FB_LINE, MTK_FB_SIZEV);
	fbdev->fb_size_in_byte = MTK_FB_SIZEV;

	/* Allocate and initialize video frame buffer */
	DISPCHECK("[FB Driver] fbdev->fb_pa_base = 0x%lx, fbdev->fb_va_base = 0x%p\n",
		(unsigned long)(fbdev->fb_pa_base), fbdev->fb_va_base);

	if (!fbdev->fb_va_base) {
		DISPERR("unable to allocate memory for frame buffer\n");
		r = -ENOMEM;
		goto cleanup;
	}
	init_state++;		/* 2 */

	r = mtkfb_fbinfo_init(fbi);
	if (r) {
		DISPERR("mtkfb_fbinfo_init fail, r = %d\n", r);
		goto cleanup;
	}
	init_state++;		/* 4 */
	DISPMSG("\nmtkfb_fbinfo_init done\n");

	if (disp_helper_get_stage() == DISP_HELPER_STAGE_NORMAL) {
		/* dal_init should after mtkfb_fbinfo_init, otherwise layer 3 will show dal background color */
		enum DAL_STATUS ret;
		unsigned long fbVA = (unsigned long)(fbdev->fb_va_base);
		unsigned long fbPA = fb_pa;

		/* / DAL init here */
		fbVA += DISP_GetFBRamSize();
		fbPA += DISP_GetFBRamSize();
		ret = DAL_Init(fbVA, fbPA);
		DISPMSG("DAL_Init done\n");
	}
#if 0
	if (disp_helper_get_stage() != DISP_HELPER_STAGE_NORMAL)
		_mtkfb_internal_test((unsigned long)(fbdev->fb_va_base), MTK_FB_XRES, MTK_FB_YRES);
#endif
	r = mtkfb_register_sysfs(fbdev);
	if (r) {
		DISPERR("mtkfb_register_sysfs fail, r = %d\n", r);
		goto cleanup;
	}
	init_state++;		/* 5 */
	DISPMSG("register_framebuffer start...\n");
	r = register_framebuffer(fbi);
	if (r != 0) {
		DISPERR("register_framebuffer failed\n");
		goto cleanup;
	}
	DISPMSG("register_framebuffer done\n");
#ifdef FPGA_DEBUG_PAN
	test_task = kthread_create(update_test_kthread, NULL, "update_test_kthread");
	wake_up_process(test_task);
#endif

	if (disp_helper_get_stage() != DISP_HELPER_STAGE_NORMAL)
		primary_display_diagnose();


	/*this function will get fb_heap base address to ion for management frame buffer */
	ion_drv_create_FB_heap(mtkfb_get_fb_base(), mtkfb_get_fb_size());

	fbdev->state = MTKFB_ACTIVE;

	MSG_FUNC_LEAVE();
	return 0;

cleanup:
	mtkfb_free_resources(fbdev, init_state);

	DISPMSG("mtkfb_probe end\n");
	return r;
}

/* Called when the device is being detached from the driver */
static int mtkfb_remove(struct device *dev)
{
	struct mtkfb_device *fbdev = dev_get_drvdata(dev);
	enum mtkfb_state saved_state = fbdev->state;

	MSG_FUNC_ENTER();
	/* FIXME: wait till completion of pending events */

	fbdev->state = MTKFB_DISABLED;
	mtkfb_free_resources(fbdev, saved_state);

	MSG_FUNC_LEAVE();
	return 0;
}

/* PM suspend */
static int mtkfb_suspend(struct device *pdev, pm_message_t mesg)
{
	NOT_REFERENCED(pdev);
	MSG_FUNC_ENTER();
	MTKFB_LOG("[FB Driver] mtkfb_suspend(): 0x%x\n", mesg.event);
	ovl2mem_wait_done();

	MSG_FUNC_LEAVE();
	return 0;
}

bool mtkfb_is_suspend(void)
{
	return primary_display_is_sleepd();
}
EXPORT_SYMBOL(mtkfb_is_suspend);

int mtkfb_ipoh_restore(struct notifier_block *nb, unsigned long val, void *ign)
{
	switch (val) {
	case PM_HIBERNATION_PREPARE:
		DISPDBG("[FB Driver] mtkfb_ipoh_restore PM_HIBERNATION_PREPARE\n");
		return NOTIFY_DONE;
	case PM_RESTORE_PREPARE:
		primary_display_ipoh_restore();
		DISPDBG("[FB Driver] mtkfb_ipoh_restore PM_RESTORE_PREPARE\n");
		return NOTIFY_DONE;
	case PM_POST_HIBERNATION:
		DISPDBG("[FB Driver] mtkfb_ipoh_restore PM_POST_HIBERNATION\n");
		return NOTIFY_DONE;
	}
	return NOTIFY_OK;
}

int mtkfb_ipo_init(void)
{
	pm_nb.notifier_call = mtkfb_ipoh_restore;
	pm_nb.priority = 0;
	register_pm_notifier(&pm_nb);

	return 0;
}

static void mtkfb_shutdown(struct device *pdev)
{
	MTKFB_LOG("[FB Driver] mtkfb_shutdown()\n");
	/* mt65xx_leds_brightness_set(MT65XX_LED_TYPE_LCD, LED_OFF); */
	if (!lcd_fps)
		msleep(30);
	else
		msleep(2 * 100000 / lcd_fps);	/* Delay 2 frames. */

	if (primary_display_is_sleepd()) {
		MTKFB_LOG("mtkfb has been power off\n");
		return;
	}
	primary_display_suspend();
	MTKFB_LOG("[FB Driver] leave mtkfb_shutdown\n");
}

void mtkfb_clear_lcm(void)
{
}

static void mtkfb_early_suspend(void)
{
	int ret = 0;

	if (disp_helper_get_stage() != DISP_HELPER_STAGE_NORMAL)
		return;

	DISPMSG("[FB Driver] enter early_suspend\n");

	/* mt65xx_leds_brightness_set(MT65XX_LED_TYPE_LCD, LED_OFF); */

	msleep(30);

	ret = primary_display_suspend();

	if (ret < 0) {
		DISPERR("primary display suspend failed\n");
		return;
	}

	DISPMSG("[FB Driver] leave early_suspend\n");

}


/* PM resume */
static int mtkfb_resume(struct device *pdev)
{
	NOT_REFERENCED(pdev);
	MSG_FUNC_ENTER();
	MTKFB_LOG("[FB Driver] mtkfb_resume()\n");
	MSG_FUNC_LEAVE();
	return 0;
}

static void mtkfb_late_resume(void)
{
	int ret = 0;

	if (disp_helper_get_stage() != DISP_HELPER_STAGE_NORMAL)
		return;

	DISPMSG("[FB Driver] enter late_resume\n");

	ret = primary_display_resume();

	if (ret) {
		DISPERR("primary display resume failed\n");
		return;
	}

	DISPMSG("[FB Driver] leave late_resume\n");

}

/*---------------------------------------------------------------------------*/
#ifdef CONFIG_PM
/*---------------------------------------------------------------------------*/
int mtkfb_pm_suspend(struct device *device)
{
	/* DISPMSG("calling %s()\n", __func__); */

	struct platform_device *pdev = to_platform_device(device);

	if (pdev == NULL) {
		disp_aee_db_print("pdev is NULL\n");
		return -1;
	}

	return mtkfb_suspend((struct device *)pdev, PMSG_SUSPEND);
}

int mtkfb_pm_resume(struct device *device)
{
	/* DISPMSG("calling %s()\n", __func__); */

	struct platform_device *pdev = to_platform_device(device);

	if (pdev == NULL) {
		disp_aee_db_print("pdev is NULL\n");
		return -1;
	}

	return mtkfb_resume((struct device *)pdev);
}

int mtkfb_pm_freeze(struct device *device)
{
	primary_display_esd_check_enable(0);
	return 0;
}

int mtkfb_pm_restore_noirq(struct device *device)
{
	/* disphal_pm_restore_noirq(device); */
	DISPDBG("%s: %d\n", __func__, __LINE__);
	is_ipoh_bootup = true;
#if 0
	if (disp_helper_get_option(DISP_OPT_DYNAMIC_SWITCH_MMSYSCLK))
		ddp_clk_prepare_enable(MM_VENCPLL);
	ddp_clk_prepare_enable(DISP_MTCMOS_CLK);
	ddp_clk_prepare_enable(DISP0_SMI_COMMON);
	ddp_clk_prepare_enable(DISP0_SMI_LARB0);
	ddp_clk_prepare_enable(DISP0_SMI_LARB4);
#else
	dpmgr_path_power_on(primary_get_dpmgr_handle(), CMDQ_DISABLE);
#endif
	DISPDBG("%s: %d\n", __func__, __LINE__);
	return 0;

}

/*---------------------------------------------------------------------------*/
#else				/*CONFIG_PM */
/*---------------------------------------------------------------------------*/
#define mtkfb_pm_suspend NULL
#define mtkfb_pm_resume  NULL
#define mtkfb_pm_restore_noirq NULL
#define mtkfb_pm_freeze NULL
/*---------------------------------------------------------------------------*/
#endif				/*CONFIG_PM */
/*---------------------------------------------------------------------------*/
static const struct of_device_id mtkfb_of_ids[] = {
	{.compatible = "mediatek,MTKFB",},
	{}
};

static const struct dev_pm_ops mtkfb_pm_ops = {
	.suspend = mtkfb_pm_suspend,
	.resume = mtkfb_pm_resume,
	.freeze = mtkfb_pm_freeze,
	.thaw = mtkfb_pm_resume,
	.poweroff = mtkfb_pm_suspend,
	.restore = mtkfb_pm_resume,
	.restore_noirq = mtkfb_pm_restore_noirq,
};

static struct platform_driver mtkfb_driver = {
	.probe = mtkfb_probe,
	.driver = {
		   .name = MTKFB_DRIVER,
#ifdef CONFIG_PM
		   .pm = &mtkfb_pm_ops,
#endif
		   .bus = &platform_bus_type,
		   .remove = mtkfb_remove,
		   .suspend = mtkfb_suspend,
		   .resume = mtkfb_resume,
		   .shutdown = mtkfb_shutdown,
		   .of_match_table = mtkfb_of_ids,
		   },
};

#ifdef CONFIG_HAS_EARLYSUSPEND
static struct early_suspend mtkfb_early_suspend_handler = {
	.level = EARLY_SUSPEND_LEVEL_DISABLE_FB,
	.suspend = mtkfb_early_suspend,
	.resume = mtkfb_late_resume,
};
#endif


int mtkfb_get_debug_state(char *stringbuf, int buf_len)
{
	int len = 0;
	struct mtkfb_device *fbdev = (struct mtkfb_device *)mtkfb_fbi->par;

	unsigned long va = (unsigned long)fbdev->fb_va_base;
	unsigned long mva = (unsigned long)fbdev->fb_pa_base;
	unsigned long pa = fbdev->fb_pa_base;
	unsigned int resv_size = vramsize;

	len +=
	    scnprintf(stringbuf + len, buf_len - len,
		      "|--------------------------------------------------------------------------------------|\n");
	/* len += scnprintf(stringbuf+len, buf_len - len, "********MTKFB Driver General Information********\n"); */
	len +=
	    scnprintf(stringbuf + len, buf_len - len,
		      "|Framebuffer VA:0x%lx, PA:0x%lx, MVA:0x%lx, Reserved Size:0x%08x|%d\n", va,
		      pa, mva, resv_size, resv_size);
	len +=
	    scnprintf(stringbuf + len, buf_len - len, "|xoffset=%d, yoffset=%d\n",
		      mtkfb_fbi->var.xoffset, mtkfb_fbi->var.yoffset);
	len +=
	    scnprintf(stringbuf + len, buf_len - len, "|framebuffer line alignment(for gpu)=%d\n",
		      MTK_FB_ALIGNMENT);
	len +=
	    scnprintf(stringbuf + len, buf_len - len,
		      "|xres=%d, yres=%d,bpp=%d,pages=%d,linebytes=%d,total size=%d\n", MTK_FB_XRES,
		      MTK_FB_YRES, MTK_FB_BPP, MTK_FB_PAGES, MTK_FB_LINE, MTK_FB_SIZEV);
	/* use extern in case DAL_LOCK is hold, then can't get any debug info */
	len +=
	    scnprintf(stringbuf + len, buf_len - len, "|AEE Layer is %s\n",
		      is_DAL_Enabled() ? "enabled" : "disabled");

	return len;
}


/* Register both the driver and the device */
int __init mtkfb_init(void)
{
	int r = 0;

	MSG_FUNC_ENTER();
	DISPCHECK("mtkfb_init Enter\n");
	if (platform_driver_register(&mtkfb_driver)) {
		PRNERR("failed to register mtkfb driver\n");
		r = -ENODEV;
		goto exit;
	}
#ifdef CONFIG_HAS_EARLYSUSPEND
	register_early_suspend(&mtkfb_early_suspend_handler);
#endif
	DISPCHECK("mtkfb_init mmm\n");
	PanelMaster_Init();
	DBG_Init();
	mtkfb_ipo_init();
exit:
	MSG_FUNC_LEAVE();
	DISPCHECK("mtkfb_init LEAVE\n");
	return r;
}


static void __exit mtkfb_cleanup(void)
{
	MSG_FUNC_ENTER();

	platform_driver_unregister(&mtkfb_driver);

#ifdef CONFIG_HAS_EARLYSUSPEND
	unregister_early_suspend(&mtkfb_early_suspend_handler);
#endif

	PanelMaster_Deinit();
	DBG_Deinit();

	MSG_FUNC_LEAVE();
}


module_init(mtkfb_init);
module_exit(mtkfb_cleanup);

MODULE_DESCRIPTION("MEDIATEK framebuffer driver");
MODULE_AUTHOR("Xuecheng Zhang <Xuecheng.Zhang@mediatek.com>");
MODULE_LICENSE("GPL");
