4. 设备树与Camera硬件抽象

在i.MX Linux BSP中,Camera子系统能否被正确枚举、上电、出图,几乎完全取决于设备树(Device Tree)对硬件拓扑的描述是否准确。内核里的CSI/V4L2驱动、I2C控制器驱动、pinctrl子系统、时钟框架和regulator框架本身都是通用代码,它们并不知道板子上接的是OV5640还是IMX219,也不知道复位脚接在哪个GPIO、MCLK由哪路PLL分频而来。设备树的作用,就是把这些“板级事实”翻译成内核可解析的节点与属性,让驱动在probe阶段按图索骥地完成资源申请与硬件初始化。

理解这一节,建议先建立一个整体视角:Camera硬件抽象不是单个节点,而是一张由SoC内部控制器节点、板级连接节点和外部传感器节点共同组成的引用网络。CSI接收器、I2C总线、GPIO控制器、CCM时钟、电源域和传感器分别位于不同的设备树层级,通过phandle(节点引用)和标准属性互相指向。任何一处引用错误,都会在启动日志中以probe defer、-EPROBE_DEFER或直接probe失败的形式暴露出来。

i.MX Camera 设备树硬件抽象关系图 SoC 内部(i.MX8M / i.MX6 等) csi / mipi_csi 节点 compatible = "fsl,imx8mm-csi" port / endpoint 描述数据流 i2c 控制器节点 compatible = "fsl,imx8mm-i2c" clock-frequency = <400000> gpio 控制器节点 gpio1 / gpio2 … 提供 reset / pwdn 引脚 ccm 时钟节点 clocks = <&clk IMX8MM_CLK_CSI1_CORE> assigned-clock-rates 设定频率 板级 / 外部器件 ov5640 / imx219 传感器节点 compatible = "ovti,ov5640" reg = <0x3c>(I2C 地址) reset-gpios / powerdown-gpios clocks / clock-names = "xclk" regulator 节点 vdd_1v8 / vdd_2v8 / dovdd regulator-always-on 或按需使能 I2C 控制通道(寄存器读写) MIPI CSI-2 / 并口数据流 reset / pwdn xclk 24MHz 供电 关键属性与引用关系速查 CSI 端点(endpoint) remote-endpoint = <&ov5640_out> data-lanes = <1 2 3 4> bus-type = <4>(MIPI CSI-2) clock-lanes = <0> 作用:绑定传感器与CSI接收器 传感器节点必备属性 compatible:匹配驱动 reg:I2C 从地址 clocks / clock-names reset-gpios / powerdown-gpios port / endpoint 数据出口 pinctrl 与电源 pinctrl-0 = <&pinctrl_csi> 引脚复用:MCLK、I2C、CSI vdd-supply = <&reg_cam> assigned-clocks 设定 MCLK 作用:上电时序与引脚状态

先看CSI节点。以i.MX8M Mini为例,CSI接收器通常写作csi@32e30000或mipi_csi@32e30000,其compatible为"fsl,imx8mm-csi"。真正决定“数据从哪来”的不是CSI节点本身,而是它下面的port/endpoint子节点。端点中的remote-endpoint属性用phandle指向传感器节点的输出端点,从而在V4L2异步子设备框架中建立一条media link。如果这条link没有建立,即使传感器驱动probe成功,/dev/video0也不会出现在应用层。端点里还会描述总线类型和lane数量,例如MIPI CSI-2场景下bus-type = <4>、data-lanes = <1 2 3 4>、clock-lanes = <0>;并口场景则用bus-width、hsync-active、vsync-active、pclk-sample等属性描述时序极性。

I2C节点承担的是控制通道。传感器作为I2C从设备挂在某条I2C总线下面,节点名通常为ov5640: camera@3c,其中@3c就是7位从地址0x3C。父节点I2C控制器需要正确配置clock-frequency,常见取100000或400000。这里有一个容易忽略的点:传感器节点的reg必须与硬件实际地址一致,且不能与同总线上其他器件冲突;如果地址写错,驱动probe时会报-ENXIO或-EREMOTEIO,表现为“找不到设备”。

GPIO节点负责复位和掉电控制。传感器节点中通过reset-gpios = <&gpio1 8 GPIO_ACTIVE_LOW>和powerdown-gpios = <&gpio1 9 GPIO_ACTIVE_HIGH>这样的属性声明引脚归属与有效电平。这里的GPIO_ACTIVE_LOW表示低电平有效,驱动在调用gpiod_set_value()时会自动做逻辑到物理的翻转。很多“传感器识别不到”的问题,根源就是有效电平写反,导致复位时序一直处于复位态。上电时序通常要求:先使能电源,再释放powerdown,最后拉高reset并保持至少1ms,之后才能访问I2C寄存器。

时钟节点通过CCM提供MCLK。传感器节点里写clocks = <&clk IMX8MM_CLK_CSI1_CORE>或指向某个固定时钟源,并用clock-names = "xclk"与驱动中的devm_clk_get(&client->dev, "xclk")对应。若传感器需要24MHz输入,可在板级节点中用assigned-clocks和assigned-clock-rates = <24000000>设定。时钟没使能时,I2C可能仍能读到ID,但出图会失败或帧率异常,因为传感器内部PLL没有参考时钟。

电源抽象由regulator节点完成。传感器节点通过vdd-supply、dovdd-supply、avdd-supply等属性引用对应的regulator。驱动probe时会调用regulator_get()和regulator_enable(),按顺序上电。如果regulator被标记为always-on,则不受驱动控制;若需要动态开关,则要确保regulator驱动本身已就绪,否则会出现-EPROBE_DEFER,传感器驱动会延迟重试,直到电源就绪。

把这些节点串起来看,一次成功的Camera枚举大致经历:I2C控制器就绪 → 传感器节点被创建 → 驱动匹配compatible → 申请GPIO、时钟、regulator → 执行上电与复位时序 → 读取芯片ID确认通信 → 注册V4L2子设备 → CSI端点建立media link → 生成/dev/video*。任何一步资源缺失,都会在dmesg中留下线索。排查时建议按“电源→时钟→复位→I2C通信→CSI link”的顺序逐项确认,而不是一上来就怀疑驱动代码。

设备树调试常用命令:ls /proc/device-tree/查看节点是否存在;cat /sys/kernel/debug/clk/clk_summary确认MCLK是否使能;i2cdetect -y 1验证传感器地址是否应答;media-ctl -p查看media link是否建立。

最后给出一个简化的设备树片段,帮助建立整体印象。注意其中&i2c1、&csi、&gpio1等标签都来自SoC级dtsi,板级dts只做覆盖和补充,这也是i.MX BSP分层设计的关键:SoC共性放在dtsi,板级差异放在dts。

&i2c1 {
    status = "okay";
    clock-frequency = <400000>;

    ov5640: camera@3c {
        compatible = "ovti,ov5640";
        reg = <0x3c>;
        pinctrl-names = "default";
        pinctrl-0 = <&pinctrl_csi>;
        clocks = <&clk IMX8MM_CLK_CSI1_CORE>;
        clock-names = "xclk";
        assigned-clocks = <&clk IMX8MM_CLK_CSI1_CORE>;
        assigned-clock-rates = <24000000>;
        reset-gpios = <&gpio1 8 GPIO_ACTIVE_LOW>;
        powerdown-gpios = <&gpio1 9 GPIO_ACTIVE_HIGH>;
        vdd-supply = <&reg_cam_1v8>;
        dovdd-supply = <&reg_cam_2v8>;

        port {
            ov5640_out: endpoint {
                remote-endpoint = <&csi_in>;
                data-lanes = <1 2>;
                clock-lanes = <0>;
                bus-type = <4>;
            };
        };
    };
};

&csi {
    status = "okay";
    port {
        csi_in: endpoint {
            remote-endpoint = <&ov5640_out>;
        };
    };
};

掌握这张“节点—属性—引用”的对应关系后,再去看BSP里现成的dts文件,就能快速定位每个属性的作用,也能在更换传感器或改板时,知道该动哪几个节点、哪些属性必须同步修改。设备树不是配置脚本,而是硬件描述;描述准确,驱动才能按预期工作。