Platform 驱动模型
一、Platform 总线
Linux 把 SoC 内置外设抽象为"platform device":
- 在设备树中描述硬件
- 驱动通过 compatible 匹配
- 加载时调用 probe,卸载时调用 remove
arch/arm/boot/dts/xxx.dts
↓ 编译
.dtb
↓ 内核启动时解析
platform_device(每个节点)
↓ 匹配
platform_driver(probe)
二、关键结构
1. platform_device
struct platform_device {
const char *name;
int id;
bool id_auto;
struct device dev;
u64 platform_dma_mask;
struct resource resource[PLATFORM_MAX_RESOURCES];
unsigned int num_resources;
const struct platform_device_id *id_entry;
struct device_node *of_node;
};
2. resource(资源)
struct resource {
resource_size_t start;
resource_size_t end;
const char *name;
unsigned long flags; // IORESOURCE_MEM / IORESOURCE_IRQ / IORESOURCE_DMA ...
};
3. platform_driver
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 state);
int (*resume)(struct platform_device *);
struct device_driver driver;
const struct platform_device_id *id_table;
};
三、完整示例
1. 设备树
myuart: serial@40010000 {
compatible = "myvendor,myuart";
reg = <0x40010000 0x1000>;
interrupts = <GIC_SPI 26 IRQ_TYPE_LEVEL_HIGH>;
clocks = <&clks IMX6UL_CLK_UART1_IPG>;
pinctrl-0 = <&pinctrl_uart1>;
pinctrl-names = "default";
status = "okay";
};
2. 驱动代码
#include <linux/platform_device.h>
#include <linux/of.h>
#include <linux/module.h>
#include <linux/io.h>
#include <linux/interrupt.h>
struct my_priv {
void __iomem *base;
int irq;
spinlock_t lock;
};
static irqreturn_t my_irq(int irq, void *dev_id)
{
struct my_priv *priv = dev_id;
u32 status = readl(priv->base + 0x10);
writel(status, priv->base + 0x10); // 清中断
return IRQ_HANDLED;
}
static int my_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
struct my_priv *priv;
int ret;
priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
if (!priv) return -ENOMEM;
spin_lock_init(&priv->lock);
// 1. 资源获取
priv->base = devm_platform_ioremap_resource(pdev, 0);
if (IS_ERR(priv->base)) return PTR_ERR(priv->base);
priv->irq = platform_get_irq(pdev, 0);
if (priv->irq < 0) return priv->irq;
// 2. 时钟、复位
// clk = devm_clk_get(dev, NULL);
// clk_prepare_enable(clk);
// 3. 中断
ret = devm_request_irq(dev, priv->irq, my_irq, 0, "myuart", priv);
if (ret) return ret;
// 4. 设备初始化
writel(0x01, priv->base + 0x00); // 复位
writel(0x08, priv->base + 0x04); // 配置
writel(0x01, priv->base + 0x08); // 使能
platform_set_drvdata(pdev, priv);
dev_info(dev, "probed\n");
return 0;
}
static int my_remove(struct platform_device *pdev)
{
dev_info(&pdev->dev, "removed\n");
return 0;
}
static const struct of_device_id my_of_match[] = {
{ .compatible = "myvendor,myuart" },
{ /* sentinel */ }
};
MODULE_DEVICE_TABLE(of, my_of_match);
static struct platform_driver my_driver = {
.probe = my_probe,
.remove = my_remove,
.driver = {
.name = "myuart",
.of_match_table = my_of_match,
},
};
module_platform_driver(my_driver);
MODULE_LICENSE("GPL");
3. Makefile
obj-m += myuart.o
四、常用 API
1. 获取资源
struct resource *r = platform_get_resource(pdev, IORESOURCE_MEM, 0);
r = platform_get_resource_byname(pdev, IORESOURCE_IRQ, "tx");
int irq = platform_get_irq(pdev, 0);
int irq = platform_get_irq_byname(pdev, "rx");
2. 设备属性
struct device_node *np = pdev->dev.of_node;
u32 val;
of_property_read_u32(np, "myprop", &val);
of_property_read_u32_array(np, "reg", arr, 2);
3. devm 自动管理资源
devm_kzalloc / devm_kmalloc
devm_request_irq
devm_ioremap / devm_platform_ioremap_resource
devm_clk_get / devm_reset_control_get
devm_regulator_get
devm_gpio_request
devm_input_allocate_device
离开时自动 release,无需在 remove 中手动清理。
五、id_table 与 ACPI
static const struct platform_device_id my_id_table[] = {
{ "myuart-v1", (void *)0x100 },
{ "myuart-v2", (void *)0x200 },
{ /* sentinel */ }
};
MODULE_DEVICE_TABLE(platform, my_id_table);
ACPI 设备(x86):
static const struct acpi_device_id my_acpi_match[] = {
{ "MYVND0001", 0 },
{ /* sentinel */ }
};
MODULE_DEVICE_TABLE(acpi, my_acpi_match);
六、子设备 / MFD
- MFD(Multi-Function Device):一个物理芯片有多个子设备
- 例:PMIC(电源管理芯片)有多个子功能
mfd_add_devices()创建子 platform_device
七、调试
ls /sys/bus/platform/devices/
ls /sys/bus/platform/drivers/
cat /sys/bus/platform/devices/myuart/uevent
// 驱动打印
dev_info(&pdev->dev, "info");
dev_err(&pdev->dev, "error: %d\n", ret);