前言
前面我们了解了LCD的基本架构《Linux驱动分析之LCD驱动架构》,接下来我们拿个具体的实例来分析分析。这样可以了解其大概是如何使用和工作的。
FrameBuffer驱动分析
内核版本:4.20
芯片平台:s3c2410
依然是使用之前的方式进行分析,大部分内容在注释。
(1)装载和卸载函数
staticstructplatform_drivers3c2410fb_driver= { .probe=s3c2410fb_probe, .remove=s3c2410fb_remove, .suspend=s3c2410fb_suspend, .resume=s3c2410fb_resume, .driver= { .name="s3c2410-lcd", }, }; int__inits3c2410fb_init(void) { intret=platform_driver_register(&s3c2410fb_driver); returnret; } staticvoid__exits3c2410fb_cleanup(void) { platform_driver_unregister(&s3c2410fb_driver); } module_init(s3c2410fb_init); module_exit(s3c2410fb_cleanup);
上面比较简单,就是注册一个platform_driver, 控制器一般都是使用platform总线。
(2)probe()
staticints3c2410fb_probe(structplatform_device*pdev) { returns3c24xxfb_probe(pdev, DRV_S3C2410); } staticints3c24xxfb_probe(structplatform_device*pdev, enums3c_drv_typedrv_type) { structs3c2410fb_info*info; structs3c2410fb_display*display; structfb_info*fbinfo; structs3c2410fb_mach_info*mach_info; structresource*res; intret; intirq; inti; intsize; u32lcdcon1; //获取板子配置信息mach_info=dev_get_platdata(&pdev->dev); //省略......//display包含了屏幕的分辨率信息等display=mach_info->displays+mach_info->default_display; //获取中断号irq=platform_get_irq(pdev, 0); //分配一个fb_infofbinfo=framebuffer_alloc(sizeof(structs3c2410fb_info), &pdev->dev); //保存fbinfo到driver dataplatform_set_drvdata(pdev, fbinfo); info=fbinfo->par; info->dev=&pdev->dev; info->drv_type=drv_type; //获取IO资源并申请res=platform_get_resource(pdev, IORESOURCE_MEM, 0); size=resource_size(res); info->mem=request_mem_region(res->start, size, pdev->name); //映射为虚拟地址info->io=ioremap(res->start, size); //获取中断基地址info->irq_base=info->io+S3C2410_LCDINTBASE; strcpy(fbinfo->fix.id, driver_name); //操作寄存器,停止LCDC输出lcdcon1=readl(info->io+S3C2410_LCDCON1); writel(lcdcon1&~S3C2410_LCDCON1_ENVID, info->io+S3C2410_LCDCON1); //初始化固定参数fbinfo->fix.type=FB_TYPE_PACKED_PIXELS; fbinfo->fix.type_aux=0; fbinfo->fix.xpanstep=0; fbinfo->fix.ypanstep=0; fbinfo->fix.ywrapstep=0; fbinfo->fix.accel=FB_ACCEL_NONE; //初始化可变参数fbinfo->var.nonstd=0; fbinfo->var.activate=FB_ACTIVATE_NOW; fbinfo->var.accel_flags=0; fbinfo->var.vmode=FB_VMODE_NONINTERLACED; //设置操作函数fbinfo->fbops=&s3c2410fb_ops; fbinfo->flags=FBINFO_FLAG_DEFAULT; fbinfo->pseudo_palette=&info->pseudo_pal; //清除画板for (i=0; i<256; i++) info->palette_buffer[i] =PALETTE_BUFF_CLEAR; //申请中断ret=request_irq(irq, s3c2410fb_irq, 0, pdev->name, info); //获取时钟并使能info->clk=clk_get(NULL, "lcd"); clk_prepare_enable(info->clk); usleep_range(1000, 1100); info->clk_rate=clk_get_rate(info->clk); /* 计算显示所需分配的内存大小 */for (i=0; i<mach_info->num_displays; i++) { unsignedlongsmem_len=mach_info->displays[i].xres; smem_len*=mach_info->displays[i].yres; smem_len*=mach_info->displays[i].bpp; smem_len>>=3; if (fbinfo->fix.smem_len<smem_len) fbinfo->fix.smem_len=smem_len; } /* 初始化显存,这里面设置了DMA */ret=s3c2410fb_map_video_memory(fbinfo); //根据屏幕信息初始化fbinfo中的可变参数fbinfo->var.xres=display->xres; fbinfo->var.yres=display->yres; fbinfo->var.bits_per_pixel=display->bpp; //初始化LCDC相关寄存器s3c2410fb_init_registers(fbinfo); //注册framebufferret=register_framebuffer(fbinfo); return0; }
上面省略了一些错误判断。
上面总结下来就两个部分:
1. 根据屏幕信息填充fb_info, 然后调用register_framebuffer进行注册
2. LCDC相对应的寄存器的配置(硬件平台相关的)
(3)各种操作屏幕的函数
staticstructfb_opss3c2410fb_ops= { .owner=THIS_MODULE, .fb_check_var=s3c2410fb_check_var, //检查可变参数合法性 .fb_set_par=s3c2410fb_set_par, //设置可变参数 .fb_blank=s3c2410fb_blank, //设置开关屏 .fb_setcolreg=s3c2410fb_setcolreg, //设置颜色寄存器//下面三个都是使用内核自带的函数 .fb_fillrect=cfb_fillrect, //绘制矩形 .fb_copyarea=cfb_copyarea, //区域拷贝 .fb_imageblit=cfb_imageblit,//绘制位图};
简单看几个函数,其实就是对寄存器的操作。
- s3c2410fb_set_par
static int s3c2410fb_set_par(struct fb_info *info) { //设置参数信息 struct fb_var_screeninfo *var = &info->var; switch (var->bits_per_pixel) { case 32: case 16: case 12: info->fix.visual = FB_VISUAL_TRUECOLOR; break; case 1: info->fix.visual = FB_VISUAL_MONO01; break; default: info->fix.visual = FB_VISUAL_PSEUDOCOLOR; break; } info->fix.line_length = (var->xres_virtual * var->bits_per_pixel) / 8; //设置寄存器,该函数中都是寄存器的操作 s3c2410fb_activate_var(info); return 0; }
- s3c2410fb_blank
static int s3c2410fb_blank(int blank_mode, struct fb_info *info) { struct s3c2410fb_info *fbi = info->par; void __iomem *tpal_reg = fbi->io; dprintk("blank(mode=%d, info=%p)\n", blank_mode, info); tpal_reg += is_s3c2412(fbi) ? S3C2412_TPAL : S3C2410_TPAL; //根据开关设置寄存器 if (blank_mode == FB_BLANK_POWERDOWN) s3c2410fb_lcd_enable(fbi, 0); else s3c2410fb_lcd_enable(fbi, 1); if (blank_mode == FB_BLANK_UNBLANK) writel(0x0, tpal_reg); else { dprintk("setting TPAL to output 0x000000\n"); writel(S3C2410_TPAL_EN, tpal_reg); } return 0; }
这些操作函数和裸机程序基本差不多,就是一些对寄存器的操作。
总结
上面其实没有多少内容,我们并不需要过多关注细节。比如寄存器配置的含义之类的,因为每个平台都不一样,即使你把这款芯片的所有细节理解了,换个平台,依然要重新来。所以我们应该理解的是框架。
随着技术的发展,特别是GPU的出现,单纯使用Framebuffer来显示越来越少,它已经渐渐成为DRM的一部分了。特别是现在的Android设备, 对显示要求越来越高。后期会带来一些DRM相关的文章!