资讯详情

【正点原子MP157连载】第三十九章 LCD驱动实验-摘自【正点原子】STM32MP1嵌入式Linux驱动开发指南V1.7

1)实验平台:正点原子STM32MP157开发板 2)购买链接:https://item.taobao.com/item.htm?&id=629270721801 3)全套实验源码 手册 视频下载地址:http://www.openedv.com/thread-318813-1-1.html 4)正点原子官方B站:https://space.bilibili.com/394620890 5)正点原子STM32MP157技术交流群:691905614 在这里插入图片描述

第三十九章 LCD驱动实验

LCD它是一种非常常用的外部,通过LCD提交人机交互效率的图片、界面等。STM32MP1提供了一个LTDC接口用于连接RGB接口液晶屏。在这一章中,让我们来学校在。Linux下驱动LCD屏。

39.1 LCD和LTDC简介 39.1.1 LCD简介 LCD全称是Liquid Crystal Display,液晶显示器是目前最常用的显示器。手机、电脑、各种人机交互设备基本使用LCD,最常见的是手机和电脑显示器。因为作者不是LCD员工,对LCD不了解具体原理,网上对LCD原理解释如下: LCD 液晶盒放置在两个平行玻璃基板中,下基板玻璃设置TFT(薄膜晶体管)通过在基板玻璃上设置彩色滤光片TFT上部信号和电压的变化可以控制液晶分子的旋转方向,从而控制每个像素点的偏振光出射。 我们现在要在那里STM32MP使用开发板LCD,所以不需要研究LCD我们只需要从使用的角度关注具体的实现原理LCD几个重点: 1、分辨率 提起LCD我们都会听到720P、1080P、2K或4K这样的字眼,这就是LCD显示器分辨率。LCD显示器由像素点组成,像素点类似于灯(在OLED在显示器中,像素点是一个小灯),这个小灯是RGB灯,也就是由R(红色)、G(绿色)和B(蓝色)由三种颜色组成,而RGB是光的三原色。1080P的意思就是LCD屏幕上的像素数量是1920*1080个像素点,即屏幕上的1080个像素点,共1920列,如图39.1.1.1所示:

图39.1.1.1 LCD像素点排布 图39.1.1.1就是1080P 显示器的像素示意图,X轴就是LCD横轴显示器,Y轴是显示器的垂直轴。图中的小方块是像素点,共1920年1080=2073600个像素点。左上角的A点是第一个像素点,右下角的C点是最后一个像素点。K就是25601440个像素点,4K是38402160个像素点。显然,在LCD分辨率越高越清晰,尺寸不变。同样,在分辨率不变的情况下,LCD尺寸越小越清晰。例如,我们常用的24英寸显示器基本上是1080P是的,我们现在用的5寸手机基本都是1080P是的,但是手机的细腻程度比24寸的显示器好很多! 由此可见,LCD显示器的分辨率是一个非常重要的参数,但分辨率辨率越高LCD越好。衡量一个LCD分辨率只是其中一个参数,还有其他参数,如色彩还原度、色彩偏差、亮度、视角、屏幕刷新率等。 2、像素格式 如上所述,一个像素点相当于一个像素点RGB小灯,通过控制R、G、B这三种颜色的亮度可以显示各种颜色。如何控制?R、G、B这三种颜色的显示亮度如何?一般像素点R、G、B这三部分分别使用8bit一个像素点是8bit3=24bit,也就是说,像素点有三个字节,称为像素格式RGB888。如果再加入8bit的Alpha(透明道是透明的,一个像素点是32bit,这种像素格式称为四个字节ARGB8888。如果学习过STM应该听说过32的话RGB我们在本章实验中使用565这种像素格式ARGB8888这种像素格式,一个像素占据了四个字节的内存,每个字节的分布如图39所示.1.1.2所示:

图39.1.1.2 ARGB8888数据格式 在图39.1.1.一个像素点是四个字节bit31bit24是Alpha通道,bit23bit16是RED通道,bit15bit14是GREEN通道,bit7bit0是BLUE通道。所以红色对应的值是0X00FF蓝色对应的值为0000X000000FF,绿色对应值为0X0000FF00。通过调节R、G、B比例可以产生其他颜色,如0X00FFFF00就是黄色,0X00000000就是黑色,0X00FFFFFF就是白色。您可以打开计算机的绘图工具,使用调色板获得所需颜色对应的值,如图39所示.1.1.3所示:

图39.1.1.3 颜色选取 3、LCD屏幕接口 LCD屏幕或显示器有多种接口,如显示器上常见的接口VGA、HDMI、DP等等,但是STM32MP开发板不支持这些接口。STM32MP1支持RGB接口的LCD,RGBLCD接口信号线如表39.1.1.1所示:

表39.1.1.1 RGB数据线 表39.1.1.1就是RGBLCD的信号线,R[7:0]、G[7:0]和B[7:0]这24条是数据线,DE、VSYNC、HSYNC和PCLK这四个是控制信号线。RGB LCD一般有两种驱动模式:DE模式和HV这两种模式的区别在于模式DE需要使用模式DE信号线,而HV不需要使用模式DE信号线,在DE不需要模式HSYNC即使不连接信号线HSYNC信号线LCD也可以正常工作。 ALIENTEK一共有四款RGB LCD屏幕分别为:ATK-4342(4.3寸,480272)、ATK-4842(4.3寸,800480)、ATK-7084(7寸,800480)和ATK-7016(7寸,1024600)600)ATK-以7016屏为例,ATK-屏幕接口原理图7016如图39.1.1.4所示:

图39.1.1.4 RGB LCD液晶屏幕接口 图中J1是对外界面,是40PIN的FPC座(0.5mm间距),通过FPC线,可以连接STM32MP1开发板上方。接口非常完美,使用RGB并支持888格式DE&HV还支持触摸屏和背光控制。并非所有右侧的电阻都是焊接的,用户可以根据自己的时间需要选择是否焊接(正点原子出厂屏不能修改!)。默认情况,R1和R6焊接,设置LCD_LR和LCD_UD,控制LCD扫描方向,从左到右,从上到下(横屏看)。而LCD_R7/G7/B7则用来设置LCD的ID,由于RGBLCD没有读写寄存器,就没有所谓的ID,在这里,我们控制模块R7/G7/B7的上下拉来自定义LCD模块的ID,帮助SOC判断当前LCD为了提高程序兼容性,面板的分辨率和相关参数。这些位置的设置关系如表39.1.1.2所示:

表39.1.1.2 ALIENTEK RGBLCD模块ID对应关系 ATK-设置7016模块M2:M0=010就可以了。这样,我们就可以在程序中读取它LCD_R7/G7/B7,得到M0:M从而判断2的值RGBLCD不同的配置可以实现模块的型号LCD模块兼容。 4、LCD时间参数 如果将LCD图像显示的过程想象成绘画,所以在显示过程中,用笔在不同的像素点上画出不同的颜色。这支笔从左到右、从上到下扫描每个像素点,并在像素上绘制相应的颜色。当画出最后一个像素点时,画出一幅图像。假如一个LCD的分辨率为1024*600,那么它的扫描如图39.1.1.5所示:

图39.1.1.5 LCD一帧图像扫描图 结合图39.1.1.让我们来看看LCD是怎么扫描显示一帧图像的。一帧图像也由一行一行组成。HSYNC它是一个水平同步信号,也称为行同步信号。当这个信号产生时,它意味着新行开始显示,所以这个信号在图39中.1.1.5的最左边。VSYNC信号是垂直同步信号,也称帧同步信号。当这个信号产生时,它意味着新的帧图像开始显示,因此图39中的信号.1.1.4的左上角。 在图39.1.1.5可以看到一圈黑边,真正有效的显示区是中间白色部分。那这一圈“黑边”是什么东西呢?这就要从显示器的“祖先”CRT显示器开始说起了,CRT显示器就是以前很常见的那种大屁股显示器,在2019年应该很少见了,如果在农村应该还是可以见到的。CRT显示器屁股后面是个电子枪,这个电子枪就是我们上面说的“画笔”,电子枪打出的电子撞击到屏幕上的荧光物质使其发光。只要控制电子枪从左到右扫完一行(也就是扫描一行),然后从上到下扫描完所有行,这样一帧图像就显示出来了。也就是说,显示一帧图像电子枪是按照‘Z’形在运动,当扫描速度很快的时候看起来就是一幅完成的画面了。 当显示完一行以后会发出HSYNC信号,此时电子枪就会关闭,然后迅速的移动到屏幕的左边,当HSYNC信号结束以后就可以显示新的一行数据了,电子枪就会重新打开。在HSYNC信号结束到电子枪重新打开之间会插入一段延时,这段延时就图39.1.1.5中的HBP。当显示完一行以后就会关闭电子枪等待HSYNC信号产生,关闭电子枪到HSYNC信号产生之间会插入一段延时,这段延时就是图39.1.1.5中的HFP信号。同理,当显示完一帧图像以后电子枪也会关闭,然后等到VSYNC信号产生,期间也会加入一段延时,这段延时就是图39.1.1.5中的VFP。VSYNC信号产生,电子枪移动到左上角,当VSYNC信号结束以后电子枪重新打开,中间也会加入一段延时,这段延时就是图39.1.1.5中的VBP。 HBP、HFP、VBP和VFP就是导致图39.1.1.5中黑边的原因,但是这是CRT显示器存在黑边的原因,现在是LCD显示器,不需要电子枪了,那么为何还会有黑边呢?这是因为RGB LCD屏幕内部是有一个IC的,发送一行或者一帧数据给IC,IC是需要反应时间的。通过这段反应时间可以让IC识别到一行数据扫描完了,要换行了,或者一帧图像扫描完了,要开始下一帧图像显示了。因此,在LCD屏幕中继续存在HBP、HFP、VPB和VFP这四个参数的主要目的是为了定有效的像素数据。这四个时间是LCD重要的时间参数,后面编写LCD驱动的时候要用到的,至于这四个时间参数具体值是多少,那要需要去查看所使用的LCD数据手册了。 5、RGB LCD屏幕时序 上面讲了行显示和帧显示,我们来看一下行显示对应的时序图,如图39.1.1.6所示:

图39.1.1.6 行显示时序 图39.1.1.6就是RGB LCD的行显示时序,我们来分析一下其中重要几个的参数: HSYNC:行同步信号,当此信号有效的话就表示开始显示新的一行数据,查阅所使用的LCD数据手册可以知道此信号是低电平有效还是高电平有效,假设此时是低电平有效。 HSPW:有些地方也叫做thp,是HSYNC信号宽度,也就是HSYNC信号持续时间。HSYNC信号不是一个脉冲,而是需要持续一段时间才是有效的,单位为CLK。 HBP:有些地方叫做thb,前面已经讲过了,术语叫做行同步信号后肩,单位是CLK。 HOZVAL:有些地方叫做thd,显示一行数据所需的时间,假如屏幕分辨率为1024*600,那么HOZVAL就是1024,单位为CLK。 HFP :有些地方叫做thf,前面已经讲过了,术语叫做行同步信号前肩,单位是CLK。 当HSYNC信号发出以后,需要等待HSPW+HBP个CLK时间才会接收到真正有效的像素数据。当显示完一行数据以后需要等待HFP个CLK时间才能发出下一个HSYNC信号,所以显示一行所需要的时间就是:HSPW + HBP + HOZVAL + HFP。 一帧图像就是由很多个行组成的,RGB LCD的帧显示时序如图39.1.1.7所示:

图39.1.1.7 帧显示时序图 图39.1.1.7就是RGB LCD的帧显示时序,我们来分析一下其中重要的几个参数: VSYNC:帧同步信号,当此信号有效的话就表示开始显示新的一帧数据,查阅所使用的LCD数据手册可以知道此信号是低电平有效还是高电平有效,假设此时是低电平有效。 VSPW:些地方也叫做tvp,是VSYNC信号宽度,也就是VSYNC信号持续时间,单位为1行的时间。 VBP:有些地方叫做tvb,前面已经讲过了,术语叫做帧同步信号后肩,单位为1行的时间。 LINE:有些地方叫做tvd,显示一帧有效数据所需的时间,假如屏幕分辨率为1024*600,那么LINE就是600行的时间。 VFP:有些地方叫做tvf,前面已经讲过了,术语叫做帧同步信号前肩,单位为1行的时间。 显示一帧所需要的时间就是:VSPW+VBP+LINE+VFP个行时间,最终的计算公式: T = (VSPW+VBP+LINE+VFP) * (HSPW + HBP + HOZVAL + HFP) 因此我们在配置一款RGB LCD的时候需要知道这几个参数:HOZVAL(屏幕有效宽度)、LINE(屏幕有效高度)、HBP、HSPW、HFP、VSPW、VBP和VFP。ALIENTEK三款RGB LCD屏幕的参数如表39.1.1.3所示:

表39.1.1.3 RGB LCD屏幕时间参数 6、像素时钟 像素时钟就是RGB LCD的时钟信号,以ATK7016这款屏幕为例,显示一帧图像所需要的时钟数就是: = (VSPW+VBP+LINE+VFP) * (HSPW + HBP + HOZVAL + HFP) = (3 + 20 + 600 + 12) * (20 + 140 + 1024 + 160) = 635 * 1344 = 853440。 显示一帧图像需要853440个时钟数,那么显示60帧就是:853440 * 60 = 51206400≈51.2M,所以像素时钟就是51.2MHz。 7、显存 在讲像素格式的时候就已经说过了,如果采用ARGB8888格式的话一个像素需要4个字节的内存来存放像素数据,那么1024600分辨率就需要1024600*4=2457600B≈2.4MB内存。但是RGB LCD内部是没有内存的,所以就需要在开发板上的DDR3中分出一段内存作为RGB LCD屏幕的显存,我们如果要在屏幕上显示什么图像的话直接操作这部分显存即可。 39.1.2 LTDC接口 LTDC是STM32MP1自带的液晶屏幕接口,用于连接RGB LCD接口的屏幕,LTDC接口特性如下: ①、24位RGB并行像素输出;每像素8位(RGB888) ②、2个带有专用FIFO的显示层(FIFO深度64x64位) ③、查色表(CLUT),每个图层最高 256 种颜色(256x24)位 ④、可针对不同显示面板编程时序 ⑤、每层有多达8个输入颜色格式可供选择,分别为:ARGB8888、RGB888、RGB565、ARGB1555、ARGB4444、L8、AL44、AL88。 图39.1.2.1为LTDC功能框架图:

图39.1.2.1 LTDC功能框架图 从图可以看出LTDC的信号可以分为两类:4个控制信号(LCD_CLK像素时钟、LCD_HSYNC水平同步、LCD_VSYNC垂直同步、LCD_DE数据有效)和3个RGB数据信号(8bit x 3)。 39.2 DRM驱动框架 39.2.1 DRM简介 在Linux系统中,主流的显示框架有两种:DRM(Direct Rendering Module)框架和FB(FrameBuffer)框架。FB框架不能处理基于3D加速GPU显卡,DRM是可以统一管理GPU显示,所以DRM相对于FB更能适应新的显示硬件。比如DRM支持多层合成、支持VSYNC、支持DMA-BUF、支持fence机制等等。 下图就是一个DRM驱动框架包括两部分:DRM core和DRM driver。DRM core提供了一个基本的DRM框架,DRM driver就可以注册进DRM框架,同时为用户空间提供一组ioctl。1ibdrm对底层接口(DRM driver提供的ioctl)进行封装,向上层提供统一的API接口。DRM driver包含了GEM模块和KMS模块,这两模块也分为好几个小模块。

图39.1.2.1 DRM驱动框架 先介绍图39.1.2.1中的部分名词意思和作用: 图形执行管理器(GEM):全称Graphics Execution Manager,这是一个内存管理器,主要负责内存的分配和释放,可以调用GPU。 DUMB:这是一个dumb缓冲区,主要负责一些简单的buffer显示,可以通过CPU直接渲染dumb,GPU不会使用dumb。 内核显示模式设置(KMS):全称Kernel Mode Setting,主要负责显示的控制,包括屏幕分辨率、屏幕刷新率和颜色深度等等。 CRTC:就是指显示控制器,在DRM里有多个显存,就可以通过操作CRTC来控制要显示那个显存。 Encoder:负责从CRTC里输出的timing时序转换成外部设备所需要的信号的模块,同时也负责控制LCD的显示。 Connector:连接物理显示设备的连接器,比如DSI、HDMI等等。 Plane:负责获取显存,在输出到CRTC里,说明CRTC必须要有一个Plane。 帧缓冲(FB):能够显示图层的buffer。 接着我们看下DRM driver里的两大模块GEM和KMS是如何连接显示器的,如下图所示:

图39.1.2.2 DRM driver模块关系图 在图39.1.2.2里,蓝色框表示KMS里的模块。plane是连接crtc和framebuffer的纽带,而encoder是连接crtc和connector的纽带。GEM是负责和物理的buffer打交道不是framebuffer。plane把获取到显存输出到crtc里,crtc通过connector接口输出到显示器。 39.2.2 ST官方的DRM驱动框架介绍 在Linux系统中,DRM驱动的核心主要就一个drm_driver结构体,驱动程序要初始化drm_driver结构体,然后调用drm_dev_init函数,将其注册到DRM core。 我们简单分析ST官方编写的在Linux下的 DRM驱动,打开stm32mp151.dtsi,然后找到ltdc节点内容,如下所示:

示例代码39.2.2.1 ltdc节点
1652    ltdc: display-controller@5a001000 { 
        
1653        compatible = "st,stm32-ltdc";
1654        reg = <0x5a001000 0x400>;
1655        interrupts = <GIC_SPI 88 IRQ_TYPE_LEVEL_HIGH>,
1656                 <GIC_SPI 89 IRQ_TYPE_LEVEL_HIGH>;
1657        clocks = <&rcc LTDC_PX>;
1658        clock-names = "lcd";
1659        resets = <&rcc LTDC_R>;
1660        status = "disabled";
1661    };
示例代码39.2.2.1中的ltdc节点信息是所有使用STM32MP1芯片的板子所共有的,并不是完整的ltdc节点信息。可以看出ltdc节点的compatible属性值为“st,stm32-ltdc”,因此在Linux源码中搜索这个字符串就可以找到STM32MP1的DRM驱动文件,这个文件为“drivers/gpu/drm/stm/drv.c”文件,打开此文件找的如下内容所示:
示例代码39.2.2.2 platform下的DRM驱动
236 static const struct of_device_id drv_dt_ids[] = { 
        
237     { 
         .compatible = "st,stm32-ltdc"},
238     { 
         /* end node */ },
239 };
240 MODULE_DEVICE_TABLE(of, drv_dt_ids);
241
242 static struct platform_driver stm_drm_platform_driver = { 
        
243     .probe = stm_drm_platform_probe,
244     .remove = stm_drm_platform_remove,
245     .driver = { 
        
246         .name = "stm32-display",
247         .of_match_table = drv_dt_ids,
248         .pm = &drv_pm_ops,
249     },
250 };

从示例代码39.2.2.2可以看出,这是一个标准的platform驱动,当驱动和设备匹配以后stm_drm_platform_probe函数就会执行。 和其他设备驱动一样,DRM也分为DRM设备和DRM驱动,drm_device结构体为DRM设备,drm_driver为DRM驱动,我们依次来看一下这两个结构体。 1、drm_device结构体 drm_device结构体定义在include/drm/drm_device.h文件里,内容如下(有省略):

示例代码39.2.2.3 drm_device结构体
1  struct drm_device { 
        
2   	struct list_head legacy_dev_list;
3   	int if_version;
4   	struct kref ref;
......
30  	u32 max_vblank_count;
31  	struct list_head vblank_event_list;
32  	spinlock_t event_lock;
33  	struct drm_agp_head *agp;
34  	struct pci_dev *pdev;
35  	unsigned int num_crtcs;
36  	struct drm_mode_config mode_config;
37  	struct mutex object_name_lock;
38  	struct idr object_name_idr;
39  	struct drm_vma_offset_manager *vma_offset_manager;
40  	struct drm_vram_mm *vram_mm;
41  	enum switch_power_state switch_power_state;
42  	struct drm_fb_helper *fb_helper;
43 };
我们在编写DRM驱动的时候需要自行申请drm_device内存并且使用初始化,这个可以直接通过drm_dev_alloc函数来完成,函数原型如下:

struct drm_device *drm_dev_alloc(struct drm_driver *driver, struct device *parent) 此函数会先调用kzalloc为drm_device分配内存,然后调用drm_dev_init初始化drm_device,函数参数和返回值含义如下: driver:drm_driver结构体指针,也就是DRM设备对应的DRM 驱动。 parent:父设备。 返回值:返回分配成功的新DRM设备,如果返回ERR_PTR的话就表示drm_device申请失败。 drm_device分配成功以后还需要使用drm_dev_register函数向内核注册,函数原型如下: int drm_dev_register(struct drm_device *dev, unsigned long flags) 函数参数和返回值含义如下: dev:需要注册到内核的drm_device。 flags:传递给驱动.load函数的标志。 返回值:0,成功;负数,失败。 2、drm_driver结构体 Linux内核为DRM驱动提供一个叫做drm_driver的结构体,drm_driver结构体包含了DRM驱动的完整属性和操作集合,因此每一个DRM驱动都必须有一个drm_driver。drm_driver结构体定义在include/drm/drm_drv.h文件里,内容如下(有省略):

示例代码39.2.2.4 drm_driver结构体
1  struct drm_driver { 
        
2   int (*load) (struct drm_device *, unsigned long flags);
3   int (*open) (struct drm_device *, struct drm_file *);
......
56 
57  int (*dumb_create)(struct drm_file *file_priv,
58             struct drm_device *dev,
59             struct drm_mode_create_dumb *args);
60  int (*dumb_map_offset)(struct drm_file *file_priv,
61                 struct drm_device *dev, uint32_t handle,
62                 uint64_t *offset);
63  int (*dumb_destroy)(struct drm_file *file_priv,
64              struct drm_device *dev,
65              uint32_t handle);
66  const struct vm_operations_struct *gem_vm_ops;
67 
68  int major;     	 	/* 驱动主设备号 */
69  int minor;      		/* 驱动次设备号 */
70  int patchlevel; 		/* 驱动补丁等级 */
71  char *name;     		/* 驱动名字 */
72  char *desc;     		/* 驱动描述 */
73  char *date;     		/* 驱动日期 */
74  u32 driver_features;	/* 驱动特性 */
75  const struct drm_ioctl_desc *ioctls;
76  int num_ioctls;
77  const struct file_operations *fops;
78 
79  struct list_head legacy_dev_list;
80  int (*firstopen) (struct drm_device *);
81  void (*preclose) (struct drm_device *, struct drm_file *file_priv);
82  int (*dma_ioctl) (struct drm_device *dev, void *data, 
struct drm_file *file_priv);
83  int (*dma_quiescent) (struct drm_device *);
84  int (*context_dtor) (struct drm_device *dev, int context);
85  int dev_priv_size;
86 };
drm_driver结构体的成员变量很多,我们重点关注driver_features、fops和dumb_create。
第57行,dumb_create是一个回调函数,用于创建gem对象,并分配物理buffer。
第74行,driver_features用来描述驱动特性,枚举类型drm_driver_feature定义了可以选择的驱动特性:
DRIVER_GEM:驱动使用GEM内存管理,此特性必须选中!
DRIVER_MODESET:驱动支持模式设置接口(KMS)。
DRIVER_RENDER:驱动支持专用渲染节点。
DRIVER_ATOMIC:驱动提供完整的原子操作,以供用户空间API函数操作。

DRIVER_SYNCOBJ:驱动支持SYNCOBJ,用于命令提交的显式同步。 DRIVER_SYNCOBJ_TIMELINE:驱动支持SYNCOBJ时间线。 DRIVER_USE_AGP: 驱动程序使用AGP接口,DRM核心将管理AGP资源。 DRIVER_LEGACY:表明这是一个使用影子附着的旧驱动程序,不使用。 DRIVER_PCI_DMA:驱动支持PCI DMA。 DRIVER_SG:驱动可以提供scatter/gather DMA功能。 DRIVER_HAVE_DMA:驱动支持DMA。 DRIVER_HAVE_IRQ:驱动支持IRQ,旧驱动使用。 DRIVER_KMS_LEGACY_CONTEXT:仅供nouveau使用! 第77行,fops这个相信大家都很熟悉了吧,就是一个简单的字符设备接口结构体。 前面我们说了,STM32MP1的LTDC是个platform驱动,当设备和驱动匹配成功以后stm_drm_platform_probe函数就会执行,函数内容如下:

示例代码39.2.2.5 stm_drm_platform_probe函数
191 static int stm_drm_platform_probe(struct platform_device *pdev)
192 { 
        
193     struct device *dev = &pdev->dev;
194     struct drm_device *ddev;
195     int ret;
196
197     DRM_DEBUG("%s\n", __func__);
198
199     dma_set_coherent_mask(dev, DMA_BIT_MASK(32));
200
201     ddev = drm_dev_alloc(&drv_driver, dev);
202     if (IS_ERR(ddev))
203         return PTR_ERR(ddev);
204
205     ret = drv_load(ddev);
206     if (ret)
207         goto err_put;
208
209     ret = drm_dev_register(ddev, 0);
210     if (ret)
211         goto err_put;
212
213     drm_fbdev_generic_setup(ddev, 16);
214
215     return 0;
216
217 err_put:
218     drm_dev_put(ddev);
219
220     return ret;
221 }
第201行,调用drm_dev_alloc函数,此函数主要完成三个功能:
①、给drm_device分配内存。
②、通过drm_dev_init函数初始化drm_device。
drm_dev_alloc会通过调用drm_dev_init函数将drm_driver和drm_device联系起来,drm_device结构体里面有个drvier指针成员变量,此成员变量指向DRM设备对应的DRM驱动的。因此,drm_dev_init函数会通过将drm_device下的driver成员变量指向drm_driver来实现两者相连。
第205 行,drv_load这个函数就是初始化KMS,放到后面分析。
第209 行,注册drm_device对象进DRM core。
接下来看下,drv_load函数,内容如下:
示例代码39.2.2.6 drv_load函数
29  #define STM_MAX_FB_WIDTH    2048
30  #define STM_MAX_FB_HEIGHT   2048
31 
32  static const struct drm_mode_config_funcs drv_mode_config_funcs = { 
        
33      .fb_create = drm_gem_fb_create,
34      .atomic_check = drm_atomic_helper_check,
35      .atomic_commit = drm_atomic_helper_commit,
36  };
......
79  static int drv_load(struct drm_device *ddev)
80  { 
        
81      struct platform_device *pdev = to_platform_device(ddev->dev);
82      struct ltdc_device *ldev;
83      int ret;
84 
85      DRM_DEBUG("%s\n", __func__);
86 
87      ldev = devm_kzalloc(ddev->dev, sizeof(*ldev), GFP_KERNEL);
88      if (!ldev)
89          return -ENOMEM;
90 
91      ddev->dev_private = (void *)ldev;
92 
93      drm_mode_config_init(ddev);
94 
95      /* 96 * set max width and height as default value. 97 * this value would be used to check framebuffer size limitation 98 * at drm_mode_addfb(). 99 */
100     ddev->mode_config.min_width = 0;
101     ddev->mode_config.min_height = 0;
102     ddev->mode_config.max_width = STM_MAX_FB_WIDTH;
103     ddev->mode_config.max_height = STM_MAX_FB_HEIGHT;
104     ddev->mode_config.funcs = &drv_mode_config_funcs;
105
106     ret = ltdc_load(ddev);
107     if (ret)
108         goto err;
109
110     drm_mode_config_reset(ddev);
111     drm_kms_helper_poll_init(ddev);
112
113     platform_set_drvdata(pdev, ddev);
114
115     return 0;
116 err:
117     drm_mode_config_cleanup(ddev);
118     return ret;
119 }

第102行,设置DRM驱动X轴(宽度)最大支持2048个像素。 第103行,设置DRM驱动Y轴(宽度)最大支持2048个像素,结合上一行,可以看出驱动里面设置的最大分辨率支持20482048。但是根据STM32MP157手册所描述,最大支持1366768分辨率的屏幕。 第104行,设置framebuffer的回调函数结构体。 第106行,这里我们要引入drm_panel结构体,此结构体作用是提供一堆控制回调函数。比如屏幕参数回调函数,背光控制函数等等。ltdc_load函数是负责初始化ltdc接口(同时connector和encoder一起初始化)。在connector初始化的时候,就会调用drm_panel结构体里的获取屏幕参数函数(所以我们只需要提供一个屏的驱动就能正常显示了)。通常encoder和connector是放在同一个驱动初始化的,目的是为了方便驱动程序设计。 重点来了!要完成整个DRM驱动的正常初始化,前面的GEM和KMS这些模块ST官方已经提供给我们了,只需提供一个drm_panel对象就行了。打开“include/drm/drm_panel.h”文件,找到如下内容所示:

示例代码39.2.2.7 drm_panel结构体
1   struct drm_panel { 
        
2       struct drm_device *drm; 					/* drm_device 对象 */ 
3       struct drm_connector *connector; 		/* connector 对象 */ 
4       struct device *dev; 						/* 设备节点 dev */
5       const struct drm_panel_funcs *funcs; 	/* 回调函数的结构体 */ 
6       struct list_head list;  
7   };

这个结构体是比较简单的相信各位能够看懂。主要是第5行,funcs对象成员。在这里我们的DRM驱动分析基本完成了。 39.3 RGB LCD驱动分析(屏的驱动) 上一小节说了,drm_panel就是DRM驱动的核心结构体,我们需要实现此结构体。我们先来看一下panel_simple结构体。这里用到了面向对象的思维,drm_panel结构体是基类,panel_simple在drm_panel基础上增加了一些成员变量,相当于继承类。 我们使用的是RGB LCD屏,所以我们的LCD驱动文件为drivers/gpu/drm/panel/panel-simple.c,打文件找到如下内容所示:

示例代码39.3.1 panel_simple结构体
99  struct panel_simple { 
        
100     struct drm_panel base;
101     bool prepared;
102     bool enabled;
103     bool no_hpd;
104
105     const struct panel_desc *desc;
106
107     struct backlight_device *backlight;
108     struct regulator *supply;
109     struct i2c_adapter *ddc;
110
111     struct gpio_desc *enable_gpio;
112
113     struct drm_display_mode override_mode;
114 };
panel_simple结构体用来管理RGB LCD设备。
第100行,base成员变量,为drm_panel结构体类型。可以看出panel_simple就是在drm_panel的基础上发展而来的,在DRM驱动注册的时候就会回调base->funcs。
第105行,desc属性就是RGB屏参数结构体。
第107行,屏的背光结构体。
接着我们看下如何跟设备树匹配的,找到如下所示内容:
示例代码39.3.2 LCD驱动的platform_driver
3491    static struct platform_driver panel_simple_platform_driver = { 
        
3492        .driver = { 
        
3493            .name = "panel-simple",
3494            .of_match_table = platform_of_match,
3495        },
3496        .probe = panel_simple_platform_probe,
3497        .remove = panel_simple_platform_remove,
3498        .shutdown = panel_simple_platform_shutdown,
3499    };
示例代码39.3.1中,这是一个标准的platform驱动框架,第3494行就是匹配表。platform_of_match内容如下所示(有省略):
示例代码39.3.3 platform_of_match结构体
3133    static const struct of_device_id platform_of_match[] = { 
        
3134        { 
        
3135            .compatible = "ampire,am-480272h3tmqw-t01h",
3136            .data = &ampire_am_480272h3tmqw_t01h,
3137 

标签: fpc补强连接器窄间距连接器用于板对fpc连接各位d2553晶体管的参数

锐单商城拥有海量元器件数据手册IC替代型号,打造 电子元器件IC百科大全!

锐单商城 - 一站式电子元器件采购平台