2. Platform驱动基础:platform总线、platform_device与platform_driver结构体、匹配机制
在Linux音频驱动开发中,绝大多数音频控制器(I2S、PCM、AC97)都挂载在platform总线上,而非PCI或USB。理解platform驱动模型是编写音频驱动的第一步。本章从总线、设备、驱动三个核心结构体出发,深入剖析匹配机制,为后续中断处理与DMA调优打下基础。
核心概念:Platform驱动模型是Linux设备驱动模型(Device Model)在SoC内部集成外设上的具体实现。它将硬件资源(寄存器地址、中断号、DMA通道)通过platform_device描述,将驱动逻辑通过platform_driver封装,由platform总线完成匹配与绑定。
2.1 platform总线:虚拟总线的职责与运作
platform总线是一种虚拟总线,用于管理那些不依附于PCI、USB、I2C等物理总线的设备。在嵌入式SoC中,大部分内部外设(如UART、SPI、I2S、DMA控制器)都通过platform总线注册。
| 总线属性 | 说明 |
|---|---|
| 总线类型 | 虚拟总线,无物理电气特性 |
| 注册方式 | platform_bus_type 在内核启动时自动注册 |
| 核心数据结构 | struct bus_type platform_bus_type |
| 匹配函数 | platform_match() — 决定device与driver是否配对 |
| 电源管理 | 通过dev_pm_ops支持系统休眠/唤醒 |
platform总线的主要职责包括:
- 维护设备链表与驱动链表:管理所有注册的platform_device和platform_driver。
- 执行匹配:当新设备或新驱动注册时,调用platform_match()遍历对方链表。
- 调用probe():匹配成功后,触发驱动的probe函数完成硬件初始化。
- 提供标准接口:如platform_get_resource()、platform_get_irq()等资源获取API。
2.2 platform_device结构体:硬件资源的描述者
platform_device用于描述一个硬件设备实例,包含设备名称、ID、资源列表(寄存器地址、中断号、DMA通道)以及设备私有数据。音频驱动中,每个I2S控制器或音频DMA通道都对应一个platform_device。
struct platform_device {
const char *name; // 设备名称,用于匹配
int id; // 设备ID,-1表示单实例
struct device dev; // 内核通用设备结构体
u32 num_resources; // 资源数量
struct resource *resource; // 资源数组
const struct platform_device_id *id_entry; // 匹配到的ID表项
/* ... 其他字段 */
};
关键字段说明:
| 字段 | 类型 | 作用 |
|---|---|---|
| name | const char * | 驱动匹配的核心标识,如"fsl-sai"、"stm32-i2s" |
| id | int | 同类型设备多实例时区分,如id=0表示第一个I2S控制器 |
| resource | struct resource * | 描述硬件资源(IORESOURCE_MEM、IORESOURCE_IRQ等) |
| dev.platform_data | void * | 板级私有数据,如音频时钟配置、GPIO引脚复用 |
音频驱动实践:在设备树(Device Tree)流行的今天,platform_device通常由内核根据设备树节点自动创建。驱动开发者只需在设备树中描述音频控制器的寄存器基址、中断号和DMA通道,内核的of_platform_populate()会完成platform_device的注册。
2.3 platform_driver结构体:驱动逻辑的封装
platform_driver封装了驱动的核心操作函数,包括probe(探测)、remove(移除)、shutdown(关机)以及电源管理回调。音频驱动的主要初始化逻辑(时钟使能、DMA配置、ALSA组件注册)都在probe函数中完成。
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; // 支持的设备ID表
};
probe函数是音频驱动的入口:
- 获取硬件资源:platform_get_resource()获取寄存器地址,platform_get_irq()获取中断号。
- ioremap映射寄存器地址到虚拟地址空间。
- 初始化硬件:复位音频控制器、配置采样率、格式。
- 注册ALSA声卡:调用snd_card_new()、snd_pcm_new()等。
- 申请中断:request_irq()或devm_request_irq()。
static int audio_i2s_probe(struct platform_device *pdev)
{
struct resource *res;
struct device *dev = &pdev->dev;
// 1. 获取寄存器资源
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
if (!res)
return -ENXIO;
// 2. 获取中断号
int irq = platform_get_irq(pdev, 0);
if (irq < 0)
return irq;
// 3. 映射寄存器
void __iomem *regs = devm_ioremap_resource(dev, res);
if (IS_ERR(regs))
return PTR_ERR(regs);
// 4. 申请中断(音频数据中断)
devm_request_irq(dev, irq, audio_i2s_isr, 0,
dev_name(dev), priv);
// 5. 注册ALSA声卡
return snd_soc_register_component(dev, &soc_component_drv,
&pcm_dai, 1);
}
2.4 匹配机制:platform_match()的四种匹配方式
platform_match()是platform总线的核心匹配函数,按优先级依次尝试以下四种匹配方式:
- 设备树匹配(OF匹配):比较device_node中的compatible属性与driver的of_match_table。
- ACPI匹配:在支持ACPI的平台上,比较ACPI ID表。
- ID表匹配:比较platform_device.name与platform_driver.id_table中的name字段。
- 名称直接匹配:比较platform_device.name与platform_driver.driver.name。
匹配优先级陷阱:如果设备树中compatible属性与驱动of_match_table匹配成功,则不会继续尝试ID表匹配和名称匹配。在调试音频驱动时,如果发现probe函数未被调用,应首先检查设备树compatible字符串是否与驱动中定义的一致。使用
ls /sys/bus/platform/devices/和cat /sys/bus/platform/drivers/<driver>/bind可辅助排查。
2.5 资源获取API:音频驱动的硬件访问基础
音频驱动通过platform总线提供的API获取硬件资源,这些API封装了resource结构体的解析逻辑:
| API函数 | 功能 | 音频驱动典型用法 |
|---|---|---|
| platform_get_resource() | 获取指定类型的资源(MEM、IRQ、DMA) | 获取I2S控制器寄存器基址 |
| platform_get_irq() | 获取中断号 | 获取音频数据中断或错误中断号 |
| platform_get_resource_byname() | 按名称获取资源 | 获取"tx"和"rx" DMA通道资源 |
| devm_ioremap_resource() | 映射寄存器并自动管理内存 | 将物理寄存器地址映射到虚拟地址 |
| platform_get_drvdata() | 获取驱动私有数据 | 获取音频驱动私有结构体指针 |
// 音频驱动中典型的资源获取模式
static int audio_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
struct audio_priv *priv;
struct resource *res;
int irq, ret;
// 分配私有数据结构
priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
if (!priv)
return -ENOMEM;
// 获取寄存器资源(索引0)
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
priv->regs = devm_ioremap_resource(dev, res);
if (IS_ERR(priv->regs))
return PTR_ERR(priv->regs);
// 获取中断资源(索引0)
irq = platform_get_irq(pdev, 0);
if (irq < 0)
return irq;
// 保存私有数据
platform_set_drvdata(pdev, priv);
// 申请中断
ret = devm_request_irq(dev, irq, audio_isr, IRQF_TRIGGER_RISING,
dev_name(dev), priv);
if (ret)
return ret;
// 继续初始化...
return 0;
}
调试技巧:在/sys文件系统中可以查看platform设备的资源信息:
输出示例:
这可以帮助验证设备树解析是否正确。
cat /sys/devices/platform/<设备名>/resources输出示例:
0x02028000-0x02028fff : /soc@0/sai@02028000IRQ: 49这可以帮助验证设备树解析是否正确。
2.6 本章小结
platform驱动模型是Linux音频驱动的基石。理解以下要点将帮助你在后续章节中快速定位问题:
- platform_device描述硬件资源,通常由设备树自动生成。
- platform_driver封装驱动逻辑,probe函数是音频初始化的入口。
- platform_match()按OF匹配→ACPI匹配→ID表匹配→名称匹配的顺序执行。
- 资源获取API(platform_get_resource、platform_get_irq)是音频驱动访问硬件的标准方式。
- 在调试时,优先检查设备树compatible属性与驱动of_match_table的一致性。