framebuffer设备驱动分析
一.设备驱动相关文件
1.1. 驱动框架相关文件
1.1.1. drivers/video/fbmem.c
a. 创建graphics类、注册FB的字符设备驱动
fbmem_init(void) { proc_create("fb", 0, NULL, &fb_proc_fops); if (register_chrdev(FB_MAJOR,"fb",&fb_fops)) printk("unable to get major %d for fb devs\n", FB_MAJOR); fb_class = class_create(THIS_MODULE, "graphics"); if (IS_ERR(fb_class)) { printk(KERN_WARNING "Unable to create fb class; errno = %ld\n", PTR_ERR(fb_class)); fb_class = NULL; } return 0; }
b. 提供register_framebuffer接口给具体framebuffer驱动编写着来注册fb设备的
/** * register_framebuffer - registers a frame buffer device * @fb_info: frame buffer info structure * * Registers a frame buffer device @fb_info. * * Returns negative errno on error, or zero for success. * */ int register_framebuffer(struct fb_info *fb_info) { int i; struct fb_event event; struct fb_videomode mode; if (num_registered_fb == FB_MAX) return -ENXIO; if (fb_check_foreignness(fb_info)) return -ENOSYS; remove_conflicting_framebuffers(fb_info->apertures, fb_info->fix.id, fb_is_primary_device(fb_info)); num_registered_fb++; for (i = 0 ; i < FB_MAX; i++) if (!registered_fb[i]) break; fb_info->node = i; mutex_init(&fb_info->lock); mutex_init(&fb_info->mm_lock); fb_info->dev = device_create(fb_class, fb_info->device, MKDEV(FB_MAJOR, i), NULL, "fb%d", i); if (IS_ERR(fb_info->dev)) { /* Not fatal */ printk(KERN_WARNING "Unable to create device for framebuffer %d; errno = %ld\n", i, PTR_ERR(fb_info->dev)); fb_info->dev = NULL; } else fb_init_device(fb_info); if (fb_info->pixmap.addr == NULL) { fb_info->pixmap.addr = kmalloc(FBPIXMAPSIZE, GFP_KERNEL); if (fb_info->pixmap.addr) { fb_info->pixmap.size = FBPIXMAPSIZE; fb_info->pixmap.buf_align = 1; fb_info->pixmap.scan_align = 1; fb_info->pixmap.access_align = 32; fb_info->pixmap.flags = FB_PIXMAP_DEFAULT; } } fb_info->pixmap.offset = 0; if (!fb_info->pixmap.blit_x) fb_info->pixmap.blit_x = ~(u32)0; if (!fb_info->pixmap.blit_y) fb_info->pixmap.blit_y = ~(u32)0; if (!fb_info->modelist.prev || !fb_info->modelist.next) INIT_LIST_HEAD(&fb_info->modelist); fb_var_to_videomode(&mode, &fb_info->var); fb_add_videomode(&mode, &fb_info->modelist); registered_fb[i] = fb_info; event.info = fb_info; if (!lock_fb_info(fb_info)) return -ENODEV; fb_notifier_call_chain(FB_EVENT_FB_REGISTERED, &event); unlock_fb_info(fb_info); return 0; }
1.1.2. drivers/video/fbsys.c
a. 这个文件是处理fb在/sys目录下的一些属性文件的。
1.1.3. drivers/video/modedb.c
a. 这个文件是管理显示模式(譬如VGA、720P等就是显示模式)的
1.2. 设备驱动部分相关文件
1.2.1. drivers/video/samsung/s3cfb.c
a. 驱动主体,后续着重讲解
1.2.2. drivers/video/samsung/s3cfb_fimd6x.c
a. 很多LCD硬件操作的函数在此文件
1.2.3. arch/arm/mach-s5pv210/mach-x210.c
a. 负责提供framebuffer的platform_device数据。
1.2.4. arch/arm/plat-s5p/devs.c
a. 为platform_device提供一些硬件描述信息的
二. platform_device平台设备分析
2.1. fb的驱动是基于platform平台总线的
2.1.1. 需要有相应platform_device(注册平台设备)和platform_driver(注册平台驱动)。与前面讲的是平台驱动部分相似。
2.1.2. platform_device相关部分在arch\arm\mach-s5pv210\mach-x210.c
2.2. platform_device相关代码分析
2.2.1. s3c_device_fb定义
a. 从定义的变量中可以看出来,并没有挂接设备的私有数据到s3c_device_fb变量中,因为platform_device结构体中device结构体下的platform_data指针并没有被赋值
b. platform_data采取在其他函数重新追究platform_data的赋值办法实现的,后面介绍。
c. 结构体中定义了相关resource,这样把相关硬件添加进dev
struct platform_device s3c_device_fb = { .name = "s3cfb", // 平台设备的名字 .id = -1, .num_resources = ARRAY_SIZE(s3cfb_resource), // 平台设备的资源数量 .resource = s3cfb_resource, // 平台设备的资源 .dev = { .dma_mask = &fb_dma_mask, .coherent_dma_mask = 0xffffffffUL } }; static struct resource s3cfb_resource[] = { [0] = { .start = S5P_PA_LCD, .end = S5P_PA_LCD + S5P_SZ_LCD - 1, .flags = IORESOURCE_MEM, }, [1] = { .start = IRQ_LCD1, .end = IRQ_LCD1, .flags = IORESOURCE_IRQ, }, [2] = { .start = IRQ_LCD0, .end = IRQ_LCD0, .flags = IORESOURCE_IRQ, }, };
2.2.2. s3cfb_set_platdata函数
2.2.2.1. 这个函数实现了platform_device结构体中device结构体下的platform_data指针的赋值
void __init s3cfb_set_platdata(struct s3c_platform_fb *pd) { struct s3c_platform_fb *npd; // 定义一个 struct s3c_platform_fb 类型的指针 int i; if (!pd) // 如果没有传入 s3c_platform_fb 结构体变量指针,则使用默认的 pd = &default_fb_data; npd = kmemdup(pd, sizeof(struct s3c_platform_fb), GFP_KERNEL); if (!npd) printk(KERN_ERR "%s: no memory for platform data\n", __func__); else { for (i = 0; i < npd->nr_wins; i++) npd->nr_buffers[i] = 1; npd->nr_buffers[npd->default_win] = CONFIG_FB_S3C_NR_BUFFERS; // 再进一步对数据结构进行填充 s3cfb_get_clk_name(npd->clk_name); npd->clk_on = s3cfb_clk_on; npd->clk_off = s3cfb_clk_off; /* starting physical address of memory region */ npd->pmem_start = s5p_get_media_memory_bank(S5P_MDEV_FIMD, 1); /* size of memory region */ npd->pmem_size = s5p_get_media_memsize_bank(S5P_MDEV_FIMD, 1); s3c_device_fb.dev.platform_data = npd; // 把传进来的 s3c_platform_fb 结构体变量挂载到 s3c_device_fb变量中 } }
2.2.2.2. 该函数的调用
a. 程序中通过宏定义可以切换platform_data的赋值,这样比直接初始化结构体时更加灵活
b. 传入该函数的结构体ek070tn93_fb_data,该结构体与LCD硬件参数相关
#ifdef CONFIG_FB_S3C_LTE480WV s3cfb_set_platdata(<e480wv_fb_data); #endif #ifdef CONFIG_FB_S3C_EK070TN93 smdkv210_backlight_off(); s3cfb_set_platdata(&ek070tn93_fb_data); #endif
2.2.3. s3c_platform_fb ek070tn93_fb_data分析
a. 结构体lcd成员很重要
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, // 使能LCD背光 .backlight_onoff = ek070tn93_backlight_off, // 关闭LCD背光 .reset_lcd = ek070tn93_reset_lcd, // 复位LCD };
b. lcd成员分析
ba. 其实我们移植驱动时一般主要要修改此结构体
static struct s3cfb_lcd ek070tn93 = { .width = S5PV210_LCD_WIDTH, .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, }, };
三. platform_driver平台设备驱动
3.1. 注册/卸载平台驱动:s3cfb_register/s3cfb_unregister
a. 这点和之前讲解的LED platform设备驱动相似
static int __init s3cfb_register(void) { platform_driver_register(&s3cfb_driver); return 0; } static void __exit s3cfb_unregister(void) { platform_driver_unregister(&s3cfb_driver); }
b. s3cfb_driver结构体分析
static struct platform_driver s3cfb_driver = { .probe = s3cfb_probe, // 平台的probe函数 .remove = __devexit_p(s3cfb_remove), .driver = { .name = S3CFB_NAME, // 平台设备驱动的名字 s3cfb .owner = THIS_MODULE, }, }; 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; /* starting physical address of memory region */ // 显存的物理起始地址 size_t pmem_size; /* size of memory region */ // 显存的字节大小 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相关的时钟关闭 };
3.2. s3cfb_probe函数分析
3.2.1. 当devices与driver匹配成功后就会调用此函数
3.2.2. s3cfb_global结构体定义
a. 设备驱动部分封装的一个全局的结构体,这个结构体主要作用是在驱动部分的2个文件(s3cfb.c和s3cfb_fimd6x.c)的函数中做数据传递用的
struct s3cfb_global { /* general */ void __iomem *regs; // SoC中LCD控制器部分相关的寄存器地址的基地址(虚拟地址) Display Controller (FIMD)模块 struct mutex lock; // 互斥锁 struct device *dev; // 表示本fb设备的device指针 struct clk *clock; struct regulator *regulator; int irq; // 本LCD使用到的中断号 struct fb_info **fb; // fb_info 的二重指针 用来指向一个 fb_info 指针数组 struct completion fb_complete; /* fimd */ int enabled; int dsi; int interlace; enum s3cfb_output_t output; // LCD的输出模式 enum s3cfb_rgb_mode_t rgb_mode; // RGB色彩模式 struct s3cfb_lcd *lcd; // 用来描述一个LCD的硬件信息 #ifdef CONFIG_HAS_WAKELOCK struct early_suspend early_suspend; struct wake_lock idle_lock; #endif #ifdef CONFIG_CPU_FREQ struct notifier_block freq_transition; struct notifier_block freq_policy; #endif };
3.2.3. s3cfb_probe函数分析
static int __devinit s3cfb_probe(struct platform_device *pdev) { struct s3c_platform_fb *pdata; // 这个是三星封装的一个用来表示平台设备层的私有数据的结构体,在x210中定义了该结构体实体 struct s3cfb_global *fbdev; // 设备驱动部分封装的一个全局的结构体,这个结构体主要作用是在驱动部分的2个文件(s3cfb.c和s3cfb_fimd6x.c)的函数中做数据传递用的 struct resource *res; // 定义一个资源结构体指针 int i, j, ret = 0; fbdev = kzalloc(sizeof(struct s3cfb_global), GFP_KERNEL); // 给 fpdev 申请分配内存 if (!fbdev) { dev_err(&pdev->dev, "failed to allocate for " "global fb structure\n"); ret = -ENOMEM; goto err_global; } fbdev->dev = &pdev->dev; // 通过 fbdev->dev 指向 pdev->dev /sys/devices/platform/s3cfb/ 这个目录作为fb设备的父设备目录 fbdev->regulator = regulator_get(&pdev->dev, "pd"); // 调整器 : 动态电流和电压控制,具体的我也不清楚 if (!fbdev->regulator) { dev_err(fbdev->dev, "failed to get regulator\n"); ret = -EINVAL; goto err_regulator; } ret = regulator_enable(fbdev->regulator); if (ret < 0) { dev_err(fbdev->dev, "failed to enable regulator\n"); ret = -EINVAL; goto err_regulator; } pdata = to_fb_plat(&pdev->dev); // 获取平台设备层的私有数据 pdev->dev-> platform_data 存放在 pdata中 if (!pdata) { dev_err(fbdev->dev, "failed to get platform data\n"); ret = -EINVAL; goto err_pdata; } fbdev->lcd = (struct s3cfb_lcd *)pdata->lcd; // 通过fbdev->lcd 指向 pdata->lcd if (pdata->cfg_gpio) // 如果平台设备的私有数据中的cfg_gpio指向了一个有效的配置LCD相关的gpio的方法 pdata->cfg_gpio(pdev); // 则调用这个函数 if (pdata->clk_on) // 打开LCD相关的时钟设置 pdata->clk_on(pdev, &fbdev->clock); res = platform_get_resource(pdev, IORESOURCE_MEM, 0); // 获取平台设备的IO资源 if (!res) { dev_err(fbdev->dev, "failed to get io memory region\n"); ret = -EINVAL; goto err_io; } res = request_mem_region(res->start, // 请求进行物理地址到虚拟地址的映射 res->end - res->start + 1, pdev->name); if (!res) { dev_err(fbdev->dev, "failed to request io memory region\n"); ret = -EINVAL; goto err_io; } fbdev->regs = ioremap(res->start, res->end - res->start + 1); // 申请物理地址到虚拟地址的映射,将映射得到的虚拟地址存放在 fbdev->regs if (!fbdev->regs) { dev_err(fbdev->dev, "failed to remap io region\n"); ret = -EINVAL; goto err_mem; } s3cfb_set_vsync_interrupt(fbdev, 1); // 使能vsync中断(场同步信号中断) s3cfb_set_global_interrupt(fbdev, 1); // 全局中断使能: 使能视频帧中断 和 使能视频中断 s3cfb_init_global(fbdev); // 全局初始化 if (s3cfb_alloc_framebuffer(fbdev)) { // 给fb_info 申请分配内存 并构建fb_info结构体 ret = -ENOMEM; goto err_alloc; } if (s3cfb_register_framebuffer(fbdev)) { // 注册fb设备 内部其实就是调用了FB驱动框架层中 register_framebuffer 函数进行注册 ret = -EINVAL; goto err_register; } s3cfb_set_clock(fbdev); // 时钟设置 s3cfb_set_window(fbdev, pdata->default_win, 1); // 虚拟窗口相关的设置 s3cfb_display_on(fbdev); // 打开LCD显示 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) pdata->backlight_on(pdev); if (!bootloaderfb && pdata->reset_lcd) pdata->reset_lcd(pdev); #endif #ifdef CONFIG_HAS_EARLYSUSPEND fbdev->early_suspend.suspend = s3cfb_early_suspend; fbdev->early_suspend.resume = s3cfb_late_resume; fbdev->early_suspend.level = EARLY_SUSPEND_LEVEL_DISABLE_FB; register_early_suspend(&fbdev->early_suspend); #endif ret = device_create_file(&(pdev->dev), &dev_attr_win_power); // 在平台设备下 /sys/devices/platform/pdev_dev/dev_attr_win_power 属性文件 if (ret < 0) // pdev_dev表示的就是我们的平台设备的名字 dev_err(fbdev->dev, "failed to add sysfs entries\n"); dev_info(fbdev->dev, "registered successfully\n"); #if !defined(CONFIG_FRAMEBUFFER_CONSOLE) && defined(CONFIG_LOGO) // 下面这个是处理Linux启动logo 相关的代码 if (fb_prepare_logo( fbdev->fb[pdata->default_win], FB_ROTATE_UR)) { 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); } #endif mdelay(100); if (pdata->backlight_on) // 打开背光 pdata->backlight_on(pdev); return 0; err_irq: s3cfb_display_off(fbdev); s3cfb_set_window(fbdev, pdata->default_win, 0); for (i = pdata->default_win; i < pdata->nr_wins + pdata->default_win; i++) { j = i % pdata->nr_wins; unregister_framebuffer(fbdev->fb[j]); } err_register: for (i = 0; i < pdata->nr_wins; i++) { if (i == pdata->default_win) s3cfb_unmap_default_video_memory(fbdev->fb[i]); framebuffer_release(fbdev->fb[i]); } kfree(fbdev->fb); err_alloc: iounmap(fbdev->regs); err_mem: release_mem_region(res->start, res->end - res->start + 1); err_io: pdata->clk_off(pdev, &fbdev->clock); err_pdata: regulator_disable(fbdev->regulator); err_regulator: kfree(fbdev); err_global: return ret; }
3.2.4. s3cfb_init_global函数
static int s3cfb_init_global(struct s3cfb_global *ctrl) { ctrl->output = OUTPUT_RGB; // 设置初始模式 ctrl->rgb_mode = MODE_RGB_P; // 设置RGB色彩模式 init_completion(&ctrl->fb_complete); // 初始化完成量(注: 完成量也是一种内核提供的同步机制) mutex_init(&ctrl->lock); s3cfb_set_output(ctrl); // 寄存器配置LCD的输出模式 s3cfb_set_display_mode(ctrl); // 寄存器配置LCD的显示模式 s3cfb_set_polarity(ctrl); // 寄存器配置信号电平翻转 s3cfb_set_timing(ctrl); // 寄存器配置LCD时序参数 s3cfb_set_lcd_size(ctrl); // 寄存器配置LCD的水平、垂直像素大小 return 0; }
3.2.5. s3cfb_alloc_framebuffer函数
static int s3cfb_alloc_framebuffer(struct s3cfb_global *ctrl) { struct s3c_platform_fb *pdata = to_fb_plat(ctrl->dev); // 通过 ctrl->dev 去获取平台设备的私有数据 int ret, i; ctrl->fb = kmalloc(pdata->nr_wins * // 给ctrl->fb 的这个fb_info指针数组分配内存 sizeof(*(ctrl->fb)), GFP_KERNEL); // 数量 nr_wins if (!ctrl->fb) { dev_err(ctrl->dev, "not enough memory\n"); ret = -ENOMEM; goto err_alloc; } for (i = 0; i < pdata->nr_wins; i++) { // 给fb_info 指针数组中的每一个指针申请分配内存 ctrl->fb[i] = framebuffer_alloc(sizeof(*ctrl->fb), ctrl->dev); if (!ctrl->fb[i]) { dev_err(ctrl->dev, "not enough memory\n"); ret = -ENOMEM; goto err_alloc_fb; } s3cfb_init_fbinfo(ctrl, i); // 初始化fb_info 这个结构体 就是去构建fb_info if (i == pdata->default_win) { if (s3cfb_map_video_memory(ctrl->fb[i])) { // 给FB显存确定内存地址和分配空间(注意只是对默认的fb设备分配了,一个虚拟的显示窗口其实就是抽象为一个fb设备,多个窗口其实是会进行叠加的) dev_err(ctrl->dev, "failed to map video memory " "for default window (%d)\n", i); ret = -ENOMEM; goto err_map_video_mem; } } } return 0; err_alloc_fb: while (--i >= 0) { if (i == pdata->default_win) s3cfb_unmap_default_video_memory(ctrl->fb[i]); err_map_video_mem: framebuffer_release(ctrl->fb[i]); } kfree(ctrl->fb); err_alloc: return ret; } struct fb_info *framebuffer_alloc(size_t size, struct device *dev) { #define BYTES_PER_LONG (BITS_PER_LONG/8) #define PADDING (BYTES_PER_LONG - (sizeof(struct fb_info) % BYTES_PER_LONG)) int fb_info_size = sizeof(struct fb_info); // 获取fb_info结构体类型的字节大小 struct fb_info *info; char *p; if (size) fb_info_size += PADDING; p = kzalloc(fb_info_size + size, GFP_KERNEL); if (!p) return NULL; info = (struct fb_info *) p; if (size) info->par = p + fb_info_size; info->device = dev; // 指定我们的 fb 设备的父类设备是平台设备 /sys/devices/platform/plat_xxxdev/ 这个目录,也就是我们将来创建的设备就在这个目录下 #ifdef CONFIG_FB_BACKLIGHT mutex_init(&info->bl_curve_mutex); #endif return info; #undef PADDING #undef BYTES_PER_LONG } static int s3cfb_map_video_memory(struct fb_info *fb) { struct fb_fix_screeninfo *fix = &fb->fix; struct s3cfb_window *win = fb->par; struct s3cfb_global *fbdev = platform_get_drvdata(to_platform_device(fb->device)); struct s3c_platform_fb *pdata = to_fb_plat(fbdev->dev); if (win->owner == DMA_MEM_OTHER) { fix->smem_start = win->other_mem_addr; fix->smem_len = win->other_mem_size; return 0; } if (fb->screen_base) // 如果我们之前就已经确定了FB的显存地址的虚拟地址,那么就直接退出,因为这个函数的作用就是给显存确定虚拟内存地址并分配内存空间 return 0; if (pdata && pdata->pmem_start && (pdata->pmem_size >= fix->smem_len)) { // 如果我们的平台设备中的私有数据中已经确定了显存的物理地址和大小 fix->smem_start = pdata->pmem_start; // 那么就使用平台设备私有数据中定义的 fb->screen_base = ioremap_wc(fix->smem_start, pdata->pmem_size); } else fb->screen_base = dma_alloc_writecombine(fbdev->dev, // 否则的话我们就自己申请分配显存空间 PAGE_ALIGN(fix->smem_len), (unsigned int *) &fix->smem_start, GFP_KERNEL); if (!fb->screen_base) return -ENOMEM; dev_info(fbdev->dev, "[fb%d] dma: 0x%08x, cpu: 0x%08x, " "size: 0x%08x\n", win->id, (unsigned int)fix->smem_start, (unsigned int)fb->screen_base, fix->smem_len); memset(fb->screen_base, 0, fix->smem_len); // 将FB显存清零 win->owner = DMA_MEM_FIMD; return 0; }

浙公网安备 33010602011771号