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";
};
匹配流程: 内核启动时解析设备树 → 遍历platform总线 → 比对compatible字符串 → 匹配成功 → 调用probe函数。

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");
注意: 资源获取顺序必须与硬件上电时序一致。通常先使能电源域,再使能时钟,最后释放复位GPIO。顺序错误可能导致摄像头初始化失败或损坏。

3.3 初始化流程SVG架构图

下图展示了从驱动注册到硬件资源获取的完整初始化流程,包含关键步骤和资源依赖关系。

CSI摄像头驱动初始化流程架构图 CSI摄像头驱动初始化流程 module_platform_driver of_match_table匹配 csi_probe() 硬件资源获取阶段 GPIO获取 devm_gpiod_get_optional 时钟获取 devm_clk_get + clk_prepare_enable 电源获取 dev_pm_domain_attach / regulator 上电时序控制(严格顺序) ① 使能电源域/Regulator → ② 使能时钟MCLK → ③ 释放复位GPIO → ④ 使能电源GPIO 顺序错误可能导致摄像头I²C无应答、MIPI信号不稳定 错误处理:任何资源获取失败 → dev_err_probe → probe返回-EPROBE_DEFER

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摄像头已具备基本工作条件,可以进入中断配置和数据流协同设计阶段。