在x210上编写lcd驱动

一、前提准备:

1、将video/samsung/s3cfb.c文件s3cfb_probe函数里的内容注释掉,目的是去掉原来的lcd驱动。

2、将/drivers/gpu/pvr的Makefile里的obj-$(CONFIG_ARCH_S5PV210) += s3c_bc.o 注释掉,在s3c_bc.c文件S3C_BC_Register函数里使用到registered_fb,但我们上一步将原来的lcd驱动去掉,所以不应该链接这个文件,否则会出现段错误。如下图。

3、重新编译内核

二、驱动编写流程

1、首先先把总线平台框架编写好、在probe函数里按下面顺序编写

1)、lcd电源设置.

2)、分配fb_info

3)、设置fb_info

4)、lcd io口映射、设置

5)、lcd 时钟设置

6)、lcd 控制器映射、设置

7)、打开lcd控制器&&背光

8)、注册

9)、测试

2、分析

2.1、lcd电源设置。

要使用lcd控制器,首先要设置lcd的电源,linux内核是通过Regulator管理电源。若不设置的话,无法操作lcd控制器的寄存器。这里涉及到三个函数,regulator_get()、regulator_is_enabled()、regulator_enable()。

struct regulator *regulator_get(struct device *dev, const char *id):获取该驱动对应的regulator

struct device *dev:驱动对应的设备指针

const char *id:电源id

如:regulator_get(&pdev->dev, "pd") 。  &pdev->dev:总线平台的驱动,驱动的init_name,要和power_domain.c的s5pv210_pd_lcd_supply的dev_name设置一样。 "pd":要和power_domain.c的s5pv210_pd_lcd_supply的supply匹配。如下图。

int regulator_is_enabled(struct regulator *regulator):通过此接口判断电源输出是否已被使能。返回值大于0时已使能。

int regulator_enable(struct regulator *regulator):使能电源

代码如下:

	/******************************************1、lcd电源设置********************************************/
	lcd_regulator = regulator_get(&pdev->dev, "pd");//获取该驱动对应的regulator
	printk("lcd_regulator = %p \n",lcd_regulator);
	if (!lcd_regulator) {
		printk("failed to get lcd_regulator\n");
		ret = -EINVAL;
	}
	ret = regulator_is_enabled(lcd_regulator);//通过此接口判断电源输出是否已被使能。返回值大于0时已使能。
	printk("ret = %d \n",ret);
	if(ret == 0)
	{
		ret = regulator_enable(lcd_regulator);//使能lcd电源输出。
		if (ret < 0) {
			printk("failed to enable regulator\n");
			ret = -EINVAL;
		}
	}

2.2、分配fb_info

lcd_info = framebuffer_alloc(0,NULL);

2.3、设置fb_info

	strcpy(lcd_info->fix.id,"s5p-lcd");
//	lcd_info->fix.smem_start =  //显存物理地址
	lcd_info->fix.smem_len = 1024*600*4;//显存大小
	lcd_info->fix.type = FB_TYPE_PACKED_PIXELS;
	lcd_info->fix.visual = FB_VISUAL_TRUECOLOR;//真彩色
	lcd_info->fix.line_length = 1024*4;//每行的字节数
	lcd_info->var.xres = 1024;//lcd宽度
	lcd_info->var.yres = 600;//lcd高度
	lcd_info->var.xres_virtual = 1024;//虚拟屏幕的宽度
	lcd_info->var.yres_virtual = 600;//虚拟屏幕的高度
	lcd_info->var.bits_per_pixel = 32;//bpp, ALPHA0-R-G-B
	lcd_info->var.red.length = 8;//位宽
	lcd_info->var.red.offset = 16;//偏移
	lcd_info->var.green.length = 8;
	lcd_info->var.green.offset = 8;
	lcd_info->var.blue.length = 8;
	lcd_info->var.blue.offset = 0;
	lcd_info->var.transp.offset = 24;
	lcd_info->var.transp.length = 8;
	lcd_info->var.activate = FB_ACTIVATE_NOW;
	
	/*设置操作函数*/
	lcd_info->fbops = &s3cfb_ops;
	lcd_info->pseudo_palette = pseudo_palette;/* Fake palette of 16 colors */ //调试板
	//lcd_info->screen_base = //显存虚拟大小
	lcd_info->screen_size = 1024*600*4;//显存虚拟大小

2.4、lcd io口映射、设置。

背光引脚GPD0_0

设置显示模式RGB=FIMD

代码如下:

	/******************************************4、lcd io口映射、设置******************************************/
	// 配置引脚用于LCD功能GPF0_0-GPF3_5
	// 背光引脚	GPD0_0(PWMTOUT0)
	gpf0_con = ioremap(0xE0200120, 4);//GPF0_CON
	if (!gpf0_con) {
		printk(KERN_ERR "failed to request io GPF0_CON\n");
		ret = -EINVAL;
		return ret;
	}
	gpf1_con = ioremap(0xE0200140 , 4);//GPF1_CON
	if (!gpf1_con) {
		printk(KERN_ERR "failed to request io GPF1_CON\n");
		ret = -EINVAL;
		return ret;
	}
	gpf2_con = ioremap(0xE0200160 , 4);//GPF2_CON
	if (!gpf2_con) {
		printk(KERN_ERR "failed to request io GPF2_CON\n");
		ret = -EINVAL;
		return ret;
	}
	gpf3_con = ioremap(0xE0200180 , 4);//GPF3_CON
	if (!gpf3_con) {
		printk(KERN_ERR "failed to request io GPF3_CON\n");
		ret = -EINVAL;
		return ret;
	}
	gpd0_con = ioremap(0xE02000A0 , 8);//GPD0_CON
	if (!gpd0_con) {
		printk(KERN_ERR "failed to request io GPD0_CON\n");
		ret = -EINVAL;
		return ret;
	}
	gpd0_dat = gpd0_con + 1;//GPD0_DAT

	//配置lcd引脚
	*gpf0_con = 0x22222222;
	*gpf1_con = 0x22222222;
	*gpf2_con = 0x22222222;
	*gpf3_con = 0x22222222;
	
	//配置背光引脚
	*gpd0_con &= ~(0xf<<0);
	*gpd0_con |= (1<<0);	
	*gpd0_dat |= (1<<0);//关闭背光

	display_con = ioremap(0xE0107008, 4);
	if (!display_con) {
		printk(KERN_ERR  "failed to remap display_con\n");
		ret = -EINVAL;
	}
	/*DISPLAY_CONTROL  0xE010_7008
	 * DISPLAY_PATH_SEL [1:0] --> 10: RGB=FIMD I80=FIMD ITU=FIMD //Display path selection
	 */
	*display_con = 2<<0;//显示模式RGB=FIMD

2.5、lcd 时钟设置。

lcd的时钟源可以选择HCLK或者SCLK_FIMD。这里选择SCLK_FIMD。

struct clk *clk_get(struct device *dev, const char *id):获取XXX时钟结构体

unsigned long clk_get_rate(struct clk *clk):获取时钟的频率

int clk_set_parent(struct clk *clk, struct clk *parent):设置XXX时钟的时钟源,也就是从哪里分频。

int clk_set_rate(struct clk *clk, unsigned long rate):设置时钟频率

int clk_enable(struct clk *clk):使能时钟

	/******************************************5、lcd 时钟设置******************************************/	
    lcd_clk = clk_get(NULL, "sclk_fimd"); //获取sclk_fimd域的时钟clk结构体
    if (!lcd_clk || IS_ERR(lcd_clk)) {  
        printk(KERN_INFO "failed to get lcd clock source\n");  
    }  
	new_rate = clk_get_rate(lcd_clk);//获取sclk_fimd域当前的时钟频率
	printk(KERN_INFO "old_rate = %d \n",new_rate);  

	clk_set_parent(lcd_clk, clk_get(NULL, "mout_mpll"));//设置sclk_fimd域的时钟源mout_mpll
	clk_set_rate(lcd_clk, 166750000);//设置sclk_fimd域的时钟频率为166MHZ
    clk_enable(lcd_clk);//使能时钟

2.6、lcd 控制器映射、设置。

s5pv210有0-4屏幕选择,我选择的是屏幕2。

主要设置的寄存器有:VIDCON0、VIDCON1、VIDTCON0、VIDTCON1、VIDTCON2、WINCON2、VIDOSD2A、VIDOSD2B、VIDOSD2D、VIDW02ADD0、VIDW02ADD1、VIDW02ADD2

VIDCON0:设置lcd时钟源为SCLK_FIMD,CLKVAL_F = 1选择对lcd时钟进行分频,CLKVAL = 3,则VCLK = 166M/(1+3) = 41.5MHZ。

VIDCON1:IHSYNC和IVSYNC设为1,即 HSYNC 和 VSYNC极性翻转,看lcd屏幕的手册。

VIDTCON0:帧同步信号。VBPD、VFPD、VSPW值设置

VIDTCON1:行同步信号。HBPD、HFPD、HSPW值设置

VIDTCON2:LINEVAL、HOZVAL设置。具体的值如下面的图。

WINCON2:ALPHA_SEL_F = 1,24位到32位用来存放透明度值;BPPMODE_F =1101,Unpacked 25 bpp ( non-palletized A:1-R:8-G:8-B:8 );BLD_PIX_F = 1,透明度混合。WSWP_F = 1,Swap Enable,显存的低位放低字节。看下图。

VIDOSD2A:显存左上角的XY坐标。

VIDOSD2B:显存右下角的XY坐标。

VIDOSD2D:屏幕大小。

VIDW02ADD0:显存地址,一定是要映射后的,不能直接操作物理地址。用这个函数dma_alloc_writecombine映射。

VIDW02ADD1:显存长度, 字节单位

VIDW02ADD2:PAGEWIDTH_F:屏幕宽度(字节);OFFSIZE_F: 屏幕显示的位置在虚拟显存的位置。

                                                           这里我的lcd屏幕的时序参数表

当WINCON2的WSWP_F置1后,低位放在显存的低字节里。PXXX表示RGB、ALPHA value表示透明度值。

	//lcd控制器
	 res = request_mem_region(S5P_PA_LCD,S5P_SZ_LCD, "s5p-lcd");
	 if (!res) {
	 	printk(KERN_ERR "failed to request io memory region\n");
	 	ret = -EINVAL;
	 }

	 lcd_regs = ioremap(S5P_PA_LCD, S5P_SZ_LCD);
	 if (!lcd_regs) {
	 	printk(KERN_ERR  "failed to remap io region\n");
	 	ret = -EINVAL;
	 }
	 printk(KERN_INFO  "lcd_regs = %p \n",lcd_regs);

	 lcd_debug();//当前寄存器值查看

	/*VIDCON0 
	 * DSI_EN [30] 	   --> 0  = disable MIPI DSI. 
	 * VIDOUT [28:26]  --> 000  = RGB interface 
	 * RGSPSEL [18]    --> 0  = RGB parallel format 
	 * CLKVAL_F [13:6] --> 3  = VCLK = HCLK / (CLKVAL+1),即VCLK = 166M/(1+3) = 41.5MHZ
	 * CLKDIR [4] 	   --> 1 = Divided by CLKVAL_F ,选择对lcd时钟进行分频,若为0则不分频
	 * CLKSEL_F [2]    --> 1 = SCLK_FIMD   Selects the video clock source. 0 = HCLK 
	 * ENVID [1]       --> 1 = Enables the video output and display control signal. //使能lcd控制器
	 * ENVID_F [0]     -->1 = Enables the video output and display control signal. //当前帧结束后使能lcd控制器	 
     *	
     	NOTE: Display On: ENVID and ENVID_F are set to “1”. 
 		Direct Off: ENVID and ENVID_F are set to “0” simultaneously. 
 		Per Frame Off: ENVID_F is set to “0” and ENVID is set to “1”. 
	 */
	cfg = (1<<2) | (1<< 4)| (3<<6);
	writel(cfg, lcd_regs + S3C_VIDCON0);


	/*VIDCON1 
	 *IVCLK  [7]  -->0 = Video data is fetched at VCLK falling edge 
	 *IHSYNC [6]  -->1 = Inverted   Specifies the HSYNC pulse polarity  HSYNC极性翻转,看lcd屏幕的手册
	 *IVSYNC [5]  -->1 = Inverted   Specifies the VSYNC pulse polarity. VSYNC极性翻转,看lcd屏幕的手册
	 *IVDEN  [4]  -->0 = Normal     Specifies the VDEN signal polarity. 
	 */
	cfg = (1<<5) | (1<< 6);
	writel(cfg, lcd_regs + S3C_VIDCON1);
	

	/*VIDCON2 
	 * RGB_ORDER_E [21:19]	--> 000 = RGB Even line
	 * RGB_ORDER_O [18:16]  --> 000 = RGB  Odd Line
	 */
	 

	/*VIDTCON0 看lcd屏幕的手册,关于时序部分帧同步信号
	 * VBPD [23:16] --> 17-1 = 16
	 * VFPD [15:8] --> 12-1=11
	 * VSPW [7:0] --> 3-1=2
	 */
	cfg =  ( (16<<16) | (11<<8) | (2<<0) );
	writel(cfg, lcd_regs + S3C_VIDTCON0);


	/*VIDTCON1 看lcd屏幕的手册,关于时序部分行同步信号
	 * HBPD [23:16] --> 120-1=119
	 * HFPD [15:8] --> 160-1=159
	 * HSPW [7:0] --> 20-1=19
	 */
	cfg = ( (119<<16) | (159<<8) | (19<<0));
	writel(cfg, lcd_regs + S3C_VIDTCON1);


	/*VIDTCON2 
	 * LINEVAL [21:11] --> 600-1=599 
	 * HOZVAL [10:0] --> 1024-1*1023
	 */
	cfg =  ( (599<<11) | (1023<<0) );
	writel(cfg, lcd_regs + S3C_VIDTCON2);

	
	/*WINCON2 
	 * InRGB [13] --> 0 = RGB 						
	 * WSWP_F [15] -->1 = Swap Enable //Specifies the Word swap control bit. 显存的低位放低字节
	 * BLD_PIX_F [6]  --> 1 = Per pixel blending //混合 
	 * BPPMODE_F [5:2] --> *1101 = Unpacked 25 bpp ( non-palletized A:1-R:8-G:8-B:8 )
	 * ALPHA_SEL_F [1]  -->1 = Using DATA[31:24] data in word boundary (only when BPPMODE_F = 4’b1101) 24位到32位用来存放透明度
	 * ENWIN_F [0] -->1 = Enables the video output and video control signal. //Enables/ disables video output and logic immediately
	 */
	cfg = (1<<1) | (13<<2) | (1<<6) | (1<<15);
	writel(cfg, lcd_regs + S3C_WINCON2);
	

	/*WINCHMAP2 
	 * CH0FISEL [18:16] --> 001 = Window 0  //Selects Channel 0’s channel. 
	 * W0FISEL [2:0] --> 001 = Channel 0  //Selects Window 0’s channel. 001 
	 */


	/*VIDOSD2A   显存左上角的XY坐标
	 * OSD_LeftTopX_F [21:11] --> 0
	 * OSD_LeftTopY_F [10:0] --> 0
	 */
	cfg = (0<<11) | (0 << 0);
	writel(cfg, lcd_regs + S3C_VIDOSD2A);


	/*VIDOSD2B  显存右下角的XY坐标
	 * OSD_RightBotX_F [21:11]  --> 1024 - 1 = 1023
	 * OSD_RightBotY_F [10:0] --> 600 - 1 = 599
	 */
	cfg = ( (1023<<11) | (599<<0) );
	writel(cfg, lcd_regs + S3C_VIDOSD2B);


 	/*VIDOSD2D  屏幕大小 
 	 * OSDSIZE [23:0] --> (1024) * (600)  //Specifies the Window Size 
 	 */
	cfg = 1024*600;
	writel(cfg, lcd_regs + S3C_VIDOSD2D);	


	/*分配显存(framebuffer), 并把地址告诉LCD控制器 */
	lcd_info->screen_base = dma_alloc_writecombine(NULL, lcd_info->fix.smem_len,&lcd_info->fix.smem_start, GFP_KERNEL);
	printk(KERN_INFO  "lcd_info->fix.smem_start = %x \n",lcd_info->fix.smem_start);
	printk(KERN_INFO  "lcd_info->screen_base = %p \n",lcd_info->screen_base);

	/*VIDW02ADD0 显存的映射后的地址
	 * VBASEL_F [31:0] -->显存地址 Specifies A [31:0] of the start address for Video frame buffer. 
	 */
	cfg = lcd_info->fix.smem_start;
	writel(cfg, lcd_regs + S3C_VIDW02ADD0);


	/*VIDW02ADD1 显存长度 1024*600*4 字节单位
	 * VBASEU_F [31:0] -->((((HOZVAL + 1)*4 + 0) * (LINEVAL + 1)) & (0xffffff)); //Specifies A [31:0] of the start address for Video frame buffer.
	 */	 
	cfg = lcd_info->fix.smem_len;
	writel(cfg, lcd_regs + S3C_VIDW02ADD1);


	/*VIDW02ADD2,  
	 *OFFSIZE_F [25:13] --> 0  屏幕显示的位置在虚拟显存的位置
	 *PAGEWIDTH_F [12:0] --> 1024 * 4 屏幕宽度(字节)
	 */
	cfg = (1024*4 << 0);
	writel(cfg, lcd_regs + S3C_VIDW02ADD2);

2.7、打开lcd控制器&&背光。

	/******************************************7、打开lcd控制器&&背光***********************************/	
	*gpd0_dat &= ~(1<<0);//打开背光

	/*SHODOWCON 
	 * W0_SHADOW _PROTECT [10] --> 0 = Updates shadow register per frame 
	 * C0_EN_F [2] --> 1 = Enables  //Enables Channel 2. 使能通道2传输
	 */
	cfg |= (1<<2);
	writel(cfg, lcd_regs + S3C_WINSHMAP);

	cfg = readl(lcd_regs + S3C_WINCON2);
	cfg |= (1<<0);
	writel(cfg, lcd_regs + S3C_WINCON2);

	cfg = readl(lcd_regs + S3C_VIDCON0);
	cfg |= ((1<<0) | (1<< 1));
	writel(cfg, lcd_regs + S3C_VIDCON0);

2.8、注册。

	/******************************************7、注册***********************************/	
	ret = register_framebuffer(lcd_info);
	printk(KERN_INFO  "register_framebuffer\n");

2.9、测试。

测试效果为,在屏幕上显示分三块,分别显示红绿蓝。

	/******************************************8、测试***********************************/	
	LCD_Fill(0,0,1024,200,0x00ff0000);
	LCD_Fill(0,200,1024,400,0x0000ff00);
	LCD_Fill(0,400,1024,600,0x000000ff);

三、全部代码

#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/errno.h>
#include <linux/clk.h>
#include <linux/mutex.h>
#include <linux/poll.h>
#include <linux/wait.h>
#include <linux/fs.h>
#include <linux/irq.h>
#include <linux/mm.h>
#include <linux/fb.h>
#include <linux/ctype.h>
#include <linux/dma-mapping.h>
#include <linux/interrupt.h>
#include <linux/platform_device.h>
#include <linux/regulator/consumer.h>
#include <linux/io.h>
#include <linux/delay.h>
#include <linux/memory.h>
#include <linux/cpufreq.h>
#include <plat/clock.h>
#include <plat/cpu-freq.h>
#include <plat/media.h>

#ifdef CONFIG_HAS_WAKELOCK
#include <linux/wakelock.h>
#include <linux/earlysuspend.h>
#include <linux/suspend.h>
#endif
#include <mach/map.h>
#include <plat/regs-fb.h>



struct regulator* lcd_regulator;
struct device	lcd_dev;
struct clk *lcd_clk; 

void __iomem	*lcd_regs;
struct fb_info* lcd_info;
static u32 pseudo_palette[16];

static volatile unsigned int* gpf0_con;
static volatile unsigned int* gpf1_con;
static volatile unsigned int* gpf2_con;
static volatile unsigned int* gpf3_con;
static volatile unsigned int* gpd0_con;
static volatile unsigned int* gpd0_dat;
static volatile unsigned int* display_con;

static int s5pfb_setcolreg(unsigned int regno, unsigned int red,unsigned int green, unsigned int blue,unsigned int transp, struct fb_info *fb);

struct fb_ops s3cfb_ops = {
	.owner = THIS_MODULE,
	.fb_fillrect = cfb_fillrect,
	.fb_copyarea = cfb_copyarea,
	.fb_imageblit = cfb_imageblit,
	.fb_setcolreg = s5pfb_setcolreg,
};


static unsigned int __chan_to_field(unsigned int chan,struct fb_bitfield bf)
{
	chan &= 0xffff;
	chan >>= 16 - bf.length;
	return chan << bf.offset;
}

static int s5pfb_setcolreg(unsigned int regno, unsigned int red,unsigned int green, unsigned int blue,unsigned int transp, struct fb_info *fb)
{
	unsigned int val = 0;
	if (regno < 16) {
		val |= __chan_to_field(red, fb->var.red);
		val |= __chan_to_field(green, fb->var.green);
		val |= __chan_to_field(blue, fb->var.blue);
		val |= __chan_to_field(transp, fb->var.transp);
		pseudo_palette[regno] = val;
	}
	return 0;
}

void LCD_Set_Point(unsigned int X_Pos,unsigned int Y_Pos,unsigned int color)
{
	*((unsigned int*)lcd_info->screen_base + Y_Pos*1024+X_Pos) = color;	
}

//在指定区域内填充单个颜色
//(sx,sy),(ex,ey):填充矩形对角坐标,区域大小为:(ex-sx+1)*(ey-sy+1)   
//color:要填充的颜色
void LCD_Fill(unsigned int sx,unsigned int sy,unsigned int ex,unsigned int ey,unsigned int color)
{          
		unsigned int i,j;
		unsigned int xlen=0;
		xlen=ex-sx;	 
		for(i=sy;i<ey;i++)
		{
			for(j=0;j<xlen;j++)LCD_Set_Point(j,i,color);	//显示颜色 	    
		}
}  

//lcd寄存器调试
void lcd_debug(void)
{
	printk("S3C_VIDCON0 = %x \n ",*((unsigned int*)(lcd_regs + S3C_VIDCON0)));

	printk("S3C_VIDCON1 = %x \n ",*((unsigned int*)(lcd_regs + S3C_VIDCON1)));

	printk("S3C_VIDCON2 = %x \n ",*((unsigned int*)(lcd_regs + S3C_VIDCON2)));

	printk("S3C_PRTCON = %x \n ",*((unsigned int*)(lcd_regs + S3C_PRTCON)));

	printk("S3C_VIDTCON0 = %x \n ",*((unsigned int*)(lcd_regs + S3C_VIDTCON0)));

	printk("S3C_VIDTCON1 = %x \n ",*((unsigned int*)(lcd_regs + S3C_VIDTCON1)));

	printk("S3C_VIDTCON2 = %x \n ",*((unsigned int*)(lcd_regs + S3C_VIDTCON2)));

	printk("S3C_WINCON0 = %x \n ",*((unsigned int*)(lcd_regs + S3C_WINCON0)));

	printk("S3C_WINCON1 = %x \n ",*((unsigned int*)(lcd_regs + S3C_WINCON1)));

	printk("S3C_WINCON2 = %x \n ",*((unsigned int*)(lcd_regs + S3C_WINCON2)));

	printk("S3C_WINCON3 = %x \n ",*((unsigned int*)(lcd_regs + S3C_WINCON3)));

	printk("S3C_WINCON4 = %x \n ",*((unsigned int*)(lcd_regs + S3C_WINCON4)));

	printk("S3C_WINSHMAP = %x \n ",*((unsigned int*)(lcd_regs + S3C_WINSHMAP)));

	printk("S3C_VIDOSD0A = %x \n ",*((unsigned int*)(lcd_regs + S3C_VIDOSD0A)));

	printk("S3C_VIDOSD0B = %x \n ",*((unsigned int*)(lcd_regs + S3C_VIDOSD0B)));

	printk("S3C_VIDOSD0C = %x \n ",*((unsigned int*)(lcd_regs + S3C_VIDOSD0C)));

	printk("S3C_VIDOSD1A = %x \n ",*((unsigned int*)(lcd_regs + S3C_VIDOSD1A)));

	printk("S3C_VIDOSD1B = %x \n ",*((unsigned int*)(lcd_regs + S3C_VIDOSD1B)));

	printk("S3C_VIDOSD1C = %x \n ",*((unsigned int*)(lcd_regs + S3C_VIDOSD1C)));

	printk("S3C_VIDOSD1D = %x \n ",*((unsigned int*)(lcd_regs + S3C_VIDOSD1D)));

	printk("S3C_VIDOSD2A = %x \n ",*((unsigned int*)(lcd_regs + S3C_VIDOSD2A)));

	printk("S3C_VIDOSD2B = %x \n ",*((unsigned int*)(lcd_regs + S3C_VIDOSD2B)));

	printk("S3C_VIDOSD2C = %x \n ",*((unsigned int*)(lcd_regs + S3C_VIDOSD2C)));

	printk("S3C_VIDOSD2D = %x \n ",*((unsigned int*)(lcd_regs + S3C_VIDOSD2D)));

	printk("S3C_VIDOSD3A = %x \n ",*((unsigned int*)(lcd_regs + S3C_VIDOSD3A)));

	printk("S3C_VIDOSD3B = %x \n ",*((unsigned int*)(lcd_regs + S3C_VIDOSD3B)));

	printk("S3C_VIDOSD3C = %x \n ",*((unsigned int*)(lcd_regs + S3C_VIDOSD3C)));

	printk("S3C_VIDOSD4A = %x \n ",*((unsigned int*)(lcd_regs + S3C_VIDOSD4A)));

	printk("S3C_VIDOSD4B = %x \n ",*((unsigned int*)(lcd_regs + S3C_VIDOSD4B)));

	printk("S3C_VIDOSD4C = %x \n ",*((unsigned int*)(lcd_regs + S3C_VIDOSD4C)));

	printk("S3C_VIDW00ADD0B0 = %x \n ",*((unsigned int*)(lcd_regs + S3C_VIDW00ADD0B0)));

	printk("S3C_VIDW00ADD0B1 = %x \n ",*((unsigned int*)(lcd_regs + S3C_VIDW00ADD0B1)));

	printk("S3C_VIDW01ADD0B0 = %x \n ",*((unsigned int*)(lcd_regs + S3C_VIDW01ADD0B0)));

	printk("S3C_VIDW01ADD0B1 = %x \n ",*((unsigned int*)(lcd_regs + S3C_VIDW01ADD0B1)));

	printk("S3C_VIDW02ADD0 = %x \n ",*((unsigned int*)(lcd_regs + S3C_VIDW02ADD0)));


	printk("S3C_VIDW03ADD0 = %x \n ",*((unsigned int*)(lcd_regs + S3C_VIDW03ADD0)));

	printk("S3C_VIDW04ADD0 = %x \n ",*((unsigned int*)(lcd_regs + S3C_VIDW04ADD0)));

	printk("S3C_VIDW00ADD1B0 = %x \n ",*((unsigned int*)(lcd_regs + S3C_VIDW00ADD1B0)));

	printk("S3C_VIDW00ADD1B1 = %x \n ",*((unsigned int*)(lcd_regs + S3C_VIDW00ADD1B1)));

	printk("S3C_VIDW01ADD1B0 = %x \n ",*((unsigned int*)(lcd_regs + S3C_VIDW01ADD1B0)));

	printk("S3C_VIDW01ADD1B1 = %x \n ",*((unsigned int*)(lcd_regs + S3C_VIDW01ADD1B1)));

	printk("S3C_VIDW02ADD1 = %x \n ",*((unsigned int*)(lcd_regs + S3C_VIDW02ADD1)));

	printk("S3C_VIDW03ADD1 = %x \n ",*((unsigned int*)(lcd_regs + S3C_VIDW03ADD1)));

	printk("S3C_VIDW04ADD1 = %x \n ",*((unsigned int*)(lcd_regs + S3C_VIDW04ADD1)));

	printk("S3C_VIDW00ADD2 = %x \n ",*((unsigned int*)(lcd_regs + S3C_VIDW00ADD2)));

	printk("S3C_VIDW01ADD2 = %x \n ",*((unsigned int*)(lcd_regs + S3C_VIDW01ADD2)));

	printk("S3C_VIDW02ADD2 = %x \n ",*((unsigned int*)(lcd_regs + S3C_VIDW02ADD2)));

	printk("S3C_VIDW03ADD2 = %x \n ",*((unsigned int*)(lcd_regs + S3C_VIDW03ADD2)));

	printk("S3C_VIDW04ADD2 = %x \n ",*((unsigned int*)(lcd_regs + S3C_VIDW04ADD2)));

	printk("S3C_VP1TCON0 = %x \n ",*((unsigned int*)(lcd_regs + S3C_VP1TCON0)));

	printk("S3C_VP1TCON1 = %x \n ",*((unsigned int*)(lcd_regs + S3C_VP1TCON1)));

	printk("S3C_VIDINTCON0 = %x \n ",*((unsigned int*)(lcd_regs + S3C_VIDINTCON0)));

	printk("S3C_VIDINTCON1 = %x \n ",*(((unsigned int*)lcd_regs + S3C_VIDINTCON1)));

	printk("S3C_W1KEYCON0 = %x \n ",*((unsigned int*)(lcd_regs + S3C_W1KEYCON0)));

	printk("S3C_W1KEYCON1 = %x \n ",*((unsigned int*)(lcd_regs + S3C_W1KEYCON1)));

	printk("S3C_W2KEYCON0 = %x \n ",*((unsigned int*)(lcd_regs + S3C_W2KEYCON0)));

	printk("S3C_W2KEYCON1 = %x \n ",*((unsigned int*)(lcd_regs + S3C_W2KEYCON1)));

	printk("S3C_W3KEYCON0 = %x \n ",*((unsigned int*)(lcd_regs + S3C_W3KEYCON0)));

	printk("S3C_W3KEYCON1 = %x \n ",*((unsigned int*)(lcd_regs + S3C_W3KEYCON1)));

	printk("S3C_W4KEYCON0 = %x \n ",*((unsigned int*)(lcd_regs + S3C_W4KEYCON0)));

	printk("S3C_W4KEYCON1 = %x \n ",*((unsigned int*)(lcd_regs + S3C_W4KEYCON1)));

	printk("S3C_W1KEYALPHA = %x \n ",*((unsigned int*)(lcd_regs + S3C_W1KEYALPHA)));

	printk("S3C_W2KEYALPHA = %x \n ",*((unsigned int*)(lcd_regs + S3C_W2KEYALPHA)));

	printk("S3C_W3KEYALPHA = %x \n ",*((unsigned int*)(lcd_regs + S3C_W3KEYALPHA)));

	printk("S3C_W4KEYALPHA = %x \n ",*((unsigned int*)(lcd_regs + S3C_W4KEYALPHA)));

	printk("S3C_DITHMODE = %x \n ",*((unsigned int*)(lcd_regs + S3C_DITHMODE)));

	printk("S3C_WIN0MAP = %x \n ",*((unsigned int*)(lcd_regs + S3C_WIN0MAP)));

	printk("S3C_WIN1MAP = %x \n ",*((unsigned int*)(lcd_regs + S3C_WIN1MAP)));

	printk("S3C_WIN2MAP = %x \n ",*((unsigned int*)(lcd_regs + S3C_WIN2MAP)));

	printk("S3C_WIN3MAP = %x \n ",*((unsigned int*)(lcd_regs + S3C_WIN3MAP)));

	printk("S3C_WIN4MAP = %x \n ",*((unsigned int*)(lcd_regs + S3C_WIN4MAP)));

	printk("S3C_WPALCON_H = %x \n ",*((unsigned int*)(lcd_regs + S3C_WPALCON_H)));

	printk("S3C_WPALCON_L = %x \n ",*((unsigned int*)(lcd_regs + S3C_WPALCON_L)));

	printk("S3C_VIDW0ALPHA0 = %x \n ",*((unsigned int*)(lcd_regs + S3C_VIDW0ALPHA0)));

	printk("S3C_VIDW0ALPHA1 = %x \n ",*((unsigned int*)(lcd_regs + S3C_VIDW0ALPHA1)));

	printk("S3C_VIDW1ALPHA0 = %x \n ",*((unsigned int*)(lcd_regs + S3C_VIDW1ALPHA0)));

	printk("S3C_VIDW1ALPHA1 = %x \n ",*((unsigned int*)(lcd_regs + S3C_VIDW1ALPHA1)));

	printk("S3C_VIDW2ALPHA0 = %x \n ",*((unsigned int*)(lcd_regs + S3C_VIDW2ALPHA0)));

	printk("S3C_VIDW2ALPHA1 = %x \n ",*((unsigned int*)(lcd_regs + S3C_VIDW2ALPHA1)));

	printk("S3C_VIDW3ALPHA0 = %x \n ",*((unsigned int*)(lcd_regs + S3C_VIDW3ALPHA0)));

	printk("S3C_VIDW3ALPHA1 = %x \n ",*((unsigned int*)(lcd_regs + S3C_VIDW3ALPHA1)));

	printk("S3C_VIDW4ALPHA0 = %x \n ",*((unsigned int*)(lcd_regs + S3C_VIDW4ALPHA0)));

	printk("S3C_VIDW4ALPHA1 = %x \n ",*((unsigned int*)(lcd_regs + S3C_VIDW4ALPHA1)));

	printk("S3C_BLENDEQ1 = %x \n ",*((unsigned int*)(lcd_regs + S3C_BLENDEQ1)));

	printk("S3C_BLENDEQ2 = %x \n ",*((unsigned int*)(lcd_regs + S3C_BLENDEQ2)));

	printk("S3C_BLENDEQ3 = %x \n ",*((unsigned int*)(lcd_regs + S3C_BLENDEQ3)));

	printk("S3C_BLENDEQ4 = %x \n ",*((unsigned int*)(lcd_regs + S3C_BLENDEQ4)));

	printk("S3C_BLENDCON = %x \n ",*((unsigned int*)(lcd_regs + S3C_BLENDCON)));
}


static int s5pfb_probe(struct platform_device *pdev)
{

	int ret;
	struct resource *res;
	u32 cfg;
	long new_rate = 0;

	/******************************************1、lcd电源设置********************************************/
	lcd_regulator = regulator_get(&pdev->dev, "pd");//获取该驱动对应的regulator
	printk("lcd_regulator = %p \n",lcd_regulator);
	if (!lcd_regulator) {
		printk("failed to get lcd_regulator\n");
		ret = -EINVAL;
	}
	ret = regulator_is_enabled(lcd_regulator);//通过此接口判断电源输出是否已被使能。返回值大于0时已使能。
	printk("ret = %d \n",ret);
	if(ret == 0)
	{
		ret = regulator_enable(lcd_regulator);//使能lcd电源输出。
		if (ret < 0) {
			printk("failed to enable regulator\n");
			ret = -EINVAL;
		}
	}
	/******************************************2、分配fb_info********************************************/
	lcd_info = framebuffer_alloc(0,NULL);
	/******************************************3、设置***************************************************/
	strcpy(lcd_info->fix.id,"s5p-lcd");
//	lcd_info->fix.smem_start =  //显存物理地址
	lcd_info->fix.smem_len = 1024*600*4;//显存大小
	lcd_info->fix.type = FB_TYPE_PACKED_PIXELS;
	lcd_info->fix.visual = FB_VISUAL_TRUECOLOR;//真彩色
	lcd_info->fix.line_length = 1024*4;//每行的字节数
	lcd_info->var.xres = 1024;//lcd宽度
	lcd_info->var.yres = 600;//lcd高度
	lcd_info->var.xres_virtual = 1024;//虚拟屏幕的宽度
	lcd_info->var.yres_virtual = 600;//虚拟屏幕的高度
	lcd_info->var.bits_per_pixel = 32;//bpp, ALPHA0-R-G-B
	lcd_info->var.red.length = 8;//位宽
	lcd_info->var.red.offset = 16;//偏移
	lcd_info->var.green.length = 8;
	lcd_info->var.green.offset = 8;
	lcd_info->var.blue.length = 8;
	lcd_info->var.blue.offset = 0;
	lcd_info->var.transp.offset = 24;
	lcd_info->var.transp.length = 8;
	lcd_info->var.activate = FB_ACTIVATE_NOW;
	
	/*设置操作函数*/
	lcd_info->fbops = &s3cfb_ops;
	lcd_info->pseudo_palette = pseudo_palette;/* Fake palette of 16 colors */ //调试板
	//lcd_info->screen_base = //显存虚拟大小
	lcd_info->screen_size = 1024*600*4;//显存虚拟大小
	/******************************************4、lcd io口映射、设置******************************************/
	// 配置引脚用于LCD功能GPF0_0-GPF3_5
	// 背光引脚	GPD0_0(PWMTOUT0)
	gpf0_con = ioremap(0xE0200120, 4);//GPF0_CON
	if (!gpf0_con) {
		printk(KERN_ERR "failed to request io GPF0_CON\n");
		ret = -EINVAL;
		return ret;
	}
	gpf1_con = ioremap(0xE0200140 , 4);//GPF1_CON
	if (!gpf1_con) {
		printk(KERN_ERR "failed to request io GPF1_CON\n");
		ret = -EINVAL;
		return ret;
	}
	gpf2_con = ioremap(0xE0200160 , 4);//GPF2_CON
	if (!gpf2_con) {
		printk(KERN_ERR "failed to request io GPF2_CON\n");
		ret = -EINVAL;
		return ret;
	}
	gpf3_con = ioremap(0xE0200180 , 4);//GPF3_CON
	if (!gpf3_con) {
		printk(KERN_ERR "failed to request io GPF3_CON\n");
		ret = -EINVAL;
		return ret;
	}
	gpd0_con = ioremap(0xE02000A0 , 8);//GPD0_CON
	if (!gpd0_con) {
		printk(KERN_ERR "failed to request io GPD0_CON\n");
		ret = -EINVAL;
		return ret;
	}
	gpd0_dat = gpd0_con + 1;//GPD0_DAT

	//配置lcd引脚
	*gpf0_con = 0x22222222;
	*gpf1_con = 0x22222222;
	*gpf2_con = 0x22222222;
	*gpf3_con = 0x22222222;
	
	//配置背光引脚
	*gpd0_con &= ~(0xf<<0);
	*gpd0_con |= (1<<0);	
	*gpd0_dat |= (1<<0);//关闭背光

	display_con = ioremap(0xE0107008, 4);
	if (!display_con) {
		printk(KERN_ERR  "failed to remap display_con\n");
		ret = -EINVAL;
	}
	/*DISPLAY_CONTROL  0xE010_7008
	 * DISPLAY_PATH_SEL [1:0] --> 10: RGB=FIMD I80=FIMD ITU=FIMD //Display path selection
	 */
	*display_con = 2<<0;//显示模式RGB=FIMD

	/******************************************5、lcd 时钟设置******************************************/	
    lcd_clk = clk_get(NULL, "sclk_fimd"); //获取sclk_fimd域的时钟clk结构体
    if (!lcd_clk || IS_ERR(lcd_clk)) {  
        printk(KERN_INFO "failed to get lcd clock source\n");  
    }  
	new_rate = clk_get_rate(lcd_clk);//获取sclk_fimd域当前的时钟频率
	printk(KERN_INFO "old_rate = %d \n",new_rate);  

	clk_set_parent(lcd_clk, clk_get(NULL, "mout_mpll"));//设置sclk_fimd域的时钟源mout_mpll
	clk_set_rate(lcd_clk, 166750000);//设置sclk_fimd域的时钟频率为166MHZ
    clk_enable(lcd_clk);//使能时钟

	new_rate = clk_get_rate(lcd_clk);
	printk(KERN_INFO "new_rate = %d \n",new_rate);  

	/******************************************6、lcd 控制器映射、设置***********************************/	
	//lcd控制器
	 res = request_mem_region(S5P_PA_LCD,S5P_SZ_LCD, "s5p-lcd");
	 if (!res) {
	 	printk(KERN_ERR "failed to request io memory region\n");
	 	ret = -EINVAL;
	 }

	 lcd_regs = ioremap(S5P_PA_LCD, S5P_SZ_LCD);
	 if (!lcd_regs) {
	 	printk(KERN_ERR  "failed to remap io region\n");
	 	ret = -EINVAL;
	 }
	 printk(KERN_INFO  "lcd_regs = %p \n",lcd_regs);

	 //lcd_debug();//当前寄存器值查看

	/*VIDCON0 
	 * DSI_EN [30] 	   --> 0  = disable MIPI DSI. 
	 * VIDOUT [28:26]  --> 000  = RGB interface 
	 * RGSPSEL [18]    --> 0  = RGB parallel format 
	 * CLKVAL_F [13:6] --> 3  = VCLK = HCLK / (CLKVAL+1),即VCLK = 166M/(1+3) = 41.5MHZ
	 * CLKDIR [4] 	   --> 1 = Divided by CLKVAL_F ,选择对lcd时钟进行分频,若为0则不分频
	 * CLKSEL_F [2]    --> 1 = SCLK_FIMD   Selects the video clock source. 0 = HCLK 
	 * ENVID [1]       --> 1 = Enables the video output and display control signal. //使能lcd控制器
	 * ENVID_F [0]     --> 1 = Enables the video output and display control signal. //当前帧结束后使能lcd控制器	 
     *	
     	NOTE: Display On: ENVID and ENVID_F are set to “1”. 
 		Direct Off: ENVID and ENVID_F are set to “0” simultaneously. 
 		Per Frame Off: ENVID_F is set to “0” and ENVID is set to “1”. 
	 */
	cfg = (1<<2) | (1<< 4)| (3<<6);
	writel(cfg, lcd_regs + S3C_VIDCON0);


	/*VIDCON1 
	 *IVCLK  [7]  -->0 = Video data is fetched at VCLK falling edge 
	 *IHSYNC [6]  -->1 = Inverted   Specifies the HSYNC pulse polarity  HSYNC极性翻转,看lcd屏幕的手册
	 *IVSYNC [5]  -->1 = Inverted   Specifies the VSYNC pulse polarity. VSYNC极性翻转,看lcd屏幕的手册
	 *IVDEN  [4]  -->0 = Normal     Specifies the VDEN signal polarity. 
	 */
	cfg = (1<<5) | (1<< 6);
	writel(cfg, lcd_regs + S3C_VIDCON1);
	

	/*VIDCON2 
	 * RGB_ORDER_E [21:19]	--> 000 = RGB Even line
	 * RGB_ORDER_O [18:16]  --> 000 = RGB  Odd Line
	 */
	 

	/*VIDTCON0 看lcd屏幕的手册,关于时序部分帧同步信号
	 * VBPD [23:16] --> 17-1 = 16
	 * VFPD [15:8] --> 12-1=11
	 * VSPW [7:0] --> 3-1=2
	 */
	cfg =  ( (16<<16) | (11<<8) | (2<<0) );
	writel(cfg, lcd_regs + S3C_VIDTCON0);


	/*VIDTCON1 看lcd屏幕的手册,关于时序部分行同步信号
	 * HBPD [23:16] --> 120-1=119
	 * HFPD [15:8] --> 160-1=159
	 * HSPW [7:0] --> 20-1=19
	 */
	cfg = ( (119<<16) | (159<<8) | (19<<0));
	writel(cfg, lcd_regs + S3C_VIDTCON1);


	/*VIDTCON2 
	 * LINEVAL [21:11] --> 600-1=599 
	 * HOZVAL [10:0] --> 1024-1*1023
	 */
	cfg =  ( (599<<11) | (1023<<0) );
	writel(cfg, lcd_regs + S3C_VIDTCON2);

	
	/*WINCON2 
	 * InRGB [13] --> 0 = RGB 						
	 * WSWP_F [15] -->1 = Swap Enable //Specifies the Word swap control bit. 显存的低位放低字节
	 * BLD_PIX_F [6]  --> 1 = Per pixel blending //混合 
	 * BPPMODE_F [5:2] --> *1101 = Unpacked 25 bpp ( non-palletized A:1-R:8-G:8-B:8 )
	 * ALPHA_SEL_F [1]  -->1 = Using DATA[31:24] data in word boundary (only when BPPMODE_F = 4’b1101) 24位到32位用来存放透明度
	 * ENWIN_F [0] -->1 = Enables the video output and video control signal. //Enables/ disables video output and logic immediately
	 */
	cfg = (1<<1) | (13<<2) | (1<<6) | (1<<15);
	writel(cfg, lcd_regs + S3C_WINCON2);
	

	/*WINCHMAP2 
	 * CH0FISEL [18:16] --> 001 = Window 0  //Selects Channel 0’s channel. 
	 * W0FISEL [2:0] --> 001 = Channel 0  //Selects Window 0’s channel. 001 
	 */


	/*VIDOSD2A   显存左上角的XY坐标
	 * OSD_LeftTopX_F [21:11] --> 0
	 * OSD_LeftTopY_F [10:0] --> 0
	 */
	cfg = (0<<11) | (0 << 0);
	writel(cfg, lcd_regs + S3C_VIDOSD2A);


	/*VIDOSD2B  显存右下角的XY坐标
	 * OSD_RightBotX_F [21:11]  --> 1024 - 1 = 1023
	 * OSD_RightBotY_F [10:0] --> 600 - 1 = 599
	 */
	cfg = ( (1023<<11) | (599<<0) );
	writel(cfg, lcd_regs + S3C_VIDOSD2B);


 	/*VIDOSD2D  屏幕大小 
 	 * OSDSIZE [23:0] --> (1024) * (600)  //Specifies the Window Size 
 	 */
	cfg = 1024*600;
	writel(cfg, lcd_regs + S3C_VIDOSD2D);	


	/*分配显存(framebuffer), 并把地址告诉LCD控制器 */
	lcd_info->screen_base = dma_alloc_writecombine(NULL, lcd_info->fix.smem_len,&lcd_info->fix.smem_start, GFP_KERNEL);
	printk(KERN_INFO  "lcd_info->fix.smem_start = %x \n",lcd_info->fix.smem_start);
	printk(KERN_INFO  "lcd_info->screen_base = %p \n",lcd_info->screen_base);

	/*VIDW02ADD0 显存的映射后的地址
	 * VBASEL_F [31:0] -->显存地址 Specifies A [31:0] of the start address for Video frame buffer. 
	 */
	cfg = lcd_info->fix.smem_start;
	writel(cfg, lcd_regs + S3C_VIDW02ADD0);


	/*VIDW02ADD1 显存长度 1024*600*4 字节单位
	 * VBASEU_F [31:0] -->((((HOZVAL + 1)*4 + 0) * (LINEVAL + 1)) & (0xffffff)); //Specifies A [31:0] of the start address for Video frame buffer.
	 */	 
	cfg = lcd_info->fix.smem_len;
	writel(cfg, lcd_regs + S3C_VIDW02ADD1);


	/*VIDW02ADD2,  
	 *OFFSIZE_F [25:13] --> 0  屏幕显示的位置在虚拟显存的位置
	 *PAGEWIDTH_F [12:0] --> 1024 * 4 屏幕宽度(字节)
	 */
	cfg = (1024*4 << 0);
	writel(cfg, lcd_regs + S3C_VIDW02ADD2);


	/******************************************7、打开lcd控制器&&背光***********************************/	
	*gpd0_dat &= ~(1<<0);//打开背光

	/*SHODOWCON 
	 * W0_SHADOW _PROTECT [10] --> 0 = Updates shadow register per frame 
	 * C0_EN_F [2] --> 1 = Enables  //Enables Channel 2. 使能通道2传输
	 */
	cfg = (1<<2);
	writel(cfg, lcd_regs + S3C_WINSHMAP);

	cfg = readl(lcd_regs + S3C_WINCON2);
	cfg |= (1<<0);
	writel(cfg, lcd_regs + S3C_WINCON2);

	cfg = readl(lcd_regs + S3C_VIDCON0);
	cfg |= ((1<<0) | (1<< 1));
	writel(cfg, lcd_regs + S3C_VIDCON0);

	/******************************************7、注册***********************************/	
	ret = register_framebuffer(lcd_info);
	printk(KERN_INFO  "register_framebuffer\n");

	/******************************************8、测试***********************************/	
	LCD_Fill(0,0,1024,200,0x00ff0000);
	LCD_Fill(0,200,1024,400,0x0000ff00);
	LCD_Fill(0,400,1024,600,0x000000ff);
	//lcd_debug();

	return 0;
}

static int  s5pfb_remove(struct platform_device *pdev)
{
	unregister_framebuffer(lcd_info);
	dma_free_writecombine(NULL,lcd_info->fix.smem_len, lcd_info->screen_base, lcd_info->fix.smem_start);
	iounmap(display_con);
	iounmap(lcd_regs);
	release_mem_region(S5P_PA_LCD,S5P_SZ_LCD);		
	iounmap(gpf0_con);//GPF0_CON
	iounmap(gpf1_con);//GPF1_CON
	iounmap(gpf2_con);//GPF2_CON
	iounmap(gpf3_con);//GPF3_CON
	iounmap(gpd0_con);//GPD0_CON
	framebuffer_release(lcd_info);
	regulator_disable(lcd_regulator);
	regulator_put(lcd_regulator);
}

static void s5p_lcd_release(struct device * dev)
{
}

struct platform_device s5p_device_fb = {
	.name		= "s5p-lcd",//总线平台名称
	.id		  = -1,
	.dev		  = {
		.init_name		= "s5p-lcd",//设备init_name,用来获取电源管理pd的regulator。与power-domain.c文件的s5pv210_pd_lcd_supply里dev_name对应
		.release = s5p_lcd_release,
	}
};

static struct platform_driver s5pfb_driver = {
	.probe =  s5pfb_probe,
	.remove = s5pfb_remove,
	.driver = {
		   .name = "s5p-lcd", //总线平台名称
		   .owner = THIS_MODULE,
	},
};

static int __init s5pfb_register(void)
{
	platform_driver_register(&s5pfb_driver);
	platform_device_register(&s5p_device_fb);
	return 0;
}
static void __exit s5pfb_unregister(void)
{
	platform_driver_unregister(&s5pfb_driver);	
	platform_device_unregister(&s5p_device_fb);
}

module_init(s5pfb_register);
module_exit(s5pfb_unregister);
MODULE_LICENSE("GPL");

























 

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值