I.MX6ULL_Linux_驅動篇(46)linux LCD驅動

LCD 是很常用的一個外設,在Linux 下LCD 的使用更加廣泛,在搭配 QT 這樣的 GUI 庫下可以制作出非常精美的 UI 界面。本章我們就來學習一下如何在 Linux 下驅動 LCD 屏幕。

Linux 下 LCD 驅動簡析

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 這樣一個設備,如圖所示:

圖中的/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 的詳細處理過程就不去深究了,本章我們的重點是驅動起來 ALPHA 開發板上的LCD。

LCD 驅動簡析

LCD 裸機例程主要分兩部分:
①、獲取 LCD 的屏幕參數。
②、根據屏幕參數信息來初始化 eLCDIF 接口控制器。

不同分辨率的 LCD 屏幕其 eLCDIF 控制器驅動代碼都是一樣的,只需要修改好對應的屏幕參數即可。屏幕參數信息屬于屏幕設備信息內容,這些肯定是要放到設備樹中的,因此我們本章實驗的主要工作就是修改設備樹, NXP 官方的設備樹已經添加了 LCD 設備節點,只是此節點的 LCD 屏幕信息是針對 NXP 官方 EVK 開發板所使用的 4.3 寸 480*272 編寫的,我們需要將其改為我們所使用的屏幕參數。我們簡單看一下 NXP 官方編寫的 Linux 下的 LCD 驅動,打開 imx6ull.dtsi,然后找到 lcdif節點內容,如下所示:

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 節點信息是所有使用 I.MX6ULL 芯片的板子所共有的,并不是完整的 lcdif 節點信息。像屏幕參數這些需要根據不同的硬件平臺去添加,比如向 imx6ullalientek-emmc.dts 中的 lcdif 節點添加其他的屬性信息。從上述代碼可以看出 lcdif 節點的 compatible 屬性值為“fsl,imx6ul-lcdif”和“fsl,imx28-lcdif”,因此在 Linux 源碼中搜索這兩個字符串即可找到 I.MX6ULL 的 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);

從上述代碼可以看出,這是一個標準的 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:需要上報的 fb_info。
返回值: 0,成功;負值,失敗。
接下來我們簡單看一下 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

繼續回到 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,內容如下:

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 函數,至于其他的函數大家自行查閱。

LCD 驅動使用

前面已經說了, 6ULL 的 eLCDIF 接口驅動程序 NXP 已經編寫好了,因此 LCD 驅動部分我們不需要去修改。我們需要做的就是按照所使用的 LCD 來修改設備樹。重點要注意三個地方:
①、 LCD 所使用的 IO 配置。
②、 LCD 屏幕節點修改,修改相應的屬性值,換成我們所使用的 LCD 屏幕參數。
③、 LCD 背光節點信息修改,要根據實際所使用的背光 IO 來修改相應的設備節點信息。
接下來我們依次來看一下上面這兩個節點改如何去修改:

LCD 屏幕 IO 配置

首先要檢查一下設備樹中 LCD 所使用的 IO 配置,這個其實 NXP 都已經給我們寫好了,不需要修改,不過我們還是要看一下。打開 imx6ull-alientek-emmc.dts 文件,在 iomuxc 節點中
找到如下內容:

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

第 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 的電氣屬性都設置為 0X79,這里將其都改為 0X49,也就是將 LCD 相關 IO 的驅動能力改為 R0/1,也就是降低 LCD 相關 IO 的驅動能力。因為前面已經說了,正點原子的 ALPHA 開發板上的 LCD 接口用了三個 SGM3157 模擬開關,為了防止模擬開關影響到網絡,因此這里需要降低 LCD 數據線的驅動能力,如果你所使用的板子沒有用到模擬開關那么就不需要將 0X79 改為 0X49。

LCD 屏幕參數節點信息修改

繼續在 imx6ull-alientek-emmc.dts 文件中找到 lcdif 節點,節點內容如下所示:

1 &lcdif {
2     pinctrl-names = "default";
3     pinctrl-0 = <&pinctrl_lcdif_dat /* 使用到的 IO */
4     &pinctrl_lcdif_ctrl
5     &pinctrl_lcdif_reset>;
6     display = <&display0>;
7     status = "okay";
8 
9    display0: display { /* LCD 屬性信息 */
10     bits-per-pixel = <16>; /* 一個像素占用幾個 bit */
11     bus-width = <24>; /* 總線寬度 */
12
13     display-timings {
14         native-mode = <&timing0>; /* 時序信息 */
15         timing0: timing0 {
16             clock-frequency = <9200000>; /* LCD 像素時鐘,單位 Hz */
17             hactive = <480>; /* LCD X 軸像素個數 */
18             vactive = <272>; /* LCD Y 軸像素個數 */
19             hfront-porch = <8>; /* LCD hfp 參數 */
20             hback-porch = <4>; /* LCD hbp 參數 */
21             hsync-len = <41>; /* LCD hspw 參數 */
22             vback-porch = <2>; /* LCD vbp 參數 */
23             vfront-porch = <4>; /* LCD vfp 參數 */
24             vsync-len = <10>; /* LCD vspw 參數 */
25
26             hsync-active = <0>; /* hsync 數據線極性 */
27             vsync-active = <0>; /* vsync 數據線極性 */
28             de-active = <1>; /* de 數據線極性 */
29             pixelclk-active = <0>; /* clk 數據線先極性 */
30         };
31     };
32   };
33 };

上述代碼就是向 imx6ull.dtsi 文件中的 lcdif 節點追加的內容,我們依次來看一下這些屬性都是寫什么含義。
第 3 行, pinctrl-0 屬性, LCD 所使用的 IO 信息,這里用到了 pinctrl_lcdif_dat、pinctrl_lcdif_ctrl和 pinctrl_lcdif_reset 這三個 IO 相關的節點,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 官方的那個屏幕參數設置的。每一個屬性的含義后面的注釋已經寫的很詳細了,大家自己去看就行了,這些時序參數就是我們重點要修改的,需要根據自己所使用的屏幕去修改。

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 中找到如下內容:

1 pinctrl_pwm1: pwm1grp {
2     fsl,pins = <
3         MX6UL_PAD_GPIO1_IO08__PWM1_OUT 0x110b0
4     >;
5 };

pinctrl_pwm1 節點就是 GPIO1_IO08 的配置節點,從第 3 行可以看出,設置 GPIO1_IO08這個 IO 復用為 PWM1_OUT,并且設置電氣屬性值為 0x110b0。
LCD 背光要用到 PWM1,因此也要設置 PWM1 節點,在 imx6ull.dtsi 文件中找到如下內容:

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

imx6ull.dtsi 文件中的 pwm1 節點信息大家不要修改,如果要修改 pwm1 節點內容的話請在imx6ull-alientek-emmc.dts 文件中修改。在整個 Linux 源碼文件中搜索 compatible 屬性的這兩個值即可找到 imx6ull 的 pwm 驅動文件, imx6ull 的 PWM 驅動文件為 drivers/pwm/pwm-imx.c,這里我們就不詳細的去分析這個文件了。繼續在 imx6ull-alientek-emmc.dts 文件中找到向 pwm1追加的內容,如下所示:

1 &pwm1 {
2     pinctrl-names = "default";
3     pinctrl-0 = <&pinctrl_pwm1>;
4     status = "okay";
5 };

第 3 行,設置 pwm1 所使用的 IO 為 pinctrl_pwm1
第 4 行,將 status 設置為 okay。
如果背光用的其他 pwm 通道,比如 pwm2,那么就需要仿照示例代碼的內容,向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-alientekemmc.dts 文件中找到如下內容:

backlight {compatible = "pwm-backlight";pwms = <&pwm1 0 5000000>;brightness-levels = <0 4 8 16 32 64 128 255>;default-brightness-level = <6>;status = "okay";
};

第 3 行,設置背光使用 pwm1, PWM 頻率為 200Hz。
第 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 設備樹節點內容我們就講完了,按照這些節點內容配置自己的開發板即可。

實驗

編譯新的設備樹

等待編譯生成新的 imx6ull-alientek-emmc.dtb 設備樹文件,一會要使用新的設備樹啟動Linux 內核。

make dtbs

使能 Linux logo 顯示

Linux 內核啟動的時候可以選擇顯示小企鵝 logo,只要這個小企鵝 logo 顯示沒問題那么我們的 LCD 驅動基本就工作正常了。這個 logo 顯示是要配置的,不過 Linux 內核一般都會默認
開啟 logo 顯示,但是奔著學習的目的,我們還是來看一下如何使能 Linux logo 顯示。打開 Linux內核圖形化配置界面,按下路徑找到對應的配置項:

圖中這三個選項分別對應黑白、 16 位、 24 位色彩格式的 logo,我們把這三個都選中,都編譯進 Linux 內核里面。設置好以后保存退出,重新編譯 Linux 內核,編譯完成以后使用新編譯出來的 imx6ull-alientek-emmc.dtb 和 zImage 鏡像啟動系統,如果 LCD 驅動工作正常的話就會在 LCD 屏幕左上角出現一個彩色的小企鵝 logo,屏幕背景色為黑色,如圖 59.4.1.2所示:

設置 LCD 作為終端控制臺

我們一直使用SecureCRT作為Linux開發板終端,開發板通過串口和SecureCRT進行通信。現在我們已經驅動起來 LCD 了,所以可以設置 LCD 作為終端,也就是開發板使用自己的顯示設備作為自己的終端,然后接上鍵盤就可以直接在開發板上敲命令了,將 LCD 設置為終端控制臺的方法如下:

設置 uboot 中的 bootargs

重啟開發板,進入 Linux 命令行,重新設置 bootargs 參數的 console 內容,命令如下所示:

setenv bootargs 'console=tty1 console=ttymxc0,115200 root=/dev/nfs rw nfsroot=192.168.1.250:/home/zuozhongkai/linux/nfs/rootfs ip=192.168.1.251:192.168.1.250:192.168.1.1:255.255.255.0::eth0:off'

注意console設置 ,這里我們設置了兩遍 console,第一次設置 console=tty1,也就是設置 LCD 屏幕為控制臺,第二遍又設置 console=ttymxc0,115200,也就是設置串口也作為控制臺。相當于我們打開了兩個 console,一個是 LCD,一個是串口,大家重啟開發板就會發現 LCD 和串口都會顯示 Linux 啟動 log 信息。但是此時我們還不能使用 LCD 作為終端進行交互,因為我們的設置還未完成。

修改/etc/inittab 文件

打開開發板根文件系統中的/etc/inittab 文件,在里面加入下面這一行:

tty1::askfirst:-/bin/sh

修改完成以后保存/etc/inittab 并退出,然后重啟開發板,重啟以后開發板 LCD 屏幕最后一行會顯示下面一行語句:

Please press Enter to activate this console.

上述提示語句說的是:按下回車鍵使能當前終端,大家可以接上一個 USB 鍵盤, Linux 內核默認已經使能了 USB 鍵盤驅動了,因此可以直接使用 USB 鍵盤。
至此,我們就擁有了兩套終端,一個是基于串口的 SecureCRT,一個就是我們開發板的 LCD屏幕,但是為了方便調試,我們以后還是以 SecureCRT 為主。我們可以通過下面這一行命令向
LCD 屏幕輸出“hello linux!”

echo hello linux > /dev/tty1

LCD 背光調節

前面已經講過了,背光設備樹節點設置了 8 個等級的背光調節,可以設置為 0~7,我們可以通過設置背光等級來實現 LCD 背光亮度的調節,進入如下目錄:

cd /sys/devices/platform/backlight/backlight/backlight

圖中的 brightness 表示當前亮度等級, max_brightness 表示最大亮度等級。當前這兩個文件內容如圖所示:

從圖中可以看出,當前屏幕亮度等級為 6,根據前面的分析可以得知,這個是 50%亮度。屏幕最大亮度等級為 7。如果我們要修改屏幕亮度,只需要向 brightness 寫入需要設置的屏幕亮
度等級即可。比如設置屏幕亮度等級為 7,那么可以使用如下命令:

echo 7 > brightness

輸入上述命令以后就會發現屏幕亮度增大了,如果設置 brightness 為 0 的話就會關閉 LCD背光,屏幕就會熄滅。

LCD 自動關閉解決方法

默認情況下 10 分鐘以后 LCD 就會熄屏,這個并不是代碼有問題,而是 Linux 內核設置的,就和我們用手機或者電腦一樣,一段時間不操作的話屏幕就會熄滅,以節省電能。解決這個問
題有多種方法, 我們依次來看一下:

按鍵盤喚醒

最簡單的就是按下回車鍵喚醒屏幕,我們在前面將 I.MX6U-ALPHA 開發板上的 KEY按鍵注冊為了回車鍵,因此按下開發板上的 KEY 按鍵即可喚醒屏幕。如果開發板上沒有按鍵的話可以外接 USB 鍵盤,然后按下 USB 鍵盤上的回車鍵喚醒屏幕。

關閉 10 分鐘熄屏功能

在 Linux 源碼中找到 drivers/tty/vt/vt.c 這個文件,在此文件中找到 blankinterval 變量,如下所示

179 static int vesa_blank_mode;
180 static int vesa_off_interval;
181 static int blankinterval = 10*60;

blankinterval 變量控制著 LCD 關閉時間,默認是 10*60,也就是 10 分鐘。將 blankinterval的值改為 0 即可關閉 10 分鐘熄屏的功能,修改完成以后需要重新編譯 Linux 內核,得到新的
zImage,然后用新的 zImage 啟動開發板。

編寫一個 APP 來關閉熄屏功能

在 ubuntu 中新建一個名為 lcd_always_on.c 的文件,然后在里面輸入如下所示內容:

#include <fcntl.h>
#include <stdio.h>
#include <sys/ioctl.h>int main(int argc, char *argv[])
{int fd;fd = open("/dev/tty1", O_RDWR);write(fd, "\033[9;0]", 8);close(fd);return 0;
}

使用如下命令編譯 lcd_always_on.c 這個文件:

arm-linux-gnueabihf-gcc lcd_always_on.c -o lcd_always_on

編譯生成 lcd_always_on 以后將此可執行文件拷貝到開發板根文件系統的/usr/bin 目錄中,然后給予可執行權限。設置 lcd_always_on 這個軟件為開機自啟動,打開/etc/init.d/rcS,在此文
件最后面加入如下內容:

/usr/bin/lcd_always_on

修改完成以后保存/etc/init.d/rcS 文件,然后重啟開發板即可。關于 Linux 下的 LCD 驅動我們就講到這里。

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/212211.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/212211.shtml
英文地址,請注明出處:http://en.pswp.cn/news/212211.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

前端入門:HTML初級指南,網頁的簡單實現!

代碼部分&#xff1a; <!DOCTYPE html> <!-- 上方為DOCTYPE聲明&#xff0c;指定文檔類型為HTML --> <html lang"en"> <!-- html標簽為整個頁面的根元素 --> <head> <!-- title標簽用于定義文檔標題 --> <title>初始HT…

單點登錄方案調研與實現

作用 在一個系統登錄后&#xff0c;其他系統也能共享該登錄狀態&#xff0c;無需重新登錄。 演進 cookie → session → token →單點登錄 Cookie 可以實現瀏覽器和服務器狀態的記錄&#xff0c;但Cookie會出現存儲體積過大和可以在前后端修改的問題 Session 為了解決Co…

【其他數學】結式 resultant

結式 resultant 2023年11月30日 #analysis 文章目錄 結式 resultant介紹Sylvester矩陣應用在消元中的應用傳遞函數的化簡 下鏈 介紹 結式用來計算曲線的交點、消元、找參數化曲線的隱含方程。 為了引出定義&#xff0c;思考如下問題&#xff1a; f ( x ) x 2 ? 5 x 6 g (…

UVM建造測試用例

&#xff08;1&#xff09;加入base_test 在一個實際應用的UVM驗證平臺中&#xff0c;my_env并不是樹根&#xff0c;通常來說&#xff0c;樹根是一個基于uvm_test派生的類。真正的測試用例都是基于base_test派生的一個類。 class base_test extends uvm_test;my_env e…

14-2(C++11)類型推導、類型計算

14-2&#xff08;C11&#xff09;類型推導、類型計算 類型推導auto關鍵字auto類型推斷本質auto與引用 聯用auto關鍵字的使用限制 類型計算類型計算分類與類型推導相比四種類型計算的規則返回值后置 類型推導 auto關鍵字 C98中&#xff0c;auto表示棧變量&#xff0c;通常省略…

Leetcode刷題筆記題解(C++):25. K 個一組翻轉鏈表

思路&#xff1a;利用棧的特性&#xff0c;K個節點壓入棧中依次彈出組成新的鏈表&#xff0c;不夠K個節點則保持不變 /*** struct ListNode {* int val;* struct ListNode *next;* ListNode(int x) : val(x), next(nullptr) {}* };*/ #include <stack> class Solution { …

在國內,現在月薪1萬是什么水平?

看到網友發帖問&#xff1a;現在月薪1W是什么水平&#xff1f; 在現如今的情況下&#xff0c;似乎月薪過萬這個標準已經成為衡量個人能力的一個標準了&#xff0c;尤其是現在互聯網橫行的時代&#xff0c;好像年入百萬&#xff0c;年入千萬就應該是屬于大眾的平均水平。 我不是…

kafka入門(四):消費者

消費者 (Consumer ) 消費者 訂閱 Kafka 中的主題 (Topic) &#xff0c;并 拉取消息。 消費者群組&#xff08; Consumer Group&#xff09; 每一個消費者都有一個對應的 消費者群組。 一個群組里的消費者訂閱的是同一個主題&#xff0c;每個消費者接收主題的一部分分區的消息…

大師學SwiftUI第18章Part2 - 存儲圖片和自定義相機

存儲圖片 在前面的示例中&#xff0c;我們在屏幕上展示了圖片&#xff0c;但也可以將其存儲到文件或數據庫中。另外有時使用相機將照片存儲到設備的相冊薄里會很有用&#xff0c;這樣可供其它應用訪問。UIKit框架提供了如下兩個保存圖片和視頻的函數。 UIImageWriteToSavedPh…

JAVA后端自學技能實操合集

JAVA后端自學技能實操 內容將會持續更新中,有需要添加什么內容可以再評論區留言,大家一起學習FastDFS使用docker安裝FastDFS(linux)集成到springboot項目中 內容將會持續更新中,有需要添加什么內容可以再評論區留言,大家一起學習 FastDFS 組名&#xff1a;文件上傳后所在的 st…

leetcode 100.相同的樹

涉及到遞歸&#xff0c;最好多畫圖理解&#xff0c;希望對你們有幫助 100.相同的樹 題目 給你兩棵二叉樹的根節點 p 和 q &#xff0c;編寫一個函數來檢驗這兩棵樹是否相同。 如果兩個樹在結構上相同&#xff0c;并且節點具有相同的值&#xff0c;則認為它們是相同的。 題目鏈接…

GPIO的使用--滴答定時器--pir人體紅外傳感器

目錄 一、滴答定時器的使用與原理 1、定義 2、原理 &#xff08;1&#xff09;向上計數?編輯 &#xff08;2&#xff09;向下計數 &#xff08;3&#xff09; 代碼流程 a、配置滴答時鐘喚醒頻率 b、滴答時鐘中斷函數 &#xff08;4&#xff09;結果 3、優化-->寄存…

Proxy Hook Trace JSON

Proxy var window {key: "qww",age: 22 } window new Proxy(window, {get(target, p, receiver) {console.log("target: ", target);console.log("p: ", p);// return window[username];/// 這里如果這樣寫. 有遞歸風險的...// return Reflec…

【線性代數與矩陣論】Jordan型矩陣

Jordan型矩陣 2023年11月3日 #algebra 文章目錄 Jordan型矩陣1. 代數重數與幾何重數2. Jordan塊與Jordan標準型2.1 最小多項式與Jordan標準型2.2 兩類重要矩陣 3. 矩陣的Jordan分解3.1 Jordan分解的應用 下鏈 1. 代數重數與幾何重數 在對向量做線性變換時&#xff0c;向量空間…

讀書筆記-《數據結構與算法》-摘要4[插入排序]

插入排序 核心&#xff1a;通過構建有序序列&#xff0c;對于未排序序列&#xff0c;在已排序序列中從后向前掃描(對于單向鏈表則只能從前往后遍歷)&#xff0c;找到相應位置并插入。實現上通常使用in-place排序(需用到O(1)的額外空間) 從第一個元素開始&#xff0c;該元素可…

如何主持一場知識競賽搶答賽

知識競賽主持說難不難&#xff0c;說簡單也不簡單&#xff0c;我就從易到難介紹一下。 入門級&#xff0c;題主不用練習太多其他花哨的技巧&#xff0c;只要注意一點&#xff0c;熟悉比賽流程。知識競賽需要給所有選手一個公平流暢的答題環境&#xff0c;所以題主自身必須非常…

干貨!接口中的大事務,該如何進行優化?

作為后端開發的程序員&#xff0c;我們常常會的一些相對比較復雜的邏輯&#xff0c;比如我們需要給前端寫一個調用的接口&#xff0c;這個接口需要進行相對比較復雜的業務邏輯操作&#xff0c;比如會進行&#xff0c;查詢、遠程接口或本地接口調用、更新、插入、計算等一些邏輯…

掌握iText:輕松處理PDF文檔-進階篇

簡體中文寫入 iText本身對簡體中文的支持有限&#xff0c;但可以通過引入額外的字體包來增強其對簡體中文的支持。例如&#xff0c;可以使用iTextAsian.jar這個亞洲字體包&#xff0c;它包含了幾種簡單的亞洲字體&#xff0c;其中包括簡體中文字體。只需要將iTextAsian.jar放到…

springboot 啟動之后報錯:Unsatisfied dependency through field ‘bbbClient’

springboot 啟動之后報錯&#xff1a;UnsatisfiedDepencyException:Error creating bean with name ‘aaaServiceImpl’: Unsatisfied dependency through field ‘bbbClient’。 這兩天一直在進行著日常 debugger 查看代碼。可是發生了一個挺“靈異”的事件。那就是我看的項目…

46. 全排列

46. 全排列 原題鏈接&#xff1a;完成情況&#xff1a;解題思路&#xff1a;參考代碼&#xff1a;_46全排列_構建數組回溯_46全排列_直接構建 錯誤經驗吸取 原題鏈接&#xff1a; 46. 全排列 https://leetcode.cn/problems/permutations/description/ 完成情況&#xff1a;…