/************************************************************************************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; }