[Beijing Xunwei] i.MX6ULL Terminator Linux LCD driver experiment Introduction to LCD driver under Linux

1 framebuffer device

LCD is the abbreviation of Liquid Crystal Display, which is often referred to as liquid crystal display. LCD can support the display of color images and the playback of video, and is a very important output device. If our system needs to use GUI (graphical interface interface), then the LCD device driver should be written as a frambuffer interface instead of just operating the underlying LCD controller interface.

The framebuffer is an interface provided by the Linux system for display devices. It abstracts the display buffer, shields the underlying differences in image hardware, and allows upper-level applications to directly manipulate the display buffer in graphics mode. Framebuffer, also called frame buffer, is a user interface provided by Linux for operating display devices. User applications can transparently access different types of display devices through the framebuffer. In this respect, the framebuffer is an abstraction of the display buffer of the hardware device. Linux abstracts the framebuffer, which can be directly read and written by user applications. By changing the content in the framebuffer, it can be immediately displayed on the LCD display.

The framebuffer is a standard character device, the major device number is 29, and the minor device number depends on the number of buffers. The framebuffer corresponds to the /dev/fbn device file. Depending on the number of graphics cards, the device files may be /dev/fb0, /dev/fb1, etc. The buffer device is also a common memory device, which can be read and written directly. For user programs, it is no different from other devices under /dev. Users can regard frameBuffer as a piece of memory, which can be written and read. The monitor will display the corresponding image interface according to the memory data. All this is done by the LCD controller and the corresponding driver. The NXP official Linux kernel has already enabled the LCD driver by default, so we can see a device like /dev/fb0, as shown in Figure 1.1:
Insert picture description here

Figure 1.1

The /dev/fb0 file in the above figure corresponds to the LCD device, and /dev/fb0 is a character device, so there must be a file_operations operation set. The file_operations operation set of fb is defined in the drivers/video/fbdev/core/fbmem.c file As shown below:

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 };

For the detailed processing of fb, we won't go deep into it. Our main task is to drive the LCD device on the development board.

2 LCD driver analysis

Since LCD is an external device, if you want to use LCD devices, you need to add LCD related device nodes in the device tree. There are already device nodes for LCD devices in the NXP official device tree, but they are not suitable for the LCD devices on our development board. The parameters need to be modified to make the LCD devices on our development board work normally.
First, open the imx6ull.dtsi file to see which device nodes are related to 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 }; 

The lcdif node is in the imx6ull.dtsi file. All development boards that contain the imx6ull.dtsi file will use this node. The lcdif node only contains some of the same parameters. Different LCD devices will have some different parameters. Add it in the device tree file, for example, in the topeet_emmc_4_3.dts device tree file, the lcdif node will be referenced to add other attribute information.
The compatible attribute values ​​in the lcdif node are "fsl,imx6ul-lcdif" and "fsl,imx28-lcdif", and then according to these two compatible attribute values, the LCD driver file can be found in the kernel source code, and the found file is drivers/video /fbdev/mxsfb.c, mxsfb.c is the LCD driver file of I.MX6ULL, in this file there are the following contents:

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); 

First of all, it can be seen that the LCD driver file is also the platform framework. When the device and driver are successfully matched, execute the mxsfb_probe function. Before looking at the mxsfb_probe function, let’s briefly understand the writing process of the framebuffer driver under Linux. The Linux kernel abstracts all framebuffer into one A structure called fb_info. The fb_info structure contains the complete set of attributes and operations of the framebuffer device, so every framebuffer device must have a fb_info. In other words, the LCD driver is the process of constructing fb_info and registering fb_info with the system. The fb_info structure is defined in the include/linux/fb.h file, and part of the content is as follows:

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 }; 

There are also many member variables in the fb_info structure, most of which are not related to us. The ones that need to be paid attention to are: var, fix, fbops, screen_base, screen_size and pseudo_palette.
Let's take a look at what functions the mxsfb_probe function implements:
① Apply for fb_info.
② Initialize each member variable in the fb_info structure.
③ Initialize the eLCDIF controller.
④ Use the register_framebuffer function to register the initialized fb_info to the Linux kernel.
The prototype of the register_framebuffer function is as follows: the
int register_framebuffer(struct fb_info *fb_info)
parameter fb_info is the structure to be registered.
Here is a brief look at the code of the mxsfb_probe function:

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 }

In line 1374, the host structure pointer variable represents the main control interface of the I.MX6ULL LCD. The mxsfb_info structure is a Framebuffer device structure for I.MX series SOC defined by NXP. This is the device structure we have been talking about. This structure contains the detailed information of the Framebuffer device of the I.MX series SOC, such as clock, eLCDIF controller register base address, fb_info, etc.
In line 1395, the first register address of the eLCDIF interface controller is obtained from the device tree. The lcdif node in the device tree has set the first address of the eLCDIF register to 0X021C8000, so res=0X021C8000.
Line 1401, request memory for host, host is a pointer to a structure of type mxsfb_info.
Line 1407, apply for memory for fb_info, that is, apply for fb_info.
Lines 1413~1414, set the fb_info member variable of host to fb_info, and set the par member variable of fb_info to host. Through this step, the previously applied host and fb_info are linked together.
Line 1416, apply for an interrupt, the interrupt service function is mxsfb_irq_handler.
In line 1425, memory mapping is performed on the first address (res) of the register obtained from the device tree to obtain the virtual address and save it to the base member variable of the host. Therefore, the entire eLCDIF register of i.MX6ULL can be accessed by accessing the base member of host. In fact, the offset value of each register of eLCDIF compared to the base address has been defined in mxsfb.c, as shown below:

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 

Line 1462, apply memory for pseudo_palette in fb_info.
In line 1473, call the mxsfb_init_fbinfo function to initialize fb_info, focusing on the var, fix, fbops, screen_base and screen_size of fb_info. Among them, fbops is the operation set of the Framebuffer device, the fbops provided by NXP is mxsfb_ops, and the content is as follows:

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 }; 

There is no detailed introduction about the various operation functions in mxsfb_ops. The mxsfb_init_fbinfo function obtains various parameter information of the LCD from the device tree by calling the mxsfb_init_fbinfo_dt function. Finally, the mxsfb_init_fbinfo function calls the mxsfb_map_videomem function to apply for the frame buffer memory (that is, video memory) of the LCD.
Lines 1489~1490 set the corresponding registers of the eLCDIF controller.
At line 1494, the register_framebuffer function is called to register fb_info with the Linux kernel.
The mxsfb.c file is very large, and there are some other important functions, such as mxsfb_remove, mxsfb_shutdown, etc. Here we briefly introduce the mxsfb_probe function, you can learn about other functions yourself.

Insert picture description here

Guess you like

Origin blog.csdn.net/BeiJingXunWei/article/details/112258700