linux s3c2440 LCD 设备驱动

本文档介绍了如何在Linux 2.6.12环境下,针对FL2440开发板上的S3C2440处理器进行LCD设备驱动的开发。内容涵盖主机环境(Redhat 9.0)、开发工具(arm-linux-gcc 3.4.1)以及驱动相关的C语言编程和结构体使用。
摘要由CSDN通过智能技术生成


主机:VM - redhat 9.0

开发板:FL2440,linux-2.6.12

arm-linux-gcc:3.4.1


/*
 *  linux/drivers/video/s3c2410fb.c
 */

#include <linux/config.h>
#include <linux/module.h>
#include <linux/moduleparam.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/init.h>
#include <linux/ioport.h>
#include <linux/cpufreq.h>
#include <linux/device.h>
#include <linux/dma-mapping.h>

#include <asm/hardware.h>
#include <asm/io.h>
#include <asm/irq.h>
#include <asm/mach-types.h>
#include <asm/uaccess.h>
#include <asm/arch/regs-gpio.h>
#include <asm/arch/regs-lcd.h>
#include <asm/arch/regs-irq.h>

#include "s3c2410fb.h"

/*
 * Complain if VAR is out of range.
 */
#define DEBUG_VAR 1

// 通过写入一个数据到此寄存器来清除SRCPND 寄存器的指定位。其只清除那些数据中被设置为1 的相应
// 位置的SRCPND 位。那些数据中被设置为0 的相应位置的位保持不变。
#define	ClearPending(x)	{	\
			  __raw_writel((1 << (x)), S3C2410_SRCPND);	\
			  __raw_writel((1 << (x)), S3C2410_INTPND);	\
			}

struct gzliu_fb_mach_info fs2410_info = {
	.pixclock	= 270000,
	.xres		= 320,
	.yres		= 240,
	.bpp		= 16,
	.hsync_len	= 8,
	.left_margin	= 5,
	.right_margin	= 15,
	.vsync_len	= 15,
	.upper_margin	= 3,
	.lower_margin	= 5,
	.sync		= FB_SYNC_HOR_HIGH_ACT | FB_SYNC_VERT_HIGH_ACT,
	.cmap_greyscale	= 0,
	.cmap_inverse	= 0,
	.cmap_static	= 0,
	.reg		= {
		.lcdcon1 = (6<<8)|(0<<7)|(3<<5)|(12<<1),
		.lcdcon2 = (3<<24) | (239<<14) | (5<<6) | (15),
		.lcdcon3 = (58<<19) | (319<<8) | (15),
		.lcdcon4 = (13<<8) | (8),
		.lcdcon5 = (1<<11) | (0<<10) | (1<<9) | (1<<8) | (0<<7) | (0<<6) | (1<<3)  |(0<<1) | (1),
	}
};

static void (*gzliu_fb_backlight_power)(int);
static void (*gzliu_fb_lcd_power)(int);

static int gzliu_fb_activate_var(struct fb_var_screeninfo *var, struct gzliu_fb_info *);
static void set_ctrlr_state(struct gzliu_fb_info *fbi, u_int state);

static inline void gzliu_fb_schedule_work(struct gzliu_fb_info *fbi, u_int state)
{
printk("@@@@@@@@@@ gzliu_fb_schedule_work() @@@@@@@@@@@@\n");
	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_work(&fbi->task);
	}
	local_irq_restore(flags);
}

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

static int gzliu_fb_setpalettereg(u_int regno, u_int red, u_int green, u_int blue,
		       u_int trans, struct fb_info *info)
{
printk("@@@@@@@@@@ gzliu_fb_setpalettereg() @@@@@@@@@@@@\n");
	struct gzliu_fb_info *fbi = (struct gzliu_fb_info *)info;
	u_int val, ret = 1;

	if (regno < fbi->palette_size) {
		if (fbi->fb.var.grayscale) {
			val = ((blue >> 8) & 0x00ff);
		} else {
			val  = ((red   >>  0) & 0xf800);
			val |= ((green >>  5) & 0x07e0);
			val |= ((blue  >> 11) & 0x001f);
		}

		fbi->palette_cpu[regno] = val;
		ret = 0;
	}
	return ret;
}

static int gzliu_fb_setcolreg(u_int regno, u_int red, u_int green, u_int blue,
		   u_int trans, struct fb_info *info)
{
printk("@@@@@@@@@@ gzliu_fb_setcolreg() @@@@@@@@@@@@\n");
	struct gzliu_fb_info *fbi = (struct gzliu_fb_info *)info;
	unsigned 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 (fbi->cmap_inverse) {
		red   = 0xffff - red;
		green = 0xffff - green;
		blue  = 0xffff - blue;
	}

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

	switch (fbi->fb.fix.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) {
			u32 *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 = gzliu_fb_setpalettereg(regno, red, green, blue, trans, info);
		break;
	}

	return ret;
}

/*
 *  gzliu_fb_check_var():
 *    Get the video params out of 'var'. If a value doesn't fit, round it up,
 *    if it's too big, return -EINVAL.
 *
 *    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 gzliu_fb_check_var(struct fb_var_screeninfo *var, struct fb_info *info)
{
printk("@@@@@@@@@@ gzliu_fb_check_var() @@@@@@@@@@@@\n");
	struct gzliu_fb_info *fbi = (struct gzliu_fb_info *)info;

	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 =
		max(var->xres_virtual, var->xres);
	var->yres_virtual =
		max(var->yres_virtual, var->yres);

        /*
	 * Setup the RGB parameters for this display.
	 *
	 * The pixel packing format is described on page 7-11 of the
	 * PXA2XX Developer's Manual.
         */
	if ( var->bits_per_pixel == 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 = var->transp.length = 0;
	} else {
		var->red.offset = var->green.offset = var->blue.offset = var->transp.offset = 0;
		var->red.length   = 8;
		var->green.length = 8;
		var->blue.length  = 8;
		var->transp.length = 0;
	}

#ifdef CONFIG_CPU_FREQ
	DPRINTK("dma period = %d ps, clock = %d kHz\n",
		gzliu_fb_display_dma_period(var),
		get_clk_frequency_khz(0));
#endif

	return 0;
}

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

/*
 * gzliu_fb_set_par():
 *	Set the user defined part of the display for the specified console
 */
static int gzliu_fb_set_par(struct fb_info *info)
{
printk("@@@@@@@@@@ gzliu_fb_set_par() @@@@@@@@@@@@\n");
	struct gzliu_fb_info *fbi = (struct gzliu_fb_info *)info;
	struct fb_var_screeninfo *var = &info->var;
	unsigned long palette_mem_size;

	DPRINTK("set_par\n");

	if (var->bits_per_pixel == 16)
		fbi->fb.fix.visual = FB_VISUAL_TRUECOLOR;
	else if (!fbi->cmap_static)
		fbi->fb.fix.visual = FB_VISUAL_PSEUDOCOLOR;
	else {
		/*
		 * Some people have weird ideas about wanting static
		 * pseudocolor maps.  I suspect their user space
		 * applications are broken.
		 */
		fbi->fb.fix.visual = FB_VISUAL_STATIC_PSEUDOCOLOR;
	}

	fbi->fb.fix.line_length = var->xres_virtual *
				  var->bits_per_pixel / 8;
	if (var->bits_per_pixel == 16)
		fbi->palette_size = 0;
	else
		fbi->palette_size = var->bits_per_pixel == 1 ? 4 : 1 << var->bits_per_pixel;

	palette_mem_size = fbi->palette_size * sizeof(u16);

printk("@@@@@@@ palette_mem_size = 0x%08lx\n", (u_long) palette_mem_size);

	fbi->palette_cpu = (u16 *)(fbi->map_cpu + PAGE_SIZE - palette_mem_size);
	fbi->palette_dma = fbi->map_dma + PAGE_SIZE - palette_mem_size;

	/*
	 * Set (any) board control register to handle new color depth
	 */
	gzliu_fb_set_truecolor(fbi->fb.fix.visual == FB_VISUAL_TRUECOLOR);
	
	if (fbi->fb.var.bits_per_pixel == 16)
		fb_dealloc_cmap(&fbi->fb.cmap);
	else
		fb_alloc_cmap(&fbi->fb.cmap, 1<<fbi->fb.var.bits_per_pixel, 0);

	gzliu_fb_activate_var(var, fbi);

	return 0;
}

/*
 * gzliu_fb_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.
 */
static int gzliu_fb_blank(int blank, struct fb_info *info)
{
printk("@@@@@@@@@@ gzliu_fb_blank() @@@@@@@@@@@@\n");
	struct gzliu_fb_info *fbi = (struct gzliu_fb_info *)info;
	int i;

	DPRINTK("gzliu_fb_blank: blank=%d\n", blank);

	switch (blank) {
	case VESA_POWERDOWN:
	case VESA_VSYNC_SUSPEND:
	case VESA_HSYNC_SUSPEND:
		if (fbi->fb.fix.visual == FB_VISUAL_PSEUDOCOLOR ||
		    fbi->fb.fix.visual == FB_VISUAL_STATIC_PSEUDOCOLOR)
			for (i = 0; i < fbi->palette_size; i++);

		gzliu_fb_schedule_work(fbi, C_DISABLE);
		break;

	case VESA_NO_BLANKING:
		if (fbi->fb.fix.visual == FB_VISUAL_PSEUDOCOLOR ||
		    fbi->fb.fix.visual == FB_VISUAL_STATIC_PSEUDOCOLOR)
			fb_set_cmap(&fbi->fb.cmap, info);
		gzliu_fb_schedule_work(fbi, C_ENABLE);
	}
	return 0;
}

static int soft_cursor_dummy(struct fb_info *info, struct fb_cursor *cursor)
{
printk("@@@@@@@@@@ soft_cursor_dummy() @@@@@@@@@@@@\n");
	return 0;
}

static struct fb_ops gzliu_fb_ops = {
	.owner		= THIS_MODULE,
	.fb_check_var	= gzliu_fb_check_var,
	.fb_set_par	= gzliu_fb_set_par,
	.fb_setcolreg	= gzliu_fb_setcolreg,
	.fb_blank	= gzliu_fb_blank,
	.fb_cursor	= soft_cursor_dummy,
};

static inline unsigned int get_pcd(unsigned int pixclock)
{
printk("@@@@@@@@@@ get_pcd() @@@@@@@@@@@@\n");
	unsigned long long pcd;

	pcd = s3c2410_hclk/(((__raw_readl(S3C2410_LCDCON1)>>8)&0x3ff)*2+1);
	return (unsigned int)pcd;
}

/*
 * gzliu_fb_activate_var():
 *	Configures LCD Controller based on entries in var parameter.  Settings are
 *	only written to the controller if changes were made.
 */
static int gzliu_fb_activate_var(struct fb_var_screeninfo *var, struct gzliu_fb_info *fbi)
{
printk("@@@@@@@@@@ gzliu_fb_activate_var() @@@@@@@@@@@@\n");
	struct gzliu_fb_lcd_reg new_regs;
	u_long flags;
	u_int half_screen_size, yres;
	unsigned long VideoPhysicalTemp = fbi->screen_dma;

	DPRINTK("Configuring gzliu_ LCD\n");

	DPRINTK("var: xres=%d hslen=%d lm=%d rm=%d\n",
		var->xres, var->hsync_len,
		var->left_margin, var->right_margin);
	DPRINTK("var: yres=%d vslen=%d um=%d bm=%d\n",
		var->yres, var->vsync_len,
		var->upper_margin, var->lower_margin);
	DPRINTK("var: pixclock=%d pcd=%d\n", var->pixclock, pcd);

#if DEBUG_VAR
	if (var->xres < 16 || var->xres > 1024)
		printk(KERN_ERR "%s: invalid xres %d\n",
			fbi->fb.fix.id, var->xres);
	switch(var->bits_per_pixel) {
	case 1:
	case 2:
	case 4:
	case 8:
	case 16:
		break;
	default:
		printk(KERN_ERR "%s: invalid bit depth %d\n",
		       fbi->fb.fix.id, var->bits_per_pixel);
		break;
	}
	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

	/* Update shadow copy atomically */
	local_irq_save(flags);

	new_regs.lcdcon1 = fbi->reg.lcdcon1 & ~S3C2410_LCDCON1_ENVID;

	new_regs.lcdcon2 = (fbi->reg.lcdcon2 & ~LCD2_LINEVAL_MSK) 
						| LCD2_LINEVAL(var->yres - 1);

	/* TFT LCD only ! */
	new_regs.lcdcon3 = (fbi->reg.lcdcon3 & ~LCD3_HOZVAL_MSK)
						| LCD3_HOZVAL(var->xres - 1);

	new_regs.lcdcon4 = fbi->reg.lcdcon4;
	new_regs.lcdcon5 = fbi->reg.lcdcon5;

	new_regs.lcdsaddr1 = 
		LCDADDR_BANK(((unsigned long)VideoPhysicalTemp >> 22))
		| LCDADDR_BASEU(((unsigned long)VideoPhysicalTemp >> 1));

	/* 16bpp */
	new_regs.lcdsaddr2 = LCDADDR_BASEL( 
		((unsigned long)VideoPhysicalTemp + (var->xres * 2 * (var->yres/*-1*/)))
		>> 1);

	new_regs.lcdsaddr3 = LCDADDR_OFFSET(0) | (LCDADDR_PAGE(var->xres) /*>> 1*/);

	yres = var->yres;

	half_screen_size = var->bits_per_pixel;
	half_screen_size = half_screen_size * var->xres * var->yres / 16;

	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;

	__raw_writel(fbi->reg.lcdcon1&~S3C2410_LCDCON1_ENVID, S3C2410_LCDCON1);
	__raw_writel(fbi->reg.lcdcon2, S3C2410_LCDCON2);
	__raw_writel(fbi->reg.lcdcon3, S3C2410_LCDCON3);
	__raw_writel(fbi->reg.lcdcon4, S3C2410_LCDCON4);
	__raw_writel(fbi->reg.lcdcon5, S3C2410_LCDCON5);
	__raw_writel(fbi->reg.lcdsaddr1, S3C2410_LCDSADDR1);
	__raw_writel(fbi->reg.lcdsaddr2, S3C2410_LCDSADDR2);
	__raw_writel(fbi->reg.lcdsaddr3, S3C2410_LCDSADDR3);

	//next code should not be used in TX06D18 LCD
	#if !defined (TX06D18_TFT_LCD )		//change by gongjun
		#if defined(CONFIG_S3C2410_SMDK) && !defined(CONFIG_SMDK_AIJI)
		    LCDLPCSEL = 0x2;
		#elif defined(CONFIG_S3C2410_SMDK) && defined(CONFIG_SMDK_AIJI) 
		    LCDLPCSEL = 0x7;
		#endif
	#endif
	
	__raw_writel(0, S3C2410_TPAL);
	__raw_writel(fbi->reg.lcdcon1|S3C2410_LCDCON1_ENVID, S3C2410_LCDCON1);

#if	1
	{
		printk("LCDCON1 0x%08x\n", __raw_readl(S3C2410_LCDCON1));
		printk("LCDCON2 0x%08x\n", __raw_readl(S3C2410_LCDCON2));
		printk("LCDCON3 0x%08x\n", __raw_readl(S3C2410_LCDCON3));
		printk("LCDCON4 0x%08x\n", __raw_readl(S3C2410_LCDCON4));
		printk("LCDCON5 0x%08x\n", __raw_readl(S3C2410_LCDCON5));
		printk("LCDSADDR1 0x%08x\n", __raw_readl(S3C2410_LCDSADDR1));
		printk("LCDSADDR2 0x%08x\n", __raw_readl(S3C2410_LCDSADDR2));
		printk("LCDSADDR3 0x%08x\n", __raw_readl(S3C2410_LCDSADDR3));
	}
#endif
	local_irq_restore(flags);

	return 0;
}

/*
 * NOTE!  The following functions are purely helpers for set_ctrlr_state.
 * Do not call them directly; set_ctrlr_state does the correct serialisation
 * to ensure that things happen in the right way 100% of time time.
 *	-- rmk
 */
static inline void __gzliu_fb_backlight_power(struct gzliu_fb_info *fbi, int on)
{
printk("@@@@@@@@@@ __gzliu_fb_backlight_power() @@@@@@@@@@@@\n");
printk("backlight o%s\n", on ? "n" : "ff");

 	if (gzliu_fb_backlight_power)
 		gzliu_fb_backlight_power(on);
}

static inline void __gzliu_fb_lcd_power(struct gzliu_fb_info *fbi, int on)
{
printk("@@@@@@@@@@ __gzliu_fb_lcd_power() @@@@@@@@@@@@\n");
printk("LCD power o%s\n", on ? "n" : "ff");

	if (gzliu_fb_lcd_power)
		gzliu_fb_lcd_power(on);
}

static void gzliu_fb_setup_gpio(struct gzliu_fb_info *fbi)
{
printk("@@@@@@@@@@ gzliu_fb_setup_gpio() @@@@@@@@@@@@\n");
	DPRINTK("setup gpio\n");
	
	// 将GPD这组GPIO的16个引脚配置为VD
	__raw_writel(0xaaaaaaaa, S3C2410_GPDCON);
	__raw_writel(7, S3C2410_LCDINTMSK);			// 3 by gjl MASK LCD Sub Interrupt
	__raw_writel(0, S3C2410_TPAL);				// Disable Temp Palette
	__raw_writel(0, S3C2410_LPCSEL);			// 0 by gjl Disable LPC3600
	__raw_writel(0, S3C2410_PRIORITY);			//0x7f add by gjl
}

static void gzliu_fb_enable_controller(struct gzliu_fb_info *fbi)
{
printk("@@@@@@@@@@ gzliu_fb_enable_controller() @@@@@@@@@@@@\n");
	__raw_writel(fbi->reg.lcdcon1&~S3C2410_LCDCON1_ENVID, S3C2410_LCDCON1);
	__raw_writel(fbi->reg.lcdcon2, S3C2410_LCDCON2);
	__raw_writel(fbi->reg.lcdcon3, S3C2410_LCDCON3);
	__raw_writel(fbi->reg.lcdcon4, S3C2410_LCDCON4);
	__raw_writel(fbi->reg.lcdcon5, S3C2410_LCDCON5);
	__raw_writel(fbi->reg.lcdsaddr1, S3C2410_LCDSADDR1);
	__raw_writel(fbi->reg.lcdsaddr2, S3C2410_LCDSADDR2);
	__raw_writel(fbi->reg.lcdsaddr3, S3C2410_LCDSADDR3);
	__raw_writel(fbi->reg.lcdcon1|S3C2410_LCDCON1_ENVID, S3C2410_LCDCON1);	
	
#if 1
	printk("LCDCON1 0x%08x\n", __raw_readl(S3C2410_LCDCON1));
	printk("LCDCON2 0x%08x\n", __raw_readl(S3C2410_LCDCON2));
	printk("LCDCON3 0x%08x\n", __raw_readl(S3C2410_LCDCON3));
	printk("LCDCON4 0x%08x\n", __raw_readl(S3C2410_LCDCON4));
	printk("LCDCON5 0x%08x\n", __raw_readl(S3C2410_LCDCON5));
	printk("LCDSADDR1 0x%08x\n", __raw_readl(S3C2410_LCDSADDR1));
	printk("LCDSADDR2 0x%08x\n", __raw_readl(S3C2410_LCDSADDR2));
	printk("LCDSADDR3 0x%08x\n", __raw_readl(S3C2410_LCDSADDR3));
#endif
}

static void gzliu_fb_disable_controller(struct gzliu_fb_info *fbi)
{
printk("@@@@@@@@@@ gzliu_fb_disable_controller() @@@@@@@@@@@@\n");
	__raw_writel(fbi->reg.lcdcon1&~S3C2410_LCDCON1_ENVID, S3C2410_LCDCON1);
}

/*
 * 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 gzliu_fb_info *fbi, u_int state)
{
printk("@@@@@@@@@@ set_ctrlr_state() @@@@@@@@@@@@\n");
	u_int old_state;

	down(&fbi->ctrlr_sem);

	old_state = fbi->state;

	/*
	 * Hack around fbcon initialisation.
	 */
	if (old_state == C_STARTUP && state == C_REENABLE)
		state = C_ENABLE;

	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 && old_state != C_DISABLE_PM) {
			fbi->state = state;
			gzliu_fb_disable_controller(fbi);
		}
		break;

	case C_DISABLE_PM:
	case C_DISABLE:
		/*
		 * Disable controller
		 */
		if (old_state != C_DISABLE) {
			fbi->state = state;
			__gzliu_fb_backlight_power(fbi, 0);
			__gzliu_fb_lcd_power(fbi, 0);
			if (old_state != C_DISABLE_CLKCHANGE)
				gzliu_fb_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;
			gzliu_fb_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) {
			gzliu_fb_disable_controller(fbi);
			gzliu_fb_setup_gpio(fbi);
			gzliu_fb_enable_controller(fbi);
		}
		break;

	case C_ENABLE_PM:
		/*
		 * Re-enable the controller after PM.  This is not
		 * perfect - think about the case where we were doing
		 * a clock change, and we suspended half-way through.
		 */
		if (old_state != C_DISABLE_PM)
			break;
		/* fall through */

	case C_ENABLE:
		/*
		 * Power up the LCD screen, enable controller, and
		 * turn on the backlight.
		 */
		if (old_state != C_ENABLE) {
			fbi->state = C_ENABLE;
			gzliu_fb_setup_gpio(fbi);
			gzliu_fb_enable_controller(fbi);
			__gzliu_fb_lcd_power(fbi, 1);
			__gzliu_fb_backlight_power(fbi, 1);
		}
		break;
	}
	up(&fbi->ctrlr_sem);
	
}/* static void set_ctrlr_state() */

/*
 * Our LCD controller task (which is called when we blank or unblank)
 * via keventd.
 */
static void gzliu_fb_task(void *dummy)
{
printk("@@@@@@@@@@ gzliu_fb_task() @@@@@@@@@@@@\n");
	struct gzliu_fb_info *fbi = dummy;
	u_int state = xchg(&fbi->task_state, -1);

	set_ctrlr_state(fbi, state);
}

void *consistent_alloc(int gfp, size_t size, dma_addr_t *dma_handle)
{
printk("@@@@@@@@@@ consistent_alloc() @@@@@@@@@@@@\n");
	struct page *page, *end, *free;
	unsigned long order;
	void *ret, *virt;

	if (in_interrupt())
		BUG();

	/*
	* // PAGE_SHIFT determines the page size //
	* 
	* #define PAGE_SHIFT		12
	* #define PAGE_SIZE		(1UL << PAGE_SHIFT)
	* #define PAGE_MASK		(~(PAGE_SIZE-1))
	* 
	* // to align the pointer to the (next) page boundary //
	* 
	* #define PAGE_ALIGN(addr)	(((addr)+PAGE_SIZE-1)&PAGE_MASK)
	*/
	size = PAGE_ALIGN(size);
	
	/*
	* include/asm-arm/page.h
	* 
	* // Pure 2^n version of get_order //
	* static inline int get_order(unsigned long size)
	* {
	* 	int order;
	* 
	* 	size = (size-1) >> (PAGE_SHIFT-1);
	* 	order = -1;
	* 	do {
	* 		size >>= 1;
	* 		order++;
	* 	} while (size);
	* 	return order;
	* }
	*/
	order = get_order(size);

	page = alloc_pages(gfp, order);
	if (!page)
		goto no_page;

	/*
	 * We could do with a page_to_phys and page_to_bus here.
	 */
	virt = page_address(page);
	
	/*
	* #define __virt_to_phys(x)	((x) - PAGE_OFFSET + PHYS_OFFSET)
	* #define __phys_to_virt(x)	((x) - PHYS_OFFSET + PAGE_OFFSET)
	* 
	* dma_handle -- 物理地址
	* ret -- 映射后的虚拟地址
	*/
	
	*dma_handle = virt_to_bus(virt);
	ret = __ioremap(virt_to_phys(virt), size, 0,0);
	if (!ret)
		goto no_remap;

#if 0 /* ioremap_does_flush_cache_all */
	/*
	 * we need to ensure that there are no cachelines in use, or
	 * worse dirty in this area.  Really, we don't need to do
	 * this since __ioremap does a flush_cache_all() anyway. --rmk
	 */
	invalidate_dcache_range(virt, virt + size);
#endif

	/*
	 * free wasted pages.  We skip the first page since we know
	 * that it will have count = 1 and won't require freeing.
	 * We also mark the pages in use as reserved so that
	 * remap_page_range works.
	 */
	page = virt_to_page(virt);
	free = page + (size >> PAGE_SHIFT);
	end  = page + (1 << order);

	for (; page < end; page++) {
		set_page_count(page, 1);
		if (page >= free)
			__free_page(page);
		else
			SetPageReserved(page);
	}
	return ret;

no_remap:
	__free_pages(page, order);
no_page:
	return NULL;
}
/*
 * gzliu_fb_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 gzliu_fb_map_video_memory(struct gzliu_fb_info *fbi)
{
printk("@@@@@@@@@@ gzliu_fb_map_video_memory() @@@@@@@@@@@@\n");
	u_long palette_mem_size;

	/*
	 * We reserve one page for the palette, plus the size
	 * of the framebuffer.
	 */
	fbi->map_size = PAGE_ALIGN(fbi->fb.fix.smem_len + PAGE_SIZE);
	//changed by gjl  dma_alloc_writecombine dma_alloc_coherent
	fbi->map_cpu = consistent_alloc(GFP_KERNEL, fbi->map_size,
	    			    &fbi->map_dma);  //fbi->dev, fbi->map_size,&fbi->map_dma, GFP_KERNEL);

	if (fbi->map_cpu) {
printk("@@@@@@ VA=0x%p, PA=0x%08x, size=0x%08x\n", fbi->map_cpu, fbi->map_dma, fbi->map_size);
		/* prevent initial garbage on screen */
		memset(fbi->map_cpu, 0, fbi->map_size);
		fbi->fb.screen_base = fbi->map_cpu + PAGE_SIZE;
		fbi->screen_dma = fbi->map_dma + PAGE_SIZE;
		/*
		 * FIXME: this is actually the wrong thing to place in
		 * smem_start.  But fbdev suffers from the problem that
		 * it needs an API which doesn't exist (in this case,
		 * dma_writecombine_mmap)
		 */
		fbi->fb.fix.smem_start = fbi->screen_dma;

		fbi->palette_size = fbi->fb.var.bits_per_pixel == 8 ? 256 : 16;

		palette_mem_size = fbi->palette_size * sizeof(u16);
printk("@@@@@@@ palette_mem_size = 0x%08lx\n", (u_long) palette_mem_size);

		fbi->palette_cpu = (u16 *)(fbi->map_cpu + PAGE_SIZE - palette_mem_size);
		fbi->palette_dma = fbi->map_dma + PAGE_SIZE - palette_mem_size;
	}

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

static struct gzliu_fb_info * __init gzliu_fb_init_fbinfo(struct device *dev)
{
printk("@@@@@@@@@@ gzliu_fb_init_fbinfo() @@@@@@@@@@@@\n");
	struct gzliu_fb_info *fbi;
	void *addr;
	struct gzliu_fb_mach_info *inf = &fs2410_info;//dev->platform_data;

	/* Alloc the gzliu_fb_info and pseudo_palette in one step */
	fbi = kmalloc(sizeof(struct gzliu_fb_info) + sizeof(u32) * 16, GFP_KERNEL);
	if (!fbi)
		return NULL;

	memset(fbi, 0, sizeof(struct gzliu_fb_info));
	fbi->dev = dev;

	strcpy(fbi->fb.fix.id, GZLIU_NAME);

	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;	// 没有硬件加速

	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;

	fbi->fb.fbops		= &gzliu_fb_ops;
	fbi->fb.flags		= FBINFO_FLAG_DEFAULT;
	fbi->fb.node		= -1;
	fbi->fb.currcon		= -1;

	addr = fbi;
	addr = addr + sizeof(struct gzliu_fb_info);
	fbi->fb.pseudo_palette	= addr;

	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;
	fbi->reg.lcdcon1		= inf->reg.lcdcon1;
	fbi->reg.lcdcon2		= inf->reg.lcdcon2;
	fbi->reg.lcdcon3		= inf->reg.lcdcon3;
	fbi->reg.lcdcon4		= inf->reg.lcdcon4;
	fbi->reg.lcdcon5		= inf->reg.lcdcon5;
	fbi->state			= C_STARTUP;
	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_WORK(&fbi->task, gzliu_fb_task, fbi);
	init_MUTEX(&fbi->ctrlr_sem);

	return fbi;
}
// add by
static void s3c2410fb_irq_fifo(int irq, void *dev_id, struct pt_regs *regs)
{
printk("@@@@@@@@@@ s3c2410fb_irq_fifo() @@@@@@@@@@@@\n");
	volatile register int a,b;
	local_irq_disable();
	
	__raw_writel(__raw_readl(S3C2410_LCDINTMSK)|=3, S3C2410_LCDINTMSK);
	__raw_writel(1, S3C2410_LCDSRCPND);
	__raw_writel(1, S3C2410_LCDINTPND);
	__raw_writel(__raw_readl(S3C2410_LCDINTMSK)&=(~(1)), S3C2410_LCDINTMSK);
	
	b=0;
	for(a=0;a<2000;a++)b++;
	
printk("@@@@@@@@ irq: %d @@@@@@@@@\n", irq);
	ClearPending(irq);
}

int __init gzliu_fb_probe(struct device *dev)
{
printk("@@@@@@@@@@ gzliu_fb_probe() @@@@@@@@@@@@\n");
	struct gzliu_fb_info *fbi;
	struct gzliu_fb_mach_info *inf;
	unsigned long flags;
	int ret;

  	printk(KERN_ERR "gzliu_fb_probe start!!!!!!!!!!!!!!!!!!!!!!!!!!\n");
	inf = &fs2410_info;//dev->platform_data;
	ret = -ENOMEM;
	fbi = NULL;
	if (!inf)
		goto failed;

	dev_dbg(dev, "got a %dx%dx%d LCD\n",inf->xres, inf->yres, inf->bpp);
	if (inf->xres == 0 || inf->yres == 0 || inf->bpp == 0) {
		dev_err(dev, "Invalid resolution or bit depth\n");
		ret = -EINVAL;
		goto failed;
	}
	gzliu_fb_backlight_power = inf->gzliu_fb_backlight_power;
	gzliu_fb_lcd_power = inf->gzliu_fb_lcd_power;

	fbi = gzliu_fb_init_fbinfo(dev);
	if (!fbi) {
		dev_err(dev, "Failed to initialize framebuffer device\n");
		ret = -ENOMEM;
		goto failed;
	}

	/* Initialize video memory */
	ret = gzliu_fb_map_video_memory(fbi);
	if (ret) {
		dev_err(dev, "Failed to allocate video RAM: %d\n", ret);
		ret = -ENOMEM;
		goto failed;
	}

	/*
	 * This makes sure that our colour bitfield
	 * descriptors are correctly initialised.
	 */
	gzliu_fb_check_var(&fbi->fb.var, &fbi->fb);
	gzliu_fb_set_par(&fbi->fb);

	/*
	* include/linux/device.h
	* 
	* static inline void dev_set_drvdata (struct device *dev, void *data)
	* {
	*	dev->driver_data = data;
	* }
	*/
	dev_set_drvdata(dev, fbi);

	/*
	* drivers/vedio/fbmem.c
	* 
	* int register_framebuffer(struct fb_info *fb_info)
	* {
	* 	int i;
	* 	struct fb_event event;
	* 
	* 	if (num_registered_fb == FB_MAX)
	* 		return -ENXIO;
	* 	num_registered_fb++;
	* 	for (i = 0 ; i < FB_MAX; i++)
	* 		if (!registered_fb[i])
	* 			break;
	* 	fb_info->node = i;
	* 
	* 	fb_info->class_device = class_simple_device_add(fb_class, MKDEV(FB_MAJOR, i),
	* 			    fb_info->device, "fb%d", i);
	* 	if (IS_ERR(fb_info->class_device)) {
	* 		// Not fatal //
	* 		printk(KERN_WARNING "Unable to create class_device for framebuffer %d; errno = %ld\n", i, PTR_ERR(fb_info->class_device));
	* 		fb_info->class_device = NULL;
	* 	} else
	* 		fb_init_class_device(fb_info);
	* 
	* 	if (fb_info->pixmap.addr == NULL) {
	* 		fb_info->pixmap.addr = kmalloc(FBPIXMAPSIZE, GFP_KERNEL);
	* 		if (fb_info->pixmap.addr) {
	* 			fb_info->pixmap.size = FBPIXMAPSIZE;
	* 			fb_info->pixmap.buf_align = 1;
	* 			fb_info->pixmap.scan_align = 1;
	* 			fb_info->pixmap.access_align = 4;
	* 			fb_info->pixmap.flags = FB_PIXMAP_DEFAULT;
	* 		}
	* 	}
	* 	fb_info->pixmap.offset = 0;
	*
	* 	if (!fb_info->modelist.prev ||
	* 	!fb_info->modelist.next ||
	* 	list_empty(&fb_info->modelist)) {
	* 		struct fb_videomode mode;
	* 
	* 		INIT_LIST_HEAD(&fb_info->modelist);
	* 		fb_var_to_videomode(&mode, &fb_info->var);
	* 		fb_add_videomode(&mode, &fb_info->modelist);
	* 	}
	* 
	* 	registered_fb[i] = fb_info;
	* 
	* 	devfs_mk_cdev(MKDEV(FB_MAJOR, i), S_IFCHR | S_IRUGO | S_IWUGO, "fb/%d", i);
	* 	event.info = fb_info;
	* 	notifier_call_chain(&fb_notifier_list, FB_EVENT_FB_REGISTERED, &event);
	* 	return 0;
	* }
	* 
	*/
	ret = register_framebuffer(&fbi->fb);
	if (ret < 0) {
		dev_err(dev, "Failed to register framebuffer device: %d\n", ret);
		goto failed;
	}
        printk("success to register framebuffer device: %d!!!\n", ret);
	// add by      
	disable_irq(IRQ_LCD);
	ret = request_irq(IRQ_LCD, s3c2410fb_irq_fifo, SA_INTERRUPT, "LCD", fbi);
	if (ret) {
		printk(KERN_ERR "s3c2440fb: request_irq failed: %d\n", ret);
		goto failed;
	}
	enable_irq(IRQ_LCD);
        
	/*
	 * Ok, now enable the LCD controller
	 */
	set_ctrlr_state(fbi, C_ENABLE);
	printk("@@@@@@@ done probe @@@@@@@@\n");
	return 0;

failed:
	dev_set_drvdata(dev, NULL);
	if (fbi)
		kfree(fbi);
	return ret;
	
}/* int __init gzliu_fb_probe() */

static struct device_driver gzliu_fb_driver = {
	.name		= "s3c2410-lcd",
	.bus		= &platform_bus_type,
	.probe	= gzliu_fb_probe,
};

int __devinit s3c2410fb_init(void)
{
printk("@@@@@@@@@@ s3c2410fb_init() @@@@@@@@@@@@\n");
	int ret;
 	printk("@@@@@ --- s3c2410fb init --- @@@@@\n");

	ret = driver_register(&gzliu_fb_driver);
	if(ret)
		printk("register device driver failed, return code is %d\n", ret);
		
	__raw_writel(__raw_readl(S3C2410_LCDINTMSK)|=3, S3C2410_LCDINTMSK);
	__raw_writel(1, S3C2410_LCDSRCPND);
	__raw_writel(1, S3C2410_LCDINTPND);
	__raw_writel(__raw_readl(S3C2410_LCDINTMSK)&=(~(1)), S3C2410_LCDINTMSK);
	
	
	return ret;
}


module_init(s3c2410fb_init);

MODULE_AUTHOR("gzliu_hit@qq.com");
MODULE_DESCRIPTION("framebuffer driver for s3c2440");
MODULE_LICENSE("GPL");


/*
 *  linux/drivers/video/s3c2410fb.h
 */

#ifndef __GZLIU_FB_H__
#define __GZLIU_FB_H__

/* Shadows for LCD controller registers */
struct gzliu_fb_lcd_reg {
	unsigned long lcdcon1;
    unsigned long lcdcon2;
    unsigned long lcdcon3;
    unsigned long lcdcon4;
    unsigned long lcdcon5;
    unsigned long lcdsaddr1;
    unsigned long lcdsaddr2;
    unsigned long lcdsaddr3;
};

struct gzliu_fb_info {
	struct fb_info		fb;
	struct device		*dev;

	u_int			max_bpp;
	u_int			max_xres;
	u_int			max_yres;

	/*
	 * These are the addresses we mapped
	 * the framebuffer memory region to.
	 */
	/* raw memory addresses */
	dma_addr_t		map_dma;	/* physical */
	u_char *		map_cpu;	/* virtual */
	u_int			map_size;

	/* addresses of pieces placed in raw buffer */
	u_char *		screen_cpu;	/* virtual address of frame buffer */
	dma_addr_t		screen_dma;	/* physical address of frame buffer */
	u16 *			palette_cpu;	/* virtual address of palette memory */
	dma_addr_t		palette_dma;	/* physical address of palette memory */
	u_int			palette_size;

	/* DMA descriptors */	
	dma_addr_t		dmadesc_fblow_dma;
	dma_addr_t		dmadesc_fbhigh_dma;
	dma_addr_t		dmadesc_palette_dma;
	
	u_int			cmap_inverse:1,
				cmap_static:1,
				unused:30;
	
	volatile u_char		state;
	volatile u_char		task_state;
	struct semaphore	ctrlr_sem;
	wait_queue_head_t	ctrlr_wait;
	struct work_struct	task;
	
	struct gzliu_fb_lcd_reg	reg;

#ifdef CONFIG_CPU_FREQ
	struct notifier_block	freq_transition;
	struct notifier_block	freq_policy;
#endif
};

struct gzliu_fb_mach_info {
	u_long		pixclock;

	u_short		xres;
	u_short		yres;

	u_char		bpp;
	u_char		hsync_len;
	u_char		left_margin;
	u_char		right_margin;

	u_char		vsync_len;
	u_char		upper_margin;
	u_char		lower_margin;
	u_char		sync;

	u_int		cmap_greyscale:1,
			cmap_inverse:1,
			cmap_static:1,
			unused:29;

	struct gzliu_fb_lcd_reg	reg;
	
	void (*gzliu_fb_backlight_power)(int);
	void (*gzliu_fb_lcd_power)(int);

};

/*
 *  Debug macros
 */
//#define	DEBUG	1
#if DEBUG
#  define DPRINTK(fmt, args...)	printk("%s: " fmt, __FUNCTION__ , ## args)
#else
#  define DPRINTK(fmt, args...)
#endif

#define TO_INF(ptr,member) container_of(ptr,struct gzliu_fb_info,member)

/*
 * These are the actions for set_ctrlr_state
 */
#define C_DISABLE		(0)
#define C_ENABLE		(1)
#define C_DISABLE_CLKCHANGE	(2)
#define C_ENABLE_CLKCHANGE	(3)
#define C_REENABLE		(4)
#define C_DISABLE_PM		(5)
#define C_ENABLE_PM		(6)
#define C_STARTUP		(7)

#define GZLIU_NAME	"GZLIU"

/*
 * Minimum X and Y resolutions
 */
#define MIN_XRES	64
#define MIN_YRES	64

#ifndef __ASSEMBLY__
#define UData(Data)	((unsigned long) (Data))
#else
#define UData(Data)	(Data)
#endif

#define Fld(Size, Shft)	(((Size) << 16) + (Shft))
#define FSize(Field)	((Field) >> 16)
#define FShft(Field)	((Field) & 0x0000FFFF)
#define FMsk(Field)	(((UData (1) << FSize (Field)) - 1) << FShft (Field))
#define FInsrt(Value, Field) \
                	(UData (Value) << FShft (Field))

#define fLCD2_LINEVAL	Fld(10,14)	/* TFT/STN: vertical size of LCD */
#define LCD2_LINEVAL(x)	FInsrt((x), fLCD2_LINEVAL)
#define LCD2_LINEVAL_MSK	FMsk(fLCD2_LINEVAL)
#define fLCD3_HOZVAL	Fld(11,8)	/* horizontal size of LCD */
#define LCD3_HOZVAL(x)	FInsrt((x), fLCD3_HOZVAL)
#define LCD3_HOZVAL_MSK	FMsk(fLCD3_HOZVAL)

#define fLCDADDR_BANK	Fld(9,21)	/* bank location for video buffer */
#define LCDADDR_BANK(x)	FInsrt((x), fLCDADDR_BANK)

#define fLCDADDR_BASEU	Fld(21,0)	/* address of upper left corner */
#define LCDADDR_BASEU(x)	FInsrt((x), fLCDADDR_BASEU)

#define fLCDADDR_BASEL	Fld(21,0)	/* address of lower right corner */
#define LCDADDR_BASEL(x)	FInsrt((x), fLCDADDR_BASEL)

#define fLCDADDR_OFFSET	Fld(11,11)	/* Virtual screen offset size
					   (# of half words) */
#define LCDADDR_OFFSET(x)	FInsrt((x), fLCDADDR_OFFSET)

#define fLCDADDR_PAGE	Fld(11,0)	/* Virtual screen page width
					   (# of half words) */
#define LCDADDR_PAGE(x)	FInsrt((x), fLCDADDR_PAGE)

#endif /* __GZLIU_FB_H__ */



  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值