Linux-2.6.38的LCD驱动分析(三)

三、解剖s3cfb_driver变量
    s3cfb_driver变量有什么作用呢?在前面的2.2节提到了它的定义,从它的原型可以看出s3cfb_driver是个platform_driver类型的变量,前面的几个小节提到了从platform_driver的名字可以看出它应该是platform_device的驱动类型。为了方便阅读,这里再贴一次s3cfb_driver的定义:

static struct platform_driver s3cfb_driver = {

      .probe            = s3cfb_probe,

       .remove          = s3cfb_remove,

       .suspend  = s3cfb_suspend,

       .resume          = s3cfb_resume,

       .driver            = {

              .name      = "s3c-fb",
              .owner    = THIS_MODULE,
       },
};
从定义可以看出,该platform_device的驱动函数有s3cfb_probe,s3cfb_remove,s3cfb_suspend和s3cfb_suspend。.resource成员前面的章节有说明,.driver成员的值相信不用再说明了吧,再明白不过了。前面的章节,s3cfb_probe被比较详细的介绍,这节中的主要任务就是解释其他的几个函数。在解释他们之前,s3cfb_probe里面在该函数结尾的时候调用了几个函数没有说到,所以在这里补上。
 
3.1 s3cfb_probe余党
       在s3cfb_probe中最好调用了s3cfb_init_registers和s3cfb_check_var函数,这里应该将他们交代清楚。很显然,s3cfb_init_registers是初始化相关寄存器。那么后者呢?这里先把s3cfb_init_registers搞定再说。在文件drivers\video\samsung\s3cfb_fimd4x.c中, s3cfb_init_registers的定义与实现如下,先根据它的指向流程,一步一步解释:
int s3cfb_init_registers(s3cfb_info_t *fbi)
{
struct clk *lcd_clock;
struct fb_var_screeninfo *var = &fbi->fb.var;
unsigned long flags = 0, page_width = 0, offset = 0;
unsigned long video_phy_temp_f1 = fbi->screen_dma_f1;
unsigned long video_phy_temp_f2 = fbi->screen_dma_f2;
int win_num =  fbi->win_id;


/* Initialise LCD with values from hare */
local_irq_save(flags);
 /* 关闭中断,在关闭中断前,中断的当前状态被保存在flags中,对于关闭中断的函数,linux内核有很多种,可以查阅相关的资料。*/
page_width = var->xres * s3cfb_fimd.bytes_per_pixel;
offset = (var->xres_virtual - var->xres) * s3cfb_fimd.bytes_per_pixel;

if (win_num == 0) {
s3cfb_fimd.vidcon0 = s3cfb_fimd.vidcon0 & ~(S3C_VIDCON0_ENVID_ENABLE | S3C_VIDCON0_ENVID_F_ENABLE);
writel(s3cfb_fimd.vidcon0, S3C_VIDCON0);

lcd_clock = clk_get(NULL, "lcd");
s3cfb_fimd.vidcon0 |= S3C_VIDCON0_CLKVAL_F((int) (s3cfb_fimd.pixclock));

#if defined(CONFIG_FB_S3C_EXT_VIRTUAL_SCREEN)
offset = 0;
s3cfb_fimd.vidw00add0b0 = video_phy_temp_f1;
s3cfb_fimd.vidw00add0b1 = video_phy_temp_f2;
s3cfb_fimd.vidw00add1b0 = S3C_VIDWxxADD1_VBASEL_F((unsigned long) video_phy_temp_f1 + (page_width + offset) * (var->yres));
s3cfb_fimd.vidw00add1b1 = S3C_VIDWxxADD1_VBASEL_F((unsigned long) video_phy_temp_f2 + (page_width + offset) * (var->yres));
#endif
  }

writel(video_phy_temp_f1, S3C_VIDW00ADD0B0 + (0x08 * win_num));
writel(S3C_VIDWxxADD1_VBASEL_F((unsigned long) video_phy_temp_f1 + (page_width + offset) * (var->yres)), S3C_VIDW00ADD1B0 + (0x08 * win_num));
writel(S3C_VIDWxxADD2_OFFSIZE_F(offset) | (S3C_VIDWxxADD2_PAGEWIDTH_F(page_width)), S3C_VIDW00ADD2 + (0x04 * win_num));

if (win_num < 2) {
writel(video_phy_temp_f2, S3C_VIDW00ADD0B1 + (0x08 * win_num));
writel(S3C_VIDWxxADD1_VBASEL_F((unsigned long) video_phy_temp_f2 + (page_width + offset) * (var->yres)), S3C_VIDW00ADD1B1 + (0x08 * win_num));
}
下面的几个writel函数开始初始化LCD控制寄存器
switch (win_num) {
case 0://window0
writel(s3cfb_fimd.wincon0, S3C_WINCON0);
writel(s3cfb_fimd.vidcon0, S3C_VIDCON0);
writel(s3cfb_fimd.vidcon1, S3C_VIDCON1);
writel(s3cfb_fimd.vidtcon0, S3C_VIDTCON0);
writel(s3cfb_fimd.vidtcon1, S3C_VIDTCON1);
writel(s3cfb_fimd.vidtcon2, S3C_VIDTCON2);
writel(s3cfb_fimd.dithmode, S3C_DITHMODE);
writel(s3cfb_fimd.vidintcon0, S3C_VIDINTCON0);
writel(s3cfb_fimd.vidintcon1, S3C_VIDINTCON1);
writel(s3cfb_fimd.vidosd0a, S3C_VIDOSD0A);
writel(s3cfb_fimd.vidosd0b, S3C_VIDOSD0B);
writel(s3cfb_fimd.vidosd0c, S3C_VIDOSD0C);
writel(s3cfb_fimd.wpalcon, S3C_WPALCON);
s3cfb_onoff_win(fbi, ON);
break;

case 1://window1
writel(s3cfb_fimd.wincon1, S3C_WINCON1);
writel(s3cfb_fimd.vidosd1a, S3C_VIDOSD1A);
writel(s3cfb_fimd.vidosd1b, S3C_VIDOSD1B);
writel(s3cfb_fimd.vidosd1c, S3C_VIDOSD1C);
writel(s3cfb_fimd.vidosd1d, S3C_VIDOSD1D);
writel(s3cfb_fimd.wpalcon, S3C_WPALCON);
s3cfb_onoff_win(fbi, OFF);
break;

case 2://window2
writel(s3cfb_fimd.wincon2, S3C_WINCON2);
writel(s3cfb_fimd.vidosd2a, S3C_VIDOSD2A);
writel(s3cfb_fimd.vidosd2b, S3C_VIDOSD2B);
writel(s3cfb_fimd.vidosd2c, S3C_VIDOSD2C);
writel(s3cfb_fimd.vidosd2d, S3C_VIDOSD2D);
writel(s3cfb_fimd.wpalcon, S3C_WPALCON);
s3cfb_onoff_win(fbi, OFF);
break;

case 3://window3
writel(s3cfb_fimd.wincon3, S3C_WINCON3);
writel(s3cfb_fimd.vidosd3a, S3C_VIDOSD3A);
writel(s3cfb_fimd.vidosd3b, S3C_VIDOSD3B);
writel(s3cfb_fimd.vidosd3c, S3C_VIDOSD3C);
writel(s3cfb_fimd.wpalcon, S3C_WPALCON);
s3cfb_onoff_win(fbi, OFF);
break;

case 4://window4
writel(s3cfb_fimd.wincon4, S3C_WINCON4);
writel(s3cfb_fimd.vidosd4a, S3C_VIDOSD4A);
writel(s3cfb_fimd.vidosd4b, S3C_VIDOSD4B);
writel(s3cfb_fimd.vidosd4c, S3C_VIDOSD4C);
writel(s3cfb_fimd.wpalcon, S3C_WPALCON);
s3cfb_onoff_win(fbi, OFF);
break;
}

local_irq_restore(flags);
//使能中断,并恢复以前的状态
return 0;
 }
 
OK,s3cfb_init_registers就简单介绍到这里。下面看看s3cfb_check_var函数要干些什么事,要说到这个函数,还得提到fb_var_screeninfo结构类型,与它对应的是fb_fix_screeninfo结构类型。这两个类型分别代表了显示屏的属性信息,这些信息可以分为可变属性信息(如:颜色深度,分辨率等)和不可变的信息(如帧缓冲的其实地址)。既然fb_var_screeninfo表示了可变的属下信息,那么这些可变信息就应该有一定范围,否则显示就会出问题,所以s3cfb_check_var函数的功能就是要在LCD的帧缓冲驱动开始运行之前将这些值初始到合法的范围内。知道了s3cfb_check_var要做什么,再去阅读s3cfb_check_var函数的代码就没什么问题了。
 
3.2 s3cfb_remove
    从这里开始将解释s3cfb_driver中的其他几个函数。那么就从s3cfb_remove开刀吧!顾名思义该函数就该知道,它要将这个platform设备从系统中移除,可以推测它应该释放掉所有的资源,包括内存空间,中断线等等。还是按照惯例,在它的实现代码中一步步的解释。\
static int s3cfb_remove(struct platform_device *pdev)
{
struct fb_info *fbinfo = platform_get_drvdata(pdev);该函数从platform_device中,获取fb_info信息
s3cfb_info_t *info = fbinfo->par;得到私有数据
int index = 0, irq;

s3cfb_stop_lcd();停止LCD控制器
msleep(1);休息以下,等待LCD停止

停止时钟

if (info->clk) {
clk_disable(info->clk);
clk_put(info->clk);
info->clk = NULL;
}

irq = platform_get_irq(pdev, 0); 得到中断线,以便释放
release_resource(info->mem);释放内存空间

for (index = 0; index < S3CFB_NUM; index++) {
s3cfb_unmap_video_memory((s3cfb_info_t *) &s3cfb_info[index]);释放缓冲区
free_irq(irq, &s3cfb_info[index]);释放该中断
unregister_framebuffer(&info[index].fb);向内核注销该帧缓冲
}

return 0;
}

3.3 s3cfb_suspend与s3cfb_resume
    在实际的设备,常常可以看到LCD在不需要的时候进入休眠状态,当需要使用的时候又开始工作,比如手机,在不需要的时候LCD就熄灭,当需要使用的时候LCD又被点亮。从实际中可以看出这对函数非常重要。虽然他们很重要,但不一定很复杂,下面看看它们是怎么样实现的。
 

int s3cfb_suspend(struct platform_device *dev, pm_message_t state)
{
struct fb_info *fbinfo = platform_get_drvdata(dev);//获取帧缓冲设备数据结构
s3cfb_info_t *info = fbinfo->par;

s3cfb_stop_lcd();停止LCD
s3c6410_pm_do_save(s3c_lcd_save, ARRAY_SIZE(s3c_lcd_save));保存当前LCD寄存器等的信息

/* sleep before disabling the clock, we need to ensure
* the LCD DMA engine is not going to get back on the bus
* before the clock goes off again (bjd) */

msleep(1);//等待一下,因为LCD停止需要一点时间
clk_disable(info->clk);//关闭LCD的时钟
return 0;
}

下面来看看s3cfb_resume
int s3cfb_resume(struct platform_device *dev)
{
struct fb_info *fbinfo = platform_get_drvdata(dev);
s3cfb_info_t *info = fbinfo->par;

clk_enable(info->clk);
s3c6410_pm_do_restore(s3c_lcd_save, ARRAY_SIZE(s3c_lcd_save));恢复LCD寄存器值

s3cfb_set_gpio();从新设置LCD GPIO
s3cfb_start_lcd();使能LCD

return 0;
}
 
OK,到现在为止,对于platform device的相关驱动就over了。不过精彩的还在后头哦!

你可能感兴趣的:(Linux-2.6.38的LCD驱动分析(三))