和总线设备驱动模型类似,framebuffer分为核心层、驱动层和设备层。
核心层:就是上一章分析的fbmem.c文件
驱动层(控制器层):一般由芯片原厂提供,实现了LCD控制器通用的操作接口和配置接口,本章用到的是三星提供的s3cfb_main.c和s3vfb_ops.c
设备层:一般由单板厂商提供,本章用到的是arch/arm/plat-s5p/dev-fimd-s5p.c文件,后续会分析为什么是它。
考虑到我是用的并不是之前的TINY4412,在此给出上述分析的文件:
https://pan.baidu.com/s/1rjM7_v2DZDgRNUErGxZrQQ
提取码为:v6ed
注意:有的开发板的驱动层可能是s3c-fb.c,这要取决于:
1. drivers/video/Makefile中的约束条件,比如我的Makefile是obj-$(CONFIG_FB_S3C) += s3c-fb.o
2. 内核根目录下的.config是否配置了CONFIG_FB_S3C这个宏,比如我的.config是# CONFIG_FB_S3C is not set,未设置
进一步分析.config,我发现了CONFIG_FB_S5P=y和CONFIG_FB_S5P_WA101S=y,在drivers/video/Makefile中有obj-$(CONFIG_FB_S5P) += samsung/,故需要查看drivers/video/samsung/Makefile。
经过.config排除drivers/video/samsung/Makefile的宏定义后,Makefile文件最终如下:
ifeq ($(CONFIG_FB_S5P),y) obj-y += s3cfb.o s3cfb-y := s3cfb_main.o s3cfb_ops.o obj-$(CONFIG_ARCH_EXYNOS4) += s3cfb_fimd6x.o obj-$(CONFIG_FB_S5P_WA101S) += s3cfb_wa101s.o endif
除此之外,我们需要完成的是设备层下的参数层,参数层存储LCD相关的一些时序,如分辨率、BPP等参数。单板厂商提供的是drivers/video/samsung/s3cfb_wa101s.c文件。
把控制器层和设备层分开,是因为芯片的LCD控制器只有一个,而开发板配套LCD选择较多。LCD控制器确定了LCD的操作方式,因此可以被普适化。
对于LCD,我们可以选择4.3寸、7寸等,不同LCD会有一定的差异,此时就可以采取数据总线的形式。我们根据LCD各自的物理特性,把相关参数添加到构建好的平台总线的设备层即可。
本章仿照总线设备驱动模型进行分析,首先介绍需要使用的结构体,之后分析platform_driver和platform_device,最后分析参数层。
一、平台驱动使用的结构体
1. s3c_platform_fb:总线对应的fb结构体,定义有LCD操作函数
struct s3c_platform_fb { int hw_ver; char clk_name[16]; int nr_wins; /* 虚拟窗口的个数 */ int nr_buffers[5]; int default_win; /* 当前默认的窗口 */ int swap; phys_addr_t pmem_start; /* 显存的物理起始地址 */ size_t pmem_size; /* 显存的字节大小 */ void *lcd; void (*cfg_gpio)(struct platform_device *dev); /* 配置LCD的GPIO */ int (*backlight_on)(struct platform_device *dev); /* 打开LCD背光 */ int (*backlight_onoff)(struct platform_device *dev, int onoff); /* 关闭LCD背光 */ int (*reset_lcd)(struct platform_device *dev); /* 重置LCD */ int (*clk_on)(struct platform_device *pdev, struct clk **s3cfb_clk); /* 打开LCD时钟 */ int (*clk_off)(struct platform_device *pdev, struct clk **clk); /* 关闭LCD时钟 */ };
2. s3cfb_fimd_desc:单板对应的fb结构体
struct s3cfb_fimd_desc { int state; int dual; struct s3cfb_global *fbdev[FIMD_MAX]; /* 通用的平台fb结构体 */ };
3. s3cfb_global:平台通用的fb结构体,定义有LCD参数
struct s3cfb_global { void __iomem *regs; /* LCD对应的寄存器 */ struct mutex lock; /* 互斥量 */ struct device *dev; /* LCD设备 */ struct clk *clock; /* LCD时钟 */ int irq; wait_queue_head_t wq; unsigned int wq_count; struct fb_info **fb; /* 通用的fb属性,指针数组 */ atomic_t enabled_win; enum s3cfb_output_t output; enum s3cfb_rgb_mode_t rgb_mode; struct s3cfb_lcd *lcd; /* 用于描述LCD物理参数 */ int system_state; #ifdef CONFIG_HAS_WAKELOCK struct early_suspend early_suspend; struct wake_lock idle_lock; #endif };
4. s3cfb_lcd:描述LCD物理参数,这个结构体是需要我们修改的
struct s3cfb_lcd { int width; /* 水平像素个数 */ int height; /* 垂直像素个数 */ int bpp; /* 每个像素位数 */ int freq; /* LCD刷新率 */ struct s3cfb_lcd_timing timing; /* LCD时序相关参数 */ struct s3cfb_lcd_polarity polarity; /* 电平是否反转 */ void (*init_ldi)(void); void (*deinit_ldi)(void); };
二、platform_driver
platform_driver定义在s3cfb_main.c中,其定义如下:
static struct platform_driver s3cfb_driver = { .probe = s3cfb_probe, .remove = s3cfb_remove, ... .driver = { .name = S3CFB_NAME, /* #define S3CFB_NAME "s3cfb" */ ... }, };
我们首先查看s3cfb_probe()函数:
1 static int s3cfb_probe(struct platform_device *pdev) 2 { 3 struct s3c_platform_fb *pdata = NULL; 4 struct resource *res = NULL; 5 struct s3cfb_global *fbdev[2]; 6 int ret = 0; 7 int i = 0; 8 ... 9 /* 分配包含平台设备s3cfb_global指针的结构体 */ 10 fbfimd = kzalloc(sizeof(struct s3cfb_fimd_desc), GFP_KERNEL); 11 ... 12 for (i = 0; i < FIMD_MAX; i++) { 13 /* 分配平台设备s3cfb_global,里面包含LCD各种属性 */ 14 fbfimd->fbdev[i] = kzalloc(sizeof(struct s3cfb_global), GFP_KERNEL); 15 fbdev[i] = fbfimd->fbdev[i]; 16 ... 17 fbdev[i]->dev = &pdev->dev; 18 s3cfb_set_lcd_info(fbdev[i]); /* 设置s3cfb_global的lcd成员为&wa101:struct s3cfb_global *ctrl; ctrl->lcd = &wa101; */ 19 20 /* 配置platform_device的引脚和时钟参数 */ 21 pdata = to_fb_plat(&pdev->dev); 22 if (pdata->cfg_gpio) 23 pdata->cfg_gpio(pdev); 24 25 if (pdata->clk_on) 26 pdata->clk_on(pdev, &fbdev[i]->clock); 27 28 /* 内存映射 */ 29 res = platform_get_resource(pdev, IORESOURCE_MEM, i); 30 ... 31 res = request_mem_region(res->start, 32 res->end - res->start + 1, pdev->name); 33 ... 34 fbdev[i]->regs = ioremap(res->start, res->end - res->start + 1); 35 ... 36 /* irq */ 37 fbdev[i]->irq = platform_get_irq(pdev, 0); 38 ... 39 /* fb寄存器设置 */ 40 s3cfb_init_global(fbdev[i]); 41 42 fbdev[i]->system_state = POWER_ON; 43 44 /* 分配 fb_info */ 45 if (s3cfb_alloc_framebuffer(fbdev[i], i)) { 46 ... 47 } 48 49 /* 注册 fb_info */ 50 if (s3cfb_register_framebuffer(fbdev[i])) { 51 ... 52 } 53 54 /* 使能显示 */ 55 s3cfb_set_clock(fbdev[i]); 56 s3cfb_enable_window(fbdev[0], pdata->default_win); 57 ... 58 s3cfb_update_power_state(fbdev[i], pdata->default_win, FB_BLANK_UNBLANK); 59 s3cfb_display_on(fbdev[i]); 60 ... 61 } 62 /* 使能背光 */ 63 #ifdef CONFIG_FB_S5P_LCD_INIT 64 /* panel control */ 65 if (pdata->backlight_on) 66 pdata->backlight_on(pdev); 67 68 if (pdata->lcd_on) 69 pdata->lcd_on(pdev); 70 #endif 71 72 ret = device_create_file(&(pdev->dev), &dev_attr_win_power); 73 ... 74 dev_info(fbdev[0]->dev, "registered successfully\n"); 75 ... 76 return 0; 77 }
probe()函数所做的事情有:
1. 分配单板对应的s3cfb_fimd_desc
2. 分配平台对应的s3cfb_global
3. 调用s3cfb_set_lcd_info()设置s3cfb_global的lcd成员
1 void s3cfb_setup_lcd() 2 { 3 int type = get_lcd_type(); /* 获取LCD类型 */ 4 ... 5 if(0x1 == type) //7.0 6 { 7 wa101.width = 800; 8 wa101.height = 1280; 9 wa101.bpp = 24; 10 wa101.freq = 50; //70; 11 } 12 ... 13 } 14 15 void s3cfb_set_lcd_info(struct s3cfb_global *ctrl) 16 { 17 s3cfb_setup_lcd(); 18 19 wa101.init_ldi = NULL; 20 ctrl->lcd = &wa101; 21 }View Code
4. 配置引脚,打开LCD时钟,设置中断
5. 映射寄存器,调用s3cfb_init_global()设置寄存器,有些寄存器的值是通过s3cfb_lcd传入的
1 int s3cfb_init_global(struct s3cfb_global *fbdev) 2 { 3 fbdev->output = OUTPUT_RGB; 4 fbdev->rgb_mode = MODE_RGB_P; 5 6 fbdev->wq_count = 0; 7 init_waitqueue_head(&fbdev->wq); 8 mutex_init(&fbdev->lock); 9 10 /* 写寄存器操作 */ 11 s3cfb_set_output(fbdev); /* 设置输出格式为RGB */ 12 s3cfb_set_display_mode(fbdev); /* 设置RGB数据格式 */ 13 s3cfb_set_polarity(fbdev); /* 设置极性是否反转 */ 14 s3cfb_set_timing(fbdev); /* 设置时序 */ 15 s3cfb_set_lcd_size(fbdev); /* 设置LCD分辨率 */ 16 17 return 0; 18 }View Code
6. 分配、注册s3cfb_global的fb[i]成员
7. 使能背光和显示
综合上一章,我们可以知道probe()函数分配和注册了fb_info,但两函数之间并没有设置fb_info,因此我们需要进一步分析。
分配函数如下:
1 int s3cfb_alloc_framebuffer(struct s3cfb_global *fbdev, int fimd_id) 2 { 3 struct s3c_platform_fb *pdata = to_fb_plat(fbdev->dev); 4 int ret = 0; 5 int i; 6 7 fbdev->fb = kmalloc(pdata->nr_wins * sizeof(struct fb_info *), GFP_KERNEL); 8 ... 9 for (i = 0; i < pdata->nr_wins; i++) { 10 fbdev->fb[i] = framebuffer_alloc(sizeof(struct s3cfb_window), fbdev->dev); 11 ... 12 ret = s3cfb_init_fbinfo(fbdev, i); 13 ... 14 if (i == pdata->default_win) 15 if (s3cfb_map_default_video_memory(fbdev, fbdev->fb[i], fimd_id)) { 16 ret = -ENOMEM; 17 ... 18 } 19 } 20 } 21 ... 22 return ret; 23 }
在framebuffer_alloc()之后,我们需要关注第12行代码:ret = s3cfb_init_fbinfo(fbdev, i);,这个函数就是我们要找的设置fb_info函数。
1 int s3cfb_init_fbinfo(struct s3cfb_global *fbdev, int id) 2 { 3 struct fb_info *fb = fbdev->fb[id]; 4 struct fb_fix_screeninfo *fix = &fb->fix; 5 struct fb_var_screeninfo *var = &fb->var; 6 struct s3cfb_window *win = fb->par; 7 struct s3cfb_alpha *alpha = &win->alpha; 8 struct s3cfb_lcd *lcd = fbdev->lcd; 9 struct s3cfb_lcd_timing *timing = &lcd->timing; 10 11 memset(win, 0, sizeof(struct s3cfb_window)); 12 platform_set_drvdata(to_platform_device(fbdev->dev), fb); 13 strcpy(fix->id, S3CFB_NAME); 14 15 /* fimd specific */ 16 win->id = id; 17 win->path = DATA_PATH_DMA; 18 win->dma_burst = 16; 19 s3cfb_update_power_state(fbdev, win->id, FB_BLANK_POWERDOWN); 20 alpha->mode = PLANE_BLENDING; 21 22 /* 设置fbinfo */ 23 fb->fbops = &s3cfb_ops; 24 fb->flags = FBINFO_FLAG_DEFAULT; 25 fb->pseudo_palette = &win->pseudo_pal; 26 #if (CONFIG_FB_S5P_NR_BUFFERS != 1) 27 fix->xpanstep = 2; 28 fix->ypanstep = 1; 29 #else 30 fix->xpanstep = 0; 31 fix->ypanstep = 0; 32 #endif 33 fix->type = FB_TYPE_PACKED_PIXELS; 34 fix->accel = FB_ACCEL_NONE; 35 fix->visual = FB_VISUAL_TRUECOLOR; 36 var->xres = lcd->width; 37 var->yres = lcd->height; 38 39 #if defined(CONFIG_FB_S5P_VIRTUAL) 40 var->xres_virtual = CONFIG_FB_S5P_X_VRES; 41 var->yres_virtual = CONFIG_FB_S5P_Y_VRES * CONFIG_FB_S5P_NR_BUFFERS; 42 #else 43 var->xres_virtual = var->xres; 44 var->yres_virtual = var->yres * CONFIG_FB_S5P_NR_BUFFERS; 45 #endif 46 var->bits_per_pixel = 32; 47 var->xoffset = 0; 48 var->yoffset = 0; 49 var->width = 0; 50 var->height = 0; 51 var->transp.length = 0; 52 53 fix->line_length = var->xres_virtual * var->bits_per_pixel / 8; 54 fix->smem_len = fix->line_length * var->yres_virtual; 55 56 var->nonstd = 0; 57 var->activate = FB_ACTIVATE_NOW; 58 var->vmode = FB_VMODE_NONINTERLACED; 59 var->hsync_len = timing->h_sw; 60 var->vsync_len = timing->v_sw; 61 var->left_margin = timing->h_bp; 62 var->right_margin = timing->h_fp; 63 var->upper_margin = timing->v_bp; 64 var->lower_margin = timing->v_fp; 65 var->pixclock = (lcd->freq * 66 (var->left_margin + var->right_margin 67 + var->hsync_len + var->xres) * 68 (var->upper_margin + var->lower_margin 69 + var->vsync_len + var->yres)); 70 var->pixclock = KHZ2PICOS(var->pixclock/1000); 71 72 s3cfb_set_bitfield(var); 73 s3cfb_set_alpha_info(var, win); 74 75 return 0; 76 }
读者如果熟悉LCD裸机操作,想更接近底层修改代码,可以修改此函数,在修改前注意备份。
三、platform_device
之前platform_driver定义的匹配方式是name匹配,搜索后确定platform_device定义在arch/arm/dev-fimd-s5p.c中
platform_device定义如下:
struct platform_device s3c_device_fb = { .name = "s3cfb", #if defined(CONFIG_ARCH_EXYNOS4) .id = 0, #else .id = -1, #endif .num_resources = ARRAY_SIZE(s3cfb_resource), .resource = s3cfb_resource, .dev = { .dma_mask = &fb_dma_mask, .coherent_dma_mask = 0xffffffffUL } };
从定义的变量来看,并没有挂接设备的私有数据到s3c_device_fb变量中,因为platform_device结构体中device结构体的platform_data指针并没有被赋值。
但是前面平台驱动中使用了平台设备的私有数据。我们可以搜索s3c_device_fb.dev.platform_data:
代码如下:
1 static struct s3c_platform_fb default_fb_data __initdata = { 2 #if defined(CONFIG_ARCH_EXYNOS4) 3 .hw_ver = 0x70, 4 #else 5 .hw_ver = 0x62, 6 #endif 7 .nr_wins = 5, 8 #if defined(CONFIG_FB_S5P_DEFAULT_WINDOW) 9 .default_win = CONFIG_FB_S5P_DEFAULT_WINDOW, 10 #else 11 .default_win = 0, 12 #endif 13 .swap = FB_SWAP_WORD | FB_SWAP_HWORD, 14 }; 15 16 void __init s3cfb_set_platdata(struct s3c_platform_fb *pd) 17 { 18 struct s3c_platform_fb *npd; 19 int i; 20 21 if (!pd) 22 pd = &default_fb_data; 23 24 npd = kmemdup(pd, sizeof(struct s3c_platform_fb), GFP_KERNEL); 25 if (!npd) 26 printk(KERN_ERR "%s: no memory for platform data\n", __func__); 27 else { 28 for (i = 0; i < npd->nr_wins; i++) 29 npd->nr_buffers[i] = 1; 30 31 #if defined(CONFIG_FB_S5P_NR_BUFFERS) 32 npd->nr_buffers[npd->default_win] = CONFIG_FB_S5P_NR_BUFFERS; 33 #else 34 npd->nr_buffers[npd->default_win] = 1; 35 #endif 36 37 s3cfb_get_clk_name(npd->clk_name); 38 npd->cfg_gpio = s3cfb_cfg_gpio; 39 npd->backlight_on = s3cfb_backlight_on; /* 最终调用的背光打开函数 */ 40 npd->backlight_off = s3cfb_backlight_off; /* 最终调用的背光关闭函数 */ 41 npd->lcd_on = s3cfb_lcd_on; /* 最终调用的LCD打开函数 */ 42 npd->lcd_off = s3cfb_lcd_off; /* 最终调用的LCD关闭函数 */ 43 npd->clk_on = s3cfb_clk_on; /* 最终调用的时钟使能函数 */ 44 npd->clk_off = s3cfb_clk_off; 45 46 s3c_device_fb.dev.platform_data = npd; 47 } 48 }
四、关系总结
各个结构体关系如下图所示:
五、s3cfb_lcd
此结构体定义在drivers/video/samsung/s3cfb_wa101s.c文件中,其定义如下:
1 #include "s3cfb.h" 2 3 static struct s3cfb_lcd wa101 = { 4 ... 5 /* CONFIG_TOUCHSCREEN_TSC2007=y */ 6 #ifdef CONFIG_TOUCHSCREEN_TSC2007 7 .width = 800, 8 .height = 480, 9 #endif 10 .bpp = 24, 11 .freq = 70, // 70, 12 13 .polarity = { 14 .rise_vclk = 1, 15 .inv_hsync = 0, 16 .inv_vsync = 1, 17 .inv_vden = 0, 18 }, 19 20 }; 21 22 extern int get_lcd_type(); 23 24 void s3cfb_setup_lcd() 25 { 26 int type = get_lcd_type(); 27 ... 28 if(0x1 == type) //7.0 29 { 30 wa101.width = 800; 31 wa101.height = 1280; 32 wa101.bpp = 24; 33 wa101.freq = 50; //70; 34 } 35 ... 36 } 37 38 /* name should be fixed as 's3cfb_set_lcd_info' */ 39 void s3cfb_set_lcd_info(struct s3cfb_global *ctrl) 40 { 41 s3cfb_setup_lcd(); 42 43 wa101.init_ldi = NULL; 44 ctrl->lcd = &wa101; 45 }
之前分析过,probe()会调用s3cfb_set_lcd_info()函数。故最终LCD参数为:
1 static struct s3cfb_lcd wa101 = { 2 .width = 800; 3 .height = 1280; 4 .bpp = 24; 5 .freq = 50; 6 7 .polarity = { 8 .rise_vclk = 1, 9 .inv_hsync = 0, 10 .inv_vsync = 1, 11 .inv_vden = 0, 12 }, 13 };