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 驅動我們就講到這里。