###### tags: `TSC` `Linux`
NXP i.MX8M Mini DRM (Direct Rendering Manager) driver解析
=======
# 1. DRM(Direct Rendering Manager)
## 簡介
在早期的 Linux display 子系統中,我們常以 FB(Frame Buffer)做為主要的框架;只要有一個 Frame Buffer,我們就可以在顯示屏幕上秀出各種資訊。但隨著硬體不斷的快速演進,DRM(Direct Rendering Manager)更能配合硬體的功能,相比之下,FB 就顯得不足以應付現有的需求。比方說,FB 不支援 overlay 的概念、不支援 hw cursor … 等等,這些以往都得在 user space 操作。所以現今 Linux 平台上開發的研發人員,已經開始使用 DRM 來取代 FB,就是因為 DRM 子系統在 kernel space 都可以幫忙處理這些事情,從下圖可以簡單看出,FB 以及 DRM 之間在架構上主要的差異。
而本文將以 i.MX8M Mini EVK 平台,介紹 DRM driver 是如何把 MIPI DSI panel 或是 MIPI DSI to HDMI bridge 註冊上子系統並且將整個 display 子系統 bring up 的整個過程。了解這部分可以幫忙開發人員在初期還點不亮 panel 的情況下,更有系統的看懂 log 上所提示的錯誤並且有效的進行 debug。

## 名詞解釋
- Frame Buffer:用於描述顯示區塊的 format、pitch、size 等,但不負責存放實際的顯示資料。
- CRTC:CRT Controller,主要負責 Frame Buffer 的更新(resolution、timing 的配置),並送給 encoder。
- Planes:影像數據源,每個 CRTC 都至少有一個或多個 plane,可進行 overlay、cursor。
- Encoder:將像素編碼打包成 Connector 所需要的格式,再送給 Connector。
- Connector:對應物理的介面,比如說:MIPI、HDMI,當前物理設備的訊息會存在 Connector 裡面(訊息包括連接狀態、EDID、可支援的 mode)。
## 軟體架構
接下來看一下 DRM 的軟體架構。在用戶空間,DRM 與其他 driver 不同,它把自己封裝成了一個 libdrm,取代一般常用的 IOCTL,藉此來提供使用者更方便更直覺的操作。後面的章節我們會簡單介紹一個 app,示範如何利用 libdrm 進行圖像顯示的一連串操作。
另一方面,在 kernel 端 DRM driver 分為兩大塊,一個是 GEM(Graphics Execution Manager),負責提供 Frame Buffer 的記憶體管理,這也是 GPU 會利用到 DRM 的地方。GEM 在本文中暫時不會提到太多;另一個是 KMS(Kernel Mode Setting),負責選擇並設置所連接的螢幕本身提供的所謂的〝mode〞,這些設定包括 resolution、color depth 以及 refresh rate。這部分將會是本文的重點,在後面的章節裡面我們會細談。

### 小結
綜合以上三個小章節,另外補充說明一下所謂的 KMS 就是由 CRTC、Planes、Encoder、Connector 所組成,所以整個架構就可以串起來了。而 KMS 利用這幾個元件的相互合作,達到 mode setting 的功用。如果再進一步說明,所謂的 mode setting 做的事情就是〝更新畫面〞和〝設定 display 參數〞。
- 更新畫面 :page-flip、overlay
- 設定 display 參數 :resolution、color depth、refresh rate
# 2. Linux DRM driver 解析
## Superdevice
DRM 類似 ALSA 是一個牽扯到許多子元件所組成的顯示子系統,所以會以〝顯示卡〞來做表示。而這張代表著整個子系統的顯示卡在 device tree 會以一個 superdevice(display-subsystem) 展現,如下面程式碼所示,並以此管理著所有的子元件。每一個新加入的子元件都會被檢視是否匹配,最後當 superdevice 收集到所有的子元件之後,便會觸發一連串初始化的動作來綁定並帶起整個顯示子系統。
:::info
superdevice(display-subsystem),而此處的 “ports” 將說明於後續章節。

:::
## 建立 master(imx_drm_core)
Superdevice 的 probe function 可參考下圖,透過 drm_of_component_probe_with_match() 可以將 CRTC 以及它有效的 remote endpoint 都添加到 match list 上。如果從 device tree 來查看,在這個例子裡面,CRTC 就是 lcdif,而它有效的 remote endpoint 是 dsim_from_lcdif,所以 lcdif 以及 dsim_from_lcdif 都會被加在 match list 裡面。另外,在這個 probe function 裡面,最後會呼叫 component_master_add_with_match() 來建立一個 master,之前的 match list 也會儲存在 master 裡,並且呼叫 try_to_bring_up_master() 嘗試做第一次 bring up。每一次的 bring up 不一定會成功,在此之後的每一個子元件加入都會重新呼叫 try_to_bring_up_master() 嘗試進行 bring up 動作,但是只有當所有的子元件都 ready 了之後,master 才會被 bring up。

:::info
- 前半段以 superdevice device tree 裡的 ports 綁定 crtc,並加入到 match list 中

- 後半段可搭配下面 device tree,remote endpoint 為 dsim_from_lcdif,同樣加入 match list 中

- 最後嘗試 bring up master

:::
## 新增 Encoder – i.MX MIPI DSI Host Controller(sec_mipi_dsim-imx)
此小節新增 MIPI DSI Host,它在 device tree 的結構可參考如下。一端與角色為 CRTC 的 lcdif 連接,另一端列舉兩個例子,一個是在原廠預設的 dts 裡可以看到 adv7535,所以可以與 HDMI panel 連接;另一個範例連接了一片 MIPI DSI 的 panel,不論是哪一種選擇,都可以看得出來 MIPI DSI Host 的位置是一個 Encoder 的角色。

:::info
- 選擇 1:fsl-imx8mm-evk-rm67191.dts,對接一片 MIPI DSI panel:

- 選擇 2:原廠預設 fsl-imx8mm-evk.dts,對接 adv7535 bridge,可以連接 HDMI panel。

- 在來看 mipi dsi 的 driver,sec_mipi_dsim-imx 的 probe function 會呼叫 component_add() 將 device 加入 component list 裡面,並且如前面章節所描述的,使用 try_to_bring_up_masters() 來嘗試 bring up master。

:::
## 新增 CRTC – LCDIF DRM CRTC(lcdif-crtc)
同理,不過此小節新增的是 CRTC,它在 device tree 的結構可參考如下,它會與角色為 Encoder 的 mipi_dsi 連接,這也呼應前面小節所談到的。

另外,driver 的部分也是同前一小節,lcdif-crtc 的 probe function 會呼叫 component_add() 將 device 加入 component list 裡面,並嘗試 bring up master。但不同的是,這裡因為 match 與 component 在 find_components() 裡發現匹配成功,所以會進行 bring up master,呼叫 master 的 imx_drm_ops 的 bind function。

匹配成功,進行 bring up master,呼叫 master 的 bind function

## Bring Up Master
bring up master 的條件是所有的子元件都匹配成功。滿足此條件之後,會呼叫 master 的 imx_drm_ops 的 bind function。在這個 imx_drm_bind() 裡面除了會初始化〝mode config〞以及〝vblank〞,更重要的,因為子元件都已經準備好了,所以有辦法將所有的子元件綁定在一起。就在 component_bind_all() 裡面,會逐一掃過所有的 match 的元件,呼叫其各自的 bind function,讓 master 與子元件建立交互關係。最後,會初始化 fbdev(drm_fbdev_cma_init())完成 DRM 子系統的初始化過程。
:::info

逐一呼叫各自的 bind function 進行綁定

:::
以我們舉的例子來看,會呼叫的 bind function 有兩個,一個是 CRTC 的 lcdif_crtc_bind(),還有另一個是 Encoder 的 imx_sec_dsim_bind()。
CRTC 主要負責接收來自多個 drm_plane 的 pixel data,並將它們做好疊圖的處理。另外也負責 maintain drm_display_mode,讓 panel 有正確的 resolution 以及 timing 的設定。下一步這些 pixel data 就可以餵給 drm_encoder 做下一步的處理。所以,lcdif_crtc_bind() 所進行的 drm_crtc 初始化,其中就包括了 plane、mode config function 等設定,完成之後的 CRTC 就 ready 好處於可等待上層指令的狀態。
:::info
- CRTC 的 lcdif_crtc_bind()

- drm_crtc 初始化,其中就包括了 plane、mode config function 等設定

Encoder 主要連接 drm_crtc 跟 drm_connector,負責將 CRTC 過來的 pixel data 轉換成適合 Connector 的格式,再交給 Connector。所以在 imx_sec_dsim_bind() 裡,因為底層連接的介面是 MIPI DSI,所以自然就會選用 type 為 DRM_MODE_ENCODER_DSI 的 codec 為 drm_encoder 進行初始化。
另外,有一個子元件〝bridge〞前面沒有提到,是一個鏈狀的結構,可以視為 Encoder 的延伸,最終才會接到 Connector(panel)。因此,會進行延伸 bridge 的 bind function,而且此動作會重複直到連接到 Connector 為止。在 Connector 完成 drm_connector_init()、drm_mode_connector_attach_encoder()、還有 drm_panel_attach() 之後,Encoder 跟 Connector 也完成它們的初始化等待上層指令。
- Encoder 的 imx_sec_dsim_bind(),選用 DRM_MODE_ENCODER_DSI,並開始嘗試進行 bridge bind

- bridge 的 bind function 會重複執行直到連接到 Connector 為止,被找到的 Connector 會進行初始化

:::
Frame Buffer 主要功能為提供一塊記憶體來儲存要秀出的圖像資料,而 DRM 的 Frame Buffer 是由 GEM 來管理。在談 drm_framebuffer 之前,系統需要開啟 CONFIG_DRM_FBDEV_EMULATION 的功能。開啟這個功能後,表示 DRM 將可以模擬一個 Frame Buffer 設備,如此上層便可使用基於 fbdev 的顯示框架。於是乎,drm_framebuffer 的初始化,drm_fbdev_cma_init(),這個 function 也成為我們最後一個在 imx_drm_bind() 裡面要分析的。
- 在 drm_fbdev_cma_init() 裡面,
1. 首先會進行 CRTC 還有 Connector 的綁定,分別實作在 drm_fb_helper_init() 還有 drm_fb_helper_single_add_all_connectors() function
2. 有了這個基礎,接下來會呼叫 drm_fb_helper_initial_config() 進行 best mode 的選定跟 fbdev 的創建。

- drm_fb_helper_initial_config(),可分解為 CRTC 的設置以及 drm frame buffer 的建立。

:::info
- drm_setup_crtcs() 會設置好每一個 CRTC,方法是從每個 Connector 得到所有的 mode,並以此為每一個 CRTC 挑選計算出來分數最好的,建立出一個 CRTC 跟 Connector 之間的 mapping。
- drm_fb_helper_single_fb_probe() 則會初始化 drm_framebuffer,其過程與一般的 Frame Buffer初始化過程差不多,最後呼叫 register_framebuffer() 完成 Frame Buffer 的註冊之後,Frame Buffer 就可以使用了。
:::
## 小結
流程至此,已經完成 DRM 子系統的初始化過程。表示 DRM driver 已經 ready 等待上層使用,所以在下一章節會簡單介紹上層如何使用 DRM。
## DRM 應用程式開發
這個章節將說明如何開發一個 DRM 應用程式。關於應用程式與 kernel space 之間的交互關係,其實我們在 Chapter 1.3 有提到一點點,我們再把以下這張架構圖拿出來複習一下。這張架構圖說明了,在 user space 的應用程式會透過 libdrm 間接對 kernel space 進行 ioctrl 操作。而一般的 DRM 應用程式需要包含的基本內容有,
1. open("/dev/dri/card0")
最基本的 open file node 取得 handle。
2. drmModeGetResources(...)
利用 DRM_IOCTL_MODE_GETRESOURCES,取得 CRTC、Encoder、Connector 等 ID 以及個數。
3. drmModeGetConnector(...)
利用上式內容,DRM_IOCTL_MODE_GETCONNECTOR 可取得 Connector 真實的資訊,包含 modes setting。
4. DRM_IOCTL_MODE_CREATE_DUMB、drmModeAddFB(...)、drmIoctl(DRM_IOCTL_MODE_MAP_DUMB)、mmap(...)
以上 function 可建立一個 dumb framebuffer 並完成映射。
5. drmModeSetCrtc(crtc_id, fb_id, connector_id, mode)
設定 CRTC 並連接到 dumb framebuffer,開始進行螢幕顯示。

基於上面的架構,我們在網路上找到一個範例程式,single buffer。這隻範例程式建立一個 dumb framebuffer,內容全部都是 0xFF(也就是白色),並把它顯示到螢幕上。參考範例程式碼連結以及實際上執行的結果如下:
範例程式連結
https://blog.csdn.net/hexiaolong2009/article/details/83721242
0. 前提需要先關掉占用 DRM 的 device

> 此時,螢幕無輸出變為黑色,
1. 執行測試程式 ./modeset-single-buffer

> 此時,螢幕輸出 dumb framebuffer 內容,0xFF,為白色,
## 參考文件
https://events.static.linuxfound.org/sites/events/files/slides/brezillon-drm-kms.pdf
https://blog.csdn.net/hexiaolong2009/category_9281458.html
https://blog.csdn.net/qq_33487044/category_8454463.html
https://www.programmersought.com/article/51166556953/
DRM wiki
https://en.wikipedia.org/wiki/Direct_Rendering_Manager
Linux DRM 原始碼
http://betteros.org/tut/graphics1.php
MIPI自学笔记
https://zhuanlan.zhihu.com/p/92682047
MIPI規格和測試
https://www.elevatesemi.com/zh/ate-articles/mipi/
# linux DRM/KMS 测试工具 modetest、kmscude、igt-gpu-tools
## modetest
- buildroot select:

- command usage:
Check ID:
```
modetest -M imx-drm | cut -f1 | grep -E ^[0-9A-Z]\|id
```
output:
```
Encoders:
id
34
Connectors:
id
35
CRTCs:
id
33
Planes:
id
31
Frame buffers:
id
```
Test
```
modetest -r
```
or
```
modetest -M imx-drm -D 0 -a -s 35@33:480x854 -P 31@33:480x854 -Fsmpte
```
-F: tiles / smpte / plain / gradient
ref:
https://gitlab.freedesktop.org/mesa/drm/-/blob/master/tests/util/pattern.c#L1236
成功會看到以下畫面:

## kmscube
projecty-mini:~# kmscube --help
```
kmscube: unrecognized option '--help'
Usage: kmscube [-ADfMmSsVv]
options:
-A, --atomic use atomic modesetting and fencing
-c, --count run for the specified number of frames
-D, --device=DEVICE use the given device
-f, --format=FOURCC framebuffer format
-M, --mode=MODE specify mode, one of:
smooth - smooth shaded cube (default)
rgba - rgba textured cube
nv12-2img - yuv textured (color conversion in shader)
nv12-1img - yuv textured (single nv12 texture)
-m, --modifier=MODIFIER hardcode the selected modifier
-p, --perfcntr=LIST sample specified performance counters using
the AMD_performance_monitor extension (comma
separated list, shadertoy mode only)
-S, --shadertoy=FILE use specified shadertoy shader
-s, --samples=N use MSAA
-V, --video=FILE video textured cube (comma separated list)
-v, --vmode=VMODE specify the video mode in the format
<mode>[-<vrefresh>]
```
Test command:
```
kmscube -A -D /dev/dri/card0 -M smooth
```
```
说明:
-A:DRM/KMS使用atomic模式
-D:指定设备节点,DRM/KMS和GPU是同一个设备节点
-M:指定OpenGL的绘图模式,可选的值包括:smooth、rgba、nv12-2img、nv12-1img
```

ref:
https://blog.csdn.net/eydwyz/article/details/109668466
https://zhuanlan.zhihu.com/p/341895843
https://blog.csdn.net/weixin_41028621/article/details/110202300
https://blog.csdn.net/pan0755/article/details/118183323
# linux DRM log for debugfs
mount -t debugfs none /sys/kernel/debug
查看狀態:
```
cd /sys/kernel/debug/dri/0
cat state
```
調整 linux DRM log level to dmesg
```
/sys/module/drm/parameters/debug
```
設置drm的調試log等級:
```
debug:Enable debug output, where each bit enables a debug category.
Bit 0 (0x01) will enable CORE messages (drm core code)
Bit 1 (0x02) will enable DRIVER messages (drm controller code)
Bit 2 (0x04) will enable KMS messages (modesetting code)
Bit 3 (0x08) will enable PRIME messages (prime code)
Bit 4 (0x10) will enable ATOMIC messages (atomic code)
Bit 5 (0x20) will enable VBL messages (vblank code) (int)
```
```
echo 0x0c > /sys/module/drm/parameters/debug
```
:::info
打開了KMS, PRIME這些的打印, kernel驅動中使用 `DRM_DEBUG_KMS` 和 `DRM_DEBUG_PRIME` 打印的信息都可以打印出來
:::
# 參考資料-待整理
- How to use the Atmel KMS/DRM LCD driver
https://www.linux4sam.org/bin/view/Linux4SAM/UsingAtmelDRMDriver
- RGB Display Device Tree
https://karo-electronics.github.io/docs/software-documentation/display-guide/display-guide.html#setting-display-resolution
- ==ST 密集==
https://wiki.st.com/stm32mpu/wiki/DRM_KMS_overview
- How to use the Atmel KMS/DRM LCD driver
https://www.linux4sam.org/bin/view/Linux4SAM/UsingAtmelDRMDriver
- Setting i.MX8M Mini and Nano MIPI-DPHY Clock
https://community.nxp.com/t5/i-MX-Processors-Knowledge-Base/Setting-i-MX8M-Mini-and-Nano-MIPI-DPHY-Clock/ta-p/1104457
- 在VCUTRD 2020.1 里设置HDMI-TX显示QT界面
https://cloud.tencent.com/developer/article/1690248
https://www.cnblogs.com/yaongtime/p/12940386.html
https://zhuanlan.zhihu.com/p/409642657
- SSD_DISP点屏参考
http://wx.comake.online/doc/doc/TAKOYAKI_DLC00V050_20211216/customer/development/software/Px/zh/display/P2/disp_p2.html
- Linux DRM PLANE完全解析
https://zhuanlan.zhihu.com/p/409642657
- Linux 中基于 DRM 的图形显示系统概述
https://z.itpub.net/article/detail/95EE17AB65D8205F77FEE0FAA6910731
```
modetest -M sun4i-drm -P 31@47:480x272+10+10 -P 35@47:480x272+100+100 -P 39@47:480x272+150+150 -P 43@47:320x240+200+200
```
如何配置MIPI DSI Clock和PCLK
https://www.796t.com/content/1549659806.html
[Android5.1][RK3288] LCD Mipi 调试方法及问题汇总
https://blog.csdn.net/dearsq/article/details/52354593
https://community.nxp.com/t5/i-MX-Processors/i-MX8M-Mini-EVK-MIPI-to-HDMI-no-HDMI-Display-with-QT5/td-p/1003199
https://wiki.phytec.com/pages/viewpage.action?pageId=91989486#id-%E8%AE%BE%E5%A4%87%E6%A0%91%E4%B8%AD%E7%9A%84LCD%E6%B6%B2%E6%99%B6%E5%B1%8F%E4%BF%AE%E6%94%B9%E6%96%B9%E6%B3%95/LCDdisplaytimingsindevicetree-DE%E6%A8%A1%E5%BC%8F%E7%9A%84%E6%98%BE%E7%A4%BA%E5%B1%8F
- How to enable DRM driver debug logging in Linux ?
https://lynxbee.com/how-to-enable-drm-driver-debug-logging-in-linux/#.Yp8i0ahBxPb
- 基于 NXP i.MX8QM Bumblebee 智能座舱 MIPI 接口 HannStar HSD123KPW2 屏幕调试
https://www.wpgdadatong.com/tw/blog/detail?BID=B3242
如何調整Linux核心啟動中的驅動初始化順序 .
https://topic.alibabacloud.com/tc/a/how-to-adjust-the-driver-initialization-sequence-in-linux-kernel-startup_1_16_31966293.html
https://meetonfriday.com/posts/c4426b79/
- Display Output, Resolution and Timings (Linux)
https://developer.toradex.com/linux-bsp/how-to/multimedia/display-output-resolution-and-timings-linux/
- 设备树中的LCD液晶屏修改方法/LCD display timings in device tree
https://wiki.phytec.com/pages/viewpage.action?pageId=91989486
https://wiki.phytec.com/pages/viewpage.action?pageId=91980174
- Linux Display Drivers
http://trac.gateworks.com/wiki/linux/display
- Linux MIPI-DSI Subsystem
https://elinux.org/images/7/73/Jagan_Teki_-_Demystifying_Linux_MIPI-DSI_Subsystem.pdf
- Test to keep u-boot logo util system screen on.
https://community.nxp.com/t5/i-MX-Graphics-Knowledge-Base/Test-to-keep-u-boot-logo-util-system-screen-on/ta-p/1129165
- i.MX8 - Multimedia - GPU - OpenGL
https://developer.ridgerun.com/wiki/index.php?title=IMX8/Multimedia/GPU/OpenGL
- i.MX Graphics User's Guide
https://www.nxp.com/docs/en/user-guide/i.MX_AA_Graphics_User's_Guide.pdf
- NXP iMX8 基于Qtwayland 配置双屏显示
https://www.toradex.com/zh-cn/blog/nxp-imx8-ji-yuqtwayland-pei-zhi-shuang-ping-xian-shi
- uboot drm框架
https://blog.csdn.net/qq_39678541/article/details/118695002
## GPU & VPU
- Decoding video with a mainline kernel on i.MX6
https://imxdev.gitlab.io/tutorial/Decoding_video_with_a_mainline_kernel_on_i.MX6/#vpu-firmware
- VPU TEST
https://source.codeaurora.org/external/imx/imx-test/tree/test/mxc_vpu_test?h=imx_4.14.62_1.0.0_beta
- How to get an fb device with Sumo on i.MX6?
https://lore.kernel.org/all/f22ea50e0a204e95b4d832566797898a@systecnet.com/T
- galcore module failed to load
```
If you look at <buildroot>/linux/linux.mk :
$(if $(BR2_PACKAGE_KERNEL_MODULE_IMX_GPU_VIV),
$(call KCONFIG_DISABLE_OPT,CONFIG_MXC_GPU_VIV,$(@D)/.config))
I deduce that just one driver can be built at a time.
https://community.nxp.com/t5/i-MX-Processors/galcore-module-failed-to-load/m-p/920637
```
- Yocto release for i.MX8MQ
```
root@nitrogen8m:~# weston-simple-egl &
root@nitrogen8m:~# cd /opt/viv_samples/vdk/
root@nitrogen8m:/opt/viv_samples/vdk# ./tutorial7
```
https://boundarydevices.com/yocto-release-for-i-mx8mq/
https://usermanual.wiki/Document/iMXReferenceManual.593756722/help


https://www.microchip.com/forums/m1187384.aspx
## ubuntu
sudo apt-get install -y libdrm-dev
sudo apt-get install -y libdrm-tests
## 顯示企鵝
CONFIG_FRAMEBUFFER_CONSOLE=y
CONFIG_FRAMEBUFFER_CONSOLE_ROTATION=y
## SDL2 + DRM
HiGFXback with the KMS/DRM graphics backend
https://higfxback.github.io/KMS-DRM.html
have SDL with kms/drm backend
https://forum.odroid.com/viewtopic.php?t=38730
https://forum.odroid.com/viewtopic.php?f=202&t=38789
SDL_envvars
https://www.libsdl.org/release/SDL-1.2.15/docs/html/sdlenvvars.html
VICE for Odroid Go Advance
https://gitlab.com/ricardoquesada/odroid-misc/-/blob/master/kmsdrm.md
You need to configure libsdl2 with option
```
--enable-video-kmsdrm
```
because kmsdrm is not enabled by default. Then you also need to disable the unsupported OpenGL, otherwise libsdl2 fails to detect OpenGLES and OpenGLES2
`--disable-video-opengl`
With the two flags above, drmkms is already working, but libsdl2 detects the GPU of STM32MP1xx as Vivante, and try to use an internal driver that does not work with STM32MP1xx. To skip this auto-detection, you can either specify the preferred video driver in the libsdl2 specific global variable, before running your application:
`export SDL_VIDEODRIVER=kmsdrm`
or disable the Vivante driver at libsdl2 configuration time, adding:
`--disable-video-vivante`
## disable cursor on linux fb device
```
vt.global_cursor_default=0
```