天天看点

Linux驱动开发篇-LCD驱动实验1.Linux下LCD驱动简析2.LCD驱动程序编写3.运行测试

目录

1.Linux下LCD驱动简析

    1)Framebuffer设备

    2)LCD驱动简析

2.LCD驱动程序编写

    1)LCD屏幕IO配置

    2)LCD屏幕参数节点信息修改

    3)LCD屏幕背光节点信息

3.运行测试

    1)LCD屏幕基本测试

    2)设置LCD作为终端控制台

    3)LCD背光调节

    4)LCD自动关闭解决方法

      LCD是很常用的一个外设,在裸机篇中我们讲解了如何编写LCD裸机驱动,在Linux下LCD的使

      用更加广泛,在搭配QT这样的GUI库下可以制作出非常精美的UI界面。本章我们就来学习一下

      如何在Linux下驱动LCD屏幕。

1.Linux下LCD驱动简析

    1)Framebuffer设备

             先来回顾一下裸机的时候LCD驱动是怎么编写的,裸机LCD驱动编写流程如下:

             1、初始化I.MX6U的eLCDIF控制器,重点是LCD屏幕宽(width)、高(height)、hspw、

                   hbp、hfp、vspw、vbp和vfp等信息。

             2、初始化LCD像素时钟。

             3、设置RGBLCD显存。

             4、应用程序直接通过操作显存来操作LCD,实现在LCD上显示字符、图片等信息。

             在Linux中应用程序最终也是通过操作RGBLCD的显存来实现在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这样一个设备,如图1所示:

Linux驱动开发篇-LCD驱动实验1.Linux下LCD驱动简析2.LCD驱动程序编写3.运行测试

 图1 /dev/fb0设备文件

              图1中的/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。

    2)LCD驱动简析

             LCD裸机例程主要分两部分:

             1、获取LCD的屏幕参数。

             2、根据屏幕参数信息来初始化eLCDIF接口控制器。

            不同分辨率的LCD屏幕其eLCDIF控制器驱动代码都是一样的,只需要修改好对应的屏幕参

            数即可。屏幕参数信息属于屏幕设备信息内容,这些肯定是要放到设备树中的,因此我们

            本章实验的主要工作就是修改设备树,NXP官方的设备树已经添加了LCD设备节点,只是

            此节点的LCD屏幕信息是针对NXP官方EVK开发板所使用的4.3寸480*272编写的,我们需

            要将其改为我们所使用的屏幕参数。我们简单看一下NXP官方编写的Linux下的LCD驱动,

            打开imx6ull.dtsi,然后找到lcdif节点内容,如下所示:

1 lcdif: [email protected] {
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

             节点信息。像屏幕参数这些需要根据不同的硬件平台去添加,比如向imx6ull-alientek-

             emmc.dts中的lcdif节点添加其他的属性信息。从以上代码可以看出lcdif节点的compatible

             属性值为“fsl,imx6ul-lcdif”和“fsl,imx28-lcdi”,因此在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函数的主要工作内容为:

              1、申请fb_info。

              2、初始化fb_info结构体中的各个成员变量。

              3、初始化eLCDIF控制器。

              4、使用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
           

             大家可以对比着《I.MX6ULL参考手册》中的eLCDIF章节检查一下以上代码中的这些寄存

             器有没有错误。继续回到以上代码中的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函数,至于其他的函数大家自行查阅。

2.LCD驱动程序编写

      前面已经说了,6ULL的eLCDIF接口驱动程序NXP已经编写好了,因此LCD驱动部分我们不需

      要去修改。我们需要做的就是按照所使用的LCD来修改设备树。重点要注意三个地方:

      1、LCD所使用的IO配置。

      2、LCD屏幕节点修改,修改相应的属性值,换成我们所使用的LCD屏幕参数。

      3、LCD背光节点信息修改,要根据实际所使用的背光IO来修改相应的设备节点信息。

      接下来我们依次来看一下上面这两个节点改如何去修改:

    1)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,为RGBLCD的24根数据线配置项。

            第30~36行,子节点pinctrl_lcdif_ctrl,RGBLCD的4根控制线配置项,包括CLK、

                                  ENABLE、VSYNC和HSYNC。

            第37~40行,子节点pinctrl_pwm1,LCD背光PWM引脚配置项。这个引脚要根据实际情况

                                  设置,这里我们建议大家在以后的学习或工作中,LCD的背光IO尽量和半导

                                  体厂商的官方开发板一致。

    2)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官方的那个屏幕参数设置的。每一个属性的含义后面的注释已经写的很详

                               细了,大家自己去看就行了,这些时序参数就是我们重点要修改的,需要根据

                              自己所使用的屏幕去修改。

             这里以正点原子的ATK7016(7寸1024*600)屏幕为例,将imx6ull-alientek-emmc.dts文件中

             的lcdif节点改为如下内容:

1 &lcdif {
2 	pinctrl-names = "default";
3 	pinctrl-0 = <&pinctrl_lcdif_dat /* 使用到的 IO */
4 				&pinctrl_lcdif_ctrl>;
5 	display = <&display0>;
6 	status = "okay";
7
8 	display0: display { /* LCD 属性信息 */
9 		bits-per-pixel = <24>; /* 一个像素占用 24bit */
10 		bus-width = <24>; /* 总线宽度 */
11
12 		display-timings {
13 			native-mode = <&timing0>; /* 时序信息 */
14 			timing0: timing0 {
15 				clock-frequency = <51200000>; /* LCD 像素时钟,单位 Hz */
16 				hactive = <1024>; /* LCD X 轴像素个数 */
17 				vactive = <600>; /* LCD Y 轴像素个数 */
18 				hfront-porch = <160>; /* LCD hfp 参数 */
19 				hback-porch = <140>; /* LCD hbp 参数 */
20 				hsync-len = <20>; /* LCD hspw 参数 */
21 				vback-porch = <20>; /* LCD vbp 参数 */
22 				vfront-porch = <12>; /* LCD vfp 参数 */
23 				vsync-len = <3>; /* LCD vspw 参数 */
24
25 				hsync-active = <0>; /* hsync 数据线极性 */
26 				vsync-active = <0>; /* vsync 数据线极性 */
27 				de-active = <1>; /* de 数据线极性 */
28				 pixelclk-active = <0>; /* clk 数据线先极性 */
29 			};
30 		};
31 	};
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中找到如下内容:

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: [email protected] {
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,也就是以上代码所定义的GPIO1_IO08这

                          个IO。

            第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节点该如何去创建,这里大概总结一下:

            1、节点名称要为“backlight”。

            2、节点的compatible属性值要为“pwm-backlight”,因此可以通过在Linux内核中搜索“pwm-

                  backlight”来查找PWM背光控制驱动程序,这个驱动程序文件为

                  drivers/video/backlight/pwm_bl.c,感兴趣的可以去看一下这个驱动程序。

            3、pwms属性用于描述背光所使用的PWM以及PWM频率,比如本章我们要使用的pwm1,

                  pwm频率设置为5KHz(NXP官方推荐设置)。

            4、brightness-levels属性描述亮度级别,范围为0~255,0表示PWM占空比为0%,也就是

                  亮度最低,255表示100%占空比,也就是亮度最高。至于设置几级亮度,大家可以自

                  行填写此属性。

            5、default-brightness-level属性为默认亮度级别。

            根据上述5点设置backlight节点,这个NXP已经给我们设置好了,大家在imx6ull-alientek-

             emmc.dts文件中找到如下内容:

1 backlight {
2 	compatible = "pwm-backlight";
3 	pwms = <&pwm1 0 5000000>;
4 	brightness-levels = <0 4 8 16 32 64 128 255>;
5 	default-brightness-level = <6>;
6 	status = "okay";
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设备树节点内容我们就讲完了,按照这些节点内容配置自己的开

                         发板即可。

3.运行测试

    1)LCD屏幕基本测试

              1、编译新的设备树

                    上一小节我们已经配置好了设备树,所以需要输入如下命令重新编译一下设备树:

make dtbs
           

                    等待编译生成新的imx6ull-alientek-emmc.dtb设备树文件,一会要使用新的设备树启

                    动Linux内核。

              2、使能Linuxlogo显示

                    Linux内核启动的时候可以选择显示小企鹅logo,只要这个小企鹅logo显示没问题那么

                    我们的LCD驱动基本就工作正常了。这个logo显示是要配置的,不过Linux内核一般都

                    会默认开启logo显示,但是奔着学习的目的,我们还是来看一下如何使能Linuxlogo显

                    示。打开Linux内核图形化配置界面,按下路径找到对应的配置项:

-> Device Drivers

        -> Graphics support

                -> Bootup logo (LOGO [=y])

                        -> Standard black and white Linux logo

                        -> Standard 16-color Linux logo

                        -> Standard 224-color Linux logo

                    如图2所示:

Linux驱动开发篇-LCD驱动实验1.Linux下LCD驱动简析2.LCD驱动程序编写3.运行测试

图2 logo配置项 

                    图2中这三个选项分别对应黑白、16位、24位色彩格式的logo,我们把这三个都选

                    中,都编译进Linux内核里面。设置好以后保存退出,重新编译Linux内核,编译完成

                    以后使用新编译出来的imx6ull-alientek-emmc.dtb和zImage镜像启动系统,如果LCD

                    驱动工作正常的话就会在LCD屏幕左上角出现一个彩色的小企鹅logo,屏幕背景色为

                    黑色,如图3所示:

Linux驱动开发篇-LCD驱动实验1.Linux下LCD驱动简析2.LCD驱动程序编写3.运行测试

 图3  Linux 启动logo显示

    2)设置LCD作为终端控制台

             我们一直使用SecureCRT作为Linux开发板终端,开发板通过串口和SecureCRT进行通

             信。现在我们已经驱动起来LCD了,所以可以设置LCD作为终端,也就是开发板使用自己

             的显示设备作为自己的终端,然后接上键盘就可以直接在开发板上敲命令了,将LCD设置

             为终端控制台的方法如下:

             1、设置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作为终端进行交互,因为我们的设

                     置还未完成。

              2、修改/etc/inittab文件

                    打开开发板根文件系统中的/etc/inittab文件,在里面加入下面这一行:

tty1::askfirst:-/bin/sh
           

                    添加完成以后的/etc/inittab文件内容如图4所示:

Linux驱动开发篇-LCD驱动实验1.Linux下LCD驱动简析2.LCD驱动程序编写3.运行测试

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

                    修改完成以后保存/etc/inittab并退出,然后重启开发板,重启以后开发板LCD屏幕最后

                   一行会显示下面一行语句:

Please press Enter to activate this console.
           

                    上述提示语句说的是:按下回车键使能当前终端,我们在第五十八章已经将I.MX6U-

                    ALPHA开发板上的KEY按键注册为了回车键,因此按下开发板上的KEY按键即可使能

                    LCD这个终端。当然了,大家也可以接上一个USB键盘,Linux内核默认已经使能了

                    USB键盘驱动了,因此可以直接使用USB键盘。

                    至此,我们就拥有了两套终端,一个是基于串口的SecureCRT,一个就是我们开发板

                   的LCD屏幕,但是为了方便调试,我们以后还是以SecureCRT为主。我们可以通过下

                   面这一行命令向LCD屏幕输出“hello linux!”

echo hello linux > /dev/tty1
           

    3)LCD背光调节

             之前已经讲过了,背光设备树节点设置了8个等级的背光调节,可以设置为0~7,我们可以

             通过设置背光等级来实现LCD背光亮度的调节,进入如下目录:

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

             此目录下的文件如图5所示:

Linux驱动开发篇-LCD驱动实验1.Linux下LCD驱动简析2.LCD驱动程序编写3.运行测试

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

             图5中的brightness表示当前亮度等级,max_bgigntness表示最大亮度等级。当前这两个文

             件内容如图6所示: 

Linux驱动开发篇-LCD驱动实验1.Linux下LCD驱动简析2.LCD驱动程序编写3.运行测试

 图6 brightness和max_brightness文件内容

             从图6可以看出,当前屏幕亮度等级为6,根据前面的分析可以,这个是50%亮度。屏幕最

             大亮度等级为7。如果我们要修改屏幕亮度,只需要向brightness写入需要设置的屏幕亮度

             等级即可。比如设置屏幕亮度等级为7,那么可以使用如下命令:

echo 7 > brightness
           

            输入上述命令以后就会发现屏幕亮度增大了,如果设置brightness为0的话就会关闭LCD背

            光,屏幕就会熄灭。

    4)LCD自动关闭解决方法

             默认情况下10分钟以后LCD就会熄屏,这个并不是代码有问题,而是Linux内核设置的,就

             和我们用手机或者电脑一样,一段时间不操作的话屏幕就会熄灭,以节省电能。解决这个

             问题有多种方法,我们依次来看一下:

             1、按键盘唤醒

                   最简单的就是按下回车键唤醒屏幕,我们在第58章将I.MX6U-ALPHA开发板上的KEY

                   按键注册为了回车键,因此按下开发板上的KEY按键即可唤醒屏幕。如果开发板上没

                   有按键的话可以外接USB键盘,然后按下USB键盘上的回车键唤醒屏幕。

            2、关闭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启动开发板。

              3、编写一个APP来关闭熄屏功能

                    在ubuntu中新建一个名为lcd_always_on.c的文件,然后在里面输入如下所示内容:

1 #include <fcntl.h>
2 #include <stdio.h>
3 #include <sys/ioctl.h>
4
5
6 int main(int argc, char *argv[])
7 {
8 	int fd;
9 	fd = open("/dev/tty1", O_RDWR);
10 	write(fd, "\033[9;0]", 8);
11 	close(fd);
12 	return 0;
13 }
           

                   使用如下命令编译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,在此文件最后面加入如下内容:

1 cd /usr/bin
2 ./lcd_always_on
3 cd ..
           

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

                   我们就讲到这里。

继续阅读