Platform 总线驱动
什么是 Platform 设备
Platform 总线是 Linux 为片上外设(SoC 内部集成的 UART、I2C 控制器、GPIO、定时器等)设计的虚拟总线。这类设备没有自动发现机制(不像 PCI/USB),需要通过设备树或板级代码显式描述。
SoC
├── ARM Cortex-A 核心
├── UART0 ← Platform 设备(地址 0xFE201000)
├── I2C0 ← Platform 设备(地址 0xFE804000)
├── SPI0 ← Platform 设备
└── GPIO ← Platform 设备完整驱动示例
以一个简单的 UART 控制器驱动为例:
c
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/of.h>
#include <linux/io.h>
#include <linux/clk.h>
#include <linux/interrupt.h>
/* 驱动私有数据 */
struct my_uart_priv {
struct device *dev;
void __iomem *base; /* 寄存器基地址 */
struct clk *clk;
int irq;
spinlock_t lock;
};
/* 寄存器偏移 */
#define UART_DR 0x00 /* 数据寄存器 */
#define UART_SR 0x04 /* 状态寄存器 */
#define UART_CR 0x08 /* 控制寄存器 */
static irqreturn_t my_uart_isr(int irq, void *dev_id)
{
struct my_uart_priv *priv = dev_id;
u32 status = readl(priv->base + UART_SR);
if (status & BIT(0)) {
/* 处理接收中断 */
u32 data = readl(priv->base + UART_DR);
dev_dbg(priv->dev, "received: 0x%02x\n", data);
return IRQ_HANDLED;
}
return IRQ_NONE;
}
static int my_uart_probe(struct platform_device *pdev)
{
struct my_uart_priv *priv;
struct resource *res;
int ret;
/* 1. 分配私有数据(devm 自动释放) */
priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL);
if (!priv)
return -ENOMEM;
priv->dev = &pdev->dev;
spin_lock_init(&priv->lock);
/* 2. 获取并映射寄存器地址 */
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
priv->base = devm_ioremap_resource(&pdev->dev, res);
if (IS_ERR(priv->base))
return PTR_ERR(priv->base);
/* 3. 获取时钟 */
priv->clk = devm_clk_get(&pdev->dev, "uart_clk");
if (IS_ERR(priv->clk)) {
dev_err(&pdev->dev, "failed to get clock\n");
return PTR_ERR(priv->clk);
}
ret = clk_prepare_enable(priv->clk);
if (ret)
return ret;
/* 4. 获取并注册中断 */
priv->irq = platform_get_irq(pdev, 0);
if (priv->irq < 0)
return priv->irq;
ret = devm_request_irq(&pdev->dev, priv->irq, my_uart_isr,
IRQF_SHARED, "my-uart", priv);
if (ret) {
dev_err(&pdev->dev, "failed to request IRQ %d\n", priv->irq);
return ret;
}
/* 5. 存储私有数据 */
platform_set_drvdata(pdev, priv);
/* 6. 硬件初始化 */
writel(BIT(0), priv->base + UART_CR); /* 使能 UART */
dev_info(&pdev->dev, "my-uart probed at %p, irq=%d\n",
priv->base, priv->irq);
return 0;
}
static int my_uart_remove(struct platform_device *pdev)
{
struct my_uart_priv *priv = platform_get_drvdata(pdev);
/* devm_* 资源自动释放,只需手动清理非 devm 资源 */
clk_disable_unprepare(priv->clk);
return 0;
}
/* 设备树匹配表 */
static const struct of_device_id my_uart_of_match[] = {
{ .compatible = "myvendor,my-uart-v1" },
{ .compatible = "myvendor,my-uart-v2", .data = (void *)2 },
{ /* sentinel */ }
};
MODULE_DEVICE_TABLE(of, my_uart_of_match);
static struct platform_driver my_uart_driver = {
.probe = my_uart_probe,
.remove = my_uart_remove,
.driver = {
.name = "my-uart",
.of_match_table = my_uart_of_match,
.pm = &my_uart_pm_ops, /* 可选 */
},
};
module_platform_driver(my_uart_driver); /* 展开为 module_init/module_exit */
MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("My UART Platform Driver");对应的设备树节点
c
/* arch/arm64/boot/dts/myvendor/myboard.dts */
/ {
soc {
#address-cells = <1>;
#size-cells = <1>;
uart0: serial@fe201000 {
compatible = "myvendor,my-uart-v1";
reg = <0xfe201000 0x1000>; /* 寄存器基地址 + 大小 */
interrupts = <GIC_SPI 32 IRQ_TYPE_LEVEL_HIGH>;
clocks = <&ccu CLK_UART0>;
clock-names = "uart_clk";
status = "okay";
};
};
};获取设备树属性
c
static int my_uart_probe(struct platform_device *pdev)
{
struct device_node *np = pdev->dev.of_node;
u32 fifo_size;
bool hw_flow_ctrl;
/* 读取整数属性 */
if (of_property_read_u32(np, "fifo-size", &fifo_size))
fifo_size = 16; /* 默认值 */
/* 读取布尔属性 */
hw_flow_ctrl = of_property_read_bool(np, "hw-flow-control");
/* 读取字符串属性 */
const char *mode;
of_property_read_string(np, "uart-mode", &mode);
/* 获取匹配的 of_device_id 中的 .data */
const struct of_device_id *match = of_match_device(my_uart_of_match, &pdev->dev);
int version = (int)(uintptr_t)match->data;
/* ... */
}platform_driver 注册宏对比
c
/* 方式一:最简洁(无需手写 init/exit) */
module_platform_driver(my_driver);
/* 方式二:需要额外初始化逻辑时 */
static int __init my_init(void)
{
/* 其他初始化 */
return platform_driver_register(&my_driver);
}
static void __exit my_exit(void)
{
platform_driver_unregister(&my_driver);
}
module_init(my_init);
module_exit(my_exit);多实例支持
同一驱动可以绑定多个设备实例(如 UART0、UART1),每次 probe 都会为新实例分配独立的 priv:
bash
# 查看绑定情况
ls /sys/bus/platform/drivers/my-uart/
# fe201000.serial fe202000.serial
# 查看设备信息
cat /sys/bus/platform/devices/fe201000.serial/uevent常见问题
| 问题 | 原因 | 解决 |
|---|---|---|
probe 未被调用 | compatible 字符串不匹配 | 检查 DTS 与 of_match_table 是否一致 |
devm_ioremap_resource 返回 EBUSY | 地址已被其他驱动占用 | 检查 DTS 地址是否重复 |
platform_get_irq 返回负值 | DTS 中 interrupts 属性缺失或格式错误 | 对照 GIC binding 文档检查 |
probe 返回 -EPROBE_DEFER | 依赖的资源(时钟/GPIO)尚未就绪 | 正常现象,内核会稍后重试 |