3. 驱动初始化流程:platform_driver注册、设备树匹配、硬件资源获取
CSI摄像头驱动的初始化是驱动与内核、硬件建立联系的第一步。在Linux内核中,CSI接口摄像头驱动通常采用 platform_driver 框架,通过设备树(Device Tree)匹配硬件设备,并在探测(probe)函数中完成GPIO、时钟、电源等关键资源的获取与初始化。本节将详细拆解这一流程,确保驱动能够正确识别硬件并进入工作状态。
3.1 platform_driver注册与设备树匹配机制
驱动初始化入口是 module_platform_driver() 宏,它将驱动注册到内核的platform总线。内核通过设备树中的 compatible 属性与驱动中的 of_match_table 进行匹配,匹配成功后调用驱动的 probe 函数。
compatible 字符串必须与驱动中 of_device_id 表内的字符串完全一致,包括大小写和通配符。
3.1.1 驱动注册代码结构
static const struct of_device_id csi_dt_ids[] = {
{ .compatible = "vendor,csi-camera", },
{ /* sentinel */ }
};
MODULE_DEVICE_TABLE(of, csi_dt_ids);
static struct platform_driver csi_driver = {
.probe = csi_probe,
.remove = csi_remove,
.driver = {
.name = "csi_camera",
.of_match_table = csi_dt_ids,
.pm = &csi_pm_ops,
},
};
module_platform_driver(csi_driver);
3.1.2 设备树节点示例
csi_camera: camera@3c {
compatible = "vendor,csi-camera";
reg = <0x3c>;
clocks = <&clkc CSI_CLK>;
clock-names = "csi_mclk";
power-domains = <&pd_csi>;
reset-gpios = <&gpio1 15 GPIO_ACTIVE_LOW>;
power-gpios = <&gpio2 5 GPIO_ACTIVE_HIGH>;
status = "okay";
};
3.2 硬件资源获取:GPIO、时钟、电源
在 probe 函数中,驱动需要从设备树或平台资源中获取三类关键硬件资源:GPIO(复位、电源使能)、时钟(MCLK)、电源域(Power Domain)。获取失败将直接导致 probe 返回错误,驱动无法加载。
3.2.1 GPIO资源获取
GPIO用于控制摄像头的复位和电源使能。推荐使用 devm_gpiod_get_optional() 系列API,支持设备树自动解析。
struct device *dev = &pdev->dev;
csi->reset_gpio = devm_gpiod_get_optional(dev, "reset", GPIOD_OUT_HIGH);
if (IS_ERR(csi->reset_gpio))
return dev_err_probe(dev, PTR_ERR(csi->reset_gpio), "Failed to get reset GPIO\n");
csi->power_gpio = devm_gpiod_get_optional(dev, "power", GPIOD_OUT_LOW);
if (IS_ERR(csi->power_gpio))
return dev_err_probe(dev, PTR_ERR(csi->power_gpio), "Failed to get power GPIO\n");
3.2.2 时钟资源获取
CSI摄像头通常需要外部主时钟(MCLK),频率一般为24MHz或27MHz。驱动通过 devm_clk_get() 获取时钟句柄,并在初始化时使能。
csi->mclk = devm_clk_get(dev, "csi_mclk");
if (IS_ERR(csi->mclk))
return dev_err_probe(dev, PTR_ERR(csi->mclk), "Failed to get MCLK\n");
ret = clk_prepare_enable(csi->mclk);
if (ret)
return dev_err_probe(dev, ret, "Failed to enable MCLK\n");
3.2.3 电源资源获取
电源管理通过 dev_pm_domain_attach() 或 regulator 框架实现。对于SoC内部电源域,使用 dev_pm_domain_attach();对于外部LDO,使用 devm_regulator_get()。
// 电源域方式
ret = dev_pm_domain_attach(dev, true);
if (ret && ret != -EPROBE_DEFER)
dev_warn(dev, "Failed to attach PM domain: %d\n", ret);
// Regulator方式(可选)
csi->avdd = devm_regulator_get(dev, "avdd");
if (IS_ERR(csi->avdd))
return dev_err_probe(dev, PTR_ERR(csi->avdd), "Failed to get AVDD\n");
ret = regulator_enable(csi->avdd);
if (ret)
return dev_err_probe(dev, ret, "Failed to enable AVDD\n");
3.3 初始化流程SVG架构图
下图展示了从驱动注册到硬件资源获取的完整初始化流程,包含关键步骤和资源依赖关系。
3.4 资源获取错误处理与重试机制
在驱动初始化过程中,资源获取可能因为依赖设备未就绪而失败。内核提供了 -EPROBE_DEFER 机制,驱动返回该错误码后,内核会将该驱动加入延迟探测队列,稍后重试。
| 资源类型 | 获取API | 典型错误 | 处理方式 |
|---|---|---|---|
| GPIO | devm_gpiod_get_optional |
-ENOENT(节点缺失) |
可选资源可忽略;必需资源返回 -EPROBE_DEFER |
| 时钟 | devm_clk_get |
-EPROBE_DEFER(时钟控制器未就绪) |
直接返回 -EPROBE_DEFER |
| 电源域 | dev_pm_domain_attach |
-EPROBE_DEFER |
返回 -EPROBE_DEFER,等待电源域控制器加载 |
| Regulator | devm_regulator_get |
-EPROBE_DEFER |
返回 -EPROBE_DEFER |
dev_err_probe() 替代 dev_err(),它会在返回 -EPROBE_DEFER 时自动抑制错误日志,避免内核日志被重复打印刷屏。
3.5 初始化完成后的状态确认
所有资源获取成功后,驱动应执行一次硬件状态确认,例如读取摄像头芯片ID寄存器,验证I²C通信是否正常。若确认失败,需释放已获取的资源并返回错误。
// 读取芯片ID验证通信
ret = csi_read_reg(csi, CSI_CHIP_ID_REG, &chip_id);
if (ret || chip_id != CSI_EXPECTED_ID) {
dev_err(dev, "Chip ID mismatch: read 0x%04x, expected 0x%04x\n",
chip_id, CSI_EXPECTED_ID);
goto err_disable_resources;
}
dev_info(dev, "CSI camera initialized successfully, chip ID: 0x%04x\n", chip_id);
至此,驱动初始化流程完成,CSI摄像头已具备基本工作条件,可以进入中断配置和数据流协同设计阶段。