【北京迅为】i.MX6ULL终结者Linux LCD驱动实验Linux下LCD驱动简介

1 framebuffer设备

LCD是Liquid Crystal Display的简称,也就是经常所说的液晶显示器。LCD能够支持彩色图像的显示和视频的播放,是一种非常重要的输出设备。如果我们的系统要用GUI(图形界面接口),这时LCD设备驱动程序就应该编写成frambuffer接口,而不是编写成仅仅操作底层的LCD控制器接口。

framebuffer是Linux系统为显示设备提供的一个接口,它将显示缓冲区抽象,屏蔽图像硬件的底层差异,允许上层应用程序在图形模式下直接对显示缓冲区进行操作。framebuffer又叫帧缓冲,是Linux为操作显示设备提供的一个用户接口。用户应用程序可以通过framebuffer透明地访问不同类型的显示设备。从这个方面来说,framebuffer是硬件设备显示缓冲区的抽象。Linux抽象出framebuffer这个帧缓冲区可以供用户应用程序直接读写,通过更改framebuffer中的内容,就可以立刻显示在LCD显示屏上。

framebuffer是一个标准的字符设备,主设备号是29,次设备号根据缓冲区的数目而定。framebuffer对应/dev/fbn设备文件。根据显卡的多少,设备文件可能是/dev/fb0、/dev/fb1等。缓冲区设备也是一种普通的内存设备,可以直接对其进行读写。对用户程序而言,它和/dev下面的其他设备没有什么区别,用户可以把frameBuffer看成一块内存,既可以写,又可以读。显示器将根据内存数据显示对应的图像界面。这一切都由LCD控制器和响应的驱动程序来完成。NXP 官方的 Linux 内核默认已经开启了 LCD 驱动,因此我们是可以看到/dev/fb0 这样一个设备,如图 1.1所示:
在这里插入图片描述

图 1.1

上图中的/dev/fb0文件对应的就是LCD设备,,/dev/fb0 是个字符设备,因此肯定有 file_operations 操作集,fb 的 file_operations 操作集定义在 drivers/video/fbdev/core/fbmem.c 文件中,如下所示:

1495 static const struct file_operations fb_fops = {
    
     
1496 		.owner = THIS_MODULE, 
1497 		.read = fb_read, 
1498 		.write = fb_write, 
1499 		.unlocked_ioctl = fb_ioctl, 
1500 	#ifdef CONFIG_COMPAT 
1501 		.compat_ioctl = fb_compat_ioctl, 
1502 	#endif 
1503 		.mmap = fb_mmap, 
1504 		.open = fb_open, 
1505 		.release = fb_release, 
1506 	#ifdef HAVE_ARCH_FB_UNMAPPED_AREA 
1507 		.get_unmapped_area = get_fb_unmapped_area, 
1508	#endif 
1509 	#ifdef CONFIG_FB_DEFERRED_IO 
1510 		.fsync = fb_deferred_io_fsync,
1511 	#endif 
1512 		.llseek = default_llseek, 
1513 };

对于fb的详细处理过程,我们就不去深入了解了,我们的主要任务是驱动开发板上的LCD设备。

2 LCD驱动解析

既然LCD是一个外部设备,那么如果要使用LCD设备,就需要在设备树中添加LCD的相关设备节点。在NXP官方的设备树中已经有LCD设备的设备节点了,但是不适合我们开发板上的LCD设备,需要修改其中的参数来让我们开发板上的LCD设备正常工作。
首先打开imx6ull.dtsi文件,看一下有哪些和LCD相关的设备节点:

1 lcdif: lcdif@021c8000 {
    
     
2      compatible = "fsl,imx6ul-lcdif", "fsl,imx28-lcdif"; 
3      reg = <0x021c8000 0x4000>; 
4      interrupts = <GIC_SPI 5 IRQ_TYPE_LEVEL_HIGH>; 
5      clocks = <&clks IMX6UL_CLK_LCDIF_PIX>, 
6      <&clks IMX6UL_CLK_LCDIF_APB>, 
7      <&clks IMX6UL_CLK_DUMMY>; 
8      clock-names = "pix", "axi", "disp_axi"; 
9      status = "disabled"; 
10 }; 

lcdif节点在imx6ull.dtsi文件中,所有包含imx6ull.dtsi文件的开发板都有会用到这个节点,lcdif节点只是包含了一些相同的参数,不同的LCD设备会有一些不同的参数,需要在自己的设备树文件中去添加,比如在topeet_emmc_4_3.dts设备树文件中会引用lcdif节点添加其他属性信息。
在lcdif节点中的compatible属性值为“fsl,imx6ul-lcdif”和“fsl,imx28-lcdif”,然后根据这两个compatible属性值可以在内核源码中找到LCD驱动文件,找到的文件为drivers/video/fbdev/mxsfb.c,mxsfb.c就是 I.MX6ULL 的 LCD 驱动文件,在此文件中有如下内容:

1362 static const struct of_device_id mxsfb_dt_ids[] = {
    
     
1363       {
    
     .compatible = "fsl,imx23-lcdif", .data = &mxsfb_devtype[0], }, 
1364       {
    
     .compatible = "fsl,imx28-lcdif", .data = &mxsfb_devtype[1], }, 
1365       {
    
     /* sentinel */ } 
1366 }; 
...... 
1625 static struct platform_driver mxsfb_driver = {
    
     
1626   .probe = mxsfb_probe,
1627   .remove = mxsfb_remove, 
1628   .shutdown = mxsfb_shutdown, 
1629   .id_table = mxsfb_devtype, 
1630   .driver = {
    
     
1631       .name = DRIVER_NAME, 
1632       .of_match_table = mxsfb_dt_ids, 
1633       .pm = &mxsfb_pm_ops, 
1634       }, 
1635   }; 
1636 
1637 module_platform_driver(mxsfb_driver); 

首先可以看出LCD驱动文件也是platform框架,当设备和驱动匹配成功后,执行mxsfb_probe函数,在看 mxsfb_probe 函数之前我们先简单了解一下 Linux 下framebuffer 驱动的编写流程,Linux 内核将所有的 framebuffer 抽象为一个叫做 fb_info 的结构体,fb_info 结构体包含了 framebuffer 设备的完整属性和操作集合,因此每一个 framebuffer 设备都必须有一个 fb_info。换言之就是,LCD 的驱动就是构建 fb_info,并且向系统注册 fb_info的过程。fb_info 结构体定义在 include/linux/fb.h 文件里面,部分内容如下:

448 struct fb_info {
    
     
449        atomic_t count; 
450        int node; 
451        int flags; 
452        struct mutex lock; /* 互斥锁 */ 
453        struct mutex mm_lock; /* 互斥锁,用于 fb_mmap 和 smem_*域*/ 
454        struct fb_var_screeninfo var; /* 当前可变参数 */ 
455        struct fb_fix_screeninfo fix; /* 当前固定参数 */ 
456        struct fb_monspecs monspecs; /* 当前显示器特性 */ 
457        struct work_struct queue; /* 帧缓冲事件队列 */ 
458        struct fb_pixmap pixmap; /* 图像硬件映射 */ 
459        struct fb_pixmap sprite; /* 光标硬件映射 */ 
460        struct fb_cmap cmap; /* 当前调色板 */ 
461        struct list_head modelist; /* 当前模式列表 */ 
462        struct fb_videomode *mode; /* 当前视频模式 */ 
463 
464 #ifdef CONFIG_FB_BACKLIGHT /* 如果 LCD 支持背光的话 */ 
465        /* assigned backlight device */ 
466        /* set before framebuffer registration, 
467        remove after unregister */ 
468        struct backlight_device *bl_dev; /* 背光设备 */ 
469 
470        /* Backlight level curve */ 
471        struct mutex bl_curve_mutex; 
472        u8 bl_curve[FB_BACKLIGHT_LEVELS];
473 #endif 
...... 
479 struct fb_ops *fbops; /* 帧缓冲操作函数集 */ 
480        struct device *device; /* 父设备 */ 
481        struct device *dev; /* 当前 fb 设备 */ 
482        int class_flag; /* 私有 sysfs 标志 */ 
...... 
486        char __iomem *screen_base; /* 虚拟内存基地址(屏幕显存) */ 
487        unsigned long screen_size; /* 虚拟内存大小(屏幕显存大小) */ 
488        void *pseudo_palette; /* 伪 16 位调色板 */ 
...... 
507 }; 

在fb_info结构体中也有很多成员变量,其中多数我们都不用关系,需要注意的有这几个:var、fix、fbops、screen_base、screen_size 和 pseudo_palette。
在来看一下mxsfb_probe 函数实现了哪些功能:
① 申请 fb_info。
② 初始化 fb_info 结构体中的各个成员变量。
③ 初始化 eLCDIF 控制器。
④ 使用 register_framebuffer 函数向 Linux 内核注册初始化好的 fb_info。
register_framebuffer函数原型如下:
int register_framebuffer(struct fb_info *fb_info)
参数fb_info即为要注册的结构体。
下面简单看一下mxsfb_probe 函数的代码:

1369 static int mxsfb_probe(struct platform_device *pdev) 
1370 {
    
     
1371       const struct of_device_id *of_id = 
1372           of_match_device(mxsfb_dt_ids, &pdev->dev); 
1373       struct resource *res; 
1374       struct mxsfb_info *host; 
1375       struct fb_info *fb_info; 
1376       struct pinctrl *pinctrl; 
1377       int irq = platform_get_irq(pdev, 0); 
1378       int gpio, ret; 
1379 
...... 
1394 
1395       res = platform_get_resource(pdev, IORESOURCE_MEM, 0); 
1396       if (!res) {
    
     
1397           dev_err(&pdev->dev, "Cannot get memory IO resource\n"); 
1398             return -ENODEV; 
1399       }
1400 
1401       host = devm_kzalloc(&pdev->dev, sizeof(struct mxsfb_info), GFP_KERNEL); 
1402       if (!host) {
    
     
1403           dev_err(&pdev->dev, "Failed to allocate IO resource\n");
1404           return -ENOMEM; 
1405       } 
1406 
1407       fb_info = framebuffer_alloc(sizeof(struct fb_info), &pdev->dev); 
1408       if (!fb_info) {
    
     
1409           dev_err(&pdev->dev, "Failed to allocate fbdev\n"); 
1410           devm_kfree(&pdev->dev, host); 
1411           return -ENOMEM; 
1412       } 
1413       host->fb_info = fb_info; 
1414       fb_info->par = host; 
1415 
1416       ret = devm_request_irq(&pdev->dev, irq, mxsfb_irq_handler, 0, 
1417       dev_name(&pdev->dev), host); 
1418         if (ret) {
    
     
1419           dev_err(&pdev->dev, "request_irq (%d) failed with 
1420           error %d\n", irq, ret); 
1421           ret = -ENODEV; 
1422           goto fb_release; 
1423       } 
1424 
1425       host->base = devm_ioremap_resource(&pdev->dev, res); 
1426         if (IS_ERR(host->base)) {
    
     
1427           dev_err(&pdev->dev, "ioremap failed\n"); 
1428            ret = PTR_ERR(host->base); 
1429           goto fb_release; 
1430       } 
...... 
1461 
1462       fb_info->pseudo_palette = devm_kzalloc(&pdev->dev, sizeof(u32) * 
1463                                   16, GFP_KERNEL); 
1464        if (!fb_info->pseudo_palette) {
    
     
1465           ret = -ENOMEM; 
1466           goto fb_release; 
1467       } 
1468 
1469       INIT_LIST_HEAD(&fb_info->modelist); 
1470
1471       pm_runtime_enable(&host->pdev->dev); 
1472 
1473         ret = mxsfb_init_fbinfo(host); 
1474       if (ret != 0) 
1475           goto fb_pm_runtime_disable; 
1476 
1477       mxsfb_dispdrv_init(pdev, fb_info); 
1478 
1479       if (!host->dispdrv) {
    
     
1480           pinctrl = devm_pinctrl_get_select_default(&pdev->dev); 
1481           if (IS_ERR(pinctrl)) {
    
     
1482               ret = PTR_ERR(pinctrl); 
1483               goto fb_pm_runtime_disable; 
1484           } 
1485       } 
1486 
1487       if (!host->enabled) {
    
     
1488           writel(0, host->base + LCDC_CTRL); 
1489           mxsfb_set_par(fb_info); 
1490           mxsfb_enable_controller(fb_info); 
1491           pm_runtime_get_sync(&host->pdev->dev); 
1492       } 
1493 
1494       ret = register_framebuffer(fb_info); 
1495       if (ret != 0) {
    
     
1496           dev_err(&pdev->dev, "Failed to register framebuffer\n"); 
1497           goto fb_destroy; 
1498       } 
...... 
1525       return ret; 
1526 }

第 1374 行,host 结构体指针变量,表示 I.MX6ULL 的 LCD 的主控接口,mxsfb_info 结构体是 NXP 定义的针对 I.MX 系列 SOC 的 Framebuffer 设备结构体。也就是我们前面一直说的设备结构体,此结构体包含了 I.MX 系列 SOC 的 Framebuffer 设备详细信息,比如时钟、eLCDIF控制器寄存器基地址、fb_info 等。
第 1395 行,从设备树中获取 eLCDIF 接口控制器的寄存器首地址,设备树中 lcdif 节点已经设置了 eLCDIF 寄存器首地址为 0X021C8000,因此 res=0X021C8000。
第 1401 行,给 host 申请内存,host 为 mxsfb_info 类型结构体指针。
第 1407 行,给 fb_info 申请内存,也就是申请 fb_info。
第 1413~1414 行,设置 host 的 fb_info 成员变量为 fb_info,设置 fb_info 的 par 成员变量为host。通过这一步就将前面申请的 host 和 fb_info 联系在了一起。
第 1416 行,申请中断,中断服务函数为 mxsfb_irq_handler。
第 1425 行,对从设备树中获取到的寄存器首地址(res)进行内存映射,得到虚拟地址,并保存到 host 的 base 成员变量。因此通过访问 host 的 base 成员即可访问 I.MX6ULL 的整个 eLCDIF寄存器。其实在 mxsfb.c 中已经定义了 eLCDIF 各个寄存器相比于基地址的偏移值,如下所示:

67 #define 	LCDC_CTRL	 	0x00 
68 #define 	LCDC_CTRL1 		0x10 
69 #define 	LCDC_V4_CTRL2 	0x20 
70 #define 	LCDC_V3_TRANSFER_COUNT 	0x20 
71 #define 	LCDC_V4_TRANSFER_COUNT 	0x30 
...... 
89 #define 	LCDC_V4_DEBUG0			 	0x1d0 
90 #define 	LCDC_V3_DEBUG0 				0x1f0 

第1462 行,给 fb_info 中的 pseudo_palette申请内存。
第 1473 行,调用 mxsfb_init_fbinfo 函数初始化 fb_info,重点是 fb_info 的 var、fix、fbops,screen_base 和 screen_size。其中 fbops 是 Framebuffer 设备的操作集,NXP 提供的 fbops 为mxsfb_ops,内容如下:

987 static struct fb_ops mxsfb_ops = {
    
     
988 		.owner = THIS_MODULE, 
989 		.fb_check_var = mxsfb_check_var, 
990 		.fb_set_par = mxsfb_set_par, 
991 		.fb_setcolreg = mxsfb_setcolreg, 
992 		.fb_ioctl = mxsfb_ioctl, 
993 		.fb_blank = mxsfb_blank, 
994 		.fb_pan_display = mxsfb_pan_display, 
995 		.fb_mmap = mxsfb_mmap, 
996 		.fb_fillrect = cfb_fillrect, 
997 		.fb_copyarea = cfb_copyarea, 
998 		.fb_imageblit = cfb_imageblit, 
999 }; 

关于 mxsfb_ops 里面的各个操作函数这里就不去详解的介绍了。mxsfb_init_fbinfo 函数通过调用 mxsfb_init_fbinfo_dt 函数从设备树中获取到 LCD 的各个参数信息。最后,mxsfb_init_fbinfo函数会调用 mxsfb_map_videomem 函数申请 LCD 的帧缓冲内存(也就是显存)。
第 1489~1490 行,设置 eLCDIF 控制器的相应寄存器。
第 1494 行,最后调用 register_framebuffer 函数向 Linux 内核注册 fb_info。
mxsfb.c 文件很大,还有一些其他的重要函数,比如 mxsfb_remove、mxsfb_shutdown 等,这里我们就简单的介绍了一下 mxsfb_probe 函数,对于其他的函数大家可以自己学习一下。

在这里插入图片描述

猜你喜欢

转载自blog.csdn.net/BeiJingXunWei/article/details/112258700
今日推荐