搜索
bottom↓
回复: 1

【正点原子Linux连载】第五十九章Linux LCD驱动实验--摘自【正点原子】I.MX6U嵌入式Linux驱动开发指南

[复制链接]

出0入234汤圆

发表于 2020-7-8 10:16:32 | 显示全部楼层 |阅读模式
本帖最后由 正点原子 于 2020-10-26 11:59 编辑

1)实验平台:正点原子阿尔法Linux开发板
2)平台购买地址:https://item.taobao.com/item.htm?id=603672744434
3)全套实验源码+手册+视频下载地址:http://www.openedv.com/thread-300792-1-1.html  
4)对正点原子Linux感兴趣的同学可以加群讨论:
876919289 QQ群头像.png
5)关注正点原子公众号,获取最新资料


100846rel79a9p4uelap24.jpg

100846f1ce1fg14zbg0va4.png

第五十九章Linux LCD驱动实验


        LCD是很常用的一个外设,在裸机篇中我们讲解了如何编写LCD裸机驱动,在Linux下LCD的使用更加广泛,在搭配QT这样的GUI库下可以制作出非常精美的UI界面。本章我们就来学习一下如何在Linux下驱动LCD屏幕。

59.1 Linux下LCD驱动简析
59.1.1 Framebuffer设备
        先来回顾一下裸机的时候LCD驱动是怎么编写的,裸机LCD驱动编写流程如下:
        ①、初始化I.MX6U的eLCDIF控制器,重点是LCD屏幕宽(width)、高(height)、hspw、hbp、hfp、vspw、vbp和vfp等信息。
        ②、初始化LCD像素时钟。
        ③、设置RGBLCD显存。
        ④、应用程序直接通过操作显存来操作LCD,实现在LCD上显示字符、图片等信息。
        在Linux中应用程序最终也是通过操作RGB LCD的显存来实现在LCD上显示字符、图片等信息。在裸机中我们可以随意的分配显存,但是在Linux系统中内存的管理很严格,显存是不需要申请的,不是你想用就能用的。而且因为虚拟内存的存在,驱动程序设置的显存和应用程序访问的显存要是同一片物理内存。
为了解决上述问题,Framebuffer诞生了,Framebuffer翻译过来就是帧缓冲,简称fb,因此大家在以后的Linux学习中见到“Framebuffer”或者“fb”的话第一反应应该想到RGBLCD或者显示设备。fb是一种机制,将系统中所有跟显示有关的硬件以及软件集合起来,虚拟出一个fb设备,当我们编写好LCD驱动以后会生成一个名为/dev/fbX(X=0~n)的设备,应用程序通过访问/dev/fbX这个设备就可以访问LCD。NXP官方的Linux内核默认已经开启了LCD驱动,因此我们是可以看到/dev/fb0这样一个设备,如图59.1.1.1所示:
image002.jpg

图59.1.1.1 /dev/fb0设备文件

        图59.1.1.1中的/dev/fb0就是LCD对应的设备文件,/dev/fb0是个字符设备,因此肯定有file_operations操作集,fb的file_operations操作集定义在drivers/video/fbdev/core/fbmem.c文件中,如下所示:
示例代码59.1.1.1 fb设备的操作集
  1. 1495staticconststruct file_operations fb_fops ={
  2. 1496.owner         = THIS_MODULE,
  3. 1497.read                 = fb_read,
  4. 1498.write         = fb_write,
  5. 1499.unlocked_ioctl = fb_ioctl,
  6. 1500 #ifdef CONFIG_COMPAT
  7. 1501.compat_ioctl = fb_compat_ioctl,
  8. 1502 #endif
  9. 1503.mmap                 = fb_mmap,
  10. 1504.open                 = fb_open,
  11. 1505.release         = fb_release,
  12. 1506 #ifdef HAVE_ARCH_FB_UNMAPPED_AREA
  13. 1507.get_unmapped_area = get_fb_unmapped_area,
  14. 1508 #endif
  15. 1509 #ifdef CONFIG_FB_DEFERRED_IO
  16. 1510.fsync         = fb_deferred_io_fsync,
  17. 1511 #endif
  18. 1512.llseek         = default_llseek,
  19. 1513};
复制代码

        关于fb的详细处理过程就不去深究了,本章我们的重点是驱动起来ALPHA开发板上的LCD。
59.1.2LCD驱动简析
        LCD裸机例程主要分两部分:
        ①、获取LCD的屏幕参数。
        ②、根据屏幕参数信息来初始化eLCDIF接口控制器。
        不同分辨率的LCD屏幕其eLCDIF控制器驱动代码都是一样的,只需要修改好对应的屏幕参数即可。屏幕参数信息属于屏幕设备信息内容,这些肯定是要放到设备树中的,因此我们本章实验的主要工作就是修改设备树,NXP官方的设备树已经添加了LCD设备节点,只是此节点的LCD屏幕信息是针对NXP官方EVK开发板所使用的4.3寸480*272编写的,我们需要将其改为我们所使用的屏幕参数。
        我们简单看一下NXP官方编写的Linux下的LCD驱动,打开imx6ull.dtsi,然后找到lcdif节点内容,如下所示:
示例代码59.1.2.1 imx6ull.dtsi文件中lcdif节点内容
  1. 1  lcdif: lcdif@021c8000 {
  2. 2              compatible ="fsl,imx6ul-lcdif","fsl,imx28-lcdif";
  3. 3              reg =<0x021c80000x4000>;
  4. 4              interrupts =<GIC_SPI 5 IRQ_TYPE_LEVEL_HIGH>;
  5. 5              clocks =<&clks IMX6UL_CLK_LCDIF_PIX>,
  6. 6<&clks IMX6UL_CLK_LCDIF_APB>,
  7. 7<&clks IMX6UL_CLK_DUMMY>;
  8. 8              clock-names ="pix","axi","disp_axi";
  9. 9              status ="disabled";
  10. 10};
复制代码

        示例代码59.1.2.1中的lcdif节点信息是所有使用I.MX6ULL芯片的板子所共有的,并不是完整的lcdif节点信息。像屏幕参数这些需要根据不同的硬件平台去添加,比如imx6ull-alientek-emmc.dts文件中会想lcdif节点添加其他的属性信息。从示例代码59.1.2.1可以看出lcdif节点的compatible属性值为“fsl,imx6ul-lcdif”和“fsl,imx28-lcdi”,因此在Linux源码中搜索这两个字符串即可找到I.MX6ULL的LCD驱动文件,这个文件为drivers/video/fbdev/mxsfb.c,mxsfb.c就是I.MX6ULL的LCD驱动文件,在此文件中找到如下内容:
示例代码59.1.2.2 platform下的LCD驱动
  1. 1362staticconststruct of_device_id mxsfb_dt_ids[]={
  2. 1363{.compatible ="fsl,imx23-lcdif",.data =&mxsfb_devtype[0],},
  3. 1364{.compatible ="fsl,imx28-lcdif",.data =&mxsfb_devtype[1],},
  4. 1365{/* sentinel */}
  5. 1366};
  6. ......
  7. 1625staticstruct platform_driver mxsfb_driver ={
  8. 1626.probe = mxsfb_probe,
  9. 1627.remove = mxsfb_remove,
  10. 1628.shutdown = mxsfb_shutdown,
  11. 1629.id_table = mxsfb_devtype,
  12. 1630.driver ={
  13. 1631.name = DRIVER_NAME,
  14. 1632.of_match_table = mxsfb_dt_ids,
  15. 1633.pm =&mxsfb_pm_ops,
  16. 1634},
  17. 1635};
  18. 1636
  19. 1637 module_platform_driver(mxsfb_driver);
复制代码

        从示例代码59.1.2.2可以看出,这是一个标准的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文件里面,内容如下(省略掉条件编译):
示例代码59.1.2.3 fb_info结构体
  1. 448struct fb_info {
  2. 449     atomic_t count;
  3. 450int node;
  4. 451int flags;
  5. 452struct mutex lock;                /* 互斥锁                        */
  6. 453struct mutex mm_lock;                /* 互斥锁,用于fb_mmap和smem_*域*/
  7. 454struct fb_var_screeninfo var;        /* 当前可变参数                */
  8. 455struct fb_fix_screeninfo fix;        /* 当前固定参数                */
  9. 456struct fb_monspecs monspecs;        /* 当前显示器特性        */
  10. 457struct work_struct queue;        /* 帧缓冲事件队列        */
  11. 458struct fb_pixmap pixmap;        /* 图像硬件映射                */
  12. 459struct fb_pixmap sprite;        /* 光标硬件映射                */
  13. 460struct fb_cmap cmap;        /* 当前调色板                */
  14. 461struct list_head modelist;        /* 当前模式列表                */
  15. 462struct fb_videomode *mode;        /* 当前视频模式                */
  16. 463
  17. 464 #ifdef CONFIG_FB_BACKLIGHT                  /* 如果LCD支持背光的话 */
  18. 465/* assigned backlight device */
  19. 466/* set before framebuffer registration,
  20. 467        remove after unregister */
  21. 468struct backlight_device *bl_dev;        /* 背光设备                */
  22. 469
  23. 470/* Backlight level curve */
  24. 471struct mutex bl_curve_mutex;
  25. 472     u8 bl_curve[FB_BACKLIGHT_LEVELS];
  26. 473 #endif
  27. ......
  28. 479struct fb_ops *fbops;                /* 帧缓冲操作函数集        */
  29. 480struct device *device;                /* 父设备                        */
  30. 481struct device *dev;                /* 当前fb设备                */
  31. 482int class_flag;                /* 私有sysfs标志        */
  32. ......
  33. 486char __iomem *screen_base;                /* 虚拟内存基地址(屏幕显存)                 */
  34. 487unsignedlong screen_size;                /* 虚拟内存大小(屏幕显存大小)         */
  35. 488void*pseudo_palette;                /* 伪16位调色板                                */
  36. ......
  37. 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函数原型如下:
intregister_framebuffer(struct fb_info *fb_info)
函数参数和返回值含义如下:
        fb_info:需要上报的fb_info。
        返回值:0,成功;负值,失败。
        接下来我们简单看一下mxsfb_probe函数,函数内容如下(有缩减):
示例代码59.1.2.4 mxsfb_probe函数
  1. 1369staticint mxsfb_probe(struct platform_device *pdev)
  2. 1370{
  3. 1371conststruct of_device_id *of_id =
  4. 1372            of_match_device(mxsfb_dt_ids,&pdev->dev);
  5. 1373struct resource *res;
  6. 1374struct mxsfb_info *host;
  7. 1375struct fb_info *fb_info;
  8. 1376struct pinctrl *pinctrl;
  9. 1377int irq = platform_get_irq(pdev,0);
  10. 1378int gpio, ret;
  11. 1379
  12. ......
  13. 1394
  14. 1395    res = platform_get_resource(pdev, IORESOURCE_MEM,0);
  15. 1396if(!res){
  16. 1397        dev_err(&pdev->dev,"Cannot get memory IO resource\n");
  17. 1398return-ENODEV;
  18. 1399}
  19. 1400
  20. 1401    host = devm_kzalloc(&pdev->dev,sizeof(struct mxsfb_info),
  21. GFP_KERNEL);
  22. 1402if(!host){
  23. 1403        dev_err(&pdev->dev,"Failed to allocate IO resource\n");
  24. 1404return-ENOMEM;
  25. 1405}
  26. 1406
  27. 1407    fb_info = framebuffer_alloc(sizeof(struct fb_info),&pdev->dev);
  28. 1408if(!fb_info){
  29. 1409        dev_err(&pdev->dev,"Failed to allocate fbdev\n");
  30. 1410        devm_kfree(&pdev->dev, host);
  31. 1411return-ENOMEM;
  32. 1412}
  33. 1413    host->fb_info = fb_info;
  34. 1414    fb_info->par = host;
  35. 1415
  36. 1416    ret = devm_request_irq(&pdev->dev, irq, mxsfb_irq_handler,0,
  37. 1417              dev_name(&pdev->dev), host);
  38. 1418if(ret){
  39. 1419        dev_err(&pdev->dev,"request_irq (%d) failed with
  40. 1420error %d\n",irq, ret);
  41. 1421        ret =-ENODEV;
  42. 1422goto fb_release;
  43. 1423}
  44. 1424
  45. 1425    host->base = devm_ioremap_resource(&pdev->dev, res);
  46. 1426if(IS_ERR(host->base)){
  47. 1427        dev_err(&pdev->dev,"ioremap failed\n");
  48. 1428        ret = PTR_ERR(host->base);
  49. 1429goto fb_release;
  50. 1430}
  51. ......
  52. 1461
  53. 1462    fb_info->pseudo_palette = devm_kzalloc(&pdev->dev,sizeof(u32)*
  54. 146316, GFP_KERNEL);
  55. 1464if(!fb_info->pseudo_palette){
  56. 1465        ret =-ENOMEM;
  57. 1466goto fb_release;
  58. 1467}
  59. 1468
  60. 1469    INIT_LIST_HEAD(&fb_info->modelist);
  61. 1470
  62. 1471    pm_runtime_enable(&host->pdev->dev);
  63. 1472
  64. 1473    ret = mxsfb_init_fbinfo(host);
  65. 1474if(ret !=0)
  66. 1475goto fb_pm_runtime_disable;
  67. 1476
  68. 1477    mxsfb_dispdrv_init(pdev, fb_info);
  69. 1478
  70. 1479if(!host->dispdrv){
  71. 1480        pinctrl = devm_pinctrl_get_select_default(&pdev->dev);
  72. 1481if(IS_ERR(pinctrl)){
  73. 1482            ret = PTR_ERR(pinctrl);
  74. 1483goto fb_pm_runtime_disable;
  75. 1484}
  76. 1485}
  77. 1486
  78. 1487if(!host->enabled){
  79. 1488        writel(0, host->base + LCDC_CTRL);
  80. 1489        mxsfb_set_par(fb_info);
  81. 1490        mxsfb_enable_controller(fb_info);
  82. 1491        pm_runtime_get_sync(&host->pdev->dev);
  83. 1492}
  84. 1493
  85. 1494    ret = register_framebuffer(fb_info);
  86. 1495if(ret !=0){
  87. 1496        dev_err(&pdev->dev,"Failed to register framebuffer\n");
  88. 1497goto fb_destroy;
  89. 1498}
  90. ......
  91. 1525return ret;
  92. 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各个寄存器相比于基地址的偏移值,如下所示:
示例代码59.1.2.4 eLCDIF各个寄存器偏移值
  1. 67 #define LCDC_CTRL                                    0x00
  2. 68 #define LCDC_CTRL1                                   0x10
  3. 69 #define LCDC_V4_CTRL2                            0x20
  4. 70 #define LCDC_V3_TRANSFER_COUNT             0x20
  5. 71 #define LCDC_V4_TRANSFER_COUNT             0x30
  6. ......
  7. 89 #define LCDC_V4_DEBUG0                          0x1d0
  8. 90 #define LCDC_V3_DEBUG0                           0x1f0
复制代码

        大家可以对比着《I.MX6ULL参考手册》中的eLCDIF章节检查一下示例代码59.1.2.4中的这些寄存器有没有错误。
        继续回到示例代码59.1.2.5中的mxsfb_probe函数,第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,内容如下:
示例代码59.1.2.5 mxsfb_ops操作集合
  1. 987staticstruct fb_ops mxsfb_ops ={
  2. 988.owner = THIS_MODULE,
  3. 989.fb_check_var = mxsfb_check_var,
  4. 990.fb_set_par = mxsfb_set_par,
  5. 991.fb_setcolreg = mxsfb_setcolreg,
  6. 992.fb_ioctl = mxsfb_ioctl,
  7. 993.fb_blank = mxsfb_blank,
  8. 994.fb_pan_display = mxsfb_pan_display,
  9. 995.fb_mmap = mxsfb_mmap,
  10. 996.fb_fillrect = cfb_fillrect,
  11. 997.fb_copyarea = cfb_copyarea,
  12. 998.fb_imageblit = cfb_imageblit,
  13. 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函数,至于其他的函数大家自行查阅。
59.2 硬件原理图分析
本章实验硬件原理图参考24.2小节即可。
59.3 LCD驱动程序编写
        前面已经说了,6ULL的eLCDIF接口驱动程序NXP已经编写好了,因此LCD驱动部分我们不需要去修改。我们需要做的就是按照所使用的LCD来修改设备树。重点要注意三个地方:
        ①、LCD所使用的IO配置。
        ①、LCD屏幕节点修改,修改相应的属性值,换成我们所使用的LCD屏幕参数。
        ②、LCD背光节点信息修改,要根据实际所使用的背光IO来修改相应的设备节点信息。
        接下来我们依次来看一下上面这两个节点改如何去修改:
        1、LCD屏幕IO配置
        首先要检查一下设备树中LCD所使用的IO配置,这个其实NXP都已经给我们写好了,不需要修改,不过我们还是要看一下。打开imx6ull-alientek-emmc.dts文件,在iomuxc节点中找到如下内容:
示例代码59.3.1 设备树LCD IO配置
  1. 1  pinctrl_lcdif_dat: lcdifdatgrp {
  2. 2      fsl,pins =<
  3. 3          MX6UL_PAD_LCD_DATA00__LCDIF_DATA00  0x79
  4. 4          MX6UL_PAD_LCD_DATA01__LCDIF_DATA01  0x79
  5. 5          MX6UL_PAD_LCD_DATA02__LCDIF_DATA02  0x79
  6. 6          MX6UL_PAD_LCD_DATA03__LCDIF_DATA03  0x79
  7. 7          MX6UL_PAD_LCD_DATA04__LCDIF_DATA04  0x79
  8. 8          MX6UL_PAD_LCD_DATA05__LCDIF_DATA05  0x79
  9. 9          MX6UL_PAD_LCD_DATA06__LCDIF_DATA06  0x79
  10. 10         MX6UL_PAD_LCD_DATA07__LCDIF_DATA07  0x79
  11. 11         MX6UL_PAD_LCD_DATA08__LCDIF_DATA08  0x79
  12. 12         MX6UL_PAD_LCD_DATA09__LCDIF_DATA09  0x79
  13. 13         MX6UL_PAD_LCD_DATA10__LCDIF_DATA10  0x79
  14. 14         MX6UL_PAD_LCD_DATA11__LCDIF_DATA11  0x79
  15. 15         MX6UL_PAD_LCD_DATA12__LCDIF_DATA12  0x79
  16. 16         MX6UL_PAD_LCD_DATA13__LCDIF_DATA13  0x79
  17. 17         MX6UL_PAD_LCD_DATA14__LCDIF_DATA14  0x79
  18. 18         MX6UL_PAD_LCD_DATA15__LCDIF_DATA15  0x79
  19. 19         MX6UL_PAD_LCD_DATA16__LCDIF_DATA16  0x79
  20. 20         MX6UL_PAD_LCD_DATA17__LCDIF_DATA17  0x79
  21. 21         MX6UL_PAD_LCD_DATA18__LCDIF_DATA18  0x79
  22. 22         MX6UL_PAD_LCD_DATA19__LCDIF_DATA19  0x79
  23. 23         MX6UL_PAD_LCD_DATA20__LCDIF_DATA20  0x79
  24. 24         MX6UL_PAD_LCD_DATA21__LCDIF_DATA21  0x79
  25. 25         MX6UL_PAD_LCD_DATA22__LCDIF_DATA22  0x79
  26. 26         MX6UL_PAD_LCD_DATA23__LCDIF_DATA23  0x79
  27. 27>;
  28. 28};
  29. 29
  30. 30 pinctrl_lcdif_ctrl: lcdifctrlgrp {
  31. 31     fsl,pins =<
  32. 32         MX6UL_PAD_LCD_CLK__LCDIF_CLK                0x79
  33. 33         MX6UL_PAD_LCD_ENABLE__LCDIF_ENABLE          0x79
  34. 34         MX6UL_PAD_LCD_HSYNC__LCDIF_HSYNC            0x79
  35. 35         MX6UL_PAD_LCD_VSYNC__LCDIF_VSYNC            0x79
  36. 36>;
  37. 37 pinctrl_pwm1: pwm1grp {
  38. 38     fsl,pins =<
  39. 39         MX6UL_PAD_GPIO1_IO08__PWM1_OUT           0x110b0
  40. 40>;
  41. 41};
复制代码

        第2~27行,子节点pinctrl_lcdif_dat,为RGB LCD的24根数据线配置项。
        第30~36行,子节点pinctrl_lcdif_ctrl,RGB LCD的4根控制线配置项,包括CLK、ENABLE、VSYNC和HSYNC。
        第37~40行,子节点pinctrl_pwm1,LCD背光PWM引脚配置项。这个引脚要根据实际情况设置,这里我们建议大家在以后的学习或工作中,LCD的背光IO尽量和半导体厂商的官方开发板一致。
        2、LCD屏幕参数节点信息修改
        继续在imx6ull-alientek-emmc.dts文件中找到lcdif节点,节点内容如下所示:
示例代码59.3.2 lcdif节点默认信息
  1. 1&lcdif {
  2. 2      pinctrl-names ="default";
  3. 3      pinctrl-0=<&pinctrl_lcdif_dat              /* 使用到的IO                */
  4. 4&pinctrl_lcdif_ctrl
  5. 5&pinctrl_lcdif_reset>;
  6. 6      display =<&display0>;
  7. 7      status ="okay";
  8. 8
  9. 9      display0: display {        /* LCD属性信息        */
  10. 10         bits-per-pixel =<16>;        /* 一个像素占用几个bit        */
  11. 11         bus-width =<24>;        /* 总线宽度        */
  12. 12
  13. 13         display-timings {
  14. 14             native-mode =<&timing0>;        /* 时序信息        */
  15. 15             timing0: timing0 {
  16. 16             clock-frequency =<9200000>;        /* LCD像素时钟,单位Hz        */
  17. 17             hactive =<480>;        /* LCD X轴像素个数        */
  18. 18             vactive =<272>;        /* LCD Y轴像素个数        */
  19. 19             hfront-porch =<8>;        /* LCD hfp参数        */
  20. 20             hback-porch =<4>;        /* LCD hbp参数        */
  21. 21             hsync-len =<41>;        /* LCD hspw参数        */
  22. 22             vback-porch =<2>;        /* LCD vbp参数        */
  23. 23             vfront-porch =<4>;        /* LCD vfp参数        */
  24. 24             vsync-len =<10>;        /* LCD vspw参数        */
  25. 25
  26. 26             hsync-active =<0>;        /* hsync数据线极性        */
  27. 27             vsync-active =<0>;        /* vsync数据线极性        */
  28. 28             de-active =<1>;        /* de数据线极性        */
  29. 29             pixelclk-active =<0>;        /* clk数据线先极性        */
  30. 30};
  31. 31};
  32. 32};
  33. 33};
复制代码

        示例代码59.3.2就是向imx6ull.dtsi文件中的lcdif节点追加的内容,我们依次来看一下示例代码59.3.2中的这些属性都是写什么含义。
        第3行,pinctrl-0属性,LCD所使用的IO信息,这里用到了pinctrl_lcdif_dat、pinctrl_lcdif_ctrl和pinctrl_lcdif_reset这三个IO相关的节点,前两个在示例代码59.3.1中已经讲解了。pinctrl_lcdif_reset是LCD复位IO信息节点,正点原子的I.MX6U-ALPHA开发板的LCD没有用到复位IO,因此pinctrl_lcdif_reset可以删除掉。
        第6行,display属性,指定LCD属性信息所在的子节点,这里为display0,下面就是display0子节点内容。
        第9~32行,display0子节点,描述LCD的参数信息,第10行的bits-per-pixel属性用于指明一个像素占用的bit数,默认为16bit。本教程我们将LCD配置为RGB888模式,因此一个像素点占用24bit,bits-per-pixel属性要改为24。第11行的bus-width属性用于设置数据线宽度,因为要配置为RGB888模式,因此bus-width也要设置为24。
        第13~30行,这几行非常重要!因为这几行设置了LCD的时序参数信息,NXP官方的EVK开发板使用了一个4.3寸的480*272屏幕,因此这里默认是按照NXP官方的那个屏幕参数设置的。每一个属性的含义后面的注释已经写的很详细了,大家自己去看就行了,这些时序参数就是我们重点要修改的,需要根据自己所使用的屏幕去修改。
        这里以正点原子的ATK7016(7寸1024*600)屏幕为例,将imx6ull-alientek-emmc.dts文件中的lcdif节点改为如下内容:
示例代码59.3.3 针对ATK7016 LCD修改后的lcdif节点信息
  1. 1&lcdif {
  2. 2      pinctrl-names ="default";
  3. 3      pinctrl-0=<&pinctrl_lcdif_dat                 /* 使用到的IO                         */
  4. 4&pinctrl_lcdif_ctrl>;
  5. 5      display =<&display0>;
  6. 6      status ="okay";
  7. 7
  8. 8      display0: display {        /* LCD属性信息        */
  9. 9          bits-per-pixel =<24>;        /* 一个像素占用24bit        */
  10. 10         bus-width =<24>;        /* 总线宽度        */
  11. 11
  12. 12         display-timings {
  13. 13             native-mode =<&timing0>;        /* 时序信息        */
  14. 14             timing0: timing0 {
  15. 15             clock-frequency =<51200000>;        /* LCD像素时钟,单位Hz        */
  16. 16             hactive =<1024>;        /* LCD X轴像素个数        */
  17. 17             vactive =<600>;                /* LCD Y轴像素个数        */
  18. 18             hfront-porch =<160>;        /* LCD hfp参数        */
  19. 19             hback-porch =<140>;        /* LCD hbp参数        */
  20. 20             hsync-len =<20>;        /* LCD hspw参数        */
  21. 21             vback-porch =<20>;        /* LCD vbp参数        */
  22. 22             vfront-porch =<12>;        /* LCD vfp参数        */
  23. 23             vsync-len =<3>;                /* LCD vspw参数        */
  24. 24
  25. 25             hsync-active =<0>;        /* hsync数据线极性        */
  26. 26             vsync-active =<0>;        /* vsync数据线极性        */
  27. 27             de-active =<1>;        /* de数据线极性        */
  28. 28             pixelclk-active =<0>;        /* clk数据线先极性        */
  29. 29};
  30. 30};
  31. 31};
  32. 32};
复制代码

        第3行,设置LCD屏幕所使用的IO,删除掉原来的pinctrl_lcdif_reset,因为没有用到屏幕复位IO,其他的IO不变。
        第9行,使用RGB888模式,所以一个像素点是24bit。
        第15~23行,ATK7016屏幕时序参数,根据自己所使用的屏幕修改即可。
3、LCD屏幕背光节点信息
正点原子的LCD接口背光控制IO连接到了I.MX6U的GPIO1_IO08引脚上,GPIO1_IO08复用为PWM1_OUT,通过PWM信号来控制LCD屏幕背光的亮度,这个我们已经在第二十九章详细的讲解过了。正点原子I.MX6U-ALPHA开发板的LCD背光引脚和NXP官方EVK开发板的背光引脚一样,因此背光的设备树节点是不需要修改的,但是考虑到其他同学可能使用别的开发板或者屏幕,LCD背光引脚和NXP官方EVK开发板可能不同,因此我们还是来看一下如何在设备树中添加背光节点信息。
        首先是GPIO1_IO08这个IO的配置,在imx6ull-alientek-emmc.dts中找到如下内容:
示例代码59.3.4 GPIO1_IO08引脚配置
  1. 1 pinctrl_pwm1: pwm1grp {
  2. 2     fsl,pins =<
  3. 3         MX6UL_PAD_GPIO1_IO08__PWM1_OUT   0x110b0
  4. 4>;
  5. 5};
复制代码

        pinctrl_pwm1节点就是GPIO1_IO08的配置节点,从第3行可以看出,设置GPIO1_IO08这个IO复用为PWM1_OUT,并且设置电气属性值为0x110b0。
        LCD背光要用到PWM1,因此也要设置PWM1节点,在imx6ull.dtsi文件中找到如下内容:
示例代码59.3.5 imx6ull.dtsi文件中的pwm1节点
  1. 1 pwm1: pwm@02080000 {
  2. 2     compatible ="fsl,imx6ul-pwm","fsl,imx27-pwm";
  3. 3     reg =<0x020800000x4000>;
  4. 4     interrupts =<GIC_SPI 83 IRQ_TYPE_LEVEL_HIGH>;
  5. 5     clocks =<&clks IMX6UL_CLK_PWM1>,
  6. 6<&clks IMX6UL_CLK_PWM1>;
  7. 7     clock-names ="ipg","per";
  8. 8     #pwm-cells =<2>;
  9. 9};
复制代码

        imx6ull.dtsi文件中的pwm1节点信息大家不要修改,如果要修改pwm1节点内容的话请在imx6ull-alientek-emmc.dts文件中修改。在整个Linux源码文件中搜索compatible属性的这两个值即可找到imx6ull的pwm驱动文件,imx6ull的PWM驱动文件为drivers/pwm/pwm-imx.c,这里我们就不详细的去分析这个文件了。继续在imx6ull-alientek-emmc.dts文件中找到向pwm1追加的内容,如下所示:
示例代码59.3.6 向pwm1节点追加的内容
  1. 1&pwm1 {
  2. 2     pinctrl-names ="default";
  3. 3     pinctrl-0=<&pinctrl_pwm1>;
  4. 4     status ="okay";
  5. 5};
复制代码

        第3行,设置pwm1所使用的IO为pinctrl_pwm1,也就是示例代码59.3.4所定义的GPIO1_IO08这个IO。
        第4行,将status设置为okay。
        如果背光用的路其他pwm,比如pwm2,那么就需要仿照示例代码59.3.6的内容,向pwm2节点追加相应的内容。pwm和相关的IO已经准备好了,但是Linux系统怎么知道PWM1_OUT就是控制LCD背光的呢?因此我们还需要一个节点来将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频率设置为5KHz(NXP官方推荐设置)。
        ④、brightness-levels属性描述亮度级别,范围为0~255,0表示PWM占空比为0%,也就是亮度最低,255表示100%占空比,也就是亮度最高。至于设置几级亮度,大家可以自行填写此属性。
        ⑤、default-brightness-level属性为默认亮度级别。
        根据上述5点设置backlight节点,这个NXP已经给我们设置好了,大家在imx6ull-alientek-emmc.dts文件中找到如下内容:
示例代码59.3.7 backlight节点内容
  1. 1 backlight {
  2. 2     compatible ="pwm-backlight";
  3. 3     pwms =<&pwm1 05000000>;
  4. 4     brightness-levels =<048163264128255>;
  5. 5     default-brightness-level =<6>;
  6. 6     status ="okay";
  7. 7};
复制代码

        第3行,设置背光使用pwm1,PWM频率为5KHz。
        第4行,设置背8级背光(0~7),分别为0、4、8、16、32、64、128、255,对应占空比为0%、1.57%、3.13%、6.27%、12.55%、25.1%、50.19%、100%,如果嫌少的话可以自行添加一些其他的背光等级值。
        第5行,设置默认背光等级为6,也就是50.19%的亮度。
        关于背光的设备树节点信息就讲到这里,整个的LCD设备树节点内容我们就讲完了,按照这些节点内容配置自己的开发板即可。
59.4 运行测试
59.4.1 LCD屏幕基本测试
        1、编译新的设备树
        上一小节我们已经配置好了设备树,所以需要输入如下命令重新编译一下设备树:
makedtbs
        等待编译生成新的imx6ull-alientek-emmc.dtb设备树文件,一会要使用新的设备树启动Linux内核。
2、使能Linuxlogo显示
        Linux内核启动的时候可以选择显示小企鹅logo,只要这个小企鹅logo显示没问题那么我们的LCD驱动基本就工作正常了。这个logo显示是要配置的,不过Linux内核一般都会默认开启logo显示,但是奔着学习的目的,我们还是来看一下如何使能Linuxlogo显示。打开Linux内核图形化配置界面,按下路径找到对应的配置项:
  1. -> Device Drivers
  2.         -> Graphics support   
  3.                 -> Bootup logo (LOGO [=y])   
  4.                         -> Standard black and white Linux logo
  5.                         -> Standard 16-color Linux logo   
  6.                         -> Standard 224-color Linux logo
复制代码

        如图59.4.1.1所示:

image004.jpg

图59.4.1.1 logo配置项

        图59.4.1.1中这三个选项分别对应黑白、16位、24位色彩格式的logo,我们把这三个都选中,都编译进Linux内核里面。设置好以后保存退出,重新编译Linux内核,编译完成以后使用新编译出来的imx6ull-alientek-emmc.dtb和zImage镜像启动系统,如果LCD驱动工作正常的话就会在LCD屏幕左上角出现一个彩色的小企鹅logo,屏幕背景色为黑色。

59.4.2 设置LCD作为终端控制台
        我们一直使用SecureCRT作为Linux开发板终端,开发板通过串口和SecureCRT进行通信。现在我们已经驱动起来LCD了,所以可以设置LCD作为终端,也就是开发板使用自己的显示设备作为自己的终端,然后接上键盘就可以直接在开发板上敲命令了,将LCD设置为终端控制台的方法如下:
        1、设置uboot中的bootargs
        重启开发板,进入Linux命令行,重新设置bootargs参数的console内容,命令如下所示:
  1. setenv bootargs 'console=tty1 console=ttymxc0,115200 root=/dev/nfs rw nfsroot=192.168.1.250:
  2. /home/zuozhongkai/linux/nfs/rootfs ip=192.168.1.251:192.168.1.250:192.168.1.1:255.255.255.0::eth0:
  3. off'
复制代码

        注意红色字体部分设置console,这里我们设置了两遍console,第一次设置console=tty1,也就是设置LCD屏幕为控制台,第二遍又设置console=ttymxc0,115200,也就是设置串口也作为控制台。相当于我们打开了两个console,一个是LCD,一个是串口,大家重启开发板就会发现LCD和串口都会显示Linux启动log信息。但是此时我们还不能使用LCD作为终端进行交互,因为我们的设置还未完成。
        2、修改/etc/inittab文件
        打开开发板根文件系统中的/etc/inittab文件,在里面加入下面这一行:
  1. tty1::askfirst:-/bin/sh
复制代码

        添加完成以后的/etc/inittab文件内容如图59.4.2.1所示:
image006.gif

图59.4.2.1 修改后的/etc/inittab文件

        修改完成以后保存/etc/inittab并退出,然后重启开发板,重启以后开发板LCD屏幕最后一行会显示下面一行语句:
  1. Please press Enter to activate this console.
复制代码

        上述提示语句说的是:按下回车键使能当前终端,我们在第五十八章已经将I.MX6U-ALPHA开发板上的KEY按键注册为了回车键,因此按下开发板上的KEY按键即可使能LCD这个终端。当然了,大家也可以接上一个USB键盘,Linux内核默认已经使能了USB键盘驱动了,因此可以直接使用USB键盘。
        至此,我们就拥有了两套终端,一个是基于串口的SecureCRT,一个就是我们开发板的LCD屏幕,但是为了方便调试,我们以后还是以SecureCRT为主。我们可以通过下面这一行命令向LCD屏幕输出“hellolinux!”
echo hello linux > /dev/tty1
59.4.3 LCD背光调节
        59.3小节已经讲过了,背光设备树节点设置了8个等级的背光调节,可以设置为0~7,我们可以通过设置背光等级来实现LCD背光亮度的调节,进入如下目录:
  1. /sys/devices/platform/backlight/backlight/backlight
复制代码

        此目录下的文件如图59.4.3.1所示:
image008.jpg

图59.4.3.1 目录下的文件和子目录

        图59.4.3.1中的brightness表示当前亮度等级,max_bgigntness表示最大亮度等级。当前这两个文件内容如图59.4.3.2所示:
image010.jpg

图59.4.3.2 brightness和max_brightness文件内容

        从图59.4.3.2可以看出,当前屏幕亮度等级为6,根据前面的分析可以,这个是50%亮度。屏幕最大亮度等级为7。如果我们要修改屏幕亮度,只需要向brightness写入需要设置的屏幕亮度等级即可。比如设置屏幕亮度等级为7,那么可以使用如下命令:
  1. echo 7 > brightness
复制代码

        输入上述命令以后就会发现屏幕亮度增大了,如果设置brightness为0的话就会关闭LCD背光,屏幕就会熄灭。
59.4.4 LCD自动关闭解决方法
        默认情况下10分钟以后LCD就会熄屏,这个并不是代码有问题,而是Linux内核设置的,就和我们用手机或者电脑一样,一段时间不操作的话屏幕就会熄灭,以节省电能。解决这个问题有多种方法,我们依次来看一下:
        1、按键盘唤醒
        最简单的就是按下回车键唤醒屏幕,我们在第58章将I.MX6U-ALPHA开发板上的KEY按键注册为了回车键,因此按下开发板上的KEY按键即可唤醒屏幕。如果开发板上没有按键的话可以外接USB键盘,然后按下USB键盘上的回车键唤醒屏幕。
        2、关闭10分钟熄屏功能
        在Linux源码中找到drivers/tty/vt/vt.c这个文件,在此文件中找到blankinterval变量,如下所示:
示例代码59.4.4.1 blankinterval变量
  1. 179staticint vesa_blank_mode;
  2. 180staticint vesa_off_interval;
  3. 181staticint blankinterval =10*60;
复制代码

        blankinterval变量控制着LCD关闭时间,默认是10*60,也就是10分钟。将blankinterval的值改为0即可关闭10分钟熄屏的功能,修改完成以后需要重新编译Linux内核,得到新的zImage,然后用新的zImage启动开发板。
        3、编写一个APP来关闭熄屏功能
        在ubuntu中新建一个名为lcd_always_on.c的文件,然后在里面输入如下所示内容:
示例代码59.4.4.2 lcd_always_on.c文件代码段
  1. 1  #include <fcntl.h>
  2. 2  #include <stdio.h>
  3. 3  #include <sys/ioctl.h>
  4. 4
  5. 5
  6. 6int main(int argc,char*argv[])
  7. 7{
  8. 8int fd;
  9. 9      fd = open("/dev/tty1", O_RDWR);
  10. 10     write(fd,"\033[9;0]",8);
  11. 11     close(fd);
  12. 12return0;
  13. 13}
复制代码

        使用如下命令编译lcd_always_on.c这个文件:
  1. arm-linux-gnueabihf-gcc lcd_always_on.c -o lcd_always_on
复制代码

        编译生成lcd_always_on以后将此可执行文件拷贝到开发板根文件系统的/usr/bin目录中,然后给予可执行权限。设置lcd_always_on这个软件为开机自启动,打开/etc/init.d/rcS,在此文件最后面加入如下内容:
示例代码59.4.4.3 lcd_always_on自启动代码
  1. 1 cd /usr/bin
  2. 2./lcd_always_on
  3. 3 cd ..
复制代码

        修改完成以后保存/etc/init.d/rcS文件,然后重启开发板即可。关于Linux下的LCD驱动我们就讲到这里。

阿莫论坛20周年了!感谢大家的支持与爱护!!

一只鸟敢站在脆弱的枝条上歇脚,它依仗的不是枝条不会断,而是自己有翅膀,会飞。

出16170入6148汤圆

发表于 2020-7-8 12:13:00 来自手机 | 显示全部楼层
打赏!

庆祝论坛“打赏”功能实施, 现在开始发技术主题,可以获得打赏
https://www.amobbs.com/thread-5735948-1-1.html
回帖提示: 反政府言论将被立即封锁ID 在按“提交”前,请自问一下:我这样表达会给举报吗,会给自己惹麻烦吗? 另外:尽量不要使用Mark、顶等没有意义的回复。不得大量使用大字体和彩色字。【本论坛不允许直接上传手机拍摄图片,浪费大家下载带宽和论坛服务器空间,请压缩后(图片小于1兆)才上传。压缩方法可以在微信里面发给自己(不要勾选“原图),然后下载,就能得到压缩后的图片】。另外,手机版只能上传图片,要上传附件需要切换到电脑版(不需要使用电脑,手机上切换到电脑版就行,页面底部)。
您需要登录后才可以回帖 登录 | 注册

本版积分规则

手机版|Archiver|amobbs.com 阿莫电子技术论坛 ( 粤ICP备2022115958号, 版权所有:东莞阿莫电子贸易商行 创办于2004年 (公安交互式论坛备案:44190002001997 ) )

GMT+8, 2024-3-29 10:05

© Since 2004 www.amobbs.com, 原www.ourdev.cn, 原www.ouravr.com

快速回复 返回顶部 返回列表