2. Platform驱动基础:platform_driver结构体、platform_device注册、设备树匹配机制
在Linux音频驱动开发中,Platform驱动是连接硬件与内核驱动的核心桥梁。本章将深入剖析Platform驱动的三大基石:platform_driver结构体、platform_device注册以及设备树匹配机制。理解这些基础是编写高质量音频驱动的第一步。
核心概念:Platform驱动并非针对PCI、USB等传统总线设备,而是用于那些直接挂在CPU总线上的“非枚举型”设备,例如大多数嵌入式SoC中的I2S、DMA控制器、音频编解码器接口。设备树(Device Tree)则是描述这些硬件资源的标准化方式。
2.1 platform_driver结构体深度解析
platform_driver是驱动开发者需要填充的核心数据结构,它定义了驱动的生命周期函数和匹配规则。其定义位于 include/linux/platform_device.h。
struct platform_driver {
int (*probe)(struct platform_device *);
int (*remove)(struct platform_device *);
void (*shutdown)(struct platform_device *);
int (*suspend)(struct platform_device *, pm_message_t);
int (*resume)(struct platform_device *);
struct device_driver driver;
const struct platform_device_id *id_table;
bool prevent_deferred_probe;
};
| 成员 | 作用 | 音频驱动中的典型用法 |
|---|---|---|
probe |
设备与驱动匹配成功时调用,完成硬件初始化、DMA缓冲区分配、注册ALSA声卡 | 初始化I2S控制器、申请DMA通道、创建snd_soc_card |
remove |
设备移除或驱动卸载时调用,释放资源 | 释放DMA缓冲区、注销声卡、关闭时钟 |
shutdown |
系统关机或重启时调用 | 静音音频输出、保存硬件状态 |
driver |
内嵌的通用设备驱动结构体,包含名称、所有者、设备树匹配表(of_match_table) | 设置.name = "my-audio-driver",绑定.of_match_table |
id_table |
非设备树平台下的传统ID匹配表 | 用于旧版板级代码,现代音频驱动已较少使用 |
关键点:
probe函数是音频驱动的“入口主战场”。几乎所有音频硬件的初始化、DMA请求、中断注册、ALSA组件绑定都在此完成。一个健壮的probe函数需要处理资源获取失败、DMA通道不可用等异常情况。
2.2 platform_device注册:从板级代码到设备树
Platform设备有两种注册方式:传统板级代码和设备树。现代内核(3.x+)已全面转向设备树,但理解传统方式有助于阅读旧代码。
2.2.1 传统板级代码注册(Legacy方式)
在 arch/arm/mach-xxx/ 下的板级文件中,通过 platform_device_register() 静态注册设备。
static struct resource audio_resources[] = {
{
.start = 0x10004000, // I2S寄存器基地址
.end = 0x10004FFF,
.flags = IORESOURCE_MEM,
},
{
.start = IRQ_I2S, // I2S中断号
.end = IRQ_I2S,
.flags = IORESOURCE_IRQ,
},
};
static struct platform_device audio_device = {
.name = "my-audio",
.id = -1,
.num_resources = ARRAY_SIZE(audio_resources),
.resource = audio_resources,
};
// 在板级初始化函数中调用
platform_device_register(&audio_device);
2.2.2 设备树方式注册(现代标准)
设备树(.dts/.dtsi)将硬件描述与内核代码分离。音频设备节点通常包含寄存器地址、中断、DMA通道、时钟等信息。
/* 设备树片段:I2S控制器节点 */
i2s0: i2s@10004000 {
compatible = "vendor,my-audio-i2s";
reg = <0x10004000 0x1000>;
interrupts = <GIC_SPI 32 IRQ_TYPE_LEVEL_HIGH>;
dmas = <&dma0 2>, <&dma0 3>; // TX和RX DMA通道
dma-names = "tx", "rx";
clocks = <&clk_audio>;
clock-names = "audio";
status = "disabled"; // 默认关闭,由板级文件或bootloader启用
};
常见陷阱:设备树中的
compatible 属性必须与驱动中 of_match_table 定义的字符串完全一致(包括大小写和标点)。status = "okay" 是启用节点的标准写法,不要遗漏。
2.3 设备树匹配机制:驱动与设备的“握手”
设备树匹配是内核在启动或模块加载时,将设备节点与对应驱动关联起来的过程。匹配的核心是 of_match_table。
2.3.1 of_match_table 定义
在驱动中,通过 of_match_table 声明支持的设备兼容性字符串。
static const struct of_device_id audio_of_match[] = {
{ .compatible = "vendor,my-audio-i2s", },
{ .compatible = "vendor,my-audio-i2s-v2", },
{ /* sentinel */ } // 必须以空项结尾
};
MODULE_DEVICE_TABLE(of, audio_of_match);
static struct platform_driver audio_driver = {
.probe = audio_probe,
.remove = audio_remove,
.driver = {
.name = "my-audio",
.of_match_table = audio_of_match,
},
};
2.3.2 匹配流程
- 设备节点创建:内核解析DTB文件,为每个
compatible属性不为空的节点创建platform_device。 - 驱动注册:调用
platform_driver_register()时,内核遍历已注册的设备列表。 - 匹配检查:将驱动的
of_match_table中的每个compatible字符串与设备节点的compatible属性进行比对。 - probe触发:一旦匹配成功,内核调用驱动的
probe函数,并传入对应的platform_device指针。
2.4 在probe中获取设备树资源
一旦 probe 被调用,驱动需要从 platform_device 中提取设备树描述的硬件资源。这是音频驱动初始化的关键步骤。
static int audio_probe(struct platform_device *pdev)
{
struct resource *res;
void __iomem *regs;
int irq;
struct device *dev = &pdev->dev;
// 1. 获取寄存器内存区域
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
regs = devm_ioremap_resource(dev, res);
if (IS_ERR(regs))
return PTR_ERR(regs);
// 2. 获取中断号
irq = platform_get_irq(pdev, 0);
if (irq < 0)
return irq;
// 3. 获取DMA通道(通过设备树dmas属性)
struct dma_chan *dma_tx = dma_request_slave_channel(dev, "tx");
struct dma_chan *dma_rx = dma_request_slave_channel(dev, "rx");
if (!dma_tx || !dma_rx) {
dev_err(dev, "Failed to request DMA channels\n");
return -ENODEV;
}
// 4. 获取时钟
struct clk *audio_clk = devm_clk_get(dev, "audio");
if (IS_ERR(audio_clk))
return PTR_ERR(audio_clk);
// ... 后续初始化ALSA声卡、DMA缓冲区等
return 0;
}
最佳实践:优先使用
devm_ 系列API(如 devm_ioremap_resource、devm_clk_get),这些资源会在驱动卸载或 probe 失败时自动释放,避免内存泄漏。DMA通道的获取建议在 probe 中完成,而不是在音频流开启时,以减少运行时延迟。
2.5 本章小结
| 知识点 | 核心要点 | 音频开发中的注意事项 |
|---|---|---|
| platform_driver | probe/remove 生命周期、driver 内嵌结构体 | probe 中完成所有硬件初始化,避免延迟初始化 |
| platform_device | 设备树节点自动生成,传统板级代码手动注册 | 设备树节点必须包含 reg、interrupts、dmas 等必要属性 |
| 设备树匹配 | of_match_table 与 compatible 属性精确匹配 | compatible 字符串必须完全一致,注意版本号管理 |
| 资源获取 | platform_get_resource、dma_request_slave_channel | 使用 devm_ 系列API自动管理资源生命周期 |