2. 实现Rockchip CRTC驱动
任务说明
本案例为Rockchip平台(以RK3568为例)实现一个最小可用的CRTC驱动,挂载到DRM/KMS框架中。核心工作包括:
- 实现CRTC硬件初始化(时钟、复位、VOP寄存器映射);
- 实现
crtc_mode_set回调,完成时序配置与使能输出; - 实现
crtc_page_flip回调,在VBlank中断中切换扫描缓冲区地址。
驱动不涉及具体HDMI/DSI等外部桥接芯片,仅完成CRTC本身与内存扫描输出的最小闭环,便于在开发板上直接验证modetest输出。
环境与依赖
- 内核版本:Linux 5.10(Rockchip BSP分支)
- 硬件:RK3568 EVB(或RK3399,需对应修改寄存器基址)
- 依赖头文件:
drm_crtc_helper.h、drm_plane_helper.h、drm_gem_dma_helper.h、rockchip_drm_drv.h(BSP自带) - 设备树:需在
vop节点中提供reg、interrupts、clocks、resets属性
关键完整代码
以下为最小CRTC驱动实现,文件可直接编译为内核模块(需与BSP的rockchipdrm主驱动链接):
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/of.h>
#include <linux/clk.h>
#include <linux/reset.h>
#include <linux/delay.h>
#include <drm/drm_crtc_helper.h>
#include <drm/drm_plane_helper.h>
#include <drm/drm_gem_dma_helper.h>
#include <drm/drm_vblank.h>
#include <drm/drm_fourcc.h>
#include "rockchip_drm_drv.h"
#define VOP_REG_OFFSET 0x0
#define WIN0_CTRL0 0x50
#define WIN0_DSP_ADDR 0x60
#define SYS_CTRL 0x04
struct rk_crtc {
struct drm_crtc base;
void __iomem *regs;
struct clk *aclk;
struct clk *dclk;
struct reset_control *rst;
int irq;
};
static inline struct rk_crtc *to_rk_crtc(struct drm_crtc *crtc)
{
return container_of(crtc, struct rk_crtc, base);
}
static void rk_crtc_enable_vblank(struct drm_crtc *crtc)
{
struct rk_crtc *rk = to_rk_crtc(crtc);
u32 val = readl(rk->regs + SYS_CTRL);
val |= BIT(1); /* 使能帧中断 */
writel(val, rk->regs + SYS_CTRL);
}
static void rk_crtc_disable_vblank(struct drm_crtc *crtc)
{
struct rk_crtc *rk = to_rk_crtc(crtc);
u32 val = readl(rk->regs + SYS_CTRL);
val &= ~BIT(1);
writel(val, rk->regs + SYS_CTRL);
}
static int rk_crtc_mode_set(struct drm_crtc *crtc,
struct drm_display_mode *mode,
struct drm_display_mode *adjusted,
int x, int y,
struct drm_framebuffer *old_fb)
{
struct rk_crtc *rk = to_rk_crtc(crtc);
u32 htotal = mode->htotal;
u32 vtotal = mode->vtotal;
u32 hsync = mode->hsync_end - mode->hsync_start;
u32 vsync = mode->vsync_end - mode->vsync_start;
/* 关闭显示通路 */
writel(0, rk->regs + SYS_CTRL);
/* 配置时序寄存器(简化:仅写总长与同步宽度) */
writel((htotal << 16) | mode->hdisplay, rk->regs + 0x10);
writel((vtotal << 16) | mode->vdisplay, rk->regs + 0x14);
writel((hsync << 16) | (mode->hsync_start - mode->hdisplay),
rk->regs + 0x18);
writel((vsync << 16) | (mode->vsync_start - mode->vdisplay),
rk->regs + 0x1c);
/* 使能VOP输出 */
writel(0x1, rk->regs + SYS_CTRL);
return 0;
}
static int rk_crtc_page_flip(struct drm_crtc *crtc,
struct drm_framebuffer *fb,
struct drm_pending_vblank_event *event,
uint32_t flags)
{
struct rk_crtc *rk = to_rk_crtc(crtc);
struct drm_gem_dma_object *gem;
if (!fb || !fb->obj[0])
return -EINVAL;
gem = drm_fb_dma_get_gem_obj(fb, 0);
if (!gem)
return -EINVAL;
/* 将新帧地址写入WIN0扫描地址寄存器 */
writel((u32)gem->dma_addr, rk->regs + WIN0_DSP_ADDR);
/* 等待VBlank后提交事件 */
if (event) {
struct drm_crtc_state *state = crtc->state;
unsigned long flags_irq;
spin_lock_irqsave(&crtc->dev->event_lock, flags_irq);
if (state && state->event) {
drm_crtc_send_vblank_event(crtc, event);
} else {
drm_crtc_vblank_get(crtc);
drm_crtc_arm_vblank_event(crtc, event);
}
spin_unlock_irqrestore(&crtc->dev->event_lock, flags_irq);
}
return 0;
}
static irqreturn_t rk_crtc_irq_handler(int irq, void *data)
{
struct drm_device *drm = data;
struct rk_crtc *rk = NULL;
u32 status;
/* 实际项目中需遍历crtc列表找到对应实例 */
rk = container_of(drm->mode_config.crtc_list.next,
struct rk_crtc, base.head);
status = readl(rk->regs + 0x40);
if (status & BIT(0)) {
writel(status, rk->regs + 0x40); /* 清中断 */
drm_crtc_handle_vblank(&rk->base);
return IRQ_HANDLED;
}
return IRQ_NONE;
}
static const struct drm_crtc_funcs rk_crtc_funcs = {
.reset = drm_atomic_helper_crtc_reset,
.destroy = drm_crtc_cleanup,
.set_config = drm_atomic_helper_set_config,
.page_flip = rk_crtc_page_flip,
.atomic_duplicate_state = drm_atomic_helper_crtc_duplicate_state,
.atomic_destroy_state = drm_atomic_helper_crtc_destroy_state,
.enable_vblank = rk_crtc_enable_vblank,
.disable_vblank = rk_crtc_disable_vblank,
};
static const struct drm_crtc_helper_funcs rk_crtc_helper_funcs = {
.mode_set = rk_crtc_mode_set,
};
static int rk_crtc_bind(struct device *dev, struct drm_device *drm)
{
struct rk_crtc *rk;
struct resource *res;
struct platform_device *pdev = to_platform_device(dev);
int ret;
rk = devm_kzalloc(dev, sizeof(*rk), GFP_KERNEL);
if (!rk)
return -ENOMEM;
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
rk->regs = devm_ioremap_resource(dev, res);
if (IS_ERR(rk->regs))
return PTR_ERR(rk->regs);
rk->aclk = devm_clk_get(dev, "aclk");
if (IS_ERR(rk->aclk))
return PTR_ERR(rk->aclk);
rk->dclk = devm_clk_get(dev, "dclk");
if (IS_ERR(rk->dclk))
return PTR_ERR(rk->dclk);
rk->rst = devm_reset_control_get(dev, "vop");
if (IS_ERR(rk->rst))
return PTR_ERR(rk->rst);
rk->irq = platform_get_irq(pdev, 0);
if (rk->irq < 0)
return rk->irq;
/* 硬件初始化 */
reset_control_assert(rk->rst);
udelay(10);
reset_control_deassert(rk->rst);
clk_prepare_enable(rk->aclk);
clk_prepare_enable(rk->dclk);
ret = devm_request_irq(dev, rk->irq, rk_crtc_irq_handler,
0, "rk-crtc", drm);
if (ret)
goto err_disable_clk;
drm_crtc_init_with_planes(drm, &rk->base, NULL, NULL,
&rk_crtc_funcs, NULL);
drm_crtc_helper_add(&rk->base, &rk_crtc_helper_funcs);
drm_crtc_vblank_reset(&rk->base);
platform_set_drvdata(pdev, rk);
return 0;
err_disable_clk:
clk_disable_unprepare(rk->dclk);
clk_disable_unprepare(rk->aclk);
return ret;
}
static void rk_crtc_unbind(struct device *dev, struct drm_device *drm)
{
struct rk_crtc *rk = platform_get_drvdata(to_platform_device(dev));
clk_disable_unprepare(rk->dclk);
clk_disable_unprepare(rk->aclk);
}
static const struct component_ops rk_crtc_ops = {
.bind = rk_crtc_bind,
.unbind = rk_crtc_unbind,
};
static int rk_crtc_probe(struct platform_device *pdev)
{
return component_add(&pdev->dev, &rk_crtc_ops);
}
static int rk_crtc_remove(struct platform_device *pdev)
{
component_del(&pdev->dev, &rk_crtc_ops);
return 0;
}
static const struct of_device_id rk_crtc_dt_ids[] = {
{ .compatible = "rockchip,rk3568-vop", },
{ /* sentinel */ }
};
MODULE_DEVICE_TABLE(of, rk_crtc_dt_ids);
static struct platform_driver rk_crtc_driver = {
.probe = rk_crtc_probe,
.remove = rk_crtc_remove,
.driver = {
.name = "rk3568-crtc",
.of_match_table = rk_crtc_dt_ids,
},
};
module_platform_driver(rk_crtc_driver);
MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("Rockchip Minimal CRTC Driver");
编译与集成
- 将上述代码保存为
drivers/gpu/drm/rockchip/rk_crtc_min.c,并在rockchip_drm_drv.c的component_list中追加该驱动(或通过module_platform_driver独立加载,需确保rockchipdrm主驱动已注册)。 - 设备树中确保VOP节点匹配
rockchip,rk3568-vop,且已提供aclk、dclk时钟与vop复位信号。 - 编译内核或模块:
make ARCH=arm64 CROSS_COMPILE=aarch64-linux-gnu- drivers/gpu/drm/rockchip/
验证方法
- 启动内核后确认驱动加载:
dmesg | grep rk-crtc,应无错误。 - 使用
modetest -M rockchip查看CRTC是否注册成功,应能看到一个CRTC及关联的plane。 - 执行
modetest -M rockchip -s 0:1920x1080触发模式设置,观察屏幕是否有输出(需连接HDMI或DSI面板)。 - 通过
modetest -M rockchip -v验证VBlank中断是否产生,计数器应持续递增。
通过条件:modetest能列出CRTC;执行模式设置命令无错误返回;VBlank计数递增;若连接显示器,画面正常显示无撕裂。