展讯8810中LCD 在uboot和Kernel中的基本流程

LCD显示的基本原理

通过framebuffer,应用程序用mmap把显存映射到应用程序虚拟地址空间,将要显示的数据写入这个内存空间就可以在屏幕上显示出来。
驱动程序分配系统内存作为显存;实现file_operations结构中的接口,为应用程序服务;实现fb_ops结构中的接口,控制和操作LCD控制器;
驱动程序将显存的起始地址和长度传给LCD控制器的寄存器,LCD控制器会自动的将显存中的数据显示在LCD屏上。

LCD的接口类型大致有:SPI/I2C/MCU/CPU/RGB/MDDI/MIPI,SPI/I2C用于低速黑白屏,MCU/CPU/RGB为并口,在智能机之前的功能机上用的多,手机进入到大屏时代后,并口的传输速度跟不上,特别是面临高清播放的应用,能力不足,所以出现了MDDI和MIPI,MDDI为高通推出,MIPI为多家重量级厂商联合成立的组织,其推出了一系列移动标准,其中就包括MIPIDSI。

Mipi推出的时间不长,但推广速度很快,包括iphone4/MeizuM9都采用mipi接口(可能现在除了高通的手机,大部分都是)
Mipi接口有物理规范,因此我们看到的支持DSI的开发板和LCD都是mipi规范的排线。但是目前mipi接口的LCD基本上买不到,网上有一些自己做开发的,也是用的iphone的屏

开发板部分,PandaBoard在CPU这块就没有将mipi的信号引出来,没法用,samsungs5pc100开发板没有引出接口,samsungs5pv210开发板一般有mipi接口驱动部分,目前只能有Omap的代码可以参考

Mipi接口支持2中mode:videomode和commandmode
Video mode和rgb接口是类似的,framebufferdriver都用systemmemory,pixel数据存放在buffer中,mipihost按照指定的时序将数据通过dbi总线发送给lcd。因为mipi的信号线和数据线是复用的,时序的同步实际上也有datapackage完成,而不像并口那样有信号线上的电平完成。

在Video mode下,整个架构和rgb等并行接口并无太大的差别。而command mode需要LCDmodule将LCDRAM集成在其中,host以command的方式将显示数据发送给LCD,LCD从自身的LCDRAM中获取数据刷新。

相较而言,commandmode比较省电,当屏幕不更新的时候,mipi总线可以idle,LCD凭借自身的RAM进行刷新动作,而videomode需要不停的传输数据到LCD,即使画面无变化,但command mode可能在大屏高分辨率高清播放的场景下能力不足

展讯SC8810LCD点亮的基本架构

1、u-boot中

我们所加的屏的驱动文件中通过init初始化函数对屏的LCD寄存器进行初始化。Sc8810_fb.c中的probe调用find_adapt_from_readid,这个函数中就是遍历我们所加屏列表文件的lcd_panel数组,并且用所加屏文件中的lcd_readid()函数读id,如果读成功则返回ID值,将这个保存到kernel中。

下面从U-boot在上电后被SPL从NAND中拷贝到SDRAM,然后执行board_init_f跳转到board_init_r开始。

u_boot/arch/arm/lib/Board.c中的board_init_r

void board_init_r (gd_t *id, ulong dest_addr)

{

//这里面的两个重要的函数

stdio_init(); /* get the devices list going.
对有关设备进行初始化,例如LCD,video, keyboard等*/

do_cboot(NULL,0,1,NULL);//进入启动函数

//…

}

依次走这两个函数,先看stdio_init()中的代码

在stdio_init()中我们对设备进行初始化,这个函数在u-boot/common/Stdio.c中

intstdio_init (void)

{

//…

#ifdefCONFIG_LCD

drv_lcd_init();//LCD的初始化

#endif

#ifdefined(CONFIG_VIDEO) || defined(CONFIG_CFB_CONSOLE)

drv_video_init();

#endif

#ifdefCONFIG_KEYBOARD

drv_keyboard_init();

#endif

//等等

return(0);

}

在drv_lcd_init()中这个函数中主要是初始化LCD,这个函数的实现在u-boot/common/Lcd.c中

intdrv_lcd_init (void)

{

structstdio_dev lcddev;

intrc = 0;

lcd_base= (void *)(gd->fb_base);

lcd_line_length= (panel_info.vl_col * NBITS (panel_info.vl_bpix)) / 8;

lcd_init(lcd_base); /* LCD初始化*/

/*Device initialization */

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

strcpy(lcddev.name, “lcd”);

lcddev.ext = 0; /* No extensions */

lcddev.flags= DEV_FLAGS_OUTPUT; /* Output only */

lcddev.putc = lcd_putc; /* ‘putc’ function */

lcddev.puts = lcd_puts; /* ‘puts’ function */

rc =stdio_register (&lcddev);

return(rc == 0) ? 1 : rc;

}

lcd_init(lcd_base)函数的实现在u-boot/common/Lcd.c中

staticint lcd_init (void *lcdbase)

{

lcd_ctrl_init(lcdbase);

lcd_is_enabled= 1;

lcd_clear(NULL, 1, 1, NULL); /* dummy args */

lcd_enable();

console_col= 0;

#ifdefCONFIG_LCD_INFO_BELOW_LOGO

console_row= 7 + BMP_LOGO_HEIGHT / VIDEO_FONT_HEIGHT;

#else

console_row= 1; /* leave 1 blank line below logo */

#endif

return0;

}

lcd_ctrl_init(lcd_base)函数的实现在u-boot/drivers/video/Sc8810_fb.c中

voidlcd_ctrl_init(void *lcdbase)

{

sc8810fb_probe(lcdbase);

}

sc8810fb_probe(lcdbase)这个函数主要是我们写的驱动入口函数,实现在u-boot/drivers/video/Sc8810_fb.c中

staticint sc8810fb_probe(void * lcdbase)

{

//…

//initadc for lcd adc compare

ADC_Init();

lcd_adapt= find_adapt_from_readid(fb);

//如果find_adapt_from_readid()没匹配到,则返回-1,执行下面这段,默认匹配panel列表中的第
一个panel

if(lcd_adapt== -1) {

lcd_adapt= 0;

}

ret =mount_panel(fb, lcd_panel[lcd_adapt].panel);

if(ret) {

printk(“unsupportedpanel!!”);

return-EFAULT;

}

//…

return0;

}

其中匹配函数find_adapt_from_readid()的实现在kernel/drivers/video/Sc8810_fb.c中

staticint find_adapt_from_readid(struct sc8810fb_info *fb)

{

inti;

uint32_tid;

//遍历整个lcd_panel数组

for(i= 0;i<(sizeof(lcd_panel))/(sizeof(lcd_panel[0]));i++) {

//first,try mount

mount_panel(fb,lcd_panel[i].panel);

//hwinit to every panel

hw_init(fb);

//readid

//我们所加屏的代码中的读id函数,如果读到

if(fb->panel->ops->lcd_readid){

id= fb->panel->ops->lcd_readid(fb->panel);

//没有读到返回的是0,执行else,默认走lcd_panel数组中的第一个panel

}else {

id= lcd_readid_default(fb->panel);

}

//ifthe id is right?

if(id== lcd_panel[i].lcd_id) {

//将这个id保存,这个保存的id在后面kernel启动时会使用到,后面再讲

save_lcd_id_to_kernel(id);

returni;

}

}

return-1;

}

通过find_adapt_from_readid(),匹配到相应的panel,这就会匹配到相应panel的结构体,也就调用到了我们所加的相应的驱动代码。

到这里lcd在stdio_init()中的代码就大致走完了.下面解释do_cboot()中的相应的代码.

do_cboot()代码的实现在u-boot/property/Cmd_cboot.c中

intdo_cboot(cmd_tbl_t *cmdtp, int flag, int argc, char *const argv[])

{

//…

#ifdefCONFIG_AUTOBOOT

normal_mode();//正常开机

#endif

boot_pwr_check();

#ifndefCONFIG_SC8810

CHG_ShutDown();

if(charger_connected()){

mdelay(10);

CHG_TurnOn();

}else{

if(is_bat_low()){

printf(“shutdown again for low battery\n”);

power_down_devices();

while(1)

;

}

}

#else

CHG_Init();

if(is_bat_low()){

printf(“shutdown again for low battery\n”);

power_down_devices();

while(1)

;

}#endif

boot_pwr_check();

board_keypad_init();//初始化键盘

boot_pwr_check();

unsignedcheck_reboot_mode(void);

unsignedrst_mode= check_reboot_mode();

if(rst_mode== RECOVERY_MODE){ //

DBG(“func:%s line: %d\n”, func, LINE);

recovery_mode();

}

elseif(rst_mode == FASTBOOT_MODE){

DBG(“func:%s line: %d\n”, func, LINE);

fastboot_mode();

}elseif(rst_mode == NORMAL_MODE){

normal_mode();

}else if (rst_mode == ALARM_MODE) {

intflag = alarm_flag_check();

if(flag == 1)

alarm_mode();

elseif (flag == 2)

normal_mode();

else{

power_down_devices();

while(1)

;

}

}else if (rst_mode == SLEEP_MODE) {

sleep_mode();

}

#ifdefCONFIG_SC8810

// normal_mode();

#endif

DBG(“func:%s line: %d\n”, func, LINE);

intrecovery_init(void);

intret =0;

ret= recovery_init();

if(ret== 1){//检查是否是recovery模式

DBG(“func:%s line: %d\n”, func, LINE);

recovery_mode_without_update();

}elseif(ret == 2){

recovery_mode();

}

//findthe power up trigger

if(boot_pwr_check()>= get_pwr_key_cnt()){//如果按power键大于12次,则认为是长按

DBG("%s:power button press\n", FUNCTION);

//goon to check other keys

mdelay(50);

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

key_code= board_key_scan();//获取另外一个按键

if(key_code!= KEY_RESERVED){

key_mode= check_key_boot(key_code);//查找对应的按键码对应的开机模式

if(key_mode!= 0)

break;

}

}

switch(key_mode){

caseBOOT_FASTBOOT:

fastboot_mode(); //fastboot模式

break;

caseBOOT_RECOVERY:

recovery_mode(); //recovery模式

break;

caseBOOT_UPDATE:

update_mode();

break;

caseBOOT_CALIBRATE:

engtest_mode();

return;//back to normal boot

break;

caseBOOT_DLOADER:

dloader_mode();

break;

default:

break;

}

}

//…

return1;

}

其中主要的是正常开机的代码normal_mode(),函数的实现在u-boot/property/Normal_mode.c中

voidnormal_mode(void)

{

#ifdefCONFIG_SC8810

//MMU_Init(CONFIG_MMU_TABLE_ADDR);

vibrator_hw_init();//马达初始化

#endif

set_vibrator(1);//马达震动一下

#ifBOOT_NATIVE_LINUX

vlx_nand_boot(BOOT_PART,CONFIG_BOOTARGS, BACKLIGHT_ON);

#else

vlx_nand_boot(BOOT_PART,NULL, BACKLIGHT_ON);

#endif

}

正常启动的函数中的主要是调用vlx_nand_boot,

//ifnot to boot native linux, cmdline=NULL, kerne_pname=boot,backlight_set=on.

voidvlx_nand_boot(char * kernel_pname, char * cmdline, int backlight_set)

{

//…

//下面这一段是执行刚开机时显示的logo

#ifdefCONFIG_SPLASH_SCREEN

#defineSPLASH_PART “boot_logo”

//…

//下面这一段是将u-boot中lcd的readid中保存的id供后面kernel使用,fb0_id来表示

{

//addlcd id

externuint32_t load_lcd_id_to_kernel();

uint32_tlcd_id = load_lcd_id_to_kernel();

str_len= strlen(buf);

sprintf(&buf[str_len]," video=sprdfb:fb0_id=0x%x,fb1_id=0x%x", lcd_id, 0);

str_len= strlen(buf);

}

//…

creat_atags(VLX_TAG_ADDR, buf, NULL,0);//这个函数是将buf处理一下并且放到地址为VLX_TAG_ADDR处

//…

start_linux();//这个函数中调用theKernel(0,machine_type, VLX_TAG_ADDR);
跳到这 个内核地址

}

2.kernel

kernel层中从Sprdfb_main.c中开始看,从module_init(sprdfb_init);到sprdfb_init()函数中

staticint __init sprdfb_init(void)

{

char*option = NULL;

if(fb_get_options(“sprdfb”, &option))

return-ENODEV;

sprdfb_setup(option);

lcdc_hardware_init();

returnplatform_driver_register(&sprdfb_driver);

}

其中这个函数中的有一个函数sprdfb_setup(option),也在当前文件中定义的,这里面就找到u-boot中读的id了。

staticint __init sprdfb_setup(char *options)

{

//…

elseif (get_opt_int(this_opt, “fb0_id”,&panel_id[0]))//这里就通过fb0_id找到uboot中保存的id

//…

}

这个函数中的操作的数据最终在回调函数probe中dev->device_id= panel_id[pdev->id];就能找到这个里面的pdev的定义:

staticstruct platform_device sprd_fb_device = {

.name =“sprdfb”,

.id =0,

.dev.platform_data= &lcd_data,

};

这个device是通过platform_driver_register(&sprdfb_driver)进行平台驱动注册,匹配平台设备得到的id=0,所以找的是panel_id[0],就得到了dev->device_id=panel_id[0]。

sprdfb_setup(option);结束后,platform_driver_register(&sprdfb_driver)进行平台设备的注册,这个结构体中有个回调函数probe,这里面是我们些的驱动代码的入口。

staticint sprdfb_probe(struct platform_device *pdev)

{

//…

dev->device_id= panel_id[pdev->id];

ret =lcdc_early_init(platform_data, dev);

//…

}

传参数dev到lcdc_early_init(platform_data,dev)中,这个函数的实现在kernel\drivers\video\sc8810\Lcdc.c中。

首先通过find_adapt_from_uboot()查找panel,如果找到返回相应的索引号,否则返回-1,如果返回-1,则在lcdc_early_init()调用find_adapt_from_readid()查找整个panel。

intlcdc_early_init(struct sprd_lcd_platform_data* platform_data,structsprdfb_device *dev)

{

//…

lcd_adapt= find_adapt_from_uboot(dev->device_id, platform_data);

if(lcd_adapt == -1) {

dev->need_reinit= 1;

lcd_adapt= find_adapt_from_readid(dev,platform_data);

}

//…

}

进入到find_adapt_fromuboot中的第一个参数就是Uboot中保存的lcd_id,这样就走到这个函数中拿Uboot中保存的lcd_id比较。

platform_driver_register(&sprdfb_driver)进行平台设备驱动注册,这个里面有platform的匹配。首先在开机的时候kernel初始化会做一些板极的初始化工作,告诉内核驱动的存在,内核根据sprdfb_driver.name找到device, 然后把device的信息通过platform_device *pdev这个参数传递给driver下挂着的各个功能函数,从而使驱动完成使命。

这里用的sprdfb_driver的定义如下:

staticstruct platform_driver sprdfb_driver = {

.probe= sprdfb_probe,

#ifndefCONFIG_HAS_EARLYSUSPEND

.suspend= sprdfb_suspend,

.resume= sprdfb_resume,

#endif

.driver= {

.name= “sprdfb”,

.owner= THIS_MODULE,

},

};

我们看到driver中的name字段是sprdfb,自然我们要匹配这个的平台设备structplatform_device结构中的name字段要和structplatform_driver匹配

staticvoid __init openphone_init(void){

platform_add_devices(devices,ARRAY_SIZE(devices));

}

devices是一个定义的全局结构体。

kernel/arch/arm/mach-sc8810/Common.c中

externstruct sprd_lcd_platform_data lcd_data;

staticstruct platform_device sprd_fb_device = {

.name =“sprdfb”,

.id =0,

.dev.platform_data= &lcd_data,

};

平台设备也有了,平台设备注册进了内核之后,那自然会调用驱动中的probe函数。

每次注册一个platform driver时,内核会对注册在platform中的没有注册驱动的设备轮询,如果name匹配的话则传struct platform_driver的sprdfb_driver这个结构体,其中有个probe函数,被回调,这个函数中主要是填充结构体struct fb_info并使用register_framebuffer()函数在系统中进行注册,并且调用lcdc_early_init()初始化。在linux中,帧缓冲设备采用structfb_info结构来描述,当然首先要为这个结构申请内存空间,为structfb_info申请内存空间的函数就是这里的 framebuffer_alloc,这两个操作之前有个如下的申请空间的操作。

structfb_info* fb;
fb =framebuffer_alloc(sizeof(struct sprdfb_device), &pdev->dev);

你可能感兴趣的:(Linux驱动开发)