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属性自动匹配绑定。
DM_VIDEO 三层驱动架构 UCLASS_VIDEO 显示控制器驱动(FIMD/LCDIF/DCSS) UCLASS_VIDEO_BRIDGE 桥接芯片驱动(DSI→LVDS/HDMI→eDP) UCLASS_PANEL 面板驱动(初始化序列/背光/时序参数) 设备树匹配 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流程包含以下关键步骤:

  1. 资源获取:通过dev_read_addr()获取寄存器基址,通过dev_read_u32_array()获取时钟、中断等资源。
  2. 时钟与电源初始化:使能显示控制器时钟、PLL、电源域。
  3. 显示控制器硬件初始化:配置时序发生器、DMA引擎、图层混合器。
  4. 帧缓冲分配:调用video_reserve()或dm_mem_alloc()分配显存。
  5. 面板/桥接probe:如果设备树中有子节点,递归probe面板或桥接驱动。
  6. 模式设置:根据设备树中的display-timings节点设置分辨率、刷新率。
  7. 注册到DM_VIDEO框架:填充struct udevice_ops中的set_mode、clear等回调。
DM_VIDEO Probe 流程 1. 资源获取 2. 时钟/电源使能 3. 硬件初始化 4. 帧缓冲分配 5. 面板/桥接probe 6. 模式设置 7. 注册到DM_VIDEO probe失败时,DM框架自动 回滚已分配资源并返回错误码

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 — 检查设备树节点内容