/************************************************************************************html
*本文爲我的學習記錄,若有錯誤,歡迎指正。linux
* 朱有鵬嵌入式課程框架
* https://blog.csdn.net/ultraman_hs/article/details/54987874
ide
************************************************************************************/函數
結合以前對Linux的framebuffer驅動框架的分析(詳見Linux字符設備驅動框架(五):Linux內核的framebuffer驅動框架),本文對LCD的驅動程序進行了分析。學習
本文基於九鼎科技的x210開發板的BSP進行分析,涉及到如下文件: spa
(1)drivers/video/samsung/s3cfb.c:驅動主體 ;
(2)drivers/video/samsung/s3cfb_fimd6x.c:其中包含不少LCD硬件操做的函數;
(2)arch/arm/mach-s5pv210/mach-x210.c:負責提供platform_device的 ;
(3)arch/arm/plat-s5p/devs.c:爲platform_device提供一些硬件描述信息的。.net
Samsung編寫的LCD設備驅動程序是經過Linux內核的platform總線驅動框架實現的,本文將分兩大部分device和driver對其進行分析。code
(1) struct platform_device s3c_device_lcd
LCD的設備信息被嵌入在struct platform_device *smdkc110_devices[]數組中,在內核初始化時,內核將調用platform_add_devices()函數將smdkc110_devices[]數組中的全部設備註冊至內核。
//所在文件:/kernel/arch/arm/mach-s5pv210/mach-x210.c //smdkc110_devices[]數組中包含了開發板的全部設備的設備信息 static struct platform_device *smdkc110_devices[] __initdata = { ... ... &s3c_device_lcd, //LCD設備信息 ... ... } //開發板初始化函數 static void __init smdkc110_machine_init(void) { ... ... platform_add_devices(smdkc110_devices, ARRAY_SIZE(smdkc110_devices)); //向內核註冊smdkc110_devices[]中的全部設備 ... ... #ifdef CONFIG_FB_S3C_LTE480WV s3cfb_set_platdata(<e480wv_fb_data); //設置platform_device s3c_device_lcd的platform_data #endif #ifdef CONFIG_FB_S3C_EK070TN93 smdkv210_backlight_off(); s3cfb_set_platdata(&ek070tn93_fb_data); //設置platform_device s3c_device_lcd的platform_data #endif }
LCD的設備信息以下:
//所在文件:/kernel/arch/arm/plat-s3c24xx/devs.c //LCD控制器的資源信息 static struct resource s3c_lcd_resource[] = { [0] = { .start = S3C24XX_PA_LCD, //控制器IO端口開始地址(0xf8000000) .end = S3C24XX_PA_LCD + S3C24XX_SZ_LCD - 1,//控制器IO端口結束地址(1M) .flags = IORESOURCE_MEM, }, [1] = { .start = IRQ_LCD,//LCD中斷 .end = IRQ_LCD, .flags = IORESOURCE_IRQ, } }; struct platform_device s3c_device_lcd = { .name = "s3c2410-lcd", //設備名稱 .id = -1, .num_resources = ARRAY_SIZE(s3c_lcd_resource), .resource = s3c_lcd_resource, //LCD設備須要的資源 .dev = { .dma_mask = &s3c_device_lcd_dmamask, .coherent_dma_mask = 0xffffffffUL } };
(2)struct s3c_platform_fb與s3cfb_set_platdata
在struct platform_device s3c_device_fb裏並無設置platdata的內容,而是經過s3cfb_set_platdata這個函數來單獨設置platdata。由於內核中實現了多個LCD設備的platdata,用戶可經過相應的宏定義來選擇設置。它在啓動時經過smdkc110_machine_init函數加載,保證platdata在內核啓動時就已經設置好。
在移植LCD驅動過程當中,LCD的配置信息在其platdata中進行修改。
經過內核源碼及.config文件的分析,此處LCD的platdata以下:
//所在文件:/kernel/arch/arm/mach-s5pv210/mach-x210.c //LCD的參數信息 static struct s3cfb_lcd ek070tn93 = { .width = S5PV210_LCD_WIDTH, //LCD分辨率參數,可修改LCD分辨率 .height = S5PV210_LCD_HEIGHT, .bpp = 32, .freq = 60, .timing = { .h_fp = 210, .h_bp = 38, .h_sw = 10, .v_fp = 22, .v_fpe = 1, .v_bp = 18, .v_bpe = 1, .v_sw = 7, }, .polarity = { .rise_vclk = 0, .inv_hsync = 1, .inv_vsync = 1, .inv_vden = 0, }, }; //LCD的platdata static struct s3c_platform_fb ek070tn93_fb_data __initdata = { .hw_ver = 0x62, .nr_wins = 5, .default_win = CONFIG_FB_S3C_DEFAULT_WINDOW, .swap = FB_SWAP_WORD | FB_SWAP_HWORD, .lcd = &ek070tn93, //LCD參數信息 .cfg_gpio = ek070tn93_cfg_gpio, //LCD的GPIO初始化函數 .backlight_on = ek070tn93_backlight_on, //背光點亮函數 .backlight_onoff = ek070tn93_backlight_off,//背光熄滅函數 .reset_lcd = ek070tn93_reset_lcd, //LCD復位函數 };
(1)驅動註冊
//所在文件:/kernel/drivers/video/samsung/s3cfb.c static struct platform_driver s3cfb_driver = { .probe = s3cfb_probe, .remove = __devexit_p(s3cfb_remove), .driver = { .name = S3CFB_NAME, .owner = THIS_MODULE, }, }; static int __init s3cfb_register(void) { platform_driver_register(&s3cfb_driver); return 0; } static void __exit s3cfb_unregister(void) { platform_driver_unregister(&s3cfb_driver); } module_init(s3cfb_register); module_exit(s3cfb_unregister);
(2)probe函數分析
static int __devinit s3cfb_probe(struct platform_device *pdev) { struct s3c_platform_fb *pdata;/*LCD的platdata,LCD屏配置信息結構體*/ struct s3cfb_global *fbdev; /*驅動程序全局變量結構體,主要做用是在驅動部分的2個文件(s3cfb.c和s3cfb_fimd6x.c)的函數中作數據傳遞用的*/ struct resource *res; /*用來保存從LCD平臺設備中獲取的LCD資源*/ int i, j, ret = 0; fbdev = kzalloc(sizeof(struct s3cfb_global), GFP_KERNEL); fbdev->dev = &pdev->dev; ... ... /*LCD電源功耗管理*/ fbdev->regulator = regulator_get(&pdev->dev, "pd"); ret = regulator_enable(fbdev->regulator); /*1)獲取LCD配置信息*/ pdata = to_fb_plat(&pdev->dev); //獲取LCD的platdata fbdev->lcd = (struct s3cfb_lcd *)pdata->lcd;//獲取LCD參數信息 ... ... /*配置GPIO端口*/ if (pdata->cfg_gpio) pdata->cfg_gpio(pdev); /*設置時鐘參數*/ if (pdata->clk_on) pdata->clk_on(pdev, &fbdev->clock); /*獲取LCD平臺設備所使用的IO端口資源,注意這個IORESOURCE_MEM標誌和LCD平臺設備定義中的一致*/ res = platform_get_resource(pdev, IORESOURCE_MEM, 0); /*申請LCD IO端口所佔用的IO空間(注意理解IO空間和內存空間的區別),request_mem_region定義在ioport.h中*/ res = request_mem_region(res->start, res->end - res->start + 1, pdev->name); /*將LCD的IO端口占用的這段IO空間映射到內存的虛擬地址,ioremap定義在io.h中 * 注意:IO空間要映射後才能使用,之後對虛擬地址的操做就是對IO空間的操做*/ fbdev->regs = ioremap(res->start, res->end - res->start + 1); #ifdef CONFIG_FB_S3C_LTE480WV /*設置寄存器初始狀態*/ s3cfb_pre_init_para(fbdev); #endif /*設置gamma 值*/ s3cfb_set_gamma(fbdev); /*設置VSYNC中斷*/ s3cfb_set_vsync_interrupt(fbdev, 1); /*設置全局中斷*/ s3cfb_set_global_interrupt(fbdev, 1); /*fb設備參數信息初始化*/ s3cfb_init_global(fbdev); /*爲framebuffer分配空間,進行內存映射,填充fb_info*/ if (s3cfb_alloc_framebuffer(fbdev)) { ret = -ENOMEM; goto err_alloc; } /*註冊fb設備到系統中*/ if (s3cfb_register_framebuffer(fbdev)) //建立framebuffer設備 { ret = -EINVAL; goto err_register; } s3cfb_set_clock(fbdev); s3cfb_set_window(fbdev, pdata->default_win, 1); s3cfb_display_on(fbdev); fbdev->irq = platform_get_irq(pdev, 0); if (request_irq(fbdev->irq, s3cfb_irq_frame, IRQF_SHARED, pdev->name, fbdev)) { dev_err(fbdev->dev, "request_irq failed\n"); ret = -EINVAL; goto err_irq; } #ifdef CONFIG_FB_S3C_LCD_INIT if (pdata->backlight_on) //打開LCD的背光 pdata->backlight_on(pdev); if (!bootloaderfb && pdata->reset_lcd) pdata->reset_lcd(pdev); if (pdata->lcd_on) pdata->lcd_on(pdev); #endif /*對設備文件系統的支持,建立fb設備文件*/ ret = device_create_file(&(pdev->dev), &dev_attr_win_power); if (ret < 0) dev_err(fbdev->dev, "failed to add sysfs entries\n"); dev_info(fbdev->dev, "registered successfully\n"); /*顯示開機logo*/ #if !defined(CONFIG_FRAMEBUFFER_CONSOLE) && defined(CONFIG_LOGO) if (fb_prepare_logo( fbdev->fb[pdata->default_win], FB_ROTATE_UR)) //準備logo { printk("Start display and show logo\n"); /* Start display and show logo on boot */ fb_set_cmap(&fbdev->fb[pdata->default_win]->cmap, fbdev->fb[pdata->default_win]); fb_show_logo(fbdev->fb[pdata->default_win], FB_ROTATE_UR);//顯示logo } #endif ... ... return 0; }
1)s3cfb_register_framebuffer
s3cfb_register_framebuffer()函數中調用register_framebuffer()註冊多個framebuffer設備,對應/dev/fb*,從而應用層能夠設置多個虛擬屏幕達到更好的顯示效果。
2)logo顯示
Linux內核啓動成功後,會在LCD中顯示logo圖標。Linux內核中提供了多個logo文件(/kernel/kernel/drivers/video/logo),內核調用fb_prepare_logo()函數來肯定須要顯示的logo,再調用fb_show_logo()函數顯示logo。用戶可經過宏定義來設置顯示相應的logo圖標。
int fb_prepare_logo(struct fb_info *info, int rotate) { ... ... fb_logo.logo = fb_find_logo(depth);//查找須要顯示的logo ... ... } const struct linux_logo * __init_refok fb_find_logo(int depth) { const struct linux_logo *logo = NULL; if (nologo) return NULL; if (depth >= 1) { #ifdef CONFIG_LOGO_LINUX_MONO /* Generic Linux logo */ logo = &logo_linux_mono; #endif #ifdef CONFIG_LOGO_SUPERH_MONO /* SuperH Linux logo */ logo = &logo_superh_mono; #endif } if (depth >= 4) { #ifdef CONFIG_LOGO_LINUX_VGA16 /* Generic Linux logo */ logo = &logo_linux_vga16; #endif ... ... return logo; }