0.前言
??LCD 是很常用的一個外設(shè),通過 LCD 可以顯示圖片、界面UI等,提高人機交互的效率。STM32MP1 提供了一個 LTDC 接口用于連接 RGB 接口的液晶屏。本節(jié)就來學(xué)習(xí)如何使用這個接口。
一、LCD 和 LTDC 簡介
1.LCD 簡介
??LCD 全稱是 Liquid Crystal Display,也就是液晶顯示器,是現(xiàn)在最常用到的顯示器。網(wǎng)上對于 LCD 的原理解釋如下:
LCD 的構(gòu)造是在兩片平行的玻璃基板當(dāng)中放置液晶盒,下基板玻璃上設(shè)置 TFT(薄膜晶體管),上基板玻璃上設(shè)置彩色濾光片,通過 TFT 上的信號與電壓改變來控制液晶分子的轉(zhuǎn)動方向,從而達到控制每個像素點偏振光出射與否而達到顯示目的。
現(xiàn)在要在 STM32MP1 開發(fā)板上使用 LCD,不需要去研究 LCD 的具體實現(xiàn)原理,只需要從使用的角度去關(guān)注 LCD 的幾個重要點:
1)分辨率
??提起 LCD 顯示器,我們都會聽到 720P、1080P、2K 或 4K 這樣的字眼,這個就是 LCD 顯示器分辨率。LCD 顯示器都是由一個個的像素點組成,像素點就類似一個燈(在 OLED 顯示器中,像素點就是一個小燈),這個小燈是 RGB 燈,也就是由 R(紅色)、 G(綠色)和 B(藍色)這三種顏色組成的,而 RGB 就是光的三原色。1080P 的意思就是 LCD 屏幕上的像素數(shù)量是 19201080 個,也就是這個屏幕一列 1080 個像素點,一共 1920 列,如下圖所示:
X 軸就是 LCD 顯示器的橫軸,Y 軸就是顯示器的豎軸。圖中的小方塊就是像素點,一共有 19201080=2073600 個像素點。左上角的 A 點是第一個像素點,右下角的 C 點就是最后一個像素點。2K 就是 25601440 個像素點,4K 是 38402160 個像素點。很明顯,在 LCD 尺寸不變的情況下,分辨率越高越清晰。同樣的,分辨率不變的情況下,LCD 尺寸越小越清晰。比如常用的 24 寸顯示器基本都是 1080P 的,而
現(xiàn)在使用的 5 寸的手機基本也是 1080P 的,但是手機顯示細膩程度就要比 24 寸的顯示器要好很多!
由此可見,LCD 顯示器的分辨率是一個很重要的參數(shù),但是并不是分辨率越高的 LCD 就越好。衡量一款 LCD 的好壞,分辨率只是其中的一個參數(shù),還有色彩還原程度、色彩偏離、亮度、可視角度、屏幕刷新率等其他參數(shù)。
2)像素格式
??一個像素點就相當(dāng)于一個 RGB 小燈,通過控制 R、G、B 這三種顏色的亮度就可以顯示出各種各樣的色彩。那該如何控制 R、G、B 這三種顏色的顯示亮度呢?一般一個像素點的 R、G、B 這三部分分別使用 8bit 的數(shù)據(jù),那么一個像素點就是 8bit*3=24bit,也就是說一個像素點 3 個字節(jié),這種像素格式稱為 RGB888。如果再加入 8bit 的 Alpha(透明)通道的話一個像素點就是 32bit,也就是 4 個字節(jié),這種像素格式稱為 ARGB8888。如果學(xué)習(xí)過 STM32 的話應(yīng)該還聽過 RGB565 這種像素格式,在本小節(jié)中使用 ARGB8888 這種像素格式,一個像素占用 4 個字節(jié)的內(nèi)存,這四個字節(jié)每個位的分配如下圖所示:
??一個像素點是 4 個字節(jié),其中 bit31 ~ bit24 是 Alpha 通道,bit23 ~ bit16 是 RED 通道,bit15 ~ bit14 是 GREEN 通道,bit7 ~ bit0 是 BLUE 通道。所以紅色對應(yīng)的值就是 0X00FF0000,藍色對應(yīng)的值就是 0X000000FF,綠色對應(yīng)的值為 0X0000FF00。通過調(diào)節(jié) R、G、B的比例可以產(chǎn)生其它的顏色,比如0X00FFFF00就是黃色,0X00000000就是黑色,0X00FFFFFF 就是白色。例如電腦的畫圖工具也可以通過這種方式設(shè)置顏色。
3)LCD 屏幕接口
??LCD 屏幕或者說顯示器有很多種接口,比如在顯示器上常見的 VGA、HDMI、DP 等等,但是 STM32MP1 開發(fā)板不支持這些接口。STM32MP1 支持 RGB 接口的 LCD,RGBLCD 接口的信號線如下表所示:
信號線 | 描述 |
---|---|
R[7:0] | 8 根紅色數(shù)據(jù)線 |
G[7:0] | 8 根綠色數(shù)據(jù)線 |
B[7:0] | 8 根藍色數(shù)據(jù)線 |
DE | 數(shù)據(jù)使能線 |
VSYNC | 垂直同步信號線 |
HSYNC | 水平同步信號線 |
PCLK | 像素時鐘信號 |
R[7:0]、G[7:0] 和 B[7:0] 這 24 根是數(shù)據(jù)線,DE、VSYNC、HSYNC 和 PCLK 這四根是控制信號線。RGB LCD 一般有兩種驅(qū)動模式:DE 模式和 HV 模式,這兩個模式的區(qū)別是 DE 模式需要用到 DE 信號線,而 HV 模式不需要用到 DE 信號線,在 DE 模式下是可以不需要 HSYNC 信號線的,即使不接 HSYNC 信號線 LCD 也可以正常工作。
正點原子一共有四款 RGB LCD 屏幕,兩款 4.3 寸和兩款 7 寸屏,每一款有兩種分辨率。筆者在買開發(fā)板時沒有購買 LCD 屏,所以按照教程中,對 7 寸,1024*600這個屏幕進行驅(qū)動開發(fā)。(猜測都是通用接口,應(yīng)該差別不大)
原理圖:
J1 就是對外接口,是一個 40PIN 的 FPC 座(0.5mm 間距),通過 FPC 線,可以連接到 STM32MP1 開發(fā)板上。該接口十分完善,采用 RGB888 格式,并支持 DE&HV 模式,還支持觸摸屏和背光控制。右側(cè)的幾個電阻,并不是都焊接的,用戶可以根據(jù)自己實際需要而選擇
是否焊接(正點原子出廠屏幕不能做修改!)。默認情況,R1和R6焊接,設(shè)置 LCD_LR 和 LCD_UD,控制 LCD 的掃描方向,是從左到右,從上到下(橫屏看)。而 LCD_R7/G7/B7 則用來設(shè)置 LCD 的 ID,由于 RGB LCD 沒有讀寫寄存器,也就沒有所謂的 ID,這里通過在模塊上面,控制R7/G7/B7 的上/下拉,來自定義 LCD 模塊的 ID,幫助 SOC 判斷當(dāng)前 LCD 面板的分辨率和相關(guān)參數(shù),以提高程序兼容性。這幾個位的設(shè)置關(guān)系如下表所示:
M2 LCD_B7 | M1 LCD_G7 | M0 LCD_R7 | LCD ID | 說明 |
---|---|---|---|---|
0 | 0 | 0 | 4342 | ATK-4342 RGBLCD 模塊,分辨率:480*272 |
0 | 0 | 1 | 7084 | ATK-7084 RGBLCD 模塊,分辨率:800*480 |
0 | 1 | 0 | 7016 | ATK-7016 RGBLCD 模塊,分辨率:1024*600 |
1 | 0 | 0 | 4384 | ATK-4384 RGBLCD 模塊,分辨率:800*480 |
X | X | X | NC | 暫時未用到 |
ATK-7016 模塊,就設(shè)置 M2:M0=010 即可。這樣在程序里面讀取 LCD_R7/G7/B7,得到 M0:M2 的值,從而判斷 RGBLCD 模塊的型號,并執(zhí)行不同的配置,即可實現(xiàn)不同 LCD 模塊的兼容。
4)LCD 時間參數(shù)
??如果將 LCD 顯示一幀圖像的過程想象成繪畫,那么在顯示的過程中就是用一根“筆”在不同的像素點畫上不同的顏色。這根筆按照從左至右、從上到下的順序掃描每個像素點,并且在像素畫上對應(yīng)的顏色,當(dāng)畫到最后一個像素點的時候一幅圖像就繪制好了。假如一個 LCD 的分辨率為 1024*600,那么其掃描如下圖所示:
??一幀圖像也是由一行一行組成的。HSYNC 是水平同步信號,也叫做行同步信號,當(dāng)產(chǎn)生此信號就表示開始顯示新的一行了,所以此信號都是在圖的最左邊。VSYNC 信號是垂直同步信號,也叫做幀同步信號,當(dāng)產(chǎn)生此信號就表示開始顯示新的一幀圖像了,所以此信號在圖的左上角。
??在圖中還有一圈“黑邊”,真正有效的顯示區(qū)域是中間的白色部分。這一圈“黑邊”要從顯示器的“祖先” CRT 顯示器開始說起,CRT 顯示器就是以前很常見的那種大屁股顯示器,CRT 顯示器屁股后面是個電子槍,這個電子槍就是上面說的“畫筆”,電子槍打出的電子撞擊到屏幕上的熒光物質(zhì)使其發(fā)光。只要控制電子槍從左到右掃完一行(也就是掃描一行),然后從上到下掃描完所有行,這樣一幀圖像就顯示出來了。也就是說,顯示一幀圖像電子槍是按照‘Z’形在運動,當(dāng)掃描速度很快的時候看起來就是一幅完成的畫面了。當(dāng)顯示完一行以后會發(fā)出 HSYNC 信號,此時電子槍就會關(guān)閉,然后迅速的移動到屏幕的左邊,當(dāng) HSYNC 信號結(jié)束以后就可以顯示新的一行數(shù)據(jù)了,電子槍就會重新打開。在 HSYNC 信號結(jié)束到電子槍重新打開之間會插入一段延時,這段延時就是圖中的 HBP。當(dāng)顯示完一行以后就會關(guān)閉電子槍等待 HSYNC 信號產(chǎn)生,關(guān)閉電子槍到 HSYNC 信號產(chǎn)生之間會插入一段延時,這段延時就是圖中的 HFP 信號。同理,當(dāng)顯示完一幀圖像以后電子槍也會關(guān)閉,然后等到 VSYNC 信號產(chǎn)生,期間也會加入一段延時,這段延時就是圖中的 VFP。VSYNC 信號產(chǎn)生,電子槍移動到左上角,當(dāng) VSYNC 信號結(jié)束以后電子槍重新打開,中間也會加入一段延時,這段延時就是圖 中的 VBP。
??HBP、HFP、VBP 和 VFP 就是導(dǎo)致圖中黑邊的原因,RGB LCD 屏幕內(nèi)部有一個 IC,發(fā)送一行或者一幀數(shù)據(jù)給 IC,IC 是需要反應(yīng)時間的。通過這段反應(yīng)時間可以讓 IC 識別到一行數(shù)據(jù)掃描完了,要換行了,或者一幀圖像掃描完了,要開始下一幀圖像顯示了。因此,在 LCD 屏幕中繼續(xù)存在 HBP、HFP、VPB 和 VFP 這四個參數(shù)的主要目的是為了鎖定有效的像素數(shù)據(jù)。這四個時間是 LCD 重要的時間參數(shù),后面編寫 LCD 驅(qū)動的時候要用到,至于這四個時間參數(shù)具體值是多少,需要去查看所使用的 LCD 數(shù)據(jù)手冊。
5)RGB LCD 屏幕時序
HSYNC:行同步信號,當(dāng)此信號有效就表示開始顯示新的一行數(shù)據(jù),查閱所使用的LCD 數(shù)據(jù)手冊可以知道此信號是低電平有效還是高電平有效,假設(shè)此時是低電平有效。
HSPW:有些地方也叫做 thp,是 HSYNC 信號寬度,也就是 HSYNC 信號持續(xù)時間。HSYNC信號不是一個脈沖,而是需要持續(xù)一段時間才是有效的,單位為 CLK。
HBP:有些地方叫做 thb,術(shù)語叫做行同步信號后肩,單位是 CLK。
HOZVAL:有些地方叫做 thd,顯示一行數(shù)據(jù)所需的時間,假如屏幕分辨率為 1024*600,那么 HOZVAL 就是 1024,單位為 CLK。
HFP:有些地方叫做 thf,術(shù)語叫做行同步信號前肩,單位是 CLK。
當(dāng) HSYNC 信號發(fā)出以后,需要等待 HSPW+HBP 個 CLK 時間才會接收到真正有效的像素數(shù)據(jù)。當(dāng)顯示完一行數(shù)據(jù)以后需要等待 HFP 個 CLK 時間才能發(fā)出下一個 HSYNC 信號,所以顯示一行所需要的時間就是:HSPW + HBP + HOZVAL + HFP。
一幀圖像就是由很多個行組成的, RGB LCD 的幀顯示時序如下圖所示:
VSYNC:幀同步信號,當(dāng)此信號有效的話就表示開始顯示新的一幀數(shù)據(jù),查閱所使用的 LCD 數(shù)據(jù)手冊可以知道此信號是低電平有效還是高電平有效,假設(shè)此時是低電平有效。
VSPW:些地方也叫做 tvp,是 VSYNC 信號寬度,也就是 VSYNC 信號持續(xù)時間,單位為 1 行的時間。
VBP:有些地方叫做 tvb,術(shù)語叫做幀同步信號后肩,單位為 1 行的時間。
LINE:有些地方叫做 tvd,顯示一幀有效數(shù)據(jù)所需的時間,假如屏幕分辨率為 1024*600,那么 LINE 就是 600 行的時間。
VFP:有些地方叫做 tvf,術(shù)語叫做幀同步信號前肩,單位為 1 行的時間。
顯示一幀所需要的時間就是: VSPW+VBP+LINE+VFP 個行時間
最終的計算公式:T = (VSPW+VBP+LINE+VFP) * (HSPW + HBP + HOZVAL + HFP)
因此在配置一款 RGB LCD 的時候需要知道這幾個參數(shù):HOZVAL(屏幕有效寬度)、LINE(屏幕有效高度)、HBP、HSPW、HFP、VSPW、VBP 和 VFP。ALIENTEK 三款 RGB LCD屏幕的參數(shù)如下表所示:
6)像素時鐘
像素時鐘就是 RGB LCD 的時鐘信號,以 ATK7016 這款屏幕為例,顯示一幀圖像所需要的時鐘數(shù)就是:
= (VSPW+VBP+LINE+VFP) * (HSPW + HBP + HOZVAL + HFP)
= (3 + 20 + 600 + 12) * (20 + 140 + 1024 + 160)
= 635 * 1344
= 853440
顯示一幀圖像需要853440個時鐘數(shù),那么顯示60幀就是: 853440 * 60 = 51206400 ≈ 51.2M,所以像素時鐘就是 51.2MHz。
7)顯存
如果采用 ARGB8888 格式,一個像素需要 4 個字節(jié)的內(nèi)存來存放像素數(shù)據(jù),那么 1024600 分辨率就需要 1024600*4 = 2457600B ≈ 2.4MB 內(nèi)存。但是 RGB LCD 內(nèi)部是沒有內(nèi)存的,所以就需要在開發(fā)板上的 DDR3 中分出一段內(nèi)存作為 RGB LCD 屏幕的顯存,如果要在屏幕上顯示圖像直接操作這部分顯存即可。
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
LTDC接口功能框架:
從圖中可以看出 LTDC 的信號可以分為兩類:4 個控制信號(LCD_CLK 像素時鐘、LCD_HSYNC 水平同步、LCD_VSYNC 垂直同步、LCD_DE 數(shù)據(jù)有效)和 3 個 RGB 數(shù)據(jù)信號(8bit x 3)。
二、DRM 驅(qū)動框架
1.DRM 簡介
??在 Linux 系統(tǒng)中,主流的顯示框架有兩種:DRM(Direct Rendering Module)框架和 FB(FrameBuffer)框架。FB 框架不能處理基于 3D 加速 GPU 顯卡,而 DRM 可以統(tǒng)一管理 GPU顯示,所以 DRM 相對于 FB 更能適應(yīng)新的顯示硬件。比如 DRM 支持多層合成、支持 VSYNC、支持 DMA-BUF、支持 fence 機制等等。
??下圖就是一個 DRM 驅(qū)動框架,包括兩部分:DRM core 和 DRM driver。DRM core 提供了一個基本的 DRM 框架,DRM driver 就可以注冊進 DRM 框架,同時為用戶空間提供一組 ioctl。libdrm 對底層接口(DRM driver 提供的 ioctl)進行封裝,向上層提供統(tǒng)一的 API 接口。DRM driver 包含了 GEM 模塊和 KMS 模塊,這兩模塊也分為好幾個小模塊。
圖形執(zhí)行管理器(GEM):全稱 Graphics Execution Manager,這是一個內(nèi)存管理器,主要負責(zé)內(nèi)存的分配和釋放,可以調(diào)用 GPU。
DUMB:這是一個 dumb 緩沖區(qū),主要負責(zé)一些簡單的 buffer 顯示,可以通過 CPU 直接渲染 dumb,GPU 不會使用 dumb。
內(nèi)核顯示模式設(shè)置(KMS):全稱 Kernel Mode Setting,主要負責(zé)顯示的控制,包括屏幕分辨率、屏幕刷新率和顏色深度等等。
CRTC:就是指顯示控制器,在 DRM 里有多個顯存,就可以通過操作 CRTC 來控制要顯示那個顯存。
Encoder:負責(zé)從 CRTC 里輸出的 timing 時序轉(zhuǎn)換成外部設(shè)備所需要的信號的模塊,同時也負責(zé)控制 LCD 的顯示。
Connector:連接物理顯示設(shè)備的連接器,比如 DSI、 HDMI 等等。
Plane:負責(zé)獲取顯存,再輸出到 CRTC 里,說明 CRTC 必須要有一個 Plane。
幀緩沖(FB):能夠顯示圖層的 buffer。
GEM 和 KMS 通過以下結(jié)構(gòu)來與顯示器對接:
藍色框表示 KMS 里的模塊。plane 是連接 crtc 和 framebuffer 的紐帶;encoder 是連接 crtc 和 connector 的紐帶。GEM 是負責(zé)和物理的 buffer 打交道。plane 把獲取到顯存輸出到 crtc 里,crtc 通過 connector 接口輸出到顯示器。
2.ST 官方的 DRM 驅(qū)動框架介紹
??在 Linux 系統(tǒng)中,DRM 驅(qū)動的核心主要就是一個 drm_driver 結(jié)構(gòu)體,驅(qū)動程序要初始化drm_driver 結(jié)構(gòu)體,然后調(diào)用 drm_dev_init 函數(shù),將其注冊到 DRM core。
?? 在設(shè)備樹文件 stm32mp151.dtsi 中,有一個 ltdc 節(jié)點:
ltdc: display-controller@5a001000 {
compatible = "st,stm32-ltdc";
reg = <0x5a001000 0x400>;
interrupts = <GIC_SPI 88 IRQ_TYPE_LEVEL_HIGH>,
<GIC_SPI 89 IRQ_TYPE_LEVEL_HIGH>;
clocks = <&rcc LTDC_PX>;
clock-names = "lcd";
resets = <&rcc LTDC_R>;
status = "disabled";
};
?? 這個文件中的 ltdc 節(jié)點信息是所有使用 STM32MP1 芯片的板子所共有的,且不是完整的 ltdc 節(jié)點信息。其中 ltdc 節(jié)點的 compatible 屬性值為“st,stm32-ltdc”,在 Linux源碼中搜索這個字符串就可以找到 STM32MP1 的 DRM 驅(qū)動文件,這個文件為 “drivers/gpu/drm/stm/drv.c”文件:
static const struct of_device_id drv_dt_ids[] = {
{.compatible = "st,stm32-ltdc"},
{},
};
MODULE_DEVICE_TABLE(of, drv_dt_ids);
static struct platform_driver stm_drm_platform_driver = {
.probe = stm_drm_platform_probe,
.remove = stm_drm_platform_remove,
.driver = {
.name = "stm32-display",
.of_match_table = drv_dt_ids,
.pm = &drv_pm_ops,
},
};
?? 可以看出,這是一個標準的 platform 驅(qū)動,當(dāng)驅(qū)動和設(shè)備匹配以后 stm_drm_platform_probe 函數(shù)就會執(zhí)行。和其他設(shè)備驅(qū)動一樣,DRM 也分為 DRM 設(shè)備和 DRM 驅(qū)動,drm_device 結(jié)構(gòu)體為 DRM 設(shè)備,drm_driver 為 DRM 驅(qū)動。
drm_device 結(jié)構(gòu)體
drm_device 結(jié)構(gòu)體定義在 include/drm/drm_device.h 文件里,部分內(nèi)容如下:
struct drm_device {
struct list_head legacy_dev_list;
int if_version;
struct kref ref;
......
u32 max_vblank_count;
struct list_head vblank_event_list;
spinlock_t event_lock;
struct drm_agp_head *agp;
struct pci_dev *pdev;
unsigned int num_crtcs;
struct drm_mode_config mode_config;
struct mutex object_name_lock;
struct idr object_name_idr;
struct drm_vma_offset_manager *vma_offset_manager;
struct drm_vram_mm *vram_mm;
enum switch_power_state switch_power_state;
struct drm_fb_helper *fb_helper;
};
?? 在編寫 DRM 驅(qū)動時需要自行申請 drm_device 內(nèi)存并且使用初始化,可以直接通過 drm_dev_alloc 函數(shù)來完成,此函數(shù)會先調(diào)用 kzalloc 為 drm_device 分配內(nèi)存,然后調(diào)用 drm_dev_init 初始化 drm_device。
原型:
struct drm_device *drm_dev_alloc(struct drm_driver *driver, struct device *parent)
參數(shù):
driver:drm_driver 結(jié)構(gòu)體指針,也就是 DRM 設(shè)備對應(yīng)的 DRM 驅(qū)動
parent:父設(shè)備
返回值:
返回分配成功的新 DRM 設(shè)備
ERR_PTR:drm_device 申請失敗
drm_device 分配成功以后還需要使用 drm_dev_register 函數(shù)向內(nèi)核注冊:
原型:
int drm_dev_register(struct drm_device *dev, unsigned long flags)
參數(shù):
dev:需要注冊到內(nèi)核的 drm_device
flags:傳遞給驅(qū)動 .load 函數(shù)的標志
返回值:
0:成功
負數(shù):失敗
drm_driver 結(jié)構(gòu)體
?? Linux 內(nèi)核為 DRM 驅(qū)動提供一個叫做 drm_driver 的結(jié)構(gòu)體,drm_driver 結(jié)構(gòu)體包含了 DRM驅(qū)動的完整屬性和操作集合,因此每一個 DRM 驅(qū)動都必須有一個 drm_driver。drm_driver 結(jié)構(gòu)體定義在 include/drm/drm_drv.h 文件里:
struct drm_driver {
int (*load) (struct drm_device *, unsigned long flags);
int (*open) (struct drm_device *, struct drm_file *);
......
int (*dumb_create)(struct drm_file *file_priv,
struct drm_device *dev,
struct drm_mode_create_dumb *args);
int (*dumb_map_offset)(struct drm_file *file_priv,
struct drm_device *dev, uint32_t handle,
uint64_t *offset);
int (*dumb_destroy)(struct drm_file *file_priv,
struct drm_device *dev,
uint32_t handle);
const struct vm_operations_struct *gem_vm_ops;
int major; /* 驅(qū)動主設(shè)備號 */
int minor; /* 驅(qū)動次設(shè)備號 */
int patchlevel; /* 驅(qū)動補丁等級 */
char *name; /* 驅(qū)動名字 */
char *desc; /* 驅(qū)動描述 */
char *date; /* 驅(qū)動日期 */
u32 driver_features; /* 驅(qū)動特性 */
const struct drm_ioctl_desc *ioctls;
int num_ioctls;
const struct file_operations *fops;
struct list_head legacy_dev_list;
int (*firstopen) (struct drm_device *);
void (*preclose) (struct drm_device *, struct drm_file *file_priv);
int (*dma_ioctl) (struct drm_device *dev, void *data,
struct drm_file *file_priv);
int (*dma_quiescent) (struct drm_device *);
int (*context_dtor) (struct drm_device *dev, int context);
int dev_priv_size;
};
成員變量比較多,重點是driver_features、fops 和 dumb_create。
①dumb_create 是一個回調(diào)函數(shù), 用于創(chuàng)建 gem 對象,并分配物理 buffer。
②driver_features 用來描述驅(qū)動特性,枚舉類型 drm_driver_feature 定義了可以選擇的驅(qū)動特性:
DRIVER_GEM:驅(qū)動使用 GEM 內(nèi)存管理,此特性必須選中!
DRIVER_MODESET:驅(qū)動支持模式設(shè)置接口(KMS)。
DRIVER_RENDER:驅(qū)動支持專用渲染節(jié)點。
DRIVER_ATOMIC:驅(qū)動提供完整的原子操作,以供用戶空間 API 函數(shù)操作。
DRIVER_SYNCOBJ:驅(qū)動支持 SYNCOBJ, 用于命令提交的顯式同步。
DRIVER_SYNCOBJ_TIMELINE:驅(qū)動支持 SYNCOBJ 時間線。
DRIVER_USE_AGP:驅(qū)動程序使用 AGP 接口, DRM 核心將管理 AGP 資源。
DRIVER_LEGACY:表明這是一個使用影子附著的舊驅(qū)動程序,不使用。
DRIVER_PCI_DMA:驅(qū)動支持 PCI DMA。
DRIVER_SG:驅(qū)動可以提供 scatter/gather DMA 功能
DRIVER_HAVE_DMA:驅(qū)動支持 DMA。
DRIVER_HAVE_IRQ:驅(qū)動支持 IRQ,舊驅(qū)動使用。
DRIVER_KMS_LEGACY_CONTEXT:僅供 nouveau 使用!
③fops 就是一個簡單的字符設(shè)備接口結(jié)構(gòu)體
當(dāng)設(shè)備和驅(qū)動匹配成功以后 stm_drm_platform_probe 函數(shù)就會執(zhí)行, 函數(shù)內(nèi)容如下:
static int stm_drm_platform_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
struct drm_device *ddev;
int ret;
DRM_DEBUG("%s\n", __func__);
dma_set_coherent_mask(dev, DMA_BIT_MASK(32));
ddev = drm_dev_alloc(&drv_driver, dev);
if (IS_ERR(ddev))
return PTR_ERR(ddev);
ret = drv_load(ddev);
if (ret)
goto err_put;
ret = drm_dev_register(ddev, 0);
if (ret)
goto err_put;
drm_fbdev_generic_setup(ddev, 16);
return 0;
err_put:
drm_dev_put(ddev);
return ret;
}
①drm_dev_alloc 函數(shù),此函數(shù)主要完成以下功能:
a.給 drm_device 分配內(nèi)存。
b.通過 drm_dev_init 函數(shù)初始化 drm_device3
drm_dev_alloc 會通過調(diào)用 drm_dev_init 函數(shù)將 drm_driver 和 drm_device 聯(lián)系起來,drm_device 結(jié)構(gòu)體里面有個 drvier 指針成員變量,此成員變量指向 DRM 設(shè)備對應(yīng)的 DRM 驅(qū)動的。因此,drm_dev_init 函數(shù)會通過將 drm_device 下的 driver 成員變量指向 drm_driver 來實現(xiàn)兩者相連。
②drv_load 這個函數(shù)就是初始化 KMS
③注冊 drm_device 對象進 DRM core
drv_load 函數(shù):
#define STM_MAX_FB_WIDTH 2048
#define STM_MAX_FB_HEIGHT 2048
static const struct drm_mode_config_funcs drv_mode_config_funcs = {
.fb_create = drm_gem_fb_create,
.atomic_check = drm_atomic_helper_check,
.atomic_commit = drm_atomic_helper_commit,
};
......
static int drv_load(struct drm_device *ddev)
{
struct platform_device *pdev = to_platform_device(ddev->dev);
struct ltdc_device *ldev;
int ret;
DRM_DEBUG("%s\n", __func__);
ldev = devm_kzalloc(ddev->dev, sizeof(*ldev), GFP_KERNEL);
if (!ldev)
return -ENOMEM;
ddev->dev_private = (void *)ldev;
drm_mode_config_init(ddev);
/*
* set max width and height as default value.
* this value would be used to check framebuffer size limitation
* at drm_mode_addfb().
*/
ddev->mode_config.min_width = 0;
ddev->mode_config.min_height = 0;
ddev->mode_config.max_width = STM_MAX_FB_WIDTH;
ddev->mode_config.max_height = STM_MAX_FB_HEIGHT;
ddev->mode_config.funcs = &drv_mode_config_funcs;
ret = ltdc_load(ddev);
if (ret)
goto err;
drm_mode_config_reset(ddev);
drm_kms_helper_poll_init(ddev);
platform_set_drvdata(pdev, ddev);
return 0;
err:
drm_mode_config_cleanup(ddev);
return ret;
}
①前兩行設(shè)置DRM 驅(qū)動 X 軸(寬度)最大支持 2048 個像素,設(shè)置 DRM 驅(qū)動 Y 軸(寬度)最大支持 2048 個像素,可以看出驅(qū)動里面設(shè)置的最大分辨率支持2048 * 2048。但是根據(jù)STM32MP157 手冊所描述,最大支持1366 * 768分辨率的屏幕。
② mode_config.funcs 設(shè)置 framebuffer 的回調(diào)函數(shù)結(jié)構(gòu)體。
③ ltdc_load 引入 drm_panel 結(jié)構(gòu)體,此結(jié)構(gòu)體作用是提供一堆控制回調(diào)函數(shù),比如屏幕參數(shù)回調(diào)函數(shù),背光控制函數(shù)等等。ltdc_load函數(shù)是負責(zé)初始化ltdc接口(同時connector 和 encoder 一起初始化)。在 connector 初始化時,就會調(diào)用 drm_panel 結(jié)構(gòu)體里的獲取屏幕參數(shù)函數(shù)(所以只需要提供一個屏的驅(qū)動就能正常顯示了)。通常 encoder 和 connector 是放在同一個驅(qū)動初始化的,目的是為了方便驅(qū)動程序設(shè)計。
要完成整個 DRM 驅(qū)動的正常初始化,前面的 GEM 和 KMS 這些模塊已經(jīng)由 ST 官方提供,開發(fā)人員只需提供一個 drm_panel 對象即可。打開“include/drm/drm_panel.h”文件:
struct drm_panel {
struct drm_device *drm; /* drm_device 對象 */
struct drm_connector *connector; /* connector 對象 */
struct device *dev; /* 設(shè)備節(jié)點 dev */
const struct drm_panel_funcs *funcs; /* 回調(diào)函數(shù)的結(jié)構(gòu)體 */
struct list_head list;
};
這里就是 drm_panel 的結(jié)構(gòu)體定義,開發(fā)者按照要求實現(xiàn)一個 drm_panel 對象后,傳入驅(qū)動框架即可完成驅(qū)動開發(fā)。
3.RGB LCD 驅(qū)動分析(屏的驅(qū)動)
??drm_panel 結(jié)構(gòu)體是基類,panel_simple 在 drm_panel 基礎(chǔ)上增加了一些成員變量,相當(dāng)于繼承類。LCD 驅(qū)動文件為 drivers/gpu/drm/panel/panelsimple.c,其中有如下內(nèi)容:
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 結(jié)構(gòu)體用來管理 RGB LCD 設(shè)備。
第 100行,base成員變量,為 drm_panel 結(jié)構(gòu)體類型??梢钥闯?panel_simple 就是在 drm_panel 的基礎(chǔ)上發(fā)展而來的,在 DRM 驅(qū)動注冊的時候就會回調(diào) base->funcs。
第 105 行,desc 屬性就是 RGB 屏參數(shù)結(jié)構(gòu)體。
第 107 行,屏的背光結(jié)構(gòu)體。
與設(shè)備樹匹配
LCD 驅(qū)動的 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 };
這是一個標準的 platform 驅(qū)動框架, 第 3494 行就是匹配表。platform_of_match 內(nèi)容如下所示(有省略):
3133 static const struct of_device_id platform_of_match[] = {
3134 {
3135 .compatible = "ampire,am-480272h3tmqw-t01h",
3136 .data = &ire_am_480272h3tmqw_t01h,
3137 }, {
3138 .compatible = "ampire,am800480r3tmqwa1h",
3139 .data = &ire_am800480r3tmqwa1h,
3140 }, {}
3141 };
platform_of_match 里面有大量的匹配項,分別針對不同的屏幕,比如:第 3135 行就是一個匹配項,compatible 內(nèi)容為“ampire,am-480272h3tmqw-t01h”。
第 3136 行,在 platform 框架里有個 data 成員變量,這個是一個 void 類型的指針,這里指向 ampire_am_480272h3tmqw_t01h,內(nèi)容如下所示:
515 static const struct drm_display_mode ampire_am_480272h3tmqw_t01h_mode = {
516 .clock = 9000, /* LCD 像素時鐘,單位 KHz */
517 .hdisplay = 480, /* LCD X 軸像素個數(shù) */
518 .hsync_start = 480 + 2, /* LCD X 軸+hbp 的像素個數(shù) */
519 .hsync_end = 480 + 2 + 41, /* LCD X 軸+hbp+hspw 的像素個數(shù) */
520 .htotal = 480 + 2 + 41 + 2, /* LCD X 軸+hbp+hspw+hfp 的像素個數(shù) */
521 .vdisplay = 272, /* LCD Y 軸像素個數(shù) */
522 .vsync_start = 272 + 2, /* LCD Y 軸+vbp 的像素個數(shù) */
523 .vsync_end = 272 + 2 + 10, /* LCD Y 軸+vbp+vspw 的像素個數(shù) */
524 .vtotal = 272 + 2 + 10 + 2, /* LCD Y 軸+vbp+vspw+vfp 的像素個數(shù) */
525 .vrefresh = 60, /* LCD 的刷新頻率為 60HZ */
526 .flags = DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_PVSYNC,
527 };
528
529 static const struct panel_desc ampire_am_480272h3tmqw_t01h = {
530 .modes = &ire_am_480272h3tmqw_t01h_mode,
531 .num_modes = 1,
532 .bpc = 8,
533 .size = {
534 .width = 105,
535 .height = 67,
536 },
537 .bus_format = MEDIA_BUS_FMT_RGB888_1X24,
538 };
第 516~525 行,drm_display_mode 結(jié)構(gòu)體就是用來設(shè)置屏幕參數(shù)。
第 529 行,定義一個 panel_desc 結(jié)構(gòu)體對象。
第 530 行,modes 變量設(shè)置為 ampire_am_480272h3tmqw_t01h_mode。
第 531 行,設(shè)置 modes 的數(shù)量。
第 532 行,設(shè)置屏幕為 8bit。
第 533~536 行,設(shè)置屏幕實際顯示區(qū)域的物理寬度,單位為毫米,此屏幕尺寸為 105mm x 67mm。
第 537 行,bus_format 屬性設(shè)置總線模式, include/uapi/linux/media-bus-format.h 里面定義了所有可選的總線類型:
1 #define MEDIA_BUS_FMT_FIXED 0x0001
2
3 /* RGB - next is 0x101d */
4 #define MEDIA_BUS_FMT_RGB444_1X12 0x1016
5 #define MEDIA_BUS_FMT_RGB444_2X8_PADHI_BE 0x1001
6 #define MEDIA_BUS_FMT_RGB444_2X8_PADHI_LE 0x1002
7 #define MEDIA_BUS_FMT_RGB555_2X8_PADHI_BE 0x1003
8 #define MEDIA_BUS_FMT_RGB555_2X8_PADHI_LE 0x1004
9 #define MEDIA_BUS_FMT_RGB565_1X16 0x1017
10 #define MEDIA_BUS_FMT_BGR565_2X8_BE 0x1005
11 #define MEDIA_BUS_FMT_BGR565_2X8_LE 0x1006
12 #define MEDIA_BUS_FMT_RGB565_2X8_BE 0x1007
13 #define MEDIA_BUS_FMT_RGB565_2X8_LE 0x1008
14 #define MEDIA_BUS_FMT_RGB666_1X18 0x1009
15 #define MEDIA_BUS_FMT_RBG888_1X24 0x100e
16 #define MEDIA_BUS_FMT_RGB666_1X24_CPADHI 0x1015
17 #define MEDIA_BUS_FMT_RGB666_1X7X3_SPWG 0x1010
18 #define MEDIA_BUS_FMT_BGR888_1X24 0x1013
19 #define MEDIA_BUS_FMT_BGR888_3X8 0x101b
20 #define MEDIA_BUS_FMT_GBR888_1X24 0x1014
21 #define MEDIA_BUS_FMT_RGB888_1X24 0x100a
22 #define MEDIA_BUS_FMT_RGB888_2X12_BE 0x100b
23 #define MEDIA_BUS_FMT_RGB888_2X12_LE 0x100c
24 #define MEDIA_BUS_FMT_RGB888_3X8 0x101c
25 #define MEDIA_BUS_FMT_RGB888_1X7X4_SPWG 0x1011
26 #define MEDIA_BUS_FMT_RGB888_1X7X4_JEIDA 0x1012
27 #define MEDIA_BUS_FMT_ARGB8888_1X32 0x100d
......
121 /* HSV - next is 0x6002 */
122 #define MEDIA_BUS_FMT_AHSV8888_1X32 0x6001
可以看出,Linux 內(nèi)核支持很多種不同的總線格式,比如 RGB、YUV、Bayer 等。以 MEDIA_BUS_FMT_RGB888_1X24 為例,這個總線格式的含義如下:
①從“RGB888”可以看出,這是一個 RGB888 格式的。
②后面的“1X24”表示一個像素點使用 24bit,如果是 2X8 就表示一個像素點使用 2 個 8bit 表示。
③有的右邊還會有“BE”或“LE”,BE 表示最高位先傳輸,LE 表示最低位先傳輸。假設(shè)設(shè)備樹里有個設(shè)備節(jié)點的 compatible 屬性為“ampire,am-480272h3tmqw-t01h”,那么就會和驅(qū)動匹配成功,然后運行 panel_simple_platform_probe 函數(shù),此函數(shù)的內(nèi)容如下所示:
3470 static int panel_simple_platform_probe(struct platform_device *pdev)
3471 {
3472 const struct of_device_id *id;
3473
3474 id = of_match_node(platform_of_match, pdev->dev.of_node);
3475 if (!id)
3476 return -ENODEV;
3477
3478 return panel_simple_probe(&pdev->dev, id->data);
3479 }
第 3474 行,使用 of_match_node 函數(shù)查找匹配的設(shè)備 ID。
第 3478 行,當(dāng)?shù)玫狡ヅ涞脑O(shè)備 ID(of_device_id)以后就可以通過提取 data 成員變量得到屏幕參數(shù)信息,比如此處 id->data 就是 ampire_am_480272h3tmqw_t01h。最后調(diào)用 panel_simple_probe 函數(shù)將其注冊到內(nèi)核,panel_simple_probe 函數(shù)也定義在 drivers/gpu/drm/panel/panel-simple.c 文件中,函數(shù)內(nèi)容如下所示:
414 static int panel_simple_probe(struct device *dev, const struct panel_desc *desc)
415 {
416 struct device_node *backlight, *ddc;
417 struct panel_simple *panel;
418 struct display_timing dt;
419 int err;
420
421 panel = devm_kzalloc(dev, sizeof(*panel), GFP_KERNEL);
422 if (!panel)
423 return -ENOMEM;
424
425 panel->enabled = false;
426 panel->prepared = false;
427 panel->desc = desc;
428
......
444 backlight = of_parse_phandle(dev->of_node, "backlight", 0);
445 if (backlight) {
446 panel->backlight = of_find_backlight_by_node(backlight);
447 of_node_put(backlight);
448
449 if (!panel->backlight)
450 return -EPROBE_DEFER;
451 }
452
......
467 drm_panel_init(&panel->base);
468 panel->base.dev = dev;
469 panel->base.funcs = &panel_simple_funcs;
470
471 err = drm_panel_add(&panel->base);
472 if (err < 0)
473 goto free_ddc;
474
475 dev_set_drvdata(dev, panel);
476
477 return 0;
478
479 free_ddc:
480 if (panel->ddc)
481 put_device(&panel->ddc->dev);
482 free_backlight:
483 if (panel->backlight)
484 put_device(&panel->backlight->dev);
485 return err;
486 }
第 427 行,設(shè)置屏幕參數(shù)。
第 444 行,從設(shè)備樹里獲取背光節(jié)點,所以我們的設(shè)備樹要提供“backlight”屬性。
第 467 行,用 drm_panel_init 函數(shù)初始化屏幕。
第 469 行,設(shè)置 panel_simple 的回調(diào)函數(shù),為 DRM 驅(qū)動注冊的時候提供屏的參數(shù)。
第 471 行,把屏幕注冊到內(nèi)核。文章來源:http://www.zghlxwxcb.cn/news/detail-719193.html
三、總結(jié)
LCD 屏的驅(qū)動分析結(jié)束,總結(jié)一下添加自己的屏要做哪些操作:
①在根節(jié)點下提供一個 LCD 設(shè)備樹,包含背光的節(jié)點和引用 ltdc 節(jié)點。
②在 panel-simple.c 文件里的 platform_of_match 結(jié)構(gòu)體里添加一組設(shè)備 ID,此設(shè)備 ID 對應(yīng)所使用的屏幕,重點是屏幕參數(shù) panel_desc 結(jié)構(gòu)體。文章來源地址http://www.zghlxwxcb.cn/news/detail-719193.html
到了這里,關(guān)于STM32MP157驅(qū)動開發(fā)——Linux LCD驅(qū)動(上)的文章就介紹完了。如果您還想了解更多內(nèi)容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!