4、CAN设备树配置:设备树语法、CAN控制器节点配置、时钟与引脚配置

设备树,说白了就是一块「硬件描述板」。它告诉内核:你有哪些外设、它们挂在哪条总线上、中断号是多少、时钟怎么配。我刚开始接触设备树时,总觉得这东西玄乎,后来调了三个月的驱动才明白——设备树配错了,内核根本认不出你的CAN控制器。

这一节,我们就来啃下设备树配置这块硬骨头。我会结合项目经验,把语法、节点、时钟、引脚这些点一一拆开。

核心要点:设备树是驱动与硬件的「翻译官」。CAN控制器节点配置的核心在于 compatible、reg、interrupts、clocks、pinctrl 这几个属性。

4.1 设备树语法速览

设备树的语法其实不复杂。它用树形结构描述硬件,每个节点就是一个设备。我习惯把设备树看作一个巨大的 JSON 对象,只不过它的写法更「C语言」一些。

/dts-v1/;

/ {
    model = "My CAN Board";
    compatible = "vendor,my-board";

    soc {
        #address-cells = <1>;
        #size-cells = <1>;
        ranges;

        can0: can@1c00000 {
            compatible = "vendor,can-controller";
            reg = <0x01c00000 0x1000>;
            interrupts = <0 25 4>;
            ...
        };
    };
};

这里有几个关键点:

  • 节点名:格式是 name@address,比如 can@1c00000。address 是基地址,用来区分同名节点。
  • 属性:键值对。值可以是字符串、数字、数组、二进制数据。
  • 标签:比如 can0:,方便在其他地方引用。

我的习惯:每个节点都加标签,哪怕暂时用不到。后续要覆盖或引用时,省去翻地址的麻烦。

4.2 CAN控制器节点配置

CAN控制器的设备树节点,核心属性就那么几个。我列个表,你一看就明白:

属性 说明 示例值
compatible 驱动匹配字符串,内核靠它找驱动 "vendor,can-controller"
reg 寄存器基地址和范围 <0x01c00000 0x1000>
interrupts 中断号、触发类型 <0 25 4>
clocks 时钟源引用 <&clkc CAN_CLK>
pinctrl-names 引脚复用状态名称 "default"
pinctrl-0 引脚复用配置引用 <&can_pins>
bus-off 总线关闭恢复时间(可选) <100>

来看一个完整的CAN控制器节点示例:

can0: can@1c00000 {
    compatible = "allwinner,sun8i-can";
    reg = <0x01c00000 0x1000>;
    interrupts = <GIC_SPI 25 IRQ_TYPE_LEVEL_HIGH>;
    clocks = <&ccu CLK_BUS_CAN>;
    resets = <&ccu RST_BUS_CAN>;
    pinctrl-names = "default";
    pinctrl-0 = <&can_pins>;
    status = "okay";
};

这里我用了 GIC_SPI 和 IRQ_TYPE_LEVEL_HIGH 宏,它们定义在 include/dt-bindings/interrupt-controller/arm-gic.h 里。我个人建议直接引用这些宏,别写裸数字——可读性差,还容易出错。

注意:status = "okay" 必须显式写。设备树默认是 "disabled",不写的话内核不会使能这个节点。我曾经因为这个坑,浪费了半天排查。

4.3 时钟配置

CAN控制器需要时钟才能工作。时钟配置不对,要么无法初始化,要么波特率跑偏。

时钟通常来自系统时钟控制器(CCU)。设备树里通过 clocks 属性引用时钟源:

clocks = <&ccu CLK_BUS_CAN>;
clock-names = "can_clk";

clock-names 是可选的,但建议加上。驱动里可以通过 devm_clk_get(&pdev->dev, "can_clk") 按名字获取,比按索引更安全。

时钟频率怎么确定?我一般这么算:

  1. 查芯片手册,找到CAN控制器的时钟源(通常是AHB或APB总线时钟)。
  2. 确认时钟树里该时钟的默认频率。
  3. 在设备树里用 assigned-clocks 和 assigned-clock-rates 强制设定频率。
&can0 {
    assigned-clocks = <&ccu CLK_BUS_CAN>;
    assigned-clock-rates = <24000000>;  // 强制设为24MHz
};

经验之谈:CAN控制器的时钟频率直接影响波特率精度。24MHz 是常见值,能整除 125K、250K、500K、1M 这些标准波特率。如果时钟频率是 26MHz 这种「奇怪」的值,波特率误差可能会超标。

4.4 引脚配置(pinctrl)

引脚配置是设备树里最容易出问题的部分。CAN控制器的 TX 和 RX 引脚必须正确复用,否则数据发不出去,也收不进来。

引脚配置通常在 pinctrl 节点里定义:

can_pins: can-pins {
    pins = "PA8", "PA9";
    function = "can";
    bias-pull-up;
};

然后在CAN控制器节点里引用:

pinctrl-names = "default";
pinctrl-0 = <&can_pins>;

这里有几个要点:

  • pins:指定哪些引脚。不同芯片的引脚名规则不同,查数据手册。
  • function:复用功能。必须是芯片支持的复用选项。
  • bias-pull-up:CAN总线建议加上拉,防止浮空。

我曾经踩过的坑:有一次配置CAN引脚,忘了加 bias-pull-up,结果总线在空闲时电平不稳定,导致误帧率飙升。加了上拉后,问题立刻消失。

4.5 知识结构总览

下面这张图,把CAN设备树配置的核心脉络画出来了。你可以把它当作配置时的检查清单:

CAN设备树配置知识结构图 CAN设备树配置核心脉络 设备树根节点 SOC 节点 CAN控制器节点 (can@...) compatible 驱动匹配字符串 reg / interrupts 基地址 / 中断号 clocks 时钟源与频率 pinctrl 引脚复用配置 配置检查清单 compatible匹配 → reg地址正确 → 中断号无误 → 时钟频率整除波特率 → 引脚复用正确

4.6 实战:完整配置示例

最后,我放一个完整的设备树配置片段。这是我在某款全志芯片上调试CAN时用的,你可以直接参考:

/ {
    model = "My CAN Board";
    compatible = "vendor,my-board";

    soc {
        #address-cells = <1>;
        #size-cells = <1>;
        ranges;

        can0: can@1c00000 {
            compatible = "allwinner,sun8i-can";
            reg = <0x01c00000 0x1000>;
            interrupts = <GIC_SPI 25 IRQ_TYPE_LEVEL_HIGH>;
            clocks = <&ccu CLK_BUS_CAN>;
            resets = <&ccu RST_BUS_CAN>;
            pinctrl-names = "default";
            pinctrl-0 = <&can_pins>;
            status = "okay";
        };
    };

    pinctrl@1c20800 {
        can_pins: can-pins {
            pins = "PA8", "PA9";
            function = "can";
            bias-pull-up;
        };
    };
};

&can0 {
    assigned-clocks = <&ccu CLK_BUS_CAN>;
    assigned-clock-rates = <24000000>;
};

调试小技巧:配置完设备树后,在 /sys/firmware/devicetree/base/ 下可以查看实际生效的设备树内容。用 dtc -I fs -O dts /sys/firmware/devicetree/base 可以反编译成可读的DTS,方便对比检查。

嗯,设备树配置这块就讲到这里。记住:compatible 是钥匙,reg 是门牌,interrupts 是门铃,clocks 是电源,pinctrl 是门锁。五样都对了,门才能打开。