4. 驱动框架搭建:platform driver模型、设备树匹配、probe函数实现、资源申请
在嵌入式Linux系统中,EPD(电子纸)显示驱动通常采用 platform driver 模型进行框架搭建。该模型将硬件设备信息与驱动逻辑解耦,通过设备树(Device Tree)进行匹配,在 probe 函数中完成资源申请与初始化。本节将详细拆解这一过程,并给出可落地的代码结构与调试要点。
设计原则: platform driver 模型是 Linux 设备驱动模型的核心机制之一,适用于非热插拔、片上集成的外设(如 SPI/I2C 接口的 EPD 控制器)。通过设备树描述硬件拓扑,驱动只需关注匹配后的初始化逻辑,极大提高了代码的可移植性。
4.1 platform driver 模型与设备树匹配
EPD 驱动首先需要定义一个 platform_driver 结构体,并注册到内核。设备树中的节点通过 compatible 属性与驱动中的 of_match_table 进行匹配。匹配成功后,内核自动调用驱动的 probe 函数。
关键数据结构:
of_device_id 数组用于声明驱动支持的设备树 compatible 字符串。内核通过该数组与设备树节点进行精确匹配。
// 设备树匹配表
static const struct of_device_id epd_of_match[] = {
{ .compatible = "vendor,epd-spi", },
{ /* sentinel */ }
};
MODULE_DEVICE_TABLE(of, epd_of_match);
// platform driver 定义
static struct platform_driver epd_driver = {
.probe = epd_probe,
.remove = epd_remove,
.driver = {
.name = "epd_spi",
.of_match_table = epd_of_match,
},
};
module_platform_driver(epd_driver);
4.2 probe 函数实现:资源申请与初始化
probe 函数是驱动初始化的核心入口。在 EPD 驱动中,它需要完成以下关键任务:
- 获取设备树资源: 解析 GPIO、SPI 控制器、电源控制等硬件资源。
- 申请内存与数据结构: 分配私有数据结构体,存储设备状态。
- 初始化硬件: 设置 SPI 模式、时钟、GPIO 方向,执行 EPD 控制器复位序列。
- 注册 FrameBuffer: 调用
register_framebuffer()将 EPD 显示设备注册到系统。
4.3 设备树资源解析与申请
设备树中描述了 EPD 模块的硬件连接信息。驱动通过 device_node 接口获取这些资源。以下是一个典型的 EPD 设备树节点示例:
// 设备树节点 (arch/arm/boot/dts/xxx.dts)
&spi1 {
status = "okay";
epd: epd@0 {
compatible = "vendor,epd-spi";
reg = <0>;
spi-max-frequency = <2000000>;
dc-gpios = <&gpio1 12 GPIO_ACTIVE_HIGH>;
rst-gpios = <&gpio1 13 GPIO_ACTIVE_HIGH>;
busy-gpios = <&gpio1 14 GPIO_ACTIVE_HIGH>;
vcc-supply = <&vcc_3v3>;
panel-width = <800>;
panel-height = <600>;
};
};
避坑提示: 设备树中的 GPIO 标志
GPIO_ACTIVE_HIGH/LOW 必须与硬件原理图一致。错误的极性会导致 EPD 无法正常复位或响应。建议在 probe 初期通过 gpiod_get_value() 打印电平状态进行验证。
在 probe 函数中,使用以下 API 解析上述资源:
| 资源类型 | 解析 API | 返回值 | 说明 |
|---|---|---|---|
| SPI 设备 | devm_spi_register_device() |
struct spi_device * |
从设备树获取 SPI 从设备 |
| GPIO 描述符 | devm_gpiod_get() |
struct gpio_desc * |
通过名称获取 GPIO,支持 _indexed 变体 |
| 电源 regulator | devm_regulator_get() |
struct regulator * |
获取供电控制句柄 |
| 整型属性 | of_property_read_u32() |
int |
读取 panel-width、panel-height 等 |
4.4 私有数据结构体设计
为每个 EPD 设备维护一个私有数据结构,用于存储硬件上下文、FrameBuffer 信息以及工作队列状态。这是驱动模块化的关键。
struct epd_priv {
struct device *dev;
struct spi_device *spi;
struct gpio_desc *dc_gpio;
struct gpio_desc *rst_gpio;
struct gpio_desc *busy_gpio;
struct regulator *vcc;
u32 panel_width;
u32 panel_height;
u32 bytes_per_pixel; // 通常为 1 (4-bit grayscale) 或 3 (RGB)
struct fb_info *fb_info;
void *fb_buffer; // FrameBuffer 内存虚拟地址
dma_addr_t fb_dma; // DMA 物理地址
struct completion refresh_done;
struct mutex lock;
};
内存申请策略: 使用
devm_kzalloc() 申请私有数据结构,利用设备资源管理(devres)自动释放,避免内存泄漏。FrameBuffer 缓冲区建议使用 dma_alloc_coherent() 分配,保证 SPI DMA 传输时缓存一致性。
4.5 probe 函数完整骨架
以下代码展示了 EPD 驱动 probe 函数的典型实现骨架,包含资源申请、初始化和错误处理路径:
static int epd_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
struct epd_priv *priv;
struct fb_info *info;
int ret;
// 1. 申请私有数据结构
priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
if (!priv)
return -ENOMEM;
priv->dev = dev;
mutex_init(&priv->lock);
init_completion(&priv->refresh_done);
platform_set_drvdata(pdev, priv);
// 2. 获取 SPI 设备 (假设通过设备树子节点)
priv->spi = devm_spi_register_device(dev, ...);
if (IS_ERR(priv->spi)) {
dev_err(dev, "failed to get SPI device\n");
return PTR_ERR(priv->spi);
}
// 3. 获取 GPIO 资源
priv->dc_gpio = devm_gpiod_get(dev, "dc", GPIOD_OUT_LOW);
if (IS_ERR(priv->dc_gpio))
return dev_err_probe(dev, PTR_ERR(priv->dc_gpio), "dc gpio failed\n");
priv->rst_gpio = devm_gpiod_get(dev, "rst", GPIOD_OUT_HIGH);
if (IS_ERR(priv->rst_gpio))
return dev_err_probe(dev, PTR_ERR(priv->rst_gpio), "rst gpio failed\n");
priv->busy_gpio = devm_gpiod_get(dev, "busy", GPIOD_IN);
if (IS_ERR(priv->busy_gpio))
return dev_err_probe(dev, PTR_ERR(priv->busy_gpio), "busy gpio failed\n");
// 4. 获取电源 regulator
priv->vcc = devm_regulator_get(dev, "vcc");
if (IS_ERR(priv->vcc))
return dev_err_probe(dev, PTR_ERR(priv->vcc), "vcc regulator failed\n");
ret = regulator_enable(priv->vcc);
if (ret)
return ret;
// 5. 读取面板参数
ret = of_property_read_u32(dev->of_node, "panel-width", &priv->panel_width);
ret |= of_property_read_u32(dev->of_node, "panel-height", &priv->panel_height);
if (ret) {
dev_err(dev, "missing panel dimensions in DT\n");
return -EINVAL;
}
priv->bytes_per_pixel = 1; // 4-bit grayscale
// 6. 执行 EPD 硬件复位序列
epd_hardware_reset(priv);
// 7. 分配 FrameBuffer 内存
info = framebuffer_alloc(0, dev);
if (!info)
return -ENOMEM;
priv->fb_info = info;
// 8. 填充 fb_info (fix/var/ops) —— 后续章节详细展开
epd_fb_fill_info(priv, info);
// 9. 注册 FrameBuffer
ret = register_framebuffer(info);
if (ret) {
dev_err(dev, "failed to register framebuffer\n");
framebuffer_release(info);
return ret;
}
dev_info(dev, "EPD probe success: %dx%d\n",
priv->panel_width, priv->panel_height);
return 0;
}
调试建议: 在 probe 函数的关键步骤(如 GPIO 获取、SPI 设置、复位完成)添加
dev_dbg() 或 dev_info() 打印。通过 dmesg 观察日志,可以快速定位资源申请失败的位置。使用 dev_err_probe() 可以优雅处理 EPROBE_DEFER 场景。
4.6 资源申请常见错误与排查
| 错误现象 | 可能原因 | 排查方法 |
|---|---|---|
| probe 返回 -ENODEV | 设备树 compatible 不匹配 | 检查 of_match_table 字符串与 DTS 是否一致 |
| GPIO 获取失败 -EBUSY | GPIO 被其他驱动占用或 pinmux 未配置 | 检查 pinctrl 设置,确认 GPIO 号正确 |
| regulator 获取失败 -EPROBE_DEFER | 电源 regulator 驱动未就绪 | 内核会自动重试 probe,无需手动处理 |
| SPI 通信无响应 | SPI 模式/频率不匹配或硬件连接错误 | 使用逻辑分析仪抓取 SPI 信号,核对时序 |
| FrameBuffer 注册失败 | fb_info 填充不完整或内存不足 | 检查 fix.smem_len、var.bits_per_pixel 等字段 |
通过以上步骤,EPD 驱动的 platform driver 框架搭建完成。probe 函数成功执行后,系统将拥有一个可用的 FrameBuffer 设备节点(如 /dev/fb0),为后续的显示链路调试奠定基础。