/*
 *  linux/drivers/video/s3c2410fb.c
 *
 *  CopyRight (C) 2002 SAMSUNG ELECTRONICS 
 *                    SW.LEE <hitchcar@sec.samsung.com>
 *
 * This file is subject to the terms and conditions of the GNU General Public
 * License.  See the file COPYING in the main directory of this archive for
 * more details.
 */

#include <linux/config.h>
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/sched.h>
#include <linux/errno.h>
#include <linux/string.h>
#include <linux/interrupt.h>
#include <linux/slab.h>
#include <linux/fb.h>
#include <linux/delay.h>
#include <linux/pm.h>
#include <linux/init.h>
#include <linux/cpufreq.h>

#include <asm/hardware.h>
#include <asm/io.h>
#include <asm/irq.h>
#include <asm/mach-types.h>
#include <asm/uaccess.h>

#include <video/fbcon.h>
#include <video/fbcon-mfb.h>
#include <video/fbcon-cfb4.h>
#include <video/fbcon-cfb8.h>
#include <video/fbcon-cfb16.h>

/*
 * enable this if your panel appears to have broken
 */
#undef CHECK_COMPAT

/*
 * debugging?
 */
#define DEBUG 0
/*
 * Complain if VAR is out of range.
 */
#define DEBUG_VAR 0


#include "s3c2410fb.h"

void (*s3c2410fb_blank_helper)(int blank);
/* EXPORT_SYMBOL(s3c2410fb_blank_helper); */


/*
 * IMHO this looks wrong.  In 8BPP, length should be 8.
 */
static struct s3c2410fb_rgb rgb_8 = {
	red:	{ offset: 0,  length: 4, },
	green:	{ offset: 0,  length: 4, },
	blue:	{ offset: 0,  length: 4, },
	transp:	{ offset: 0,  length: 0, },
};

/* S3C2410 
 * 256 Palette Usage (TFT)
 * 256 color palette consist of 256(depth) * 16 bit SPSRAM
 */
 
static struct s3c2410fb_rgb def_rgb_16 = {
	red:	{ offset: 11, length: 5, },
	green:	{ offset: 5,  length: 6, },
	blue:	{ offset: 0,  length: 5, },
	transp:	{ offset: 0,  length: 0, },
};


/*******
 * role : Fill Machine dependant data ,according to  STN or TFT 
 *
 */
static void  __init s3c2410fb_get_machine_info(struct s3c2410fb_info *fbi)
{
 
         static struct s3c2410fb_mach_info inf  __initdata = {
#if defined(TFT240_320)
	  pixclock:       0,              bpp:            16,
	  xres:		240,		yres:		320,
#elif defined(CONFIG_SOMETHING_OHTERS) /* Not Tested .. */
	  pixclock:       0,              bpp:            8,
	  xres:		320,		yres:		240,
#endif
	  hsync_len:	0,		vsync_len:	0,
	  left_margin:	0,		upper_margin:	0,
	  right_margin:	0,		lower_margin:	0,

	  sync:		FB_ACTIVATE_NOW , /* value 0 */ 
	};

	fbi->max_xres			= inf.xres;
	fbi->fb.var.xres		= inf.xres;
	fbi->fb.var.xres_virtual	= inf.xres;
	fbi->max_yres			= inf.yres;
	fbi->fb.var.yres		= inf.yres;
	fbi->fb.var.yres_virtual	= inf.yres;
	fbi->max_bpp			= inf.bpp;
	fbi->fb.var.bits_per_pixel	= inf.bpp;
	fbi->fb.var.pixclock		= inf.pixclock;
	fbi->fb.var.hsync_len		= inf.hsync_len;
	fbi->fb.var.left_margin		= inf.left_margin;
	fbi->fb.var.right_margin	= inf.right_margin;
	fbi->fb.var.vsync_len		= inf.vsync_len;
	fbi->fb.var.upper_margin	= inf.upper_margin;
	fbi->fb.var.lower_margin	= inf.lower_margin;
	fbi->fb.var.sync		= inf.sync;
	fbi->fb.var.grayscale		= inf.cmap_greyscale;
	fbi->cmap_inverse		= inf.cmap_inverse;
	fbi->cmap_static		= inf.cmap_static;	
}

static void s3c2410fb_lcd_port_init(void );
static int s3c2410fb_activate_var(struct fb_var_screeninfo *var, struct s3c2410fb_info *);
static void set_ctrlr_state(struct s3c2410fb_info *fbi, u_int state);


#define FR_WIDTH            240
#define FR_HEIGHT           320
struct FrameBuffer {
   unsigned short pixel[FR_HEIGHT][FR_WIDTH];
};
struct FrameBuffer *FBuf;

#ifdef YOU_WANT_TO_DRAW_TETRAGON
static void lcd_demo(void)
{
    // Test LCD Initialization. by displaying R.G.B and White.
  int i,j;
	for(i=0 ;i<FR_HEIGHT/2;i++)
	{
		for(j=0;j<FR_WIDTH;j++)
		{
		  if(j<FR_WIDTH/2)
		    FBuf->pixel[i][j]= 0xffff;
		  else
		    FBuf->pixel[i][j]= 0xf800;
		}
	}
	for(i=FR_HEIGHT/2 ;i<FR_HEIGHT;i++)
	{
		for(j=0;j<FR_WIDTH;j++)
		{
		if(j<FR_WIDTH/2)
		    FBuf->pixel[i][j]= 0x07e0;
		else
		    FBuf->pixel[i][j]= 0x001f;
		}
	}    
}
#endif


static void s3c2410fb_lcd_port_init(void)
{
    rGPCUP=0xffffffff; // Disable Pull-up register
    rGPCCON=0xaaaaaaaa; //Initialize VD[7:0],LCDVF[2:0],VM,VFRAME,VLINE,VCLK,LEND 
    rGPDUP=0xffffffff; // Disable Pull-up register
    rGPDCON=0xaaaaaaaa; //Initialize VD[23:8]
}

                                  
static inline void s3c2410fb_schedule_task(struct s3c2410fb_info *fbi, u_int state)
{
	unsigned long flags;

	local_irq_save(flags);
	/*
	 * We need to handle two requests being made at the same time.
	 * There are two important cases:
	 *  1. When we are changing VT (C_REENABLE) while unblanking (C_ENABLE)
	 *     We must perform the unblanking, which will do our REENABLE for us.
	 *  2. When we are blanking, but immediately unblank before we have
	 *     blanked.  We do the "REENABLE" thing here as well, just to be sure.
	 */
	if (fbi->task_state == C_ENABLE && state == C_REENABLE)
		state = (u_int) -1;
	if (fbi->task_state == C_DISABLE && state == C_ENABLE)
		state = C_REENABLE;

	if (state != (u_int)-1) {
		fbi->task_state = state;
		schedule_task(&fbi->task);
	}
	local_irq_restore(flags);
}

/*
 * Get the VAR structure pointer for the specified console
 */
static inline struct fb_var_screeninfo *get_con_var(struct fb_info *info, int con)
{
	struct s3c2410fb_info *fbi = (struct s3c2410fb_info *)info;
	return (con == fbi->currcon || con == -1) ? &fbi->fb.var : &fb_display[con].var;
}

/*
 * Get the DISPLAY structure pointer for the specified console
 *
 * struct display fb_display[MAX_NR_CONSOLES]; from fbcon.c
 */

static inline struct display *get_con_display(struct fb_info *info, int con)
{
	struct s3c2410fb_info *fbi = (struct s3c2410fb_info *)info;
	return (con < 0) ? fbi->fb.disp : &fb_display[con];
}

/*
 * Get the CMAP pointer for the specified console
 *
 * struct fb_cmap {
 *	__u32 start;			 First entry	
 *	__u32 len;			 Number of entries 
 *	__u16 *red;			 Red values
 *	__u16 *green;
 *	__u16 *blue;
 *	__u16 *transp;			 transparency, can be NULL 
 * };
 *
 */
static inline struct fb_cmap *get_con_cmap(struct fb_info *info, int con)
{
	struct s3c2410fb_info *fbi = (struct s3c2410fb_info *)info;
	return (con == fbi->currcon || con == -1) ? &fbi->fb.cmap : &fb_display[con].cmap;
}

static inline u_int
chan_to_field(u_int chan, struct fb_bitfield *bf)
{
	chan &= 0xffff;
	chan >>= 16 - bf->length;
	return chan << bf->offset;
}

/*
 * Convert bits-per-pixel to a hardware palette PBS value.
 */
static inline u_int
palette_pbs(struct fb_var_screeninfo *var)
{
	int ret = 0;
	switch (var->bits_per_pixel) {
#ifdef FBCON_HAS_CFB4
	case 4:  ret = 0 << 12;	break;
#endif
#ifdef FBCON_HAS_CFB8
	case 8:  ret = 1 << 12; break;
#endif
#ifdef FBCON_HAS_CFB16
	case 16: ret = 2 << 12; break;
#endif
	}
	return ret;
}


/******
 * bit mask RGB=565  -> RRRR RGGG GGGB BBBB
 *                      1111 1000 0000 0000   0xf800
 *                      0000 0111 1110 0000   0x07e0
 *                      0000 0000 0001 1111   0x001f
 *******************************************************/
static int
s3c2410fb_setpalettereg(u_int regno, u_int red, u_int green, u_int blue,
		       u_int trans, struct fb_info *info)
{
	struct s3c2410fb_info *fbi = (struct s3c2410fb_info *)info;
	u_int val, ret = 1;

	if (regno < fbi->palette_size) {
		val = ((red >> 5) & 0xf800);
		val |= ((green >> 11) & 0x07e0);
		val |= ((blue >> 16) & 0x001f);

		if (regno == 0)
			val |= palette_pbs(&fbi->fb.var);

		fbi->palette_cpu[regno] = val;

		ret = 0;
	}
	return ret;
}

static int
s3c2410fb_setcolreg(u_int regno, u_int red, u_int green, u_int blue,
		   u_int trans, struct fb_info *info)
{
	struct s3c2410fb_info *fbi = (struct s3c2410fb_info *)info;
	struct display *disp = get_con_display(info, fbi->currcon);
	u_int val;
	int ret = 1;

	/*
	 * If inverse mode was selected, invert all the colours
	 * rather than the register number.  The register number
	 * is what you poke into the framebuffer to produce the
	 * colour you requested.
	 */
      
	if (disp->inverse) {
		red   = 0xffff - red;
		green = 0xffff - green;
		blue  = 0xffff - blue;
	}

	/*
	 * If greyscale is true, then we convert the RGB value
	 * to greyscale no mater what visual we are using.
	 */
	if (fbi->fb.var.grayscale)
		red = green = blue = (19595 * red + 38470 * green +
					7471 * blue) >> 16;

	switch (fbi->fb.disp->visual) {
	case FB_VISUAL_TRUECOLOR:
		/*
		 * 12 or 16-bit True Colour.  We encode the RGB value
		 * according to the RGB bitfield information.
		 */
		if (regno < 16) {
			u16 *pal = fbi->fb.pseudo_palette;

			val  = chan_to_field(red, &fbi->fb.var.red);
			val |= chan_to_field(green, &fbi->fb.var.green);
			val |= chan_to_field(blue, &fbi->fb.var.blue);

			pal[regno] = val;
			ret = 0;
		}
		break;

	case FB_VISUAL_STATIC_PSEUDOCOLOR:
	case FB_VISUAL_PSEUDOCOLOR:
		ret = s3c2410fb_setpalettereg(regno, red, green, blue, trans, info);
		break;
	}
	return ret;
}

/*
 *  s3c2410fb_decode_var():
 *    Get the video params out of 'var'. If a value doesn't fit, round it up,
 *    if it's too big, return -EINVAL.
 *
 *    Suggestion: Round up in the following order: bits_per_pixel, xres,
 *    yres, xres_virtual, yres_virtual, xoffset, yoffset, grayscale,
 *    bitfields, horizontal timing, vertical timing.
 */
static int
s3c2410fb_validate_var(struct fb_var_screeninfo *var,
		      struct s3c2410fb_info *fbi)
{
	int ret = -EINVAL;

	if (var->xres < MIN_XRES)
		var->xres = MIN_XRES;
	if (var->yres < MIN_YRES)
		var->yres = MIN_YRES;
	if (var->xres > fbi->max_xres)
		var->xres = fbi->max_xres;
	if (var->yres > fbi->max_yres)
		var->yres = fbi->max_yres;
	var->xres_virtual =
	    var->xres_virtual < var->xres ? var->xres : var->xres_virtual;
	var->yres_virtual =
	    var->yres_virtual < var->yres ? var->yres : var->yres_virtual;

	switch (var->bits_per_pixel) {
#ifdef FBCON_HAS_CFB4
	case 4:		ret = 0; break;
#endif
#ifdef FBCON_HAS_CFB8
	case 8:		ret = 0; break;
#endif
#ifdef FBCON_HAS_CFB16
	case 16:	ret = 0; break;
#endif
	default:
		break;
	}

	return ret;
}

static inline void s3c2410fb_set_truecolor(u_int is_true_color)
{
	DPRINTK("true_color = %d\n", is_true_color);
}

static void
s3c2410fb_hw_set_var(struct fb_var_screeninfo *var, struct s3c2410fb_info *fbi)
{
	u_long palette_mem_size;

	fbi->palette_size =  256;
	palette_mem_size = fbi->palette_size * sizeof(u16);
	fbi->palette_cpu = (u16 *)(fbi->map_cpu + PAGE_SIZE - palette_mem_size);
	fbi->palette_dma = fbi->map_dma + PAGE_SIZE - palette_mem_size;
	fb_set_cmap(&fbi->fb.cmap, 1, s3c2410fb_setcolreg, &fbi->fb);

	/* Set board control register to handle new color depth */
	s3c2410fb_set_truecolor(var->bits_per_pixel >= 16);
	s3c2410fb_activate_var(var, fbi);
	fbi->palette_cpu[0] = (fbi->palette_cpu[0] &
					 0xcfff) | palette_pbs(var);
}

/*
 * s3c2410fb_set_var():
 *	Set the user defined part of the display for the specified console
 */
static int
s3c2410fb_set_var(struct fb_var_screeninfo *var, int con, struct fb_info *info)
{
	struct s3c2410fb_info *fbi = (struct s3c2410fb_info *)info;
	struct fb_var_screeninfo *dvar = get_con_var(&fbi->fb, con);
	struct display *display = get_con_display(&fbi->fb, con);
	int err, chgvar = 0, rgbidx;

	/*
	 * Decode var contents into a par structure, adjusting any
	 * out of range values.
	 */
	err = s3c2410fb_validate_var(var, fbi);
	if (err)	return err;
	if (var->activate & FB_ACTIVATE_TEST)
	{	
		return 0;
	}

	if ((var->activate & FB_ACTIVATE_MASK) != FB_ACTIVATE_NOW)
	{
		return -EINVAL;
	}

	if (dvar->xres != var->xres)
		chgvar = 1;
	if (dvar->yres != var->yres)
		chgvar = 1;
	if (dvar->xres_virtual != var->xres_virtual)
		chgvar = 1;
	if (dvar->yres_virtual != var->yres_virtual)
		chgvar = 1;
	if (dvar->bits_per_pixel != var->bits_per_pixel)
		chgvar = 1;
	if (con < 0)
		chgvar = 0;

	switch (var->bits_per_pixel) {
	case 16:
		display->visual		= FB_VISUAL_TRUECOLOR;
		display->line_length	= var->xres * 2;
		display->dispsw		= &fbcon_cfb16;
		display->dispsw_data	= fbi->fb.pseudo_palette;
		rgbidx			= RGB_16;
		break;
	default:
		rgbidx = 0;
		display->dispsw = &fbcon_dummy;
		break;
	}

	display->screen_base	= fbi->screen_cpu;
	display->next_line	= display->line_length;
	display->type		= fbi->fb.fix.type;
	display->type_aux	= fbi->fb.fix.type_aux;
	display->ypanstep	= fbi->fb.fix.ypanstep;
	display->ywrapstep	= fbi->fb.fix.ywrapstep;
	display->can_soft_blank	= 1;
	display->inverse	= fbi->cmap_inverse;

	*dvar			= *var;
	dvar->activate		&= ~FB_ACTIVATE_ALL;

	/*
	 * Copy the RGB parameters for this display
	 * from the machine specific parameters.
	 */
	dvar->red		= fbi->rgb[rgbidx]->red;
	dvar->green		= fbi->rgb[rgbidx]->green;
	dvar->blue		= fbi->rgb[rgbidx]->blue;
	dvar->transp		= fbi->rgb[rgbidx]->transp;

	/*
	 * Update the old var.  The fbcon drivers still use this.
	 * Once they are using fbi->fb.var, this can be dropped.
	 */
	display->var = *dvar;

	/*
	 * If we are setting all the virtual consoles, also set the
	 * defaults used to create new consoles.
	 */
	if (var->activate & FB_ACTIVATE_ALL)
		fbi->fb.disp->var = *dvar;

	/*
	 * If the console has changed and the console has defined
	 * a changevar function, call that function.
	 */
	if (chgvar && info && fbi->fb.changevar)
		fbi->fb.changevar(con);

	/* If the current console is selected, activate the new var. */
	if (con != fbi->currcon)
		return 0;
	s3c2410fb_hw_set_var(dvar, fbi);

	return 0;
}

static int
__do_set_cmap(struct fb_cmap *cmap, int kspc, int con,
	      struct fb_info *info)
{
	struct s3c2410fb_info *fbi = (struct s3c2410fb_info *)info;
	struct fb_cmap *dcmap = get_con_cmap(info, con);
	int err = 0;

	if (con == -1)
		con = fbi->currcon;

	/* no colormap allocated? (we always have "this" colour map allocated) */
	if (con >= 0)
		err = fb_alloc_cmap(&fb_display[con].cmap, fbi->palette_size, 0);

	if (!err && con == fbi->currcon)
		err = fb_set_cmap(cmap, kspc, s3c2410fb_setcolreg, info);

	if (!err)
		fb_copy_cmap(cmap, dcmap, kspc ? 0 : 1);

	return err;
}

static int
s3c2410fb_set_cmap(struct fb_cmap *cmap, int kspc, int con,
		  struct fb_info *info)
{
	struct display *disp = get_con_display(info, con);

	if (disp->visual == FB_VISUAL_TRUECOLOR ||
	    disp->visual == FB_VISUAL_STATIC_PSEUDOCOLOR)
		return -EINVAL;

	return __do_set_cmap(cmap, kspc, con, info);
}

static int
s3c2410fb_get_fix(struct fb_fix_screeninfo *fix, int con, struct fb_info *info)
{
	struct display *display = get_con_display(info, con);

	*fix = info->fix;

	fix->line_length = display->line_length;
	fix->visual	 = display->visual;
	return 0;
}

static int
s3c2410fb_get_var(struct fb_var_screeninfo *var, int con, struct fb_info *info)
{
	*var = *get_con_var(info, con);
	return 0;
}

static int
s3c2410fb_get_cmap(struct fb_cmap *cmap, int kspc, int con, struct fb_info *info)
{
	struct fb_cmap *dcmap = get_con_cmap(info, con);
	fb_copy_cmap(dcmap, cmap, kspc ? 0 : 2);
	return 0;
}

static struct fb_ops s3c2410fb_ops = {
	owner:		THIS_MODULE,
	fb_get_fix:	s3c2410fb_get_fix,
	fb_get_var:	s3c2410fb_get_var,
	fb_set_var:	s3c2410fb_set_var,
	fb_get_cmap:	s3c2410fb_get_cmap,
	fb_set_cmap:	s3c2410fb_set_cmap,
};

/*
 *  s3c2410fb_switch():       
 *	Change to the specified console.  Palette and video mode
 *      are changed to the console's stored parameters.
 *
 *	Uh oh, this can be called from a tasklet (IRQ)
 */
static int s3c2410fb_switch(int con, struct fb_info *info)
{
	struct s3c2410fb_info *fbi = (struct s3c2410fb_info *)info;
	struct display *disp;
	struct fb_cmap *cmap;

	if (con == fbi->currcon)
		return 0;

	if (fbi->currcon >= 0) {
		disp = fb_display + fbi->currcon;

		/*
		 * Save the old colormap and video mode.
		 */
		disp->var = fbi->fb.var;

		if (disp->cmap.len)
			fb_copy_cmap(&fbi->fb.cmap, &disp->cmap, 0);
	}

	fbi->currcon = con;
	disp = fb_display + con;

	/*
	 * Make sure that our colourmap contains 256 entries.
	 */
	fb_alloc_cmap(&fbi->fb.cmap, 256, 0);

	if (disp->cmap.len)
		cmap = &disp->cmap;
	else
		cmap = fb_default_cmap(1 << disp->var.bits_per_pixel);

	fb_copy_cmap(cmap, &fbi->fb.cmap, 0);

	fbi->fb.var = disp->var;
	fbi->fb.var.activate = FB_ACTIVATE_NOW;

	s3c2410fb_set_var(&fbi->fb.var, con, info);
	return 0;
}

/*
 * Formal definition of the VESA spec:
 *  On
 *  	This refers to the state of the display when it is in full operation
 *  Stand-By
 *  	This defines an optional operating state of minimal power reduction with
 *  	the shortest recovery time
 *  Suspend
 *  	This refers to a level of power management in which substantial power
 *  	reduction is achieved by the display.  The display can have a longer 
 *  	recovery time from this state than from the Stand-by state
 *  Off
 *  	This indicates that the display is consuming the lowest level of power
 *  	and is non-operational. Recovery from this state may optionally require
 *  	the user to manually power on the monitor
 *
 *  Now, the fbdev driver adds an additional state, (blank), where they
 *  turn off the video (maybe by colormap tricks), but don't mess with the
 *  video itself: think of it semantically between on and Stand-By.
 *
 *  So here's what we should do in our fbdev blank routine:
 *
 *  	VESA_NO_BLANKING (mode 0)	Video on,  front/back light on
 *  	VESA_VSYNC_SUSPEND (mode 1)  	Video on,  front/back light off
 *  	VESA_HSYNC_SUSPEND (mode 2)  	Video on,  front/back light off
 *  	VESA_POWERDOWN (mode 3)		Video off, front/back light off
 *
 *  This will match the matrox implementation.
 */

/*
 * s3c2410fb_blank():
 *	Blank the display by setting all palette values to zero.  Note, the 
 * 	12 and 16 bpp modes don't really use the palette, so this will not
 *      blank the display in all modes.
 *      blank = 0 unblank ;
 *      blank > 0 , VESA level         
 */
static void s3c2410fb_blank(int blank, struct fb_info *info)
{

#ifdef S3C2410_REAL_BLANK
	struct s3c2410fb_info *fbi = (struct s3c2410fb_info *)info;
	int i,j;
        struct FrameBuffer * pBlankFB;
#endif

#ifdef S3C2410_SUPPORT_PALETTE
	switch (blank) {
	case VESA_POWERDOWN:
	case VESA_VSYNC_SUSPEND:
	case VESA_HSYNC_SUSPEND:
	case VESA_NO_BLANKING:
		if (s3c2410fb_blank_helper)
			s3c2410fb_blank_helper(blank);
		if (fbi->fb.disp->visual == FB_VISUAL_PSEUDOCOLOR ||
		    fbi->fb.disp->visual == FB_VISUAL_STATIC_PSEUDOCOLOR)
			fb_set_cmap(&fbi->fb.cmap, 1, s3c2410fb_setcolreg, info);
		s3c2410fb_schedule_task(fbi, C_ENABLE);
	}
#endif

#ifdef S3C2410_REAL_BLANK
        pBlankFB = (struct FrameBuffer *) (fbi->screen_cpu);

        for(i=0 ;i<FR_HEIGHT;i++)
               for(j=0;j<FR_WIDTH;j++)
                           pBlankFB->pixel[i][j]= 0xffff; 
#endif 
}

static int s3c2410fb_updatevar(int con, struct fb_info *info)
{
	DPRINTK("entered\n");
	return 0;
}

/*
 * s3c2410fb_activate_var():
 *	Configures LCD Controller based on entries in var parameter.  Settings are      
 *	only written to the controller if changes were made.  
 */
static int s3c2410fb_activate_var(struct fb_var_screeninfo *var, struct s3c2410fb_info *fbi)
{
	struct s3c2410fb_lcd_reg new_regs;
	u_long flags;

#if DEBUG_VAR
	if (var->xres < 16        || var->xres > 1024)
		printk(KERN_ERR "%s: invalid xres %d\n",
			fbi->fb.fix.id, var->xres);
	if (var->hsync_len < 1    || var->hsync_len > 64)
		printk(KERN_ERR "%s: invalid hsync_len %d\n",
			fbi->fb.fix.id, var->hsync_len);
	if (var->left_margin < 1  || var->left_margin > 255)
		printk(KERN_ERR "%s: invalid left_margin %d\n",
			fbi->fb.fix.id, var->left_margin);
	if (var->right_margin < 1 || var->right_margin > 255)
		printk(KERN_ERR "%s: invalid right_margin %d\n",
			fbi->fb.fix.id, var->right_margin);
	if (var->yres < 1         || var->yres > 1024)
		printk(KERN_ERR "%s: invalid yres %d\n",
			fbi->fb.fix.id, var->yres);
	if (var->vsync_len < 1    || var->vsync_len > 64)
		printk(KERN_ERR "%s: invalid vsync_len %d\n",
			fbi->fb.fix.id, var->vsync_len);
	if (var->upper_margin < 0 || var->upper_margin > 255)
		printk(KERN_ERR "%s: invalid upper_margin %d\n",
			fbi->fb.fix.id, var->upper_margin);
	if (var->lower_margin < 0 || var->lower_margin > 255)
		printk(KERN_ERR "%s: invalid lower_margin %d\n",
			fbi->fb.fix.id, var->lower_margin);
#endif


#ifdef TFT240_320 
    	new_regs.lcdcon1=(CLKVAL_TFT<<8)|(MVAL_USED<<7)|(3<<5)|(12<<1)|0;	
	                               /*   TFT LCD panel,16bpp TFT,ENVID=off */
	new_regs.lcdcon2=(VBPD<<24)|((var->yres-1)<<14)|(VFPD<<6)|(VSPW);
	new_regs.lcdcon3=(HBPD<<19)|((var->xres-1)<<8)|(HFPD);
	new_regs.lcdcon4=(MVAL<<8)|(HSPW);
	new_regs.lcdcon5=(1<<11)|(1<<9)|(1<<8)|(1<<3)|1;
	//	new_regs.lcdcon5=(1<<11)|(1<<9)|(1<<8)|(1<<3);

	new_regs.lcdsaddr1=
	  (((unsigned long)fbi->screen_dma>>22)<<21)|M5D((unsigned long)fbi->screen_dma>>1);

	new_regs.lcdsaddr2=
	  M5D(((unsigned long)fbi->screen_dma+(var->xres*var->yres*2))>>1); 
	new_regs.lcdsaddr3=(((var->xres - var->xres)/1)<<11)|(var->xres/1); 
	//	new_regs.lcdintmsk = 3;
	//	new_regs.lpcsel  = 0x7; /* Enable PCD3600 */ 
	//	new_regs.tpal = 0x0;

#endif /* ONLY 240 * 320  */


	/* Update shadow copy atomically */
	local_irq_save(flags);
	fbi->reg_lcdcon1 = new_regs.lcdcon1;
	fbi->reg_lcdcon2 = new_regs.lcdcon2;
	fbi->reg_lcdcon3 = new_regs.lcdcon3;
	fbi->reg_lcdcon4 = new_regs.lcdcon4;
	fbi->reg_lcdcon5 = new_regs.lcdcon5;
	fbi->reg_lcdsaddr1 = new_regs.lcdsaddr1;
	fbi->reg_lcdsaddr2 = new_regs.lcdsaddr2;
	fbi->reg_lcdsaddr3 = new_regs.lcdsaddr3;
	fbi->reg_lpcsel = new_regs.lpcsel;
	local_irq_restore(flags);

	/*
	 * Only update the registers if the controller is enabled
	 * and something has changed.
	 */
	if(
            (rLCDCON1   != fbi->reg_lcdcon1)   || (rLCDCON2   != fbi->reg_lcdcon2)  ||
            (rLCDCON3   != fbi->reg_lcdcon3)   || (rLCDCON4   != fbi->reg_lcdcon4)  || 
            (rLCDCON5   != fbi->reg_lcdcon5)   || (rLCDSADDR1 != fbi->reg_lcdsaddr1)|| 
            (rLCDSADDR2 != fbi->reg_lcdsaddr2) || (rLCDSADDR3 != fbi->reg_lcdsaddr3)
          )
	  {
		s3c2410fb_schedule_task(fbi, C_REENABLE);
	  }

	return 0;
}


static void s3c2410fb_enable_controller(struct s3c2410fb_info *fbi)
{
	DPRINTK("Enabling LCD controller\n");

	/*
	 * Make sure the mode bits are present in the first palette entry
	 */
	fbi->palette_cpu[0] &= 0xcfff;
	fbi->palette_cpu[0] |= palette_pbs(&fbi->fb.var);

	rLCDCON1  =fbi->reg_lcdcon1;	
	rLCDCON2  =fbi->reg_lcdcon2;	
	rLCDCON3  =fbi->reg_lcdcon3;	
	rLCDCON4  =fbi->reg_lcdcon4;	
	rLCDCON5  =fbi->reg_lcdcon5;	
	rLCDSADDR1=fbi->reg_lcdsaddr1;
	rLCDSADDR2=fbi->reg_lcdsaddr2;
	rLCDSADDR3=fbi->reg_lcdsaddr3;
	//	rLPCSEL   &= (~(fbi->reg_lpcsel));
	//	rLCDINTMSK |= fbi->reg_lcdintmsk;

	rLCDINTMSK |= 3;
	rTPAL=0;	// temporary palette register, Disabled
	rLPCSEL &= ~(7);
	rLPCSEL |= 7;
	rLCDCON1|=1; 	// ENVID=ON

	FBuf = (struct FrameBuffer *)fbi->screen_cpu;
#ifdef YOU_WANT_TO_DRAW_TETRAGON
	lcd_demo();
	{
	  unsigned long  i ;
	  for (i = 0; i < 0xc000000;i++);
	}
#endif

}

static void s3c2410fb_disable_controller(struct s3c2410fb_info *fbi)
{
	DECLARE_WAITQUEUE(wait, current);

	DPRINTK("Disabling LCD controller\n");


	add_wait_queue(&fbi->ctrlr_wait, &wait);
	set_current_state(TASK_UNINTERRUPTIBLE);

	rLCDCON1=fbi->reg_lcdcon1;	
	rLCDCON2=fbi->reg_lcdcon2;	
	rLCDCON3=fbi->reg_lcdcon3;
	rLCDCON4=fbi->reg_lcdcon4;	
	rLCDCON5=fbi->reg_lcdcon5;	
	rLCDSADDR1=fbi->reg_lcdsaddr1;
	rLCDSADDR2=fbi->reg_lcdsaddr2;
	rLCDSADDR3=fbi->reg_lcdsaddr3;
	rLPCSEL   = fbi->reg_lpcsel;
		
	rLCDCON1 = rLCDCON1&~(1); /* ENVID Clear */

	schedule_timeout(20 * HZ / 1000);
	current->state = TASK_RUNNING;
	remove_wait_queue(&fbi->ctrlr_wait, &wait);
}



/*
 * This function must be called from task context only, since it will
 * sleep when disabling the LCD controller, or if we get two contending
 * processes trying to alter state.
 */
static void set_ctrlr_state(struct s3c2410fb_info *fbi, u_int state)
{
	u_int old_state;

	down(&fbi->ctrlr_sem);

	old_state = fbi->state;

	switch (state) {
	case C_DISABLE_CLKCHANGE:
		/*
		 * Disable controller for clock change.  If the
		 * controller is already disabled, then do nothing.
		 */
		if (old_state != C_DISABLE) {
			fbi->state = state;
			s3c2410fb_disable_controller(fbi);
		}
		break;

	case C_DISABLE:
		/*
		 * Disable controller
		 */
		if (old_state != C_DISABLE) {
			fbi->state = state;

			if (old_state != C_DISABLE_CLKCHANGE)
				s3c2410fb_disable_controller(fbi);
		}
		break;

	case C_ENABLE_CLKCHANGE:
		/*
		 * Enable the controller after clock change.  Only
		 * do this if we were disabled for the clock change.
		 */
		if (old_state == C_DISABLE_CLKCHANGE) {
			fbi->state = C_ENABLE;
			s3c2410fb_enable_controller(fbi);
		}
		break;

	case C_REENABLE:
		/*
		 * Re-enable the controller only if it was already
		 * enabled.  This is so we reprogram the control
		 * registers.
		 */
		if (old_state == C_ENABLE) {
			s3c2410fb_disable_controller(fbi);
			s3c2410fb_enable_controller(fbi);
		}
		break;

	case C_ENABLE:
		/*
		 * Power up the LCD screen, enable controller, and
		 * turn on the backlight.
		 */
		if (old_state != C_ENABLE) {
			fbi->state = C_ENABLE;
			s3c2410fb_enable_controller(fbi);
		}
		break;
	}
	up(&fbi->ctrlr_sem);
}

/*
 * Our LCD controller task (which is called when we blank or unblank)
 * via keventd.
 */
static void s3c2410fb_task(void *dummy)
{
	struct s3c2410fb_info *fbi = dummy;
	u_int state = xchg(&fbi->task_state, -1);
	set_ctrlr_state(fbi, state);
}


/*
 * s3c2410fb_map_video_memory():
 *      Allocates the DRAM memory for the frame buffer.  This buffer is  
 *	remapped into a non-cached, non-buffered, memory region to  
 *      allow palette and pixel writes to occur without flushing the 
 *      cache.  Once this area is remapped, all virtual memory
 *      access to the video memory should occur at the new region.
 */
static int __init s3c2410fb_map_video_memory(struct s3c2410fb_info *fbi)
{
	/*
	 * We reserve one page for the palette, plus the size
	 * of the framebuffer.
	 *
	 * fbi->map_dma is bus address (physical)
         * fbi->screen_dma = fbi->map_dma + PAGE_SIZE
	 * fbi->map_cpu is virtual address
         * fbi->screen_cpu = fbi->map_cpu + PAGE_SIZE
         *
	 * S3C2400 Result 
	 * map_size is    0x00027000
	 * map_cpu  is    0xC2800000
	 * smem_start is  0x0CFC1000
	 * palette_mem_size     0x20
	 */
  
	fbi->map_size = PAGE_ALIGN(fbi->fb.fix.smem_len  + PAGE_SIZE );
	fbi->map_cpu = consistent_alloc(GFP_KERNEL , fbi->map_size,
					&fbi->map_dma);
	if (fbi->map_cpu) {
		fbi->screen_cpu = fbi->map_cpu  + PAGE_SIZE ;
		fbi->screen_dma = fbi->map_dma  + PAGE_SIZE ;
		fbi->fb.fix.smem_start = fbi->screen_dma;
	}

	return fbi->map_cpu ? 0 : -ENOMEM;
}

/* Fake monspecs to fill in fbinfo structure */
static struct fb_monspecs monspecs __initdata = {
	30000, 70000, 50, 65, 0	/* Generic */
};


static struct s3c2410fb_info * __init s3c2410fb_init_fbinfo(void)
{
	struct s3c2410fb_info *fbi;

	fbi = kmalloc(sizeof(struct s3c2410fb_info) + sizeof(struct display) +
		      sizeof(u16) * 16, GFP_KERNEL);
	if (!fbi)
		return NULL;

	memset(fbi, 0, sizeof(struct s3c2410fb_info) + sizeof(struct display));

	fbi->currcon		= -1;

	strcpy(fbi->fb.fix.id, S3C2410_NAME);
	fbi->fb.node		= -1;    /* What is this ? */ 
	fbi->fb.fix.type	= FB_TYPE_PACKED_PIXELS;
	fbi->fb.fix.type_aux	= 0;
	fbi->fb.fix.xpanstep	= 0;
	fbi->fb.fix.ypanstep	= 0;
	fbi->fb.fix.ywrapstep	= 0;
	fbi->fb.fix.accel	= FB_ACCEL_NONE; /* No hardware Accelerator */

	fbi->fb.var.nonstd	= 0;
	fbi->fb.var.activate	= FB_ACTIVATE_NOW;
	fbi->fb.var.height	= -1;
	fbi->fb.var.width	= -1;
	fbi->fb.var.accel_flags	= 0;
	fbi->fb.var.vmode	= FB_VMODE_NONINTERLACED;

	strcpy(fbi->fb.modename, S3C2410_NAME);
	strcpy(fbi->fb.fontname, "Acorn8x8");

	fbi->fb.fbops		= &s3c2410fb_ops;
	fbi->fb.changevar	= NULL;
	fbi->fb.switch_con	= s3c2410fb_switch;
	fbi->fb.updatevar	= s3c2410fb_updatevar;
	fbi->fb.blank		= s3c2410fb_blank;
	fbi->fb.flags		= FBINFO_FLAG_DEFAULT;
	fbi->fb.node		= -1;
	fbi->fb.monspecs	= monspecs;
	fbi->fb.disp		= (struct display *)(fbi + 1); /* golbal display */
	fbi->fb.pseudo_palette	= (void *)(fbi->fb.disp + 1);

	fbi->rgb[RGB_8]		= &rgb_8;
	fbi->rgb[RGB_16]	= &def_rgb_16;

	s3c2410fb_get_machine_info(fbi);

	fbi->state		= C_DISABLE;
	fbi->task_state		= (u_char)-1;
	fbi->fb.fix.smem_len	=  (fbi->max_xres * fbi->max_yres * fbi->max_bpp) / 8;

	init_waitqueue_head(&fbi->ctrlr_wait);
	INIT_TQUEUE(&fbi->task, s3c2410fb_task, fbi);
	init_MUTEX(&fbi->ctrlr_sem);

	return fbi;
}

int __init s3c2410fb_init(void)
{
	struct s3c2410fb_info *fbi;
	int ret;

	s3c2410fb_lcd_port_init();
	fbi = s3c2410fb_init_fbinfo();
	ret = -ENOMEM;
	if (!fbi)
		goto failed;

	/* Initialize video memory */
	ret = s3c2410fb_map_video_memory(fbi);
	if (ret)
		goto failed;

	s3c2410fb_set_var(&fbi->fb.var, -1, &fbi->fb);
	ret = register_framebuffer(&fbi->fb);
	if (ret < 0)
		goto failed;
	/*
	 * Ok, now enable the LCD controller
	 */
	set_ctrlr_state(fbi, C_ENABLE);
	/* This driver cannot be unloaded at the moment */
	MOD_INC_USE_COUNT;
	return 0;

failed:
	if (fbi)    kfree(fbi);
	return ret;
}

int __init s3c2410fb_setup(char *options)
{
	return 0;
}

MODULE_DESCRIPTION("S3C2410/SAMSUNG framebuffer driver");
MODULE_LICENSE("GPL");

