正点原子IMX6ULL史诗级新内核Linux7.0移植教程6DRM 显示系统移植LCD 驱动完整迁移指南小请假条笔者从今天开始出差今天更新完毕之后到4月1日前不会更新公众号了也就是3月29日30日和31日更新的话可能只会缓慢更新手头有的存货。相关仓库已经开源https://github.com/Awesome-Embedded-Learning-Studio/imx-forge欢迎各位大佬看看批评指正前言这是整个移植最复杂的一章说实话从 NXP BSP 迁移到主线内核最难的部分就是显示系统。为什么因为整个架构都变了从旧的 Framebuffer 框架迁移到 DRM/KMS 框架驱动代码位置不同、设备树写法不同、调试方法也不同。但好消息是一旦你理解了 DRM 的基本概念后续的调试就会容易很多。这篇文章会带你完整走过 LCD 驱动的迁移过程从设备树编写到最终验证。我会尽量解释清楚每一步的原因而不是只给你一堆命令。第一步——理解 DRM 架构DRMDirect Rendering Manager是 Linux 内核的图形子系统最初用于 3D 图形加速后来扩展到所有显示设备。对于嵌入式系统我们主要关心 KMSKernel Mode Setting部分也就是内核负责设置显示模式的那部分代码。DRM 的核心组件应用程序 / GUI 框架 ↓ (read/write /dev/fb0 或 DRM ioctl) DRM 核心子系统 ↓ mxsfb CRTC (drivers/gpu/drm/mxsfb/) ↓ [OF graph: lcdif port → panel port] panel-dpi (drivers/gpu/drm/panel/panel-dpi.c) ↓ [物理并行 RGB 信号] LCD 屏幕硬件CRTC显示控制器负责从内存读取像素数据并输出到屏幕Encoder编码器把 CRTC 的数字信号转换成适合传输的格式对于 eLCDIF 这个就是直接输出Connector连接器描述物理连接方式比如 DPI、LVDS、HDMIPanel面板描述屏幕的时序参数和特性对于 i.MX6ULL 的 eLCDIF 控制器架构比较简单CRTC 直接连接 Panel没有中间的 Encoder。为什么不用 panel-simple 而是 panel-dpi你可能见过panel-simple驱动它支持很多常见的屏幕。但panel-simple要求屏幕在驱动代码里预定义而panel-dpi是一个通用驱动时序参数完全由设备树的panel-timing节点指定。对于我们的 7 寸 1024×600 屏幕用panel-dpi最合适因为时序参数不是标准的。第二步——编写 panel 节点panel 节点独立于 lcdif 之外作为根节点的子节点/ { panel: panel-dpi { compatible panel-dpi; backlight backlight_display; /* 屏幕物理尺寸 */ width-mm 154; height-mm 86; /* 时序参数 */ panel-timing { clock-frequency 51200000; hactive 1024; vactive 600; hfront-porch 160; hback-porch 140; hsync-len 20; vback-porch 20; vfront-porch 12; vsync-len 3; hsync-active 0; vsync-active 0; de-active 1; pixelclk-active 0; }; /* OF graph 连接 */ port { panel_in: endpoint { remote-endpoint lcdif_out; }; }; }; };时序参数说明每个参数的含义参数含义我们的值clock-frequency像素时钟频率51.2 MHzhactive水平有效像素1024vactive垂直有效像素600hfront-porch水平前肩160 像素周期hback-porch水平后肩140 像素周期hsync-len水平同步长度20 像素周期vfront-porch垂直前肩12 行vback-porch垂直后肩20 行vsync-len垂直同步长度3 行hsync-active水平同步极性0低电平有效vsync-active垂直同步极性0低电平有效de-active数据使能极性1高电平有效pixelclk-active像素时钟极性0下降沿采样这些参数来自屏幕的数据手册不同屏幕的值不同。写错了屏幕不会正常显示甚至可能损坏硬件。第三步——配置背光背光用 PWM 控制节点在根节点下/ { backlight_display: backlight-display { compatible pwm-backlight; pwms pwm1 0 5000000 0; brightness-levels 0 4 8 16 32 64 128 255; default-brightness-level 6; status okay; }; };然后在 panel 节点里引用backlight backlight_display;。背光驱动会在/sys/class/backlight/下创建 sysfs 接口你可以通过修改brightness文件控制亮度echo255/sys/class/backlight/backlight-display/brightness# 最亮echo0/sys/class/backlight/backlight-display/brightness# 最暗第四步——配置 lcdif 节点lcdif 节点需要删除旧的display属性并添加 OF graph 连接lcdif { assigned-clocks clks IMX6UL_CLK_LCDIF_PRE_SEL; assigned-clock-parents clks IMX6UL_CLK_PLL5_VIDEO_DIV; pinctrl-names default; pinctrl-0 pinctrl_lcdif_dat pinctrl_lcdif_ctrl; status okay; /* 删除基础文件里的 display 属性 */ /delete-property/ display; /* OF graph 连接 */ port { lcdif_out: endpoint { remote-endpoint panel_in; }; }; };为什么需要 /delete-property/ displayimx6ul.dtsi基础文件里的 lcdif 节点定义了display display0属性。这是旧写法的遗留但基础文件我们不能直接修改它是只读的。在板级设备树里用/delete-property/ display;可以在编译时覆盖删除这个属性。如果不删除DTB 编译会报错error: phandle_references: Reference to non-existent node display0因为display0节点已经被我们删除了用新的 panel 节点替代但display属性还在引用它导致悬空引用。第五步——配置 pinctrllcdif 的引脚配置包括数据线和控制线iomuxc { pinctrl_lcdif_dat: lcdifdatgrp { fsl,pins MX6UL_PAD_LCD_DATA00__LCDIF_DATA00 0x49 MX6UL_PAD_LCD_DATA01__LCDIF_DATA01 0x49 MX6UL_PAD_LCD_DATA02__LCDIF_DATA02 0x49 MX6UL_PAD_LCD_DATA03__LCDIF_DATA03 0x49 MX6UL_PAD_LCD_DATA04__LCDIF_DATA04 0x49 MX6UL_PAD_LCD_DATA05__LCDIF_DATA05 0x49 MX6UL_PAD_LCD_DATA06__LCDIF_DATA06 0x49 MX6UL_PAD_LCD_DATA07__LCDIF_DATA07 0x49 MX6UL_PAD_LCD_DATA08__LCDIF_DATA08 0x49 MX6UL_PAD_LCD_DATA09__LCDIF_DATA09 0x49 MX6UL_PAD_LCD_DATA10__LCDIF_DATA10 0x49 MX6UL_PAD_LCD_DATA11__LCDIF_DATA11 0x49 MX6UL_PAD_LCD_DATA12__LCDIF_DATA12 0x49 MX6UL_PAD_LCD_DATA13__LCDIF_DATA13 0x49 MX6UL_PAD_LCD_DATA14__LCDIF_DATA14 0x49 MX6UL_PAD_LCD_DATA15__LCDIF_DATA15 0x49 MX6UL_PAD_LCD_DATA16__LCDIF_DATA16 0x49 MX6UL_PAD_LCD_DATA17__LCDIF_DATA17 0x49 MX6UL_PAD_LCD_DATA18__LCDIF_DATA18 0x49 MX6UL_PAD_LCD_DATA19__LCDIF_DATA19 0x49 MX6UL_PAD_LCD_DATA20__LCDIF_DATA20 0x49 MX6UL_PAD_LCD_DATA21__LCDIF_DATA21 0x49 MX6UL_PAD_LCD_DATA22__LCDIF_DATA22 0x49 MX6UL_PAD_LCD_DATA23__LCDIF_DATA23 0x49 ; }; pinctrl_lcdif_ctrl: lcdifctrlgrp { fsl,pins MX6UL_PAD_LCD_CLK__LCDIF_CLK 0x49 MX6UL_PAD_LCD_ENABLE__LCDIF_ENABLE 0x49 MX6UL_PAD_LCD_HSYNC__LCDIF_HSYNC 0x49 MX6UL_PAD_LCD_VSYNC__LCDIF_VSYNC 0x49 ; }; };配置值0x49的含义位 0-3驱动强度0x9 强驱动位 4PEED快速使能位 5PUE上拉/下拉使能位 6PKE上拉/下拉保持使能位 7ODE开漏使能0禁用具体含义参考 i.MX6ULL 参考手册的 IOMUX 章节配置。第六步——编译和烧录设备树写好后编译 DTBmakeARCHarmCROSS_COMPILEarm-linux-gnueabihf- dtbs把生成的imx6ull-aes.dtb烧录到板子。烧录方式取决于你的启动配置SD 卡、eMMC、NFS 等。第七步——验证 DRM 驱动加载启动后首先检查 dmesg 里的 DRM 相关日志dmesg|grep-Edrm|mxsfb|panel你应该看到类似这样的输出[ 0.912345] mxsfb 21c8000.lcdif: Initializing [ 0.987654] panel-dpi: panel enabled [ 1.123456] mxsfb 21c8000.lcdif: bound panel-dpi (ops panel_dpi_ops) [ 1.234567] drm drm: GMA500 GEM enabled [ 1.345678] mxsfb 21c8000.lcdif: registered panic notifier [ 1.456789] [drm] Initialized mxsfb 1.0.0 for 21c8000.lcdif on minor 0关键是bound panel-dpi这一行说明驱动成功找到了 panel 设备。如果看到Cannot connect bridge (-ENODEV)说明 OF graph 连接有问题或者 panel 驱动没有加载。检查 DRM 设备节点ls/dev/dri/# 应该看到card0 renderD128ls/dev/fb*# 应该看到fb0 DRM 的兼容层检查 connector 状态cat/sys/class/drm/card0-HDMI-A-1/status# 应该输出connectedcat/sys/class/drm/card0-HDMI-A-1/modes# 应该输出1024x600如果 connector 状态是disconnected说明 panel 驱动没有正常 probe。第八步——显示测试方法一fbset 测试# 安装 fbsetaptinstallfbset# 查看 framebuffer 信息fbset-i你应该看到mode 1024x600 geometry 1024 600 1024 600 32 timings 0 0 0 0 0 0 0 accel false rgba 8/16,8/8,8/0,8/0,0/0 endmode方法二写屏幕测试# 显示随机颜色花屏cat/dev/urandom/dev/fb0sleep2kill%1# 清屏全黑ddif/dev/zeroof/dev/fb0如果屏幕有反应说明显示系统工作正常。方法三modetest 测试如果安装了modetest工具modetest-Mmxsfb这会列出所有的 DRM 设备和模式可以用来调试。常见问题排查问题一Cannot connect bridge (-ENODEV)这是最常见的报错原因和解决方法原因解决方法设备树用旧式display0写法改成 panel port/endpoint 写法CONFIG_DRM_PANEL_SIMPLE没开启开启该配置并重新编译panel 节点状态是disabled改成status okayOF graph 连接错误检查remote-endpoint的 phandle问题二屏幕全黑但背光亮这种情况通常是时序参数错误。用示波器测量 LCD 接口的信号对比数据手册的时序图。常见错误clock-frequency 不对hsync/vsync 极性反了前肩后肩的值太小问题三屏幕有花屏花屏通常是数据线引脚配置错误时钟频率太高或太低总线宽度不匹配24bit vs 18bit问题四背光不亮检查背光驱动ls/sys/class/backlight/echo255/sys/class/backlight/*/brightness如果还不亮可能是硬件问题PWM 信号没输出、背光电源没供电。下一章预告到这里你应该已经成功让 LCD 显示了。恭喜这是主线移植最难的一关。下一篇文章我们会讲触摸屏的移植GT9147 驱动配置I2C 设备树编写中断和复位引脚配置GPIO 冲突问题解决触摸校准与测试显示有了触摸也有了图形界面才能正常工作。我们下一章见。参考命令速查# 检查 DRM 日志dmesg|grep-Edrm|mxsfb|panel# 检查设备节点ls/dev/dri/ /dev/fb*# 检查 connector 状态cat/sys/class/drm/card0-HDMI-A-1/statuscat/sys/class/drm/card0-HDMI-A-1/modes# 显示测试cat/dev/urandom/dev/fb0sleep2;kill%1ddif/dev/zeroof/dev/fb0# 背光控制ls/sys/class/backlight/echo255/sys/class/backlight/*/brightness延伸阅读DRM/KMS Documentation - DRM 子系统文档Panel Framework Documentation - Panel 框架文档OF Graph Documentation - OF graph 规范
正点原子IMX6ULL史诗级新内核Linux7.0移植教程(6)DRM 显示系统移植:LCD 驱动完整迁移指南
正点原子IMX6ULL史诗级新内核Linux7.0移植教程6DRM 显示系统移植LCD 驱动完整迁移指南小请假条笔者从今天开始出差今天更新完毕之后到4月1日前不会更新公众号了也就是3月29日30日和31日更新的话可能只会缓慢更新手头有的存货。相关仓库已经开源https://github.com/Awesome-Embedded-Learning-Studio/imx-forge欢迎各位大佬看看批评指正前言这是整个移植最复杂的一章说实话从 NXP BSP 迁移到主线内核最难的部分就是显示系统。为什么因为整个架构都变了从旧的 Framebuffer 框架迁移到 DRM/KMS 框架驱动代码位置不同、设备树写法不同、调试方法也不同。但好消息是一旦你理解了 DRM 的基本概念后续的调试就会容易很多。这篇文章会带你完整走过 LCD 驱动的迁移过程从设备树编写到最终验证。我会尽量解释清楚每一步的原因而不是只给你一堆命令。第一步——理解 DRM 架构DRMDirect Rendering Manager是 Linux 内核的图形子系统最初用于 3D 图形加速后来扩展到所有显示设备。对于嵌入式系统我们主要关心 KMSKernel Mode Setting部分也就是内核负责设置显示模式的那部分代码。DRM 的核心组件应用程序 / GUI 框架 ↓ (read/write /dev/fb0 或 DRM ioctl) DRM 核心子系统 ↓ mxsfb CRTC (drivers/gpu/drm/mxsfb/) ↓ [OF graph: lcdif port → panel port] panel-dpi (drivers/gpu/drm/panel/panel-dpi.c) ↓ [物理并行 RGB 信号] LCD 屏幕硬件CRTC显示控制器负责从内存读取像素数据并输出到屏幕Encoder编码器把 CRTC 的数字信号转换成适合传输的格式对于 eLCDIF 这个就是直接输出Connector连接器描述物理连接方式比如 DPI、LVDS、HDMIPanel面板描述屏幕的时序参数和特性对于 i.MX6ULL 的 eLCDIF 控制器架构比较简单CRTC 直接连接 Panel没有中间的 Encoder。为什么不用 panel-simple 而是 panel-dpi你可能见过panel-simple驱动它支持很多常见的屏幕。但panel-simple要求屏幕在驱动代码里预定义而panel-dpi是一个通用驱动时序参数完全由设备树的panel-timing节点指定。对于我们的 7 寸 1024×600 屏幕用panel-dpi最合适因为时序参数不是标准的。第二步——编写 panel 节点panel 节点独立于 lcdif 之外作为根节点的子节点/ { panel: panel-dpi { compatible panel-dpi; backlight backlight_display; /* 屏幕物理尺寸 */ width-mm 154; height-mm 86; /* 时序参数 */ panel-timing { clock-frequency 51200000; hactive 1024; vactive 600; hfront-porch 160; hback-porch 140; hsync-len 20; vback-porch 20; vfront-porch 12; vsync-len 3; hsync-active 0; vsync-active 0; de-active 1; pixelclk-active 0; }; /* OF graph 连接 */ port { panel_in: endpoint { remote-endpoint lcdif_out; }; }; }; };时序参数说明每个参数的含义参数含义我们的值clock-frequency像素时钟频率51.2 MHzhactive水平有效像素1024vactive垂直有效像素600hfront-porch水平前肩160 像素周期hback-porch水平后肩140 像素周期hsync-len水平同步长度20 像素周期vfront-porch垂直前肩12 行vback-porch垂直后肩20 行vsync-len垂直同步长度3 行hsync-active水平同步极性0低电平有效vsync-active垂直同步极性0低电平有效de-active数据使能极性1高电平有效pixelclk-active像素时钟极性0下降沿采样这些参数来自屏幕的数据手册不同屏幕的值不同。写错了屏幕不会正常显示甚至可能损坏硬件。第三步——配置背光背光用 PWM 控制节点在根节点下/ { backlight_display: backlight-display { compatible pwm-backlight; pwms pwm1 0 5000000 0; brightness-levels 0 4 8 16 32 64 128 255; default-brightness-level 6; status okay; }; };然后在 panel 节点里引用backlight backlight_display;。背光驱动会在/sys/class/backlight/下创建 sysfs 接口你可以通过修改brightness文件控制亮度echo255/sys/class/backlight/backlight-display/brightness# 最亮echo0/sys/class/backlight/backlight-display/brightness# 最暗第四步——配置 lcdif 节点lcdif 节点需要删除旧的display属性并添加 OF graph 连接lcdif { assigned-clocks clks IMX6UL_CLK_LCDIF_PRE_SEL; assigned-clock-parents clks IMX6UL_CLK_PLL5_VIDEO_DIV; pinctrl-names default; pinctrl-0 pinctrl_lcdif_dat pinctrl_lcdif_ctrl; status okay; /* 删除基础文件里的 display 属性 */ /delete-property/ display; /* OF graph 连接 */ port { lcdif_out: endpoint { remote-endpoint panel_in; }; }; };为什么需要 /delete-property/ displayimx6ul.dtsi基础文件里的 lcdif 节点定义了display display0属性。这是旧写法的遗留但基础文件我们不能直接修改它是只读的。在板级设备树里用/delete-property/ display;可以在编译时覆盖删除这个属性。如果不删除DTB 编译会报错error: phandle_references: Reference to non-existent node display0因为display0节点已经被我们删除了用新的 panel 节点替代但display属性还在引用它导致悬空引用。第五步——配置 pinctrllcdif 的引脚配置包括数据线和控制线iomuxc { pinctrl_lcdif_dat: lcdifdatgrp { fsl,pins MX6UL_PAD_LCD_DATA00__LCDIF_DATA00 0x49 MX6UL_PAD_LCD_DATA01__LCDIF_DATA01 0x49 MX6UL_PAD_LCD_DATA02__LCDIF_DATA02 0x49 MX6UL_PAD_LCD_DATA03__LCDIF_DATA03 0x49 MX6UL_PAD_LCD_DATA04__LCDIF_DATA04 0x49 MX6UL_PAD_LCD_DATA05__LCDIF_DATA05 0x49 MX6UL_PAD_LCD_DATA06__LCDIF_DATA06 0x49 MX6UL_PAD_LCD_DATA07__LCDIF_DATA07 0x49 MX6UL_PAD_LCD_DATA08__LCDIF_DATA08 0x49 MX6UL_PAD_LCD_DATA09__LCDIF_DATA09 0x49 MX6UL_PAD_LCD_DATA10__LCDIF_DATA10 0x49 MX6UL_PAD_LCD_DATA11__LCDIF_DATA11 0x49 MX6UL_PAD_LCD_DATA12__LCDIF_DATA12 0x49 MX6UL_PAD_LCD_DATA13__LCDIF_DATA13 0x49 MX6UL_PAD_LCD_DATA14__LCDIF_DATA14 0x49 MX6UL_PAD_LCD_DATA15__LCDIF_DATA15 0x49 MX6UL_PAD_LCD_DATA16__LCDIF_DATA16 0x49 MX6UL_PAD_LCD_DATA17__LCDIF_DATA17 0x49 MX6UL_PAD_LCD_DATA18__LCDIF_DATA18 0x49 MX6UL_PAD_LCD_DATA19__LCDIF_DATA19 0x49 MX6UL_PAD_LCD_DATA20__LCDIF_DATA20 0x49 MX6UL_PAD_LCD_DATA21__LCDIF_DATA21 0x49 MX6UL_PAD_LCD_DATA22__LCDIF_DATA22 0x49 MX6UL_PAD_LCD_DATA23__LCDIF_DATA23 0x49 ; }; pinctrl_lcdif_ctrl: lcdifctrlgrp { fsl,pins MX6UL_PAD_LCD_CLK__LCDIF_CLK 0x49 MX6UL_PAD_LCD_ENABLE__LCDIF_ENABLE 0x49 MX6UL_PAD_LCD_HSYNC__LCDIF_HSYNC 0x49 MX6UL_PAD_LCD_VSYNC__LCDIF_VSYNC 0x49 ; }; };配置值0x49的含义位 0-3驱动强度0x9 强驱动位 4PEED快速使能位 5PUE上拉/下拉使能位 6PKE上拉/下拉保持使能位 7ODE开漏使能0禁用具体含义参考 i.MX6ULL 参考手册的 IOMUX 章节配置。第六步——编译和烧录设备树写好后编译 DTBmakeARCHarmCROSS_COMPILEarm-linux-gnueabihf- dtbs把生成的imx6ull-aes.dtb烧录到板子。烧录方式取决于你的启动配置SD 卡、eMMC、NFS 等。第七步——验证 DRM 驱动加载启动后首先检查 dmesg 里的 DRM 相关日志dmesg|grep-Edrm|mxsfb|panel你应该看到类似这样的输出[ 0.912345] mxsfb 21c8000.lcdif: Initializing [ 0.987654] panel-dpi: panel enabled [ 1.123456] mxsfb 21c8000.lcdif: bound panel-dpi (ops panel_dpi_ops) [ 1.234567] drm drm: GMA500 GEM enabled [ 1.345678] mxsfb 21c8000.lcdif: registered panic notifier [ 1.456789] [drm] Initialized mxsfb 1.0.0 for 21c8000.lcdif on minor 0关键是bound panel-dpi这一行说明驱动成功找到了 panel 设备。如果看到Cannot connect bridge (-ENODEV)说明 OF graph 连接有问题或者 panel 驱动没有加载。检查 DRM 设备节点ls/dev/dri/# 应该看到card0 renderD128ls/dev/fb*# 应该看到fb0 DRM 的兼容层检查 connector 状态cat/sys/class/drm/card0-HDMI-A-1/status# 应该输出connectedcat/sys/class/drm/card0-HDMI-A-1/modes# 应该输出1024x600如果 connector 状态是disconnected说明 panel 驱动没有正常 probe。第八步——显示测试方法一fbset 测试# 安装 fbsetaptinstallfbset# 查看 framebuffer 信息fbset-i你应该看到mode 1024x600 geometry 1024 600 1024 600 32 timings 0 0 0 0 0 0 0 accel false rgba 8/16,8/8,8/0,8/0,0/0 endmode方法二写屏幕测试# 显示随机颜色花屏cat/dev/urandom/dev/fb0sleep2kill%1# 清屏全黑ddif/dev/zeroof/dev/fb0如果屏幕有反应说明显示系统工作正常。方法三modetest 测试如果安装了modetest工具modetest-Mmxsfb这会列出所有的 DRM 设备和模式可以用来调试。常见问题排查问题一Cannot connect bridge (-ENODEV)这是最常见的报错原因和解决方法原因解决方法设备树用旧式display0写法改成 panel port/endpoint 写法CONFIG_DRM_PANEL_SIMPLE没开启开启该配置并重新编译panel 节点状态是disabled改成status okayOF graph 连接错误检查remote-endpoint的 phandle问题二屏幕全黑但背光亮这种情况通常是时序参数错误。用示波器测量 LCD 接口的信号对比数据手册的时序图。常见错误clock-frequency 不对hsync/vsync 极性反了前肩后肩的值太小问题三屏幕有花屏花屏通常是数据线引脚配置错误时钟频率太高或太低总线宽度不匹配24bit vs 18bit问题四背光不亮检查背光驱动ls/sys/class/backlight/echo255/sys/class/backlight/*/brightness如果还不亮可能是硬件问题PWM 信号没输出、背光电源没供电。下一章预告到这里你应该已经成功让 LCD 显示了。恭喜这是主线移植最难的一关。下一篇文章我们会讲触摸屏的移植GT9147 驱动配置I2C 设备树编写中断和复位引脚配置GPIO 冲突问题解决触摸校准与测试显示有了触摸也有了图形界面才能正常工作。我们下一章见。参考命令速查# 检查 DRM 日志dmesg|grep-Edrm|mxsfb|panel# 检查设备节点ls/dev/dri/ /dev/fb*# 检查 connector 状态cat/sys/class/drm/card0-HDMI-A-1/statuscat/sys/class/drm/card0-HDMI-A-1/modes# 显示测试cat/dev/urandom/dev/fb0sleep2;kill%1ddif/dev/zeroof/dev/fb0# 背光控制ls/sys/class/backlight/echo255/sys/class/backlight/*/brightness延伸阅读DRM/KMS Documentation - DRM 子系统文档Panel Framework Documentation - Panel 框架文档OF Graph Documentation - OF graph 规范