Skip to content

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)尚未就绪正常现象,内核会稍后重试

褚成志的笔记