1.LCD控制器模块介绍
1.1 硬件框图
LCD控制器的作用:承上启下
IMX6ULL的LCD控制器名称为eLCDIF(Enhanced LCD Interface,增强型LCD接口),主要特性如下:
-
支持MPU模式:有些显示屏自带显存,只需要把命令、数据发送给显示屏即可;就是前面讲的8080接口
-
VSYNC模式:跟MPU模式类似,多了VSYNC信号。针对高速数据传输(行场信号)
-
支持DOTCLK模式:RGB接口,就是前面讲的TFT-RGB接口
-
支持ITU-R BT.656接口,可以把4:2:2 YcbCr格式的数据转换为模拟电视信号
-
8/16/18/24/32 bit 的bpp数据都支持,取决于IO的复用设置及寄存器配置
-
MPU模式,VSYNC模式,DOTCLK模式,都可以配置时序参数。
上图是IMX6ULL的LCD控制器框图。 我们在内存中划出一块内存,称之为显存,软件把数据写入显存。 设置好LCD控制器之后,它会通过AXI总线协议从显存把RGB数据读入FIFO,再到达LCD接口(LCD Interface)。 LCD控制器有两个时钟域:外设总线时钟域,LCD像素时钟域。前者是用来让LCD控制器正常工作,后者是用来控制电子枪移动。 上图的Read_Data操作,在MPU模式下才用到;我们采用的是DCLK模式,因此不予考虑。
1.2 数据传输与处理
-
举例说明:LCD控制器的作用将来自FB的数据经处理发送给屏幕。
原始出局经过处理:例如提取数据,交换位数,填充成标准RGB888格式,确定具体有效像素位数
1.3 时序控制
两个时序,一个是LCD控制器外系统同步时钟,第二个是数据传入显示屏时钟。
具体看看寄存器说明。
2. LCD控制器寄存器简介
查看任何芯片的LCD控制器寄存器时,记住几个要点:
① 怎么把LCD的信息告诉LCD控制器:即分辨率、行列时序、像素时钟等;
② 怎么把显存地址、像素格式告诉LCD控制器
顺序就是:显存->LCD控制器->LCD
上图是我们将要使用到的寄存器,下面逐个讲解这些寄存器,在LCD控制编程会用到。
2.1 LCDIF_CTRL寄存器
2.2 LCDIF_CTRL1寄存器
本实验中使用TFT LCD,LCD控制器使用DOTCLK模式。本寄存器中其他用不到的位,就不介绍了。
2.3 LCDIF_TRANSFER_COUNT寄存器
2.4 LCDIF_VDCTRL0寄存器
本寄存器用来设置Vsync信号相关的时序,及极性。
Vsync(垂直同步)信号是显示设备(如显示器、电视或LCD屏幕)中用于同步图像刷新周期的信号。它确保屏幕的每个像素在同一时间点开始和结束刷新,从而避免图像撕裂(tearing)和重影(flicker)。
2.5 LCDIF_VDCTRL1寄存器
2.6 LCDIF_VDCTRL2寄存器
2.7 LCDIF_VDCTRL3寄存器
2.8 LCDIF_VDCTRL4寄存器
2.9 LCDIF_CUR_BUF寄存器
2.10 LCDIF_NEXT_BUF寄存器
3. 分析内核自带的LCD驱动程序
3.1 驱动程序框架
Linux驱动程序 = 驱动程序框架 + 硬件编程。 在前面已经基于QEMU编写了LCD驱动程序,对LCD驱动程序的框架已经分析清楚。 核心就是:
-
分配fb_info
-
设置fb_info
-
注册fb_info
-
硬件相关的设置
-
3.1.1 入口函数注册platform_driver
3.1.2 设备树有对应节点
3.1.3 probe函数分析
探测函数,在Linux内核驱动程序中,
probe
函数是一个非常重要的概念,它用于初始化和检测设备。以下是probe
函数的一些关键点: -
目的:
probe
函数的主要目的是检测系统中是否存在某个设备,并初始化该设备以便操作系统可以与之通信。 -
自动调用:在驱动程序中,
probe
函数通常由操作系统自动调用,当系统启动时或设备被热插拔时。 -
设备树集成:在基于设备树的系统中,
probe
函数会使用设备树中提供的信息来检测和初始化设备。 -
返回值:
-
如果
probe
函数成功初始化设备,它通常会返回0。 -
如果检测到设备不存在或初始化失败,
probe
函数会返回一个错误码。
-
-
资源分配:
probe
函数中通常会进行资源的分配,如内存、中断、DMA通道等。这些资源对于设备的正常工作是必需的。 -
设备注册:
probe
函数还会将设备注册到操作系统中,这样其他驱动程序或应用程序就可以通过设备文件与之通信。 -
错误处理:如果
probe
函数在初始化过程中遇到错误,它需要进行适当的错误处理,比如释放已经分配的资源,并向操作系统报告错误。 -
可重入性:
probe
函数需要是可重入的,即可以被多次调用而不会引起问题。这在热插拔设备的情况下尤其重要。
3.2 编写硬件相关的代码
我们只需要针对IMX6ULL的编写硬件相关的代码,涉及3部分:
-
GPIO设置
-
LCD引脚
-
背光引脚
-
-
时钟设置
-
确定LCD控制器的时钟
-
根据LCD的DCLK计算相关时钟
-
-
LCD控制器本身的设置
-
比如设置Framebuffer的地址
-
设置Framebuffer中数据格式、LCD数据格式
-
设置时序
-
3.2.1 GPIO设置
有两种方法:
-
直接读写相关寄存器
-
使用设备树,在设备树中设置pinctrl
设备树arch/arm/boot/dts/100ask_imx6ull-14x14.dts
中:
3.2.2 时钟设置
IMX6ULL的LCD控制器涉及2个时钟:
代码里直接使用时钟子系统的代码。
-
在设备树里指定频率:
-
文件:arch/arm/boot/dts/100ask_imx6ull-14x14.dts
-
代码:clock-frequency
-
-
从设备树获得dot clock,存入display_timing
-
文件:drivers\video\of_display_timing.c
-
代码:
-
-
使用display_timing来设置videomode
-
文件:drivers\video\videomode.c
-
代码:
-
根据videomode的值,使用时钟子系统的函数设置时钟:
-
文件:drivers\video\fbdev\mxc\ldb.c
-
代码:
3.2.3 LCD控制器的配置
以设置分辨率为例。(从底层设备树获得频率信息,传递给LCD控制器,和FB)
-
在设备树里指定频率:
-
文件:arch/arm/boot/dts/100ask_imx6ull-14x14.dts
-
代码:clock-frequency
-
从设备树获得分辨率,存入display_timing
-
文件:drivers\video\of_display_timing.c
-
代码:
使用display_timing来设置videomode
-
文件:drivers\video\videomode.c
-
代码:
根据videomode的值,设置fb_videomode
-
文件:drivers\video\fbdev\core\fbmon.c
-
代码:
根据fb_videomode的值,设置fb_info中的var:
-
文件:drivers\video\fbdev\core\modedb.c
-
代码:
根据var的分辨率,设置寄存器
-
文件:drivers\video\fbdev\mxsfb.c
-
代码:
4. 编程_LCD驱动程序框架_使用设备树
4.1 编程思路
概述:Linux驱动程序 = 驱动程序框架 + 硬件编程。
其中LCD驱动程序的框架核心是:
-
分配fb_info
-
设置fb_info
-
注册fb_info
-
硬件相关的设置
主要有三个主要核心:
1.入口函数注册platform_driver
2. 设备树有对应节点(将驱动程序注册)
3. 编写probe函数(和硬件相关,在内核中将编写的驱动程序和底层硬件联系起来)
-
分配fb_info
-
设置fb_info
-
注册fb_info
-
硬件相关的设置
-
引脚设置
-
时钟设置
-
LCD控制器设置
-
4.2 总体框架代码分析
lcd_regs
是一个结构体,用于表示LCD控制器的硬件寄存器。这个结构体通常包含LCD控制器的物理地址和一些用于控制LCD显示的寄存器值。
这段代码定义了一个内联函数 chan_to_field
,其作用是将颜色通道值转换为帧缓冲设备所需的特定格式。这个函数通常用于处理真彩色(True Color)模式下的调色板或颜色寄存器设置。
这段代码是一个用于设置颜色寄存器的函数,是Linux帧缓冲设备驱动程序的一部分。这个函数的目的是将红、绿、蓝颜色值转换并存储到帧缓冲设备的伪调色板中。
这段代码定义了一个 fb_ops
结构体,它是Linux内核中用于帧缓冲设备操作的一组函数指针。这个结构体是帧缓冲设备驱动程序的核心部分,它告诉内核如何操作帧缓冲设备。
- 成员解释:
.owner
:指定拥有这个操作结构体的模块。通常设置为THIS_MODULE
,表示当前模块。.fb_setcolreg
:设置颜色寄存器的函数。这里设置为mylcd_setcolreg
,这是前面讨论的自定义函数,用于将红、绿、蓝颜色值转换并存储到帧缓冲设备的伪调色板中。.fb_fillrect
:填充矩形区域的函数。这里设置为cfb_fillrect
,这是内核提供的函数,用于在帧缓冲中填充矩形区域。.fb_copyarea
:复制区域的函数。这里设置为cfb_copyarea
,这是内核提供的函数,用于在帧缓冲中复制矩形区域。.fb_imageblit
:图像位块传输的函数。这里设置为cfb_imageblit
,这是内核提供的函数,用于在帧缓冲中进行图像位块传输。
函数说明:
- mylcd_setcolreg:自定义函数,用于设置帧缓冲设备的颜色寄存器。它将颜色值转换为设备所需的格式,并存储到伪调色板中。
- cfb_fillrect:内核提供的函数,用于在帧缓冲中填充矩形区域。这个函数可以快速地用单一颜色填充大区域,常用于清除屏幕或绘制简单的图形。
- cfb_copyarea:内核提供的函数,用于在帧缓冲中复制矩形区域。这个函数可以将一个区域的内容复制到另一个区域,常用于滚动或动画效果。
- cfb_imageblit:内核提供的函数,用于在帧缓冲中进行图像位块传输。这个函数可以将图像从一个位置快速地传输到另一个位置,常用于绘制复杂的图形或图像。
作用:
通过定义这个 fb_ops
结构体,驱动程序告诉内核如何操作帧缓冲设备。这些操作包括设置颜色、填充矩形、复制区域和图像传输。这些函数被内核在需要进行图形操作时调用,从而实现对帧缓冲设备的控制。
总结:内核调动 fb_ops实现对帧缓冲设备的控制;由内核产生需求,然后寻找对应设备号,从而实现内核对注册设备的控制,即内核控制帧缓冲设备,帧缓冲设备再控制LCD控制器,LCD控制器再控制底层硬件LCD.
构建驱动的探测函数
第一步分配fb_info(分配内存),设置fb_info(设置参数):分辨率,颜色格式。
设置设备号ID,写入内核空间的“名字”,内存长度等于长*宽*像素位数
设置内存虚拟地址和物理地址
注册fb_info,硬件操作则是给LCD控制器结构体直接赋值.。包括绑定它的硬件地址。
其中映射LCD控制寄存器:
-
调用
ioremap
函数:ioremap
是Linux内核中用于映射硬件寄存器地址到内核虚拟地址空间的函数。这使得内核代码能够访问这些硬件寄存器,就像访问普通的内存地址一样。 -
参数解释:
0x021C8000
:这是LCD控制器寄存器的物理地址。在这个例子中,物理地址被硬编码为0x021C8000
。sizeof(struct lcd_regs)
:这是lcd_regs
结构体的大小,表示需要映射的内存区域的大小。struct lcd_regs
结构体包含了LCD控制器的寄存器。
-
返回值:
ioremap
函数返回一个指向映射区域的虚拟地址的指针。这个指针被赋值给mylcd_regs
变量。这意味着mylcd_regs
现在指向了LCD控制器寄存器的虚拟地址,可以通过这个指针访问LCD控制器的寄存器。
-
作用:
- 映射LCD控制器的寄存器使得驱动程序能够直接访问和控制LCD硬件。例如,可以读取或写入LCD的状态、控制显示参数等。
-
为什么需要映射:
- 硬件寄存器通常位于物理内存地址空间中,而内核代码运行在虚拟内存空间中。如果不进行映射,内核代码无法直接访问这些物理地址。
- 通过
ioremap
映射,内核代码可以透明地访问这些硬件寄存器,就像它们是普通的内存地址一样
上述操作的反操作:用于释放资源
这段代码定义了一个设备树(Device Tree)匹配表,用于在Linux内核中自动识别和配置硬件设备。设备树匹配表是设备驱动程序与设备树节点相关联的一种机制,使得内核能够根据设备树中提供的信息加载和初始化驱动程序。
代码解释:
-
定义匹配表:
struct of_device_id
是一个结构体,通常包含一个compatible
字符串,用于与设备树中的compatible
属性匹配。- 第一个条目
{ .compatible = "100ask,lcd_drv", }
指定了一个匹配规则,内核会查找设备树中compatible
属性为"100ask,lcd_drv"
的节点。 - 第二个条目
{ }
是一个空条目,表示匹配表的结束。
-
模块设备表宏:
- 这个宏将设备树匹配表与模块关联起来。它告诉内核如何使用这个匹配表来识别和绑定设备。
of
表示这是一个设备树匹配表。mylcd_of_match
是匹配表的名称。
设备树匹配的工作原理:
- 设备树节点:在设备树源文件(.dts)中,每个硬件设备都有一个对应的节点,节点中包含设备的描述和配置信息,如
compatible
属性。 - 驱动程序匹配:当内核启动时,它会遍历设备树,查找与设备树匹配表中
compatible
字符串匹配的节点。 - 驱动程序加载:一旦找到匹配的节点,内核会加载相应的驱动程序,并使用设备树中的信息来初始化驱动程序。
驱动程序中的使用:
- 探测函数:在驱动程序的探测函数(如
mylcd_probe
)中,内核会使用设备树中的信息来初始化硬件设备。 - 移除函数:在驱动程序的移除函数(如
mylcd_remove
)中,内核会反初始化硬件设备,并释放相关资源。
通过设备树匹配表,驱动程序可以灵活地处理不同的硬件设备,而不需要为每种设备编写单独的识别代码。这种方法简化了硬件识别和配置的过程,使得内核能够更智能地管理硬件设备。
这段代码定义了一个名为 mylcd_driver
的平台驱动结构体,它是Linux内核中用于管理特定硬件设备(在这个例子中是LCD设备)的驱动程序的核心组成部分。
-
platform_driver 结构体:
- 这是一个用于平台设备(Platform Device)的驱动程序结构体。平台设备是指那些不是通过总线(如PCI或USB)连接到系统的设备,而是直接集成在硬件平台上的设备。
-
.driver 成员:
- 这个成员是
device_driver
结构体,它包含了驱动程序的一些基本信息。 .name
:驱动程序的名称。在这个例子中,名称被设置为"mylcd"
。这个名称在系统日志和/proc/device-tree中用于标识驱动程序。.of_match_table
:设备树匹配表。这个表定义了驱动程序可以支持的设备类型。内核使用这个表来决定哪些设备树节点应该由这个驱动程序处理。在这个例子中,它被设置为mylcd_of_match
,这是一个之前定义的数组,包含了与设备树中compatible
属性匹配的字符串。
- 这个成员是
-
.probe 成员:
- 探测函数。当内核发现一个设备树节点与
.of_match_table
中的某个条目匹配时,它会调用这个函数来初始化和注册设备。在这个例子中,探测函数被设置为mylcd_probe
。这个函数负责分配资源、初始化硬件和注册帧缓冲设备。
- 探测函数。当内核发现一个设备树节点与
-
.remove 成员:
- 移除函数。当设备需要被移除或系统关闭时,内核会调用这个函数来反初始化和释放资源。在这个例子中,移除函数被设置为
mylcd_remove
。这个函数负责注销帧缓冲设备、释放内存和取消映射硬件寄存器。
- 移除函数。当设备需要被移除或系统关闭时,内核会调用这个函数来反初始化和释放资源。在这个例子中,移除函数被设置为
这段代码是一个Linux内核模块的初始化函数,用于在模块加载时注册一个平台驱动程序。
4.3 配置引脚代码分析
配置引脚:在probe函数中,增加代码关于配置引脚
声明了一个指向 gpio_desc
结构的指针 bl_gpio
。这个结构是Linux内核中用于表示通用输入输出(GPIO)引脚的描述符
gpiod_get
函数用于从设备树中获取一个GPIO引脚的描述符。&pdev->dev
:这是一个指向device
结构体的指针,通常由平台设备(platform device)提供。"backlight"
:这是设备树中定义的GPIO引脚的名称。设备树中的GPIO引脚通过名称标识,这里指定的名称是"backlight"
。0
:这是gpiod_get
函数的第三个参数,用于指定GPIO引脚的索引。大多数情况下,只有一个GPIO引脚与某个功能(如背光控制)相关联,因此索引通常为0。
gpiod_direction_output
函数用于将GPIO引脚配置为输出模式。bl_gpio
:这是从gpiod_get
函数返回的GPIO引脚描述符。1
:这是初始输出值。在这个例子中,将GPIO引脚初始化为高电平(1)
- 这行代码被注释掉了,但它的目的是展示如何使用
gpiod_set_value
函数来设置GPIO引脚的值。 bl_gpio
:这是GPIO引脚描述符。status
:这是一个变量,表示要设置的GPIO值。如果status
为1,则GPIO引脚输出高电平;如果为0,则输出低电平。
驱动程序中的GPIO处理:
- 获取GPIO描述符:使用
gpiod_get
函数获取设备树中定义的GPIO引脚描述符。 - 配置GPIO模式:使用
gpiod_direction_output
函数将GPIO引脚配置为输出模式,并设置初始值。 - 控制GPIO输出:使用
gpiod_set_value
函数动态控制GPIO引脚的输出值,从而控制背光的开关。
通过这种方式,驱动程序可以灵活地控制硬件设备上的GPIO引脚,实现各种功能,如背光控制、电源管理等。
4.4 配置时钟代码分析
4.4.1 LCDIF设备树
关键属性解释:
-
compatible:设备兼容字符串,用于匹配内核中的驱动程序。内核根据这些字符串加载相应的驱动程序。
"fsl,imx6ul-lcdif"
:表示与Freescale i.MX6ULL系列的LCDIF兼容。"fsl,imx28-lcdif"
:表示与Freescale i.MX28系列的LCDIF兼容。
-
reg:指定设备的寄存器映射信息。
0x021c8000
是LCDIF控制器的物理地址,0x4000
是寄存器映射的大小。<0x021c8000 0x4000>
:表示LCDIF寄存器映射的起始地址和长度。
-
interrupts:指定设备使用的中断资源。
GIC_SPI 5 IRQ_TYPE_LEVEL_HIGH
表示使用通用中断控制器(GIC),中断号为5,触发类型为高电平触发。 -
clocks:指定设备使用的时钟源列表。时钟源是设备正常工作所必需的。
<&clks IMX6UL_CLK_LCDIF_PIX>
:像素时钟,用于控制显示像素的传输。<&clks IMX6UL_CLK_LCDIF_APB>
:APB总线时钟,用于控制与应用处理器的通信。<&clks IMX6UL_CLK_DUMMY>
:一个虚拟时钟,通常用于占位或测试。
-
clock-names:为上述时钟源指定名称,方便在设备树和驱动程序中引用。
"pix"
:像素时钟。"axi"
:APB总线时钟。"disp_axi"
:显示总线时钟。
-
status:设备的初始状态。
"disabled"
表示设备在系统启动时不会自动启用,需要通过驱动程序显式启用。
通过这些属性,设备树文件为内核提供了足够的信息来初始化和控制LCDIF设备。内核中的LCDIF驱动程序会读取这些信息,并根据这些配置来控制LCDIF的行为。
定义了3个时钟:
-
pix:Pixel clock,用于LCD接口,设置为LCD手册上的参数
-
axi:AXI clock,用于传输数据、读写寄存器,使能即可
-
disp_axi:一个虚拟的时钟,可以不用设置
4.4.2 时钟代码分析
代码确保了在初始化LCD显示接口时,所有必要的时钟资源都被正确获取。如果任何时钟资源获取失败,代码将停止进一步的初始化,并进行适当的错误处理。这有助于确保系统的稳定性和可靠性。
-
获得时钟
host->clk_pix = devm_clk_get(&host->pdev->dev, "pix"); if (IS_ERR(host->clk_pix)) { host->clk_pix = NULL; ret = PTR_ERR(host->clk_pix); goto fb_release; } host->clk_axi = devm_clk_get(&host->pdev->dev, "axi"); if (IS_ERR(host->clk_axi)) { host->clk_axi = NULL; ret = PTR_ERR(host->clk_axi); dev_err(&pdev->dev, "Failed to get axi clock: %d\n", ret); goto fb_release; } host->clk_disp_axi = devm_clk_get(&host->pdev->dev, "disp_axi"); if (IS_ERR(host->clk_disp_axi)) { host->clk_disp_axi = NULL; ret = PTR_ERR(host->clk_disp_axi); dev_err(&pdev->dev, "Failed to get disp_axi clock: %d\n", ret); goto fb_release; }
-
获取时钟资源:
devm_clk_get
函数用于获取一个时钟资源。这个函数尝试查找名为"pix"
、"axi"
和"disp_axi"
的时钟,并获取对它们的引用。
- host->clk_pix:指向像素时钟资源的指针。像素时钟是控制LCD显示像素传输的时钟。
- host->clk_axi:指向AXI总线时钟资源的指针。AXI时钟通常用于控制与处理器的通信。
- host->clk_disp_axi:指向显示AXI总线时钟资源的指针。显示AXI时钟可能用于控制与显示控制器的通信。
-
设置频率:只需要设置pixel clock的频率
ret = clk_set_rate(host->clk_pix, PICOS2KHZ(fb_info->var.pixclock) * 1000U);
-
使能时钟
clk_enable_pix(host); clk_prepare_enable(host->clk_pix); clk_enable_axi(host); clk_prepare_enable(host->clk_axi); clk_enable_disp_axi(host); clk_prepare_enable(host->clk_disp_axi);
5. 设备树文件分析
5.1 设备树文件的作用
概述:设备树用于描述硬件的配置和特性,而驱动程序则利用这些信息来控制硬件设备。
设备树文件的作用是为Linux内核提供硬件配置信息,以便内核能够初始化和控制硬件设备。以下是设备树文件的主要作用和关键点:
-
硬件描述:设备树文件描述了硬件设备的属性和连接方式,包括处理器、内存、外设等。
-
设备识别:通过设备树,内核能够识别连接到系统的所有硬件设备,并了解它们的配置。
-
资源分配:设备树文件指定了设备的内存地址、中断号、时钟频率等资源,帮助内核正确地分配和使用这些资源。
-
电源管理:设备树可以包含电源管理相关的信息,如电源域、电源序列等,帮助内核管理设备的电源状态。
-
初始化参数:设备树为设备的初始化提供了必要的参数,如显示设备的分辨率、颜色深度、时序信息等。
-
驱动匹配:设备树中的
compatible
属性用于匹配相应的设备驱动程序。内核根据这些属性加载和绑定合适的驱动。 -
动态设备支持:设备树支持动态添加和移除设备,使得内核能够适应硬件配置的变化。
-
简化硬件抽象:设备树提供了一个统一的硬件抽象层,使得驱动程序开发更加简单和可移植。
-
调试和诊断:设备树文件的内容有助于系统调试和诊断,因为它清晰地展示了硬件的配置和状态。
-
安全性:设备树还可以包含安全相关的配置,如信任区域(Trusted Zone)的设置。
在设备树文件中,每个设备或组件都被定义为一个节点,节点可以包含属性和子节点。属性提供了设备的具体配置信息,而子节点则表示更复杂的硬件结构。
例如,设备树文件中可能包含以下类型的节点:
- 内存节点:描述系统内存的布局和大小。
- 处理器节点:描述CPU的属性和特性。
- 外设节点:如显示控制器、网络控制器、存储设备等,每个节点包含该外设的详细配置。
设备树文件通常在系统启动时由引导加载器(如U-Boot)传递给内核,内核解析这些信息并据此初始化硬件设备。这种机制使得Linux内核能够灵活地支持各种硬件平台,而无需为每种硬件编写特定的初始化代码。
5.2 分析设备树设备框架(leds)
关键属性和节点解释:
-
leds节点:
- 这是一个顶级节点,代表一组LED设备。它使用
gpio-leds
作为compatible
属性,表示这些LED设备通过GPIO控制。
- 这是一个顶级节点,代表一组LED设备。它使用
-
pinctrl-names和pinctrl-0:
pinctrl-names = "default";
:指定了引脚控制的名称,这里使用默认名称。pinctrl-0 = <&pinctrl_leds>;
:引用了名为pinctrl_leds
的引脚控制组,这通常在其他地方定义,指定了如何控制GPIO引脚。
-
status:
status = "disabled";
:这个属性表明当系统启动时,这些LED设备将被禁用。这意味着在系统启动时,这些LED不会自动亮起。
-
led0子节点:
- 这是
leds
节点下的子节点,代表一个具体的LED设备。节点名称led0
是用户定义的,用于标识这个LED设备。
- 这是
-
label:
label = "cpu";
:为LED设备提供一个标签,通常用于识别或描述LED的功能。在这个例子中,标签是"cpu",可能表示这个LED与CPU活动相关。
-
gpios:
gpios = <&gpio5 3 GPIO_ACTIVE_LOW>;
:指定了控制LED的GPIO引脚。这里指定了GPIO控制器的设备引用(&gpio5
),引脚号(3
),以及电平活动状态(GPIO_ACTIVE_LOW
)。这意味着当GPIO引脚被拉低时,LED将亮起。
-
default-state:
default-state = "on";
:定义了LED的默认状态。在这个例子中,LED默认为开启状态。注意,尽管默认状态为开启,但由于status = "disabled";
,LED在系统启动时不会亮起。
-
linux,default-trigger:
linux,default-trigger = "heartbeat";
:指定了LED的默认触发器。触发器是Linux系统中用于控制LED行为的机制。这里使用"heartbeat"触发器,通常用于指示系统活动。当系统运行时,LED会以一定的频率闪烁,模仿心跳。
通过这种方式,设备树文件为内核提供了足够的信息来初始化和控制LED设备。内核中的LED类驱动程序会读取这些信息,并根据这些配置来控制LED的行为。
5.3 LCD设备树节点分析
设备树文件(Device Tree Source,DTS)用于描述硬件的结构和配置,特别是在嵌入式系统中,如Linux内核启动过程中。设备树文件采用结构化的层次格式,包括节点和属性,每个节点描述一个硬件设备或子系统。以下是设备树文件的格式和作用的详细分析,以 framebuffer-mylcd
节点为例:
1. 节点(Node):
每个节点描述一个硬件设备或子系统。节点以大括号 {}
包围,并且可以包含属性和子节点。节点名称通常是设备的功能描述。
framebuffer-mylcd
是节点的名称,表示一个帧缓冲液晶显示设备。
2. 属性(Property):
属性用于描述节点的特性。属性通常以键值对的形式出现,键是属性名称,值是属性值。属性可以是字符串、数值、布尔值等。
属性示例:
compatible = "100ask,lcd_drv";
- 作用:描述设备的兼容性,用于设备驱动程序匹配。这里的值
"100ask,lcd_drv"
表示设备兼容的驱动程序是100ask
项目中的lcd_drv
驱动。
- 作用:描述设备的兼容性,用于设备驱动程序匹配。这里的值
pinctrl-names = "default";
- 作用:指定引脚控制器设置的名称。这里的值
"default"
表示默认的引脚配置。
- 作用:指定引脚控制器设置的名称。这里的值
pinctrl-0 = <&mylcd_pinctrl>;
- 作用:指定引脚控制器的配置。
<&mylcd_pinctrl>
是一个指向另一个节点的引用,描述了引脚控制器的具体配置。
- 作用:指定引脚控制器的配置。
backlight-gpios = <&gpio1 8 GPIO_ACTIVE_HIGH>;
- 作用:定义用于控制背光的GPIO引脚。
<&gpio1 8 GPIO_ACTIVE_HIGH>
指定了GPIO控制器和引脚编号以及其激活状态。
- 作用:定义用于控制背光的GPIO引脚。
3. 引用(Reference)
在设备树中,一个节点可以引用其他节点的属性。引用使用&
符号来表示。
<&mylcd_pinctrl>
表示引用名为mylcd_pinctrl
的引脚控制器节点。
设备树文件的主要作用是将硬件配置从内核代码中分离出来,使得内核可以在启动时动态解析硬件配置。这种方式带来了以下几个好处:
- 硬件独立性:内核代码不需要硬编码硬件配置,提高了代码的可移植性。
- 灵活性:硬件配置可以通过修改设备树文件进行调整,而不需要重新编译内核。
- 可维护性:设备树文件清晰地描述了硬件结构,便于维护和理解。
通过设备树文件,嵌入式系统在启动时可以根据硬件的描述自动加载合适的驱动程序并进行初始化,从而实现硬件的正常工作。
具体而言:
在设备树里指定LCD参数
给compatible赋值,用于和启动程序相匹配,匹配后驱动程序能够从设备树节点获取硬件信息。
引脚控制名称默认,引用
配置GPIO背光
指定LCD控制器接口使用的时钟源;两个时钟,一个外部时钟,一个用于内部控制发数据
指定显示设备节点,指针引用。
以下代码是显示设备节点的详细赋值,包括:
像素位数,总线宽度,显示时序
其中显示时序,指定默认显示模式
其中显示模式包括:时钟频率,水平活动像素数,垂直活动像素数,
水平前沿时间,水平后沿时间:
水平前沿时间(Horizontal Front Porch)
- 定义:水平前沿时间是指从水平同步脉冲结束到水平活动像素开始显示的时间间隔。
- 作用:这段时间用于准备显示数据,确保像素数据在显示开始前已经稳定,从而避免图像的失真。
- 影响:水平前沿时间过长可能会导致显示效率降低,但过短可能会影响图像质量,特别是在高分辨率显示时。
水平后沿时间(Horizontal Back Porch)
- 定义:水平后沿时间是指从水平活动像素结束到下一个水平同步脉冲开始的时间间隔。
- 作用:这段时间用于结束当前行的显示,并准备下一行的显示。它提供了一个缓冲期,以便在切换到下一行之前完成像素数据的传输和设置。
- 影响:水平后沿时间同样影响显示效率和图像质量。过长的时间可能会导致屏幕刷新率降低,而过短的时间可能会在行与行之间产生可见的间隙
显示技术中,特别是在液晶显示屏(LCD)和其他类型的显示设备的时序参数中,"垂直前沿时间"(Vertical Front Porch)和"垂直后沿时间"(Vertical Back Porch)是描述显示信号时序的两个重要概念。以下是对这两个术语的详细解释:
垂直前沿时间(Vertical Front Porch)
- 定义:垂直前沿时间是指从垂直同步脉冲结束到垂直活动像素开始显示的时间间隔。
- 作用:这段时间用于准备显示数据,确保像素数据在显示开始前已经稳定,从而避免图像的失真。它还提供了一个短暂的延迟,以便在新的一帧图像开始之前完成上一帧图像的显示。
- 影响:垂直前沿时间的长短会影响显示的刷新率和图像质量。过长的时间可能会降低刷新率,而过短的时间可能不足以确保数据的稳定传输。
垂直后沿时间(Vertical Back Porch)
- 定义:垂直后沿时间是指从垂直活动像素结束到下一个垂直同步脉冲开始的时间间隔。
- 作用:这段时间用于结束当前帧的显示,并准备下一帧的显示。它提供了一个缓冲期,以便在切换到下一帧之前完成像素数据的传输和设置。
- 影响:垂直后沿时间同样影响显示的刷新率和图像质量。过长的时间可能会导致屏幕刷新率降低,而过短的时间可能会在帧与帧之间产生可见的间隙。
电平活动状态:
-
高电平活动(Active High):
- 当HSync信号为高电平时,表示每行像素的显示开始。
- 这种配置下,信号的高电平触发显示设备开始扫描新的一行像素。
-
低电平活动(Active Low):
- 当HSync信号为低电平时,表示每行像素的显示开始。
- 这种配置下,信号的低电平触发显示设备开始扫描新的一行像素。
5.4 根据设备树文件信息编程
5.4.1 从设备树获得参数
时序参数、引脚极性等信息,都被保存在一个display_timing结构体里:
5.4.2 使用参数配置LCD控制器
在LCD驱动程序原有框架下增添以下代码:
这段代码定义了一个结构体 imx6ull_lcdif
,它代表i.MX6ULL处理器中的LCD接口(LCDIF)的寄存器布局。LCDIF是负责驱动LCD显示的硬件接口。以下是对代码中每个成员的解释和注释:
在这个结构体中:
- 每个
volatile unsigned int
成员代表一个寄存器,volatile
关键字表示这些寄存器的值可能会在程序不可控的情况下改变。 - 每个
RESERVED_x[12]
成员是一个填充数组,通常用于在结构体中保留空间,以保持对齐或为将来的扩展预留空间。 - 寄存器名称通常反映了它们的功能,如控制、时序、虚拟显示控制、DVI控制、色彩空间转换等。这些寄存器用于配置和控制LCDIF的操作。
定义三个指针,两个时钟指针一个GPIO指针
1. static struct gpio_desc *bl_gpio;
- 类型:
struct gpio_desc *
是一个指向gpio_desc
结构的指针。gpio_desc
是Linux内核中用于表示通用输入输出(GPIO)引脚的描述符。 - 作用:这个指针通常用于操作特定的GPIO引脚。例如,可以使用这个指针来控制一个引脚的输出电平,或者读取一个引脚的输入状态。
- 用途:在这个上下文中,
bl_gpio
很可能是用来控制背光(Backlight)的GPIO引脚。背光是LCD显示屏背后的光源,通常需要通过GPIO引脚来控制其开关和亮度。
2. static struct clk* clk_pix;
- 类型:
struct clk*
是一个指向clk
结构的指针。clk
是Linux内核中用于表示时钟源的描述符。 - 作用:这个指针用于表示和操作一个时钟源。时钟源是为硬件设备提供时钟信号的来源,对于需要时钟信号的设备(如LCD控制器)来说非常重要。
- 用途:在这个上下文中,
clk_pix
很可能是用来表示与像素时钟(Pixel Clock)相关的时钟源。像素时钟是控制LCD显示屏像素传输速率的时钟信号,对于确保图像正确显示至关重要。
3. static struct clk* clk_axi;
- 类型:同样是一个指向
clk
结构的指针。 - 作用:用于表示和操作另一个时钟源。
- 用途:
clk_axi
很可能是用来表示与AXI总线时钟(AXI Bus Clock)相关的时钟源。AXI(Advanced eXtensible Interface)是ARM架构中的一种总线协议,用于处理器与外围设备之间的高速数据传输。AXI总线时钟是控制AXI总线数据传输速率的时钟信号。
LCD控制器的使能函数
- 通过将
CTRL
寄存器的最低位设置为1,函数启用了LCD控制器。这通常意味着:- 启动显示输出:LCD控制器开始发送数据到显示设备。
- 激活显示信号:如VSYNC、HSYNC等控制信号被激活,开始控制显示设备的刷新。
重要性
- 这种操作通常在LCD控制器初始化完成后执行,是开始显示输出的最后一步。
- 确保在启用控制器之前,所有相关的寄存器(如时序控制、数据格式、缓冲区地址等)已经被正确配置。
LCD控制器初始化函数:
变量初始化:
配置LCD控制器的CTRL寄存器
设置LCDIF的寄存器CTRL1
检测(probe)函数新增代码,根据上述新增代码增加相关代码,初始化变量
6. 上机实验
实验步骤:
-
去除内核自带的驱动程序
修改内核文件:drivers/video/fbdev/Makefile
,把内核自带驱动程序mxsfb.c对应的那行注释掉,如下:
-
加入我们编写的驱动程序、设备树文件
-
重新编译内核、设备树
-
上机测试:使用编译出来的内核、设备树启动板子
-
使用NFS挂载的目的:
- NFS(网络文件系统)挂载允许嵌入式设备通过网络共享并访问文件系统。在上机测试阶段,挂载NFS可能用于以下目的:
- 文件共享:共享开发机上的文件系统,方便在目标设备上访问和测试文件。
- 远程调试:通过NFS挂载,开发者可以远程修改和调试设备上的文件。
- 系统更新:通过NFS分发新的系统映像或应用程序,无需物理接触设备。
- 资源节省:减少目标设备的存储需求,因为部分或全部文件系统可以存储在NFS服务器上。
- NFS(网络文件系统)挂载允许嵌入式设备通过网络共享并访问文件系统。在上机测试阶段,挂载NFS可能用于以下目的:
-
挂载NFS的步骤:
- 配置NFS服务器,确保它能够提供所需的文件系统。
- 在目标设备的启动脚本或配置中添加NFS挂载点。
- 确保网络连接正常,目标设备能够访问NFS服务器。
- 启动时,目标设备将挂载NFS共享,使其上的文件可供使用。