3. U-Boot LCD驱动模型:DM_VIDEO框架、驱动绑定与probe流程
U-Boot从2016版本开始引入基于驱动模型(Driver Model, DM)的显示子系统——DM_VIDEO。该框架统一了LCD、HDMI、VGA等显示设备的驱动编写方式,将硬件初始化、模式设置、帧缓冲管理纳入标准化的生命周期管理。理解DM_VIDEO的驱动绑定与probe流程,是完成LCD外设从U-Boot到内核帧缓冲移植的关键桥梁。
3.1 DM_VIDEO框架层次结构
DM_VIDEO采用三层架构,自底向上分为:UCLASS_VIDEO(驱动类)、UCLASS_VIDEO_BRIDGE(桥接类)和UCLASS_PANEL(面板类)。这种分层设计使得SoC显示控制器、外部桥接芯片和LCD面板可以独立开发与组合。
核心设计思想: 将显示控制器驱动(负责时序生成、DMA传输)与面板驱动(负责初始化序列、背光控制)解耦,通过设备树中的
compatible属性自动匹配绑定。
3.2 驱动绑定机制
DM_VIDEO的驱动绑定基于设备树(Device Tree)和U-Boot的DM核心。绑定过程分为静态绑定和动态绑定两种路径:
| 绑定类型 | 触发时机 | 匹配依据 | 典型场景 |
|---|---|---|---|
| 静态绑定 | U-Boot启动阶段,DM初始化时 | 设备树节点中的compatible字符串 |
SoC内部显示控制器(如i.MX6的IPUv3) |
| 动态绑定 | 运行时通过dm_scan_fdt_dev()扫描 |
父设备probe后扫描子节点 | 通过I2C/SPI连接的桥接芯片或面板 |
驱动绑定核心函数调用链如下:
board_init_r()
└─ dm_init_and_scan(true)
└─ dm_scan_fdt_node(gd->dm_root, gd->fdt_blob, 0, pre_reloc_only)
└─ lists_bind_fdt(node, &dev, NULL, true)
├─ driver_check_compatible(drv, node) // 匹配compatible
└─ device_bind(dev, drv, name, ...) // 创建udevice实例
关键点: 设备树中必须包含
u-boot,dm-pre-reloc或bootph-all属性,驱动才能在重定位前(pre-relocation)完成绑定。对于LCD驱动,通常需要在重定位前完成绑定以显示启动Logo。
3.3 Probe流程详解
当驱动绑定成功后,DM框架会在合适的时机调用device_probe(),触发驱动的probe函数。对于DM_VIDEO驱动,probe流程包含以下关键步骤:
- 资源获取:通过
dev_read_addr()获取寄存器基址,通过dev_read_u32_array()获取时钟、中断等资源。 - 时钟与电源初始化:使能显示控制器时钟、PLL、电源域。
- 显示控制器硬件初始化:配置时序发生器、DMA引擎、图层混合器。
- 帧缓冲分配:调用
video_reserve()或dm_mem_alloc()分配显存。 - 面板/桥接probe:如果设备树中有子节点,递归probe面板或桥接驱动。
- 模式设置:根据设备树中的
display-timings节点设置分辨率、刷新率。 - 注册到DM_VIDEO框架:填充
struct udevice_ops中的set_mode、clear等回调。
3.4 关键数据结构与回调函数
DM_VIDEO驱动需要实现的核心数据结构包括:
| 数据结构/回调 | 作用 | 必须实现 |
|---|---|---|
struct video_ops |
包含set_mode、clear、fill等操作回调 |
是 |
struct udevice_id |
设备树compatible匹配表 | 是 |
struct video_priv |
存储分辨率、行宽、帧缓冲地址等私有数据 | 推荐 |
struct video_uc_platdata |
平台数据,用于传递帧缓冲基址和大小 | 是 |
一个典型的DM_VIDEO驱动probe函数实现框架:
static int myfb_probe(struct udevice *dev)
{
struct video_priv *priv = dev_get_uclass_priv(dev);
struct video_uc_platdata *plat = dev_get_uclass_platdata(dev);
fdt_addr_t base;
int ret;
// 1. 获取寄存器基址
base = dev_read_addr(dev);
if (base == FDT_ADDR_T_NONE)
return -EINVAL;
// 2. 使能时钟(示例)
ret = clk_enable_by_name(dev, "lcd_clk");
if (ret && ret != -ENOENT)
return ret;
// 3. 硬件初始化
myfb_hw_init(base);
// 4. 分配帧缓冲
plat->base = video_reserve(dev, plat->size);
plat->align = PAGE_SIZE;
// 5. 设置默认模式(从设备树读取timing)
ret = video_read_timing(dev, &priv->timing);
if (ret)
return ret;
// 6. 配置显示控制器
myfb_set_mode(base, &priv->timing);
// 7. 填充video_priv
priv->xsize = priv->timing.hactive.typ;
priv->ysize = priv->timing.vactive.typ;
priv->bpix = VIDEO_BPP32; // 32bpp
return 0;
}
U_BOOT_DRIVER(myfb) = {
.name = "myfb",
.id = UCLASS_VIDEO,
.of_match = myfb_ids,
.probe = myfb_probe,
.priv_auto = sizeof(struct myfb_priv),
.plat_auto = sizeof(struct video_uc_platdata),
};
3.5 设备树节点与驱动绑定示例
设备树中LCD相关节点的典型结构:
/ {
display-subsystem {
compatible = "fsl,imx6q-display-subsystem";
ports = <&ipu1_di0>;
};
&ipu1_di0 {
#address-cells = <1>;
#size-cells = <0>;
port@0 {
reg = <0>;
ipu1_di0_lvds: endpoint {
remote-endpoint = <&lvds0_in>;
};
};
};
&ldb {
lvds0: lvds-channel@0 {
port@0 {
lvds0_in: endpoint {
remote-endpoint = <&ipu1_di0_lvds>;
};
};
display-timings {
native-mode = <&timing0>;
timing0: 1024x768 {
hactive = <1024>;
vactive = <768>;
clock-frequency = <65000000>;
hfront-porch = <160>;
hback-porch = <160>;
hsync-len = <20>;
vfront-porch = <12>;
vback-porch = <23>;
vsync-len = <3>;
};
};
};
};
};
常见陷阱:
- 设备树中
display-timings节点必须包含native-mode属性,否则DM_VIDEO框架无法确定默认时序。 - 如果面板需要初始化序列(如设置寄存器),必须在面板驱动的probe中完成,而非显示控制器驱动中。
- 帧缓冲地址必须对齐到缓存行大小(通常32字节),否则在启用D-Cache时可能出现显示异常。
3.6 驱动绑定与probe的调试方法
当驱动无法正常probe时,可以通过以下手段定位问题:
- 使能DM调试日志:在U-Boot配置中定义
CONFIG_DM_DEBUG,查看设备绑定和probe的详细日志。 - 检查设备树节点状态:确保节点
status = "okay",且父节点状态正常。 - 验证compatible匹配:使用
dm tree命令查看设备是否成功绑定到对应驱动。 - 检查资源获取:确认
dev_read_addr()返回的地址与SoC手册一致,时钟名称与设备树匹配。
调试命令速查:
dm tree — 查看所有已绑定设备及其驱动dm uclass — 查看UCLASS_VIDEO下的设备列表video info — 查看当前显示模式与帧缓冲地址fdt addr + fdt list — 检查设备树节点内容