Framebuffer
在 Linux 中应用程序最终也是通过操作 RGB LCD 的显存来实现在 LCD 上显示字符、图片等信息。 因为虚拟内存的存在,驱动程序设置的显存和应用程序访问的显存要是同一片物理内存。
Framebuffer 诞生了, Framebuffer 翻译过来就是帧缓冲,简称 fb 。fb 是一种机制,将系统中所有跟显示有关的硬件以及软件集合起来,虚拟出一个 fb 设备。帧缓冲是linux系统中的一种显示驱动接口,它将显示设备(比如LCD)进行抽象、屏蔽了不同显示设备硬件的实现,对应用层抽象为一块显示内存(显存),它允许上层应用程序直接对显示缓冲区进行读写操作,而用户不关心物理显存的位置等具体细节,这些都由Framebuffer设备驱动来完成。
显示设备被称为 FrameBuffer 设备(帧缓冲设备),所以 LCD 显示屏自然而言就是 FrameBuffer 设备。 FrameBuffer 设备对应的设备文件为/dev/fbX(X 为数字, 0、 1、 2、 3 等) , Linux下可支持多个 FrameBuffer 设备,最多可达 32 个,分别为/dev/fb0 到/dev/fb31, 开发板出厂系统中, /dev/fb0设备节点便是 LCD 屏 。
fb 的 file_operations 操作集定义在drivers/video/fbdev/core/fbmem.c 文件中
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| 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 };
|
LCD设备节点
1 2 3 4 5 6 7 8 9 10 11
| 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 };
|
之后还要向 imx6ullalientek-emmc.dts 中的 lcdif 节点添加其他的属性信息。
属性值为“fsl,imx6ul-lcdif”和“fsl,imx28-lcdif”,因此在 Linux 源码中搜索这两个字符串即可找到 I.MX6ULL 的 LCD 驱动文件,这个文件为 drivers/video/fbdev/mxsfb.c,mxsfb.c就是 I.MX6ULL 的 LCD 驱动文件。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| 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 { } 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);
|
这是一个标准的 platform 驱动,当驱动和设备匹配以后mxsfb_probe 函数就会执行。 LCD 的驱动就是构建 fb_info,并且向系统注册 fb_info的过程。
fb_info 结构体定义在 include/linux/fb.h 文件里面
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39
| 448 struct fb_info { 449 atomic_t count; 450 int node; 451 int flags; 452 struct mutex lock; 453 struct mutex mm_lock; 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 465 466
468 struct backlight_device *bl_dev; 469 470 471 struct mutex bl_curve_mutex; 472 u8 bl_curve[FB_BACKLIGHT_LEVELS];原子哥在线教学:www.yuanzige.com 论坛:www.openedv.com 1416 I.MX6U 嵌入式 Linux 驱动开发指南 473 #endif ...... 479 struct fb_ops *fbops; 480 struct device *device; 481 struct device *dev; 482 int class_flag; ...... 486 char __iomem *screen_base; 487 unsigned long screen_size; 488 void *pseudo_palette; ...... 507 };
|
mxsfb_probe 函数的主要工作内容为 :
①、申请 fb_info。
②、初始化 fb_info 结构体中的各个成员变量。
③、初始化 eLCDIF 控制器。
④、使用 register_framebuffer 函数向 Linux 内核注册初始化好的 fb_info。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92
| 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,dev_name(&pdev->dev), host); 1417 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) * 16, GFP_KERNEL); 1463 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 }
|
NXP 提供的 fbops 为mxsfb_ops
1 2 3 4 5 6 7 8 9 10 11 12 13
| 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_init_fbinfo 函数通过调用 mxsfb_init_fbinfo_dt 函数从设备树中获取到 LCD 的各个参数信息。最后, mxsfb_init_fbinfo
函数会调用 mxsfb_map_videomem 函数申请 LCD 的帧缓冲内存(也就是显存)。
驱动编写
重点要注意三个地方:
①、 LCD 所使用的 IO 配置。
②、 LCD 屏幕节点修改,修改相应的属性值,所使用的 LCD 屏幕参数。
③、 LCD 背光节点信息修改,要根据实际所使用的背光 IO 来修改相应的设备节点信息。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42
| 1 pinctrl_lcdif_dat: lcdifdatgrp { 2 fsl,pins = < 3 MX6UL_PAD_LCD_DATA00__LCDIF_DATA00 0x79 4 MX6UL_PAD_LCD_DATA01__LCDIF_DATA01 0x79 5 MX6UL_PAD_LCD_DATA02__LCDIF_DATA02 0x79 6 MX6UL_PAD_LCD_DATA03__LCDIF_DATA03 0x79 7 MX6UL_PAD_LCD_DATA04__LCDIF_DATA04 0x79 8 MX6UL_PAD_LCD_DATA05__LCDIF_DATA05 0x79 9 MX6UL_PAD_LCD_DATA06__LCDIF_DATA06 0x79 10 MX6UL_PAD_LCD_DATA07__LCDIF_DATA07 0x79 11 MX6UL_PAD_LCD_DATA08__LCDIF_DATA08 0x79 12 MX6UL_PAD_LCD_DATA09__LCDIF_DATA09 0x79 13 MX6UL_PAD_LCD_DATA10__LCDIF_DATA10 0x79 14 MX6UL_PAD_LCD_DATA11__LCDIF_DATA11 0x79 15 MX6UL_PAD_LCD_DATA12__LCDIF_DATA12 0x79 16 MX6UL_PAD_LCD_DATA13__LCDIF_DATA13 0x79 17 MX6UL_PAD_LCD_DATA14__LCDIF_DATA14 0x79 18 MX6UL_PAD_LCD_DATA15__LCDIF_DATA15 0x79 19 MX6UL_PAD_LCD_DATA16__LCDIF_DATA16 0x79 20 MX6UL_PAD_LCD_DATA17__LCDIF_DATA17 0x79 21 MX6UL_PAD_LCD_DATA18__LCDIF_DATA18 0x79 22 MX6UL_PAD_LCD_DATA19__LCDIF_DATA19 0x79 23 MX6UL_PAD_LCD_DATA20__LCDIF_DATA20 0x79 24 MX6UL_PAD_LCD_DATA21__LCDIF_DATA21 0x79 25 MX6UL_PAD_LCD_DATA22__LCDIF_DATA22 0x79 26 MX6UL_PAD_LCD_DATA23__LCDIF_DATA23 0x79 27 >; 28 }; 29 30 pinctrl_lcdif_ctrl: lcdifctrlgrp { 31 fsl,pins = < 32 MX6UL_PAD_LCD_CLK__LCDIF_CLK 0x79 33 MX6UL_PAD_LCD_ENABLE__LCDIF_ENABLE 0x79 34 MX6UL_PAD_LCD_HSYNC__LCDIF_HSYNC 0x79 35 MX6UL_PAD_LCD_VSYNC__LCDIF_VSYNC 0x79 36 >; 37 pinctrl_pwm1: pwm1grp { 38 fsl,pins = < 39 MX6UL_PAD_GPIO1_IO08__PWM1_OUT 0x110b0 40 >; 41 };
|
之后修改lcdif节点
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32
| &lcdif{ pinctrl-names = "default"; pinctrl-0 = <&pinctrl_lcdif_dat &pinctrl_lcdif_ctrl >; display = <&display0>; status = "okay"; display0: display { bits-per-pixel = <24>; bus-width = <24>;
display-timings { native-mode = <&timing0>; timing0: timing0 { clock-frequency = <33300000>; hactive = <800>; vactive = <480>; hfront-porch = <210>; hback-porch = <46>; hsync-len = <1>; vback-porch = <23>; vfront-porch = <22>; vsync-len = <1>;
hsync-active = <0>; vsync-active = <0>; de-active = <1>; pixelclk-active = <0>; }; }; }; };
|
背光节点信息
1 2 3 4 5
| 1 pinctrl_pwm1: pwm1grp { 2 fsl,pins = < 3 MX6UL_PAD_GPIO1_IO08__PWM1_OUT 0x110b0 4 >; 5 };
|
LCD 背光要用到 PWM1,因此也要设置 PWM1 节点,在 imx6ull.dtsi 文件中找到如下内容:
1 2 3 4 5 6 7 8 9
| 1 pwm1: pwm@02080000 { 2 compatible = "fsl,imx6ul-pwm", "fsl,imx27-pwm"; 3 reg = <0x02080000 0x4000>; 4 interrupts = <GIC_SPI 83 IRQ_TYPE_LEVEL_HIGH>; 5 clocks = <&clks IMX6UL_CLK_PWM1>, 6 <&clks IMX6UL_CLK_PWM1>; 7 clock-names = "ipg", "per"; 8 #pwm-cells = <2>; 9 };
|
在整个 Linux 源码文件中搜索 compatible 属性的这两个值即可找到 imx6ull 的 pwm 驱动文件。
继续在 imx6ull-alientek-emmc.dts 文件中找到向 pwm1追加的内容,如下所示:
1 2 3 4 5
| 1 &pwm1 { 2 pinctrl-names = "default"; 3 pinctrl-0 = <&pinctrl_pwm1>; 4 status = "okay"; 5 };
|
要让Linux知道PWM1_OUT是控制背光的,需要一个节点来将 LCD 背光和 PWM1_OUT连 接 起 来 。 这 个 节 点 就 是 backlight , backlight 节 点 描 述 可 以 参 考Documentation/devicetree/indings/video/backlight/pwm-backlight.txt 这个文档,此文档详细讲解了
backlight 节点该如何去创建,这里大概总结一下:
- 节点名称要为“backlight”。
- 节点的 compatible 属性值要为“pwm-backlight”,因此可以通过在 Linux 内核中搜索“ pwm-backlight ” 来 查 找 PWM 背 光 控 制 驱 动 程 序 , 这 个 驱 动 程 序 文 件 为drivers/video/backlight/pwm_bl.c,感兴趣的可以去看一下这个驱动程序。
- pwms属性用于描述背光所使用的PWM以及PWM频率,比如本章我们要使用的pwm1,pwm 频率设置为 200Hz(NXP 官方推荐设置)。
- brightness-levels 属性描述亮度级别,范围为 0~255, 0 表示 PWM 占空比为 0%,也就是亮度最低, 255 表示 100%占空比,也就是亮度最高
- default-brightness-level 属性为默认亮度级别。
1 2 3 4 5 6 7
| backlight { compatible = "pwm-backlight"; pwms = <&pwm1 0 5000000>; brightness-levels = <0 4 8 16 32 64 128 255>; default-brightness-level = <6>; status = "okay"; };
|
运行测试
设置
使能 Linux logo 显示 ,make menuconfig打开 Linux内核图形化配置界面,按下路径找到对应的配置项:

重新编译 Linux 内核,编译完成以后使用新编译出来的 imx6ull-alientek-emmc.dtb 和 zImage 镜像启动系统 ,如果显示正常的话,LCD左上角会出现一个彩色的小企鹅logo

设置 LCD 作为终端控制台
设置uboot中的bootargs
1 2
| setenv bootargs 'console=tty1 console=ttymxc0,115200 root=/dev/nfs rw nfsroot=192.168.1.250: /home/moss/linux/nfs/rootfs ip=192.168.0.128:192.168.1.250:192.168.0.1:255.255.255.0::eth0:off'
|
设置了两遍 console,第一次设置 console=tty1,也就是设置 LCD 屏幕为控制台,第二遍又设置 console=ttymxc0,115200,也就是设置串口也作为控制台。相当于我们打开了两个 console,一个是 LCD,一个是串口,大家重启开发板就会发现 LCD 和串口都会显示 Linux 启动 log 信息。
修改/etc/inittab 文件
打开开发板根文件系统中的/etc/inittab 文件,在里面加入下面这一行:
添加完成以后的/etc/inittab 文件内容如图
·····
修改完成以后保存/etc/inittab 并退出,然后重启开发板,重启以后开发板 LCD 屏幕最后一行会显示下面一行语句:
1
| Please press Enter to activate this console.
|
至此,我们就拥有了两套终端,一个是基于串口的 SecureCRT,一个就是我们开发板的 LCD屏幕,但是为了方便调试,我们以后还是以 SecureCRT 为主。我们可以通过下面这一行命令向LCD 屏幕输出“hello linux!”
1
| echo hello linux > /dev/tty1
|
通过设置背光等级来实现 LCD 背光亮度的调节,进入如下目录:/sys/devices/platform/backlight/backlight/backlight

1
| echo 7 > brightness //设置背光等级为7
|