在STM32MP157上实战DRM驱动从零开始移植一个能用的显示驱动当拿到一块搭载STM32MP157的开发板时最令人兴奋的莫过于让那块LCD屏幕亮起来。作为嵌入式Linux开发者我们早已厌倦了老旧的Framebuffer驱动而DRMDirect Rendering Manager作为现代Linux图形显示框架不仅能提供更高效的显示管理还能为后续可能的GPU加速打下基础。本文将带你从零开始在正点原子的STM32MP157开发板上移植一个完整的DRM显示驱动。1. 开发环境准备与基础知识在开始之前我们需要确保开发环境已经就绪。推荐使用Ubuntu 18.04作为开发主机系统因为它提供了稳定的交叉编译工具链和必要的开发库。必备工具清单ARM交叉编译工具链gcc-arm-9.2-2019.12-x86_64-arm-none-linux-gnueabihf开发板配套的Linux内核源码建议使用5.4或以上版本libdrm库源码版本2.4.109或更高开发板硬件文档特别是LCD接口部分DRM框架的核心概念需要先理解清楚。与传统的Framebuffer不同DRM将显示系统抽象为几个关键组件CRTC (显示控制器) - Encoder (编码器) - Connector (物理接口) ↑ Plane (图层)每个组件都有其特定的功能CRTC负责时序生成和帧切换Plane处理图像图层叠加Encoder将数字信号转换为物理接口所需格式Connector代表实际的物理显示接口提示STM32MP157的显示子系统称为LTDCLCD-TFT Display Controller它已经在内核中有基本支持但我们需要为其实现完整的DRM驱动。2. 驱动框架搭建与核心结构体首先创建一个新的驱动模块命名为stm32_drm.c。DRM驱动的核心是drm_driver结构体它定义了驱动的基本特性和回调函数。static struct drm_driver stm32_drm_driver { .driver_features DRIVER_MODESET | DRIVER_GEM | DRIVER_ATOMIC, .dumb_create stm32_drm_gem_dumb_create, .prime_handle_to_fd drm_gem_prime_handle_to_fd, .prime_fd_to_handle drm_gem_prime_fd_to_handle, .gem_prime_import_sg_table drm_gem_cma_prime_import_sg_table, .gem_prime_mmap drm_gem_prime_mmap, .fops stm32_drm_fops, .name stm32-drm, .desc STM32MP157 DRM Driver, .date 20230715, .major 1, .minor 0, };接下来实现probe函数这是驱动初始化的入口static int stm32_drm_probe(struct platform_device *pdev) { struct device *dev pdev-dev; struct drm_device *ddev; int ret; ddev drm_dev_alloc(stm32_drm_driver, dev); if (IS_ERR(ddev)) return PTR_ERR(ddev); platform_set_drvdata(pdev, ddev); ret stm32_modeset_init(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; }关键函数stm32_modeset_init负责初始化KMS子系统static int stm32_modeset_init(struct drm_device *ddev) { struct stm32_drm_private *priv; int ret; priv devm_kzalloc(ddev-dev, sizeof(*priv), GFP_KERNEL); if (!priv) return -ENOMEM; ddev-dev_private priv; drm_mode_config_init(ddev); ddev-mode_config.min_width 0; ddev-mode_config.min_height 0; ddev-mode_config.max_width 2048; ddev-mode_config.max_height 2048; ddev-mode_config.funcs stm32_drm_mode_config_funcs; /* 初始化CRTC、Encoder、Connector和Planes */ ret stm32_ltdc_load(ddev); if (ret) goto err_cleanup; drm_mode_config_reset(ddev); drm_kms_helper_poll_init(ddev); return 0; err_cleanup: drm_mode_config_cleanup(ddev); return ret; }3. 硬件抽象层实现STM32MP157的LTDC控制器需要被抽象为DRM的各个组件。我们从CRTC开始这是显示控制的核心。CRTC实现static const struct drm_crtc_funcs stm32_crtc_funcs { .reset drm_atomic_helper_crtc_reset, .destroy drm_crtc_cleanup, .set_config drm_atomic_helper_set_config, .page_flip drm_atomic_helper_page_flip, .atomic_duplicate_state drm_atomic_helper_crtc_duplicate_state, .atomic_destroy_state drm_atomic_helper_crtc_destroy_state, .atomic_print_state stm32_crtc_atomic_print_state, }; static const struct drm_crtc_helper_funcs stm32_crtc_helper_funcs { .atomic_check stm32_crtc_atomic_check, .atomic_begin stm32_crtc_atomic_begin, .atomic_flush stm32_crtc_atomic_flush, .atomic_enable stm32_crtc_atomic_enable, .atomic_disable stm32_crtc_atomic_disable, };Plane实现STM32MP157支持多层显示我们需要为每个图层实现planestatic const struct drm_plane_funcs stm32_plane_funcs { .update_plane drm_atomic_helper_update_plane, .disable_plane drm_atomic_helper_disable_plane, .destroy drm_plane_cleanup, .reset drm_atomic_helper_plane_reset, .atomic_duplicate_state drm_atomic_helper_plane_duplicate_state, .atomic_destroy_state drm_atomic_helper_plane_destroy_state, .atomic_print_state stm32_plane_atomic_print_state, }; static const struct drm_plane_helper_funcs stm32_plane_helper_funcs { .prepare_fb drm_gem_fb_prepare_fb, .cleanup_fb drm_gem_fb_cleanup_fb, .atomic_check stm32_plane_atomic_check, .atomic_update stm32_plane_atomic_update, .atomic_disable stm32_plane_atomic_disable, };Encoder和Connector实现对于RGB接口的LCD屏幕encoder和connector可以简化实现static const struct drm_encoder_funcs stm32_encoder_funcs { .destroy drm_encoder_cleanup, }; static const struct drm_connector_funcs stm32_connector_funcs { .fill_modes drm_helper_probe_single_connector_modes, .destroy drm_connector_cleanup, .reset drm_atomic_helper_connector_reset, .atomic_duplicate_state drm_atomic_helper_connector_duplicate_state, .atomic_destroy_state drm_atomic_helper_connector_destroy_state, }; static const struct drm_connector_helper_funcs stm32_connector_helper_funcs { .get_modes stm32_connector_get_modes, .mode_valid stm32_connector_mode_valid, };4. 设备树配置与硬件初始化正确的设备树配置对驱动工作至关重要。以下是STM32MP157的LTDC节点示例ltdc: display-controller5a001000 { 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 okay; port { #address-cells 1; #size-cells 0; ltdc_out_rgb: endpoint0 { reg 0; remote-endpoint panel_in; }; }; }; panel_rgb: panel-rgb { compatible panel-dpi; label LCD; status okay; port { panel_in: endpoint { remote-endpoint ltdc_out_rgb; }; }; panel-timing { clock-frequency 51200000; hactive 1024; vactive 600; hfront-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; }; };驱动中需要解析这些配置并初始化硬件static int stm32_ltdc_load(struct drm_device *ddev) { struct stm32_drm_private *priv ddev-dev_private; struct device *dev ddev-dev; struct platform_device *pdev to_platform_device(dev); struct resource *res; void __iomem *regs; int irq, ret; /* 获取寄存器资源 */ res platform_get_resource(pdev, IORESOURCE_MEM, 0); regs devm_ioremap_resource(dev, res); if (IS_ERR(regs)) return PTR_ERR(regs); priv-regs regs; /* 获取中断 */ irq platform_get_irq(pdev, 0); if (irq 0) return irq; /* 初始化CRTC */ ret stm32_crtc_init(ddev); if (ret) return ret; /* 初始化Encoder和Connector */ ret stm32_encoder_connector_init(ddev); if (ret) return ret; /* 初始化Planes */ ret stm32_planes_init(ddev); if (ret) return ret; /* 配置LTDC硬件寄存器 */ stm32_ltdc_setup(priv); return 0; }5. 测试与验证驱动编译并加载后可以通过多种方式验证其功能。1. 检查设备节点# ls /dev/dri/ card0 renderD1282. 使用modetest工具测试首先查看可用的显示配置# modetest -M stm然后测试实际显示# modetest -M stm -s 3235:1024x600 -P 3335:testimage.bin3. 自定义测试程序基于libdrm编写简单的测试程序#include xf86drm.h #include xf86drmMode.h int main(int argc, char **argv) { int fd; drmModeConnector *conn; drmModeRes *res; fd drmOpen(stm, NULL); if (fd 0) { printf(Failed to open DRM device\n); return -1; } res drmModeGetResources(fd); if (!res) { printf(Failed to get DRM resources\n); goto err; } /* 查找连接的显示器 */ for (int i 0; i res-count_connectors; i) { conn drmModeGetConnector(fd, res-connectors[i]); if (conn-connection DRM_MODE_CONNECTED) { printf(Found connected connector %d\n, conn-connector_id); break; } drmModeFreeConnector(conn); } /* 设置显示模式 */ drmModeCrtc *crtc drmModeGetCrtc(fd, res-crtcs[0]); drmModeModeInfo *mode conn-modes[0]; drmModeSetCrtc(fd, crtc-crtc_id, -1, 0, 0, conn-connector_id, 1, mode); drmModeFreeCrtc(crtc); drmModeFreeConnector(conn); drmModeFreeResources(res); drmClose(fd); return 0; err: drmClose(fd); return -1; }6. 性能优化与调试技巧当基本功能工作后可以考虑以下优化1. 双缓冲与垂直同步static void stm32_crtc_atomic_flush(struct drm_crtc *crtc, struct drm_crtc_state *old_state) { struct stm32_drm_private *priv crtc-dev-dev_private; unsigned long flags; if (crtc-state-event) { spin_lock_irqsave(crtc-dev-event_lock, flags); if (drm_crtc_vblank_get(crtc) 0) drm_crtc_arm_vblank_event(crtc, crtc-state-event); else drm_crtc_send_vblank_event(crtc, crtc-state-event); spin_unlock_irqrestore(crtc-dev-event_lock, flags); crtc-state-event NULL; } /* 触发LTDC寄存器更新 */ writel(LTDC_SRCR_VBR, priv-regs LTDC_SRCR); }2. DMA-BUF支持static struct drm_gem_object * stm32_drm_gem_prime_import_sg_table(struct drm_device *dev, struct dma_buf_attachment *attach, struct sg_table *sgt) { struct stm32_drm_private *priv dev-dev_private; struct drm_gem_cma_object *cma_obj; cma_obj drm_gem_cma_prime_import_sg_table(dev, attach, sgt); if (IS_ERR(cma_obj)) return ERR_CAST(cma_obj); /* 可根据需要添加特殊处理 */ return cma_obj-base; }3. 常见问题排查无显示输出检查LTDC时钟配置、GPIO复用设置、时序参数显示花屏验证framebuffer的像素格式和内存对齐性能低下启用CMA区域的连续内存分配减少内存拷贝注意调试时可以启用DRM的调试输出在内核命令行添加drm.debug0x1e参数。7. 进阶功能实现1. 多平面混合static int stm32_plane_atomic_check(struct drm_plane *plane, struct drm_plane_state *state) { struct drm_framebuffer *fb state-fb; struct drm_crtc_state *crtc_state; if (!fb) return 0; crtc_state drm_atomic_get_existing_crtc_state(state-state, state-crtc); if (!crtc_state) return -EINVAL; /* 检查缩放限制 */ if (state-src_w ! state-crtc_w || state-src_h ! state-crtc_h) { if (plane-type ! DRM_PLANE_TYPE_PRIMARY) { DRM_ERROR(Only primary plane supports scaling\n); return -EINVAL; } } return 0; }2. 色彩管理static void stm32_crtc_set_gamma(struct drm_crtc *crtc, u16 *red, u16 *green, u16 *blue, uint32_t size, struct drm_modeset_acquire_ctx *ctx) { struct stm32_drm_private *priv crtc-dev-dev_private; int i; for (i 0; i size; i) { priv-gamma.red[i] red[i] 8; priv-gamma.green[i] green[i] 8; priv-gamma.blue[i] blue[i] 8; } /* 更新硬件LUT */ stm32_ltdc_update_gamma(priv); }3. 电源管理static int stm32_drm_pm_suspend(struct device *dev) { struct drm_device *ddev dev_get_drvdata(dev); struct stm32_drm_private *priv ddev-dev_private; drm_kms_helper_poll_disable(ddev); priv-suspend_state drm_atomic_helper_suspend(ddev); if (IS_ERR(priv-suspend_state)) { drm_kms_helper_poll_enable(ddev); return PTR_ERR(priv-suspend_state); } /* 关闭LTDC时钟 */ clk_disable_unprepare(priv-clk); return 0; } static int stm32_drm_pm_resume(struct device *dev) { struct drm_device *ddev dev_get_drvdata(dev); struct stm32_drm_private *priv ddev-dev_private; /* 重新启用LTDC时钟 */ clk_prepare_enable(priv-clk); /* 恢复显示状态 */ drm_atomic_helper_resume(ddev, priv-suspend_state); drm_kms_helper_poll_enable(ddev); return 0; }在STM32MP157上实现完整的DRM驱动确实需要投入不少精力但一旦完成你将获得一个现代化、功能丰富的图形显示系统。从最初的硬件初始化到最终的原子模式设置每一步都需要仔细考虑硬件特性和软件框架的配合。