1. RK3568平台开发:深入解析platform驱动注册机制
在嵌入式Linux开发中,platform驱动是连接硬件设备与内核的关键桥梁。作为Rockchip主力芯片之一,RK3568的驱动开发离不开对platform机制的深入理解。今天我将结合多年开发经验,详细剖析platform驱动的注册过程,特别是针对RK3568平台的实现细节和实战技巧。
2. platform驱动基础概念
2.1 什么是platform驱动
platform驱动是Linux内核中用于管理片上系统(SoC)外围设备的标准框架。与传统的字符设备驱动不同,platform驱动具有以下特点:
- 不依赖于物理总线(如PCI、USB)
- 专为SoC内部集成的外设设计(如GPIO、I2C控制器)
- 通过名称或设备树进行匹配
- 提供统一的资源管理接口
在RK3568开发中,大多数外设(如显示控制器、视频编解码器等)都采用platform驱动架构。
2.2 platform驱动核心结构体
platform驱动的核心是platform_driver结构体,定义在include/linux/platform_device.h中:
c复制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;
bool prevent_deferred_probe;
};
关键成员解析:
probe:设备匹配成功后调用的初始化函数remove:设备移除时调用的清理函数driver:继承自基础设备驱动结构体id_table:用于非设备树匹配的ID表
3. 驱动注册过程详解
3.1 注册函数调用链
RK3568平台上典型的驱动注册流程如下:
c复制platform_driver_register()
└── __platform_driver_register()
└── driver_register()
└── bus_add_driver()
这个调用链展示了从平台驱动注册到底层驱动注册的完整过程。
3.2 platform_driver_register解析
platform_driver_register是驱动开发者最常接触的接口,其实现值得深入研究:
c复制#define platform_driver_register(drv) \
__platform_driver_register(drv, THIS_MODULE)
这个宏定义揭示了两个关键点:
- 将驱动注册与当前模块绑定
- 实际工作由
__platform_driver_register完成
3.3 __platform_driver_register实现
在drivers/base/platform.c中,我们可以找到核心实现:
c复制int __platform_driver_register(struct platform_driver *drv,
struct module *owner)
{
drv->driver.owner = owner;
drv->driver.bus = &platform_bus_type;
if (drv->probe)
drv->driver.probe = platform_drv_probe;
if (drv->remove)
drv->driver.remove = platform_drv_remove;
return driver_register(&drv->driver);
}
关键操作解析:
- 设置驱动所有者(防止模块卸载时驱动仍在运行)
- 绑定到platform总线类型
- 封装probe/remove回调
- 调用通用驱动注册接口
特别注意:RK3568的某些外设驱动需要额外设置
prevent_deferred_probe标志,以避免由电源管理导致的延迟探测问题。
4. probe函数实现要点
4.1 probe函数的作用时机
probe函数在以下情况下被调用:
- 驱动与设备成功匹配
- 驱动模块被动态加载
- 设备热插拔(RK3568支持有限)
4.2 典型probe函数结构
c复制static int my_driver_probe(struct platform_device *pdev)
{
// 1. 获取设备资源
struct resource *res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
// 2. 分配驱动私有数据结构
struct my_private *priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL);
// 3. 初始化硬件
priv->regs = devm_ioremap_resource(&pdev->dev, res);
// 4. 注册设备接口
misc_register(&my_misc_device);
// 5. 保存私有数据
platform_set_drvdata(pdev, priv);
return 0;
}
4.3 RK3568特有注意事项
- 时钟管理:
c复制priv->clk = devm_clk_get(&pdev->dev, "core");
if (IS_ERR(priv->clk)) {
dev_err(&pdev->dev, "failed to get core clock\n");
return PTR_ERR(priv->clk);
}
ret = clk_prepare_enable(priv->clk);
if (ret) {
dev_err(&pdev->dev, "failed to enable clock\n");
return ret;
}
- 电源域控制:
c复制priv->power = devm_regulator_get(&pdev->dev, "vdd");
if (IS_ERR(priv->power)) {
ret = PTR_ERR(priv->power);
if (ret != -EPROBE_DEFER)
dev_err(&pdev->dev, "failed to get regulator\n");
return ret;
}
ret = regulator_enable(priv->power);
if (ret) {
dev_err(&pdev->dev, "failed to enable regulator\n");
return ret;
}
5. 设备匹配机制
5.1 设备树匹配(推荐方式)
RK3568强烈建议使用设备树进行匹配:
c复制static const struct of_device_id my_driver_of_match[] = {
{ .compatible = "rockchip,rk3568-mydevice" },
{},
};
MODULE_DEVICE_TABLE(of, my_driver_of_match);
static struct platform_driver my_driver = {
.driver = {
.name = "my-device",
.of_match_table = my_driver_of_match,
},
.probe = my_driver_probe,
.remove = my_driver_remove,
};
5.2 ID表匹配(传统方式)
c复制static const struct platform_device_id my_driver_id_table[] = {
{ "rk3568-mydevice", 0 },
{},
};
static struct platform_driver my_driver = {
.driver = {
.name = "my-device",
},
.id_table = my_driver_id_table,
.probe = my_driver_probe,
.remove = my_driver_remove,
};
6. 资源管理最佳实践
6.1 资源获取方式
RK3568驱动中获取资源的推荐方法:
- 内存映射资源:
c复制res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
regs = devm_ioremap_resource(&pdev->dev, res);
- 中断资源:
c复制irq = platform_get_irq(pdev, 0);
ret = devm_request_irq(&pdev->dev, irq, my_interrupt_handler,
IRQF_TRIGGER_HIGH, "my-device", priv);
6.2 设备资源管理
推荐使用devm_系列函数自动管理资源:
c复制// 自动释放的内存分配
priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL);
// 自动取消的IRQ注册
devm_request_irq(&pdev->dev, irq, handler, flags, name, dev);
// 自动解除的IO映射
devm_ioremap_resource(&pdev->dev, res);
7. 常见问题排查
7.1 驱动未触发probe
排查步骤:
- 检查
dmesg | grep probe输出 - 确认设备树compatible字符串匹配
- 验证驱动是否成功注册(检查
/sys/bus/platform/drivers) - 检查依赖资源是否就绪(时钟、电源等)
7.2 资源获取失败
典型错误处理模式:
c复制res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
if (!res) {
dev_err(&pdev->dev, "no memory resource specified\n");
return -EINVAL;
}
regs = devm_ioremap_resource(&pdev->dev, res);
if (IS_ERR(regs)) {
dev_err(&pdev->dev, "failed to map registers\n");
return PTR_ERR(regs);
}
7.3 RK3568特有错误
- 时钟配置错误:
shell复制[ 12.345678] clk: failed to reparent clk_sdmmc0 to clk_gpll: -22
解决方法:检查设备树中时钟配置是否冲突
- 电源域未就绪:
shell复制[ 12.345678] regulator: vdd_logic: failed to get the current voltage(-22)
解决方法:确保相关电源域在驱动probe前已初始化
8. 性能优化技巧
8.1 延迟探测处理
对于依赖其他驱动的设备,实现延迟探测:
c复制static int my_driver_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
struct my_private *priv;
priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
if (!priv)
return -ENOMEM;
priv->clk = devm_clk_get(dev, "core");
if (IS_ERR(priv->clk))
return PTR_ERR(priv->clk);
// ...其他初始化...
return 0;
}
8.2 模块初始化优化
对于多组件驱动,使用module_platform_driver宏:
c复制static int __init my_driver_init(void)
{
return platform_driver_register(&my_driver);
}
module_init(my_driver_init);
static void __exit my_driver_exit(void)
{
platform_driver_unregister(&my_driver);
}
module_exit(my_driver_exit);
可以简化为:
c复制module_platform_driver(my_driver);
9. 调试技巧
9.1 调试信息输出
合理使用dev_dbg和dev_vdbg:
c复制// 在probe函数开始处
dev_dbg(&pdev->dev, "starting probe\n");
// 在关键操作前后
dev_vdbg(&pdev->dev, "register value before: 0x%08x\n",
readl(priv->regs + REG_CTRL));
writel(new_val, priv->regs + REG_CTRL);
dev_vdbg(&pdev->dev, "register value after: 0x%08x\n",
readl(priv->regs + REG_CTRL));
9.2 sysfs调试接口
为驱动添加调试节点:
c复制static ssize_t debug_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct my_private *priv = dev_get_drvdata(dev);
return sprintf(buf, "status: 0x%08x\n",
readl(priv->regs + REG_STATUS));
}
static DEVICE_ATTR_RO(debug);
static int my_driver_probe(struct platform_device *pdev)
{
// ...其他初始化...
device_create_file(&pdev->dev, &dev_attr_debug);
// ...
}
10. 实战经验分享
在RK3568平台上开发platform驱动时,有几个特别需要注意的点:
-
时钟管理:RK3568的时钟树比较复杂,建议在驱动probe前确认时钟源是否就绪。我曾经遇到过一个案例,驱动probe失败是因为依赖的PLL时钟尚未初始化完成。
-
电源域依赖:某些外设需要特定的电源域先上电才能正常工作。在设备树中正确配置
power-domains属性至关重要。 -
DMA缓冲区对齐:RK3568的某些IP(如VPU)对DMA缓冲区有特殊的对齐要求(通常需要64字节对齐),不满足会导致性能下降或功能异常。
-
中断处理:对于高性能外设(如千兆网卡),建议使用NAPI机制处理中断,避免中断风暴导致系统响应迟缓。
-
设备树覆盖:调试阶段可以灵活使用设备树覆盖技术,避免频繁烧写整个设备树镜像。命令示例:
shell复制fdtoverlay -i original.dtb -o new.dtb overlay.dtbo