三星SOC芯片上集成了显示控制器(有称作fimd的,Fully Interactive Mobile Display)。它的驱动分为mainline版本的和legacy版本的,mainline版本的是通用的,由Ben Dooks <ben@simtec.co.uk> 管理着的,代码比较规范。下面我们来分析一下这个驱动,这个驱动在driver/video/里,文件名是s3c-fb.c 与其相关的,主要还有arch/arm/plat-s5p/dev-fimd0.c和arch/arm/mach-XXXX/setup-fimd0.c 。 前者是定义platform device的,后者是定义一些比较depend on 具体芯片的东西,例如GPIO。
我们这里主要分析s3c-fb.c中的probe函数, probe函数里最重要的是s3c_fb_probe_win函数,一会也一并分析。
首先解释一个概念,很多书中有framebuffer这个概念,但是在三星的显示控制器文档或代码中,常出现win或window的概念,显示控制器可以控制0~5个windows,代码中分给它们分别编号win0, win1,win2......这里一张win或window就对应一个framebuffer, 每个framebuffer有自己的一个FBI(fb_info)结构。
代码中, 显示控制器是s3c_fb结构体, window是s3c_fb_win结构体。
代码中有两种data,一种是platform data(在板文件中定义),另一种是driver data(在驱动文件中定义),在它们各自的结构体里面,又可以分为两部份,一是用于sfb的data, 另一是用于win的data。
framebuffer是fb_info结构体,里面主要存储设置参数的数据结构有两个,fb_var_screeninfo和fb_fix_screeninfo结构体。
************************************************ platform data***************************************************
- static struct s3c_fb_platdata smdkv210_lcd0_pdata __initdata = {
- .win[0] = &smdkv210_fb_win0, -------->用于win的部分,下文称作“platform data中win的部分”
- .vidcon0 = VIDCON0_VIDOUT_RGB | VIDCON0_PNRMODE_RGB, -------->下面开始是sfb的部分,
- .vidcon1 = VIDCON1_INV_HSYNC | VIDCON1_INV_VSYNC,
- .setup_gpio = s5pv210_fb_gpio_setup_24bpp,
- };
************************************************ driver data***************************************************
- static struct s3c_fb_driverdata s3c_fb_data_s5pv210 = {
- .variant = { ---------->用于sfb的driverdata
- .nr_windows = 5,
- .vidtcon = VIDTCON0,
- .wincon = WINCON(0),
- .winmap = WINxMAP(0),
- .keycon = WKEYCON,
- .osd = VIDOSD_BASE,
- .osd_stride = 16,
- .buf_start = VIDW_BUF_START(0),
- .buf_size = VIDW_BUF_SIZE(0),
- .buf_end = VIDW_BUF_END(0),
- .palette = {
- [0] = 0x2400,
- [1] = 0x2800,
- [2] = 0x2c00,
- [3] = 0x3000,
- [4] = 0x3400,
- },
- .has_shadowcon= 1,
- .has_blendcon = 1,
- .has_alphacon = 1,
- .has_clksel = 1,
- .has_fixvclk = 1,
- },
- .win[0] = &s3c_fb_data_s5p_wins[0], ---------->用于各个win的部分,下文称作“driver data中win的部份”
- .win[1] = &s3c_fb_data_s5p_wins[1],
- .win[2] = &s3c_fb_data_s5p_wins[2],
- .win[3] = &s3c_fb_data_s5p_wins[3],
- .win[4] = &s3c_fb_data_s5p_wins[4],
- };
************************************************ s3c_fb_probe() ***************************************************
- static int __devinit s3c_fb_probe(struct platform_device *pdev)
- {
- const struct platform_device_id *platid; -------> 因为一个驱动要适合很多版本的设备,每个版本的设备的设置 参数都不一样,所以要用到platid来选择哪个版本的设备,像“s5pv210-fb”, "s3c2443-fb"..., 这些就是platid(也在s3c-fb.c中定义了), 也表明了,这个驱动能适合于这些设备。
- struct s3c_fb_driverdata *fbdrv; ----> driver data
- struct device *dev = &pdev->dev;
- struct s3c_fb_platdata *pd; ----> platform data
- struct s3c_fb *sfb; -----> 一个最重要的数据结据, 它代表了一个显示控制器,显示控制器的所有东东都放在这里了。但这里把它做成一个局部变量了。
- struct resource *res; -----> 资源
- int win;
- int default_win;
- ...
- platid = platform_get_device_id(pdev); ----> 从platform device 里 的id_entry 变量中获取platid,由一个宏实现
- fbdrv = (struct s3c_fb_driverdata *)platid->driver_data; ---> 获取platid对应的driver data,driver data在s3c-fb.c中定义,主要是一定设置参数
- ...
- pd = pdev->dev.platform_data; --> 获取platform data,它在板文件中定义,这个data里包含了关于显示控制器的数据,也包含了win的数据。
- ...
- sfb = kzalloc(sizeof(struct s3c_fb), GFP_KERNEL); ------> 给这个最重要的结构分配内存空间
- ...
- sfb->dev = dev; ---> 向sfb 填入 显示控制器的 device 结构体
- sfb->pdata = pd; ---> 向sfb 填入 显示控制器的 platform data 结构体
- sfb->variant = fbdrv->variant; ---> driver data结构体里 有 variant成员, 具体variant可以看下面s3c_fb_variant结构。
- ...
- sfb->bus_clk = clk_get(dev, "lcd"); -->用"lcd"这个名字,去clock文件中找到自己的bus clock
- ...
- clk_enable(sfb->bus_clk); ---> bus clock有什么用,我还不太清楚
- ...
- if (!sfb->variant.has_clksel) {
- sfb->lcd_clk = clk_get(dev, "sclk_fimd"); --> 如果driver data里没定义 源时钟, 就用“sclk_fimd”此名字去clock文件找到自己的源时钟
- ...
- clk_enable(sfb->lcd_clk);
- ....
- res = platform_get_resource(pdev, IORESOURCE_MEM, 0); --> 获取资源的物理起始地址,终地址,大小,类型等,放在res结构中。实际上是寄存器们的物理起,终地址。
- ...
- sfb->regs_res = request_mem_region(res->start, resource_size(res), dev_name(dev)); ---> 分配内存
- ...
- sfb->regs = ioremap(res->start, resource_size(res)); ----> 内存映射, 将寄存器的访问地址映射到刚才分配的内存上, sfb->regs为起始地址。
- ... // 中断的请求略
- platform_set_drvdata(pdev, sfb); ---> 将sfb 填入pdev->dev->p->driverdata 结构体中
- ...
- pd->setup_gpio(); --> 执行setup_gpio函数,此函数在上面说的 setup_fimd0.c 中定义了。用来配置GPIO端口给FIMD使用。
- ...
- writel(pd->vidcon1, sfb->regs + VIDCON1); -->设置VIDCON1 寄存器
- /* set video clock running at under-run */
- if (sfb->variant.has_fixvclk) { --> run vclk
- reg = readl(sfb->regs + VIDCON1);
- reg &= ~VIDCON1_VCLK_MASK;
- reg |= VIDCON1_VCLK_RUN;
- writel(reg, sfb->regs + VIDCON1);
- }
- for (win = 0; win < fbdrv->variant.nr_windows; win++)
- s3c_fb_clear_win(sfb, win); ---> 将各个window的wincon寄存器清0,VIDOSDxA, VIDOSDxB, VIDOSDxC清0,禁止update各个window的shadow
- /* initialise colour key controls */
- for (win = 0; win < (fbdrv->variant.nr_windows - 1); win++) {
- void __iomem *regs = sfb->regs + sfb->variant.keycon;
- regs += (win * 8);
- writel(0xffffff, regs + WKEYCON0);
- writel(0xffffff, regs + WKEYCON1);
- }
- /* we have the register setup, start allocating framebuffers */
- default_win = sfb->pdata->default_win; ---> platform data 在板文件中定义了
- for (i = 0; i < fbdrv->variant.nr_windows; i++) {
- win = i;
- if (i == 0)
- win = default_win;
- if (i == default_win)
- win = 0;
- if (!pd->win[win])
- continue;
- if (!pd->win[win]->win_mode.pixclock)-----> 像素时钟
- s3c_fb_missing_pixclock(&pd->win[win]->win_mode);----->如果像素时钟没预先定义,则由预先设定的刷新率和LCD参数来计算像素时钟
- ret = s3c_fb_probe_win(sfb, win, fbdrv->win[win], &sfb->windows[win]); ----->分配 及 注册framebuffer的重要函数,下面会详细说
- if (ret < 0) {
- dev_err(dev, "failed to create window %d\n", win);
- for (; win >= 0; win--)
- s3c_fb_release_win(sfb, sfb->windows[win]); ----> 注册不成功的话就释放之前注册成功过的window
- goto err_irq;
- }
- }
- }
- ...
- platform_set_drvdata(pdev, sfb);---> 再一次将sfb 填入pdev->dev->p->driverdata 结构体中,之前曾经这样操作过一次,现在再来一次,是因为sfb里的数据更新了很多
- ...
- #ifdef CONFIG_HAS_EARLYSUSPEND--> 如果有定义earlysuspend的话, 则注册early suspend函数
- sfb->early_suspend.suspend = s3c_fb_early_suspend;
- sfb->early_suspend.resume = s3c_fb_late_resume;
- sfb->early_suspend.level = EARLY_SUSPEND_LEVEL_DISABLE_FB;
- register_early_suspend(&sfb->early_suspend);
- #endif
- return 0;
- ...
- 还有一些错训处理在此略过
- } ---- > probe函数完成
************************************************ s3c_fb_probe_win() *************************************************************
- static int __devinit s3c_fb_probe_win(struct s3c_fb *sfb, unsigned int win_no, --->将显示控制器的结构体作为参数传进来,它有寄存器起始地址等丰富信息;还将win的号码也作为参数传进来
- struct s3c_fb_win_variant *variant, ----> driver data中win的部份, 它是由fbdrv->win[win]作参数传过来的。
- struct s3c_fb_win **res) ---> per window private data for each framebuffer,它里面含有指向FBI(fb_info)结构体的针指
- {
- struct fb_var_screeninfo *var;
- struct fb_videomode *initmode;
- struct s3c_fb_pd_win *windata; -->per window setup data, 也就是platform data中win的部份
- struct s3c_fb_win *win;
- struct fb_info *fbinfo;
- int palette_size;
- int ret;
- init_waitqueue_head(&sfb->vsync_info.wait); ---> 初始化等待队列头
- palette_size = variant->palette_sz * 4; ---> 调色板大小 , 这方面内容我还不了解,为什么乘以4?
- fbinfo = framebuffer_alloc(sizeof(struct s3c_fb_win) + palette_size * sizeof(u32), sfb->dev); ---> 重头戏来了, 分配fb_info结构体,返回一个fb_info结构体地址,这个结构体现在没什么内容,只赋值了par(win的起始地址)和device (父设备)两个变量,
- windata = sfb->pdata->win[win_no]; --->windata指向 platform data中win的部份
- initmode = &windata->win_mode;
- ...
- win = fbinfo->par;
- *res = win; --->par就是win的起始地址,现在把起始地址给*res,那么*res就是指向s3c_fb_win的指针
- var = &fbinfo->var; ---> 现在fbinfo->var还是空的, 只是将地址给var而已
- win->variant = *variant; --->将win的参数填进win->variant里
- win->fbinfo = fbinfo; ---让win->fbinfo指向这个FBI结构实体
- win->parent = sfb; ---win的parent是显示控制器,所以它指向sfb结构体>
- win->windata = windata; --->让win->windata指向 platform data中win的部分
- win->index = win_no;
- win->palette_buffer = (u32 *)(win + 1); --->这个也不太理解?
- ret = s3c_fb_alloc_memory(sfb, win); ----> 下面详解
- ...
- /* setup the r/b/g positions for the window's palette */ --->设置调色板, 未理解
- if (win->variant.palette_16bpp) {
- /* Set RGB 5:6:5 as default */
- win->palette.r.offset = 11;
- win->palette.r.length = 5;
- win->palette.g.offset = 5;
- win->palette.g.length = 6;
- win->palette.b.offset = 0;
- win->palette.b.length = 5;
- } else {
- /* Set 8bpp or 8bpp and 1bit alpha */
- win->palette.r.offset = 16;
- win->palette.r.length = 8;
- win->palette.g.offset = 8;
- win->palette.g.length = 8;
- win->palette.b.offset = 0;
- win->palette.b.length = 8;
- }
- /* setup the initial video mode from the window */
- fb_videomode_to_var(&fbinfo->var, initmode); --> 给FBI填上各个参数,此函数详见appendix
- fbinfo->fix.type
- = FB_TYPE_PACKED_PIXELS;
- fbinfo->fix.accel= FB_ACCEL_NONE;
- fbinfo->var.activate= FB_ACTIVATE_NOW;
- fbinfo->var.vmode= FB_VMODE_NONINTERLACED;
- fbinfo->var.bits_per_pixel = windata->default_bpp;
- fbinfo->var.width= windata->width;
- fbinfo->var.height= windata->height;
- fbinfo->fbops
- = &s3c_fb_ops; ---->对framebuffer的操作, 详见appendix
- fbinfo->flags
- = FBINFO_FLAG_DEFAULT;
- fbinfo->pseudo_palette = &win->pseudo_palette;
- /* prepare to actually start the framebuffer */
- ret = s3c_fb_check_var(&fbinfo->var, fbinfo); -->检查可变参数 Framebuffer layer call to verify the given information and allow us to update various information depending on the hardware capabilities.
- ...
- ret = fb_alloc_cmap(&fbinfo->cmap, win->variant.palette_sz, 1);
- if (ret == 0)
- fb_set_cmap(&fbinfo->cmap, fbinfo);
- else
- dev_err(sfb->dev, "failed to allocate fb cmap\n");
- s3c_fb_set_par(fbinfo);
- ...
- ret = register_framebuffer(fbinfo);
- ...
- return 0;
- } s3c_fb_probe_win结束
************************************************ s3c_fb_alloc_memory() ************************************************
- static int __devinit s3c_fb_alloc_memory(struct s3c_fb *sfb,
- struct s3c_fb_win *win)
- {
- struct s3c_fb_pd_win *windata = win->windata; ---> platform data中win的部分
- unsigned int real_size, virt_size, size;
- struct fb_info *fbi = win->fbinfo; ---> 让fbi指向FBI结构体
- dma_addr_t map_dma;
- ...
- real_size = windata->win_mode.xres * windata->win_mode.yres;
- virt_size = windata->virtual_x * windata->virtual_y; ---> 虚拟size
- size = (real_size > virt_size) ? real_size : virt_size; 一张framebuffer的大小,是按虚拟分辨率和实际分辨率两者中较大的来算的
- size *= (windata->max_bpp > 16) ? 32 : windata->max_bpp;
- size /= 8;
- fbi->fix.smem_len = size;-----> 要分配的内存大小,
- size = PAGE_ALIGN(size); ---- 页对齐
- ... 关于CMA 与ION部分省略
- fbi->screen_base = dma_alloc_writecombine(sfb->dev, size, &map_dma, GFP_KERNEL); -->分配framebuffer的内存
- memset(fbi->screen_base, 0x0, size); -->将framebuffer的内存清空为0
- fbi->fix.smem_start = map_dma;
- return 0;
- } ---->结束
*********************************************************Appendix******************************************************
- struct s3c_fb_variant {
- unsigned int is_2443:1;
- unsigned shortnr_windows;
- unsigned int vidtcon;
- unsigned shortwincon;
- unsigned shortwinmap;
- unsigned shortkeycon;
- unsigned shortbuf_start;
- unsigned shortbuf_end;
- unsigned shortbuf_size;
- unsigned shortosd;
- unsigned shortosd_stride;
- unsigned shortpalette[S3C_FB_MAX_WIN];
- unsigned int has_prtcon:1;
- unsigned int has_shadowcon:1;
- unsigned int has_blendcon:1;
- unsigned int has_alphacon:1;
- unsigned int has_clksel:1;
- unsigned int has_fixvclk:1;
- }
- /**
- * fb_videomode_to_var - convert fb_videomode to fb_var_screeninfo
- * @var: pointer to struct fb_var_screeninfo
- * @mode: pointer to struct fb_videomode
- */
- void fb_videomode_to_var(struct fb_var_screeninfo *var,
- const struct fb_videomode *mode)
- {
- var->xres = mode->xres;
- var->yres = mode->yres;
- var->xres_virtual = mode->xres;
- var->yres_virtual = mode->yres;
- var->xoffset = 0;
- var->yoffset = 0;
- var->pixclock = mode->pixclock;
- var->left_margin = mode->left_margin;
- var->right_margin = mode->right_margin;
- var->upper_margin = mode->upper_margin;
- var->lower_margin = mode->lower_margin;
- var->hsync_len = mode->hsync_len;
- var->vsync_len = mode->vsync_len;
- var->sync = mode->sync;
- var->vmode = mode->vmode & FB_VMODE_MASK;
- }
- static struct fb_ops s3c_fb_ops = {
- .owner = THIS_MODULE,
- .fb_open = s3c_fb_open,
- .fb_release = s3c_fb_release,
- .fb_check_var = s3c_fb_check_var,
- .fb_set_par = s3c_fb_set_par,
- .fb_blank = s3c_fb_blank,
- .fb_setcolreg = s3c_fb_setcolreg,
- .fb_fillrect = cfb_fillrect,
- .fb_copyarea = cfb_copyarea,
- .fb_imageblit = cfb_imageblit,
- .fb_pan_display= s3c_fb_pan_display,
- .fb_ioctl = s3c_fb_ioctl,
- };
- /**
- * struct s3c_fb_win - per window private data for each framebuffer.
- * @windata: The platform data supplied for the window configuration.
- * @parent: The hardware that this window is part of.
- * @fbinfo: Pointer pack to the framebuffer info for this window.
- * @varint: The variant information for this window.
- * @palette_buffer: Buffer/cache to hold palette entries.
- * @pseudo_palette: For use in TRUECOLOUR modes for entries 0..15/
- * @index: The window number of this window.
- * @palette: The bitfields for changing r/g/b into a hardware palette entry.
- */
- struct s3c_fb_win {
- struct s3c_fb_pd_win*windata;
- struct s3c_fb *parent;
- struct fb_info*fbinfo;
- struct s3c_fb_palettepalette;
- struct s3c_fb_win_variant variant;
- u32 *palette_buffer;
- u32 pseudo_palette[16];
- unsigned int index;
- #ifdef CONFIG_ION_EXYNOS
- struct ion_handle *fb_ion_handle;
- #endif
- };