4. DPU显示控制器驱动:DPU硬件模块介绍、DPU驱动初始化、中断处理、时钟与电源管理
DPU(Display Processing Unit)是高通骁龙平台显示子系统的核心硬件模块,负责将GPU渲染完成的帧缓冲区数据经过合成、缩放、色彩校正、时序生成等流水线处理,最终输出到物理显示接口(如DP、DSI、HDMI)。本章深入剖析DPU的硬件架构、驱动初始化流程、中断处理机制以及时钟与电源管理策略。
4.1 DPU硬件模块介绍
SA8155平台的DPU集成在MDSS(Mobile Display Subsystem)内部,包含多个专用硬件块,每个块完成显示流水线中的一个特定阶段。下图展示了DPU内部的主要模块及其数据流关系。
DPU核心模块功能说明:
| 模块缩写 | 全称 | 功能描述 |
|---|---|---|
| SSPP | Source Surface Processor | 从内存中获取图层数据,支持最多4个独立图层输入 |
| LM | Layer Mixer | 执行Alpha混合与图层叠加,生成合成帧 |
| PIPE | Color Processing Pipe | Gamma校正(GC)、逆Gamma(IGC)、色彩变换(PCC) |
| INTF | Interface Controller | 生成显示时序信号,驱动DP/DSI/HDMI物理层 |
| VBIF | Video Bus Interface | 通过AXI总线与系统内存交互,获取帧数据 |
| DMA | Direct Memory Access | 控制帧数据从VBIF到SSPP的传输 |
架构要点: DPU采用流水线设计,每一帧数据从VBIF进入,经过SSPP解包、LM混合、PIPE色彩处理,最后由INTF按时序输出。这种设计允许每个阶段独立配置,支持多图层合成和实时色彩调整。
4.2 DPU驱动初始化
DPU驱动初始化遵循DRM/KMS子系统的标准流程,分为平台设备探测、子模块绑定、CRTC/Encoder/Connector创建三个阶段。初始化顺序直接影响显示输出的正确性。
4.2.1 初始化流程
4.2.2 关键数据结构
DPU驱动使用 dpu_kms 结构体作为顶层抽象,包含所有子模块的实例指针:
struct dpu_kms {
struct drm_device *dev;
struct platform_device *pdev;
/* 寄存器映射基地址 */
void __iomem *mmio;
unsigned long mmio_len;
/* 子模块实例 */
struct dpu_hw_sspp *sspp[SSPP_MAX];
struct dpu_hw_lm *lm[LM_MAX];
struct dpu_hw_intf *intf[INTF_MAX];
struct dpu_hw_dspp *dspp[DSPP_MAX];
/* 时钟与电源 */
struct clk_bulk_data *clocks;
int num_clocks;
struct regulator *vdd;
/* 中断相关 */
struct dpu_irq_controller irq_controller;
spinlock_t irq_lock;
};
初始化要点: 驱动在probe阶段必须按顺序完成:
1) 从设备树获取寄存器地址范围并ioremap;
2) 获取所有时钟句柄(包括mdp_clk、ahb_clk、vsync_clk等);
3) 注册中断处理函数,绑定到DPU中断控制器;
4) 最后调用dpu_kms_init()创建DRM核心对象。
1) 从设备树获取寄存器地址范围并ioremap;
2) 获取所有时钟句柄(包括mdp_clk、ahb_clk、vsync_clk等);
3) 注册中断处理函数,绑定到DPU中断控制器;
4) 最后调用dpu_kms_init()创建DRM核心对象。
4.3 中断处理
DPU中断系统负责处理垂直同步(VSYNC)、帧完成、错误恢复等事件。中断控制器采用分层设计:硬件层产生原始中断,驱动层进行屏蔽、状态读取和分发。
4.3.1 中断类型与映射
| 中断源 | 硬件位域 | 触发条件 | 驱动处理动作 |
|---|---|---|---|
| VSYNC | INTF_VSYNC | 每帧垂直消隐期开始 | 唤醒drm_vblank_work,更新CRC |
| 帧完成 | DMA_FRAME_DONE | DMA传输完成 | 触发commit完成回调,释放fence |
| 溢出错误 | FIFO_OVERFLOW | FIFO缓冲区溢出 | 重置对应管道,打印错误日志 |
| 总线错误 | VBIF_AXI_ERROR | AXI总线访问异常 | 触发系统错误恢复流程 |
4.3.2 中断处理流程
中断处理注意事项:
• 中断处理函数必须在原子上下文中执行,不能调用可能导致睡眠的函数(如kmalloc、mutex_lock)。
• 对于VSYNC中断,必须使用
• 错误中断(如FIFO溢出)需要记录寄存器快照,便于后期调试。
• 中断处理函数必须在原子上下文中执行,不能调用可能导致睡眠的函数(如kmalloc、mutex_lock)。
• 对于VSYNC中断,必须使用
drm_crtc_handle_vblank() 通知DRM核心,否则用户空间的page flip等待会超时。• 错误中断(如FIFO溢出)需要记录寄存器快照,便于后期调试。
4.4 时钟与电源管理
DPU的时钟和电源管理直接影响显示性能和功耗。SA8155平台为DPU提供了多个独立时钟域和电源域,驱动需要根据显示状态动态调节。
4.4.1 时钟域划分
| 时钟名称 | 时钟源 | 典型频率 | 控制模块 |
|---|---|---|---|
| mdp_clk | DPU核心时钟 | 200-500 MHz | 所有内部流水线 |
| ahb_clk | AHB总线时钟 | 100-200 MHz | 寄存器配置接口 |
| vsync_clk | 垂直同步时钟 | 60 Hz | INTF时序生成 |
| axi_clk | AXI总线时钟 | 300-600 MHz | VBIF与DMA |
| dsi_clk | DSI链路时钟 | 根据分辨率动态 | DSI物理层 |
4.4.2 电源管理策略
DPU驱动实现了基于运行时PM(Runtime PM)的电源管理,在显示空闲时关闭时钟和电源域以降低功耗。核心接口如下:
/* 运行时PM回调 */
static int dpu_runtime_suspend(struct device *dev)
{
struct dpu_kms *dpu_kms = dev_get_drvdata(dev);
/* 关闭所有DPU时钟 */
clk_bulk_disable_unprepare(dpu_kms->num_clocks,
dpu_kms->clocks);
/* 关闭电源域 */
regulator_disable(dpu_kms->vdd);
dev_dbg(dev, "DPU runtime suspended\n");
return 0;
}
static int dpu_runtime_resume(struct device *dev)
{
struct dpu_kms *dpu_kms = dev_get_drvdata(dev);
int ret;
/* 使能电源域 */
ret = regulator_enable(dpu_kms->vdd);
if (ret)
return ret;
/* 使能所有时钟 */
ret = clk_bulk_prepare_enable(dpu_kms->num_clocks,
dpu_kms->clocks);
if (ret) {
regulator_disable(dpu_kms->vdd);
return ret;
}
/* 重新初始化DPU硬件状态 */
dpu_hw_init(dpu_kms);
dev_dbg(dev, "DPU runtime resumed\n");
return 0;
}
static const struct dev_pm_ops dpu_pm_ops = {
SET_RUNTIME_PM_OPS(dpu_runtime_suspend,
dpu_runtime_resume, NULL)
};
4.4.3 动态频率调节
DPU驱动根据当前显示分辨率和刷新率动态调整 mdp_clk 频率,避免不必要的功耗浪费。频率计算公式如下:
/* 计算所需最小MDP时钟频率 */
static unsigned long dpu_calc_mdp_clk(struct drm_display_mode *mode)
{
unsigned long pclk = mode->clock; /* pixel clock in kHz */
unsigned long mdp_clk;
/*
* MDP时钟需要至少为像素时钟的2倍,
* 以支持双像素处理模式。
* 对于4K@60,pclk ≈ 594000 kHz,
* 则mdp_clk ≥ 1188000 kHz。
*/
mdp_clk = pclk * 2;
/* 考虑额外开销,增加20%余量 */
mdp_clk = mdp_clk * 120 / 100;
return mdp_clk;
}
电源管理最佳实践:
• 在DPU空闲时(如屏幕关闭)调用
• 在每次commit操作前调用
• 使用
• 在suspend/resume过程中保存和恢复DPU关键寄存器状态,确保恢复后显示正常。
• 在DPU空闲时(如屏幕关闭)调用
pm_runtime_put_sync() 触发挂起。• 在每次commit操作前调用
pm_runtime_get_sync() 确保时钟已使能。• 使用
clk_bulk API统一管理多个时钟,减少代码重复。• 在suspend/resume过程中保存和恢复DPU关键寄存器状态,确保恢复后显示正常。