Article Directory
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:
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.