第四节:设备树配置——CAN节点设备树语法、时钟与中断配置、引脚复用(Pinmux)设置实战

好,咱们直接进入正题。设备树这玩意儿,说白了就是给内核看的“硬件说明书”。你想想看,Linux内核要驱动CAN控制器,总得知道它挂在哪条总线上、中断号是多少、时钟频率多少、引脚怎么接吧?这些信息,全写在设备树里。

我个人习惯,拿到一块新板子,第一件事就是翻它的设备树。不是看代码,是看硬件是怎么被描述的。今天咱们就拿MTK8676这颗芯片来实战,把CAN节点的设备树配置讲透。

4.1 CAN节点设备树语法基础

先看一个典型的CAN节点长什么样。我直接贴一段我在MTK8676上实际用过的配置:

/* 这是CAN0节点的完整配置 */
&can0 {
    status = "okay";
    pinctrl-names = "default", "sleep";
    pinctrl-0 = &can0_pins_default;
    pinctrl-1 = &can0_pins_sleep;
    
    /* 时钟配置 */
    clocks = &topckgen CLK_TOP_CAN0_SEL,
             &topckgen CLK_TOP_CAN0_DIV;
    clock-names = "can_clk", "can_div_clk";
    assigned-clocks = &topckgen CLK_TOP_CAN0_SEL;
    assigned-clock-parents = &topckgen CLK_TOP_CAN0_PLL;
    assigned-clock-rates = <40000000>;  /* 40MHz */
    
    /* 中断配置 */
    interrupts = &gic GIC_SPI 123 IRQ_TYPE_LEVEL_HIGH;
    
    /* 总线频率 */
    bus-clock = <40000000>;  /* CAN控制器时钟 */
    sjw = <1>;
    sample-point = <875>;   /* 87.5%采样点 */
};

嗯,这里要注意几个关键点。首先是status = "okay",这个不用多说,使能节点。但我在项目中遇到过有人忘了写这一行,结果内核死活不加载驱动,查了半天才发现是status默认是disabled。

核心语法要点:

  • pinctrl-names:定义引脚状态名称,通常有default和sleep两种
  • pinctrl-0:指向具体的引脚配置节点
  • clocks:时钟源列表,顺序要和clock-names对应
  • assigned-clocks:需要由内核分配的时钟
  • interrupts:中断号、触发类型

4.2 时钟配置——别让CAN跑错频率

时钟配置这块,我吃过不少亏。CAN总线对时钟精度要求很高,尤其是高速CAN(1Mbps),时钟偏差超过1%就可能出问题。

你看上面代码里,我配了40MHz的时钟。为什么是40MHz?因为MTK8676的CAN控制器内部有预分频器,40MHz可以很方便地分频出各种CAN波特率。比如要配500kbps,分频系数就是40M / 500k / 2 = 40,刚好整数。

我曾经在一个项目里,时钟源选错了,配了个26MHz的。结果算出来的分频系数不是整数,导致实际波特率偏差0.8%。虽然看起来不大,但在高温下,CAN收发器的晶振本身就有漂移,两者叠加,通信就开始丢帧了。排查了整整两天才找到原因。

我的建议:时钟配置时,尽量选一个能被常用波特率整除的频率。40MHz、80MHz、20MHz都是好选择。如果芯片支持,优先用PLL输出的专用时钟,别用外部晶振直通。

再看assigned-clock-rates这个属性。它的作用是告诉内核:你帮我把这个时钟设成40MHz。内核会在驱动加载时,自动配置时钟树,确保CAN模块拿到准确的频率。

4.3 中断配置——别漏了共享中断

中断配置看起来简单,就一行interrupts属性。但这里有个坑:MTK8676的CAN控制器支持多个中断源,比如发送完成、接收完成、错误警告、总线关闭等。这些中断共享同一个中断号。

/* 中断配置详解 */
interrupts = &gic GIC_SPI 123 IRQ_TYPE_LEVEL_HIGH;
/*            |     |      |            |
 *            |     |      |            └─ 触发类型:高电平触发
 *            |     |      └─ 中断号:123
 *            |     └─ SPI类型(共享外设中断)
 *            └─ GIC控制器
 */

为什么用电平触发而不是边沿触发?我个人习惯,CAN总线中断用电平触发更可靠。因为CAN控制器在错误频繁时,可能会连续产生中断。边沿触发容易丢失中断,电平触发就不会。

注意:MTK8676的GIC支持SPI和PPI两种中断类型。CAN属于外设,用SPI。中断号范围一般是32~223,具体要看芯片手册。我见过有人把中断号写成了32以内的,那是PPI中断,给CPU内部用的,配上去根本不会触发。

4.4 引脚复用(Pinmux)设置——硬件工程师的“接线图”

引脚复用,说白了就是告诉芯片:这个引脚现在要当CAN的TX/RX用,别当GPIO或者UART用。MTK8676的引脚复用是通过pinctrl子系统实现的。

看一个完整的引脚配置例子:

/* CAN0引脚复用配置 */
&pio {
    can0_pins_default: can0-default {
        pins_can0_tx {
            pinmux = <MT8676_PIN_42__FUNC_CAN0_TX>;
            drive-strength = <MTK_DRIVE_8mA>;
            bias-pull-up;
        };
        pins_can0_rx {
            pinmux = <MT8676_PIN_43__FUNC_CAN0_RX>;
            bias-pull-up;
            input-enable;
        };
    };
    
    can0_pins_sleep: can0-sleep {
        pins_can0_tx {
            pinmux = <MT8676_PIN_42__FUNC_GPIO42>;
            output-low;
        };
        pins_can0_rx {
            pinmux = <MT8676_PIN_43__FUNC_GPIO43>;
            input-enable;
        };
    };
};

这里有几个关键点:

  • pinmux:核心属性,指定引脚号和功能。格式是<引脚号__功能宏>
  • drive-strength:驱动能力。CAN总线建议用8mA以上,不然信号质量差
  • bias-pull-up:上拉。CAN总线在隐性状态时,总线电平靠上拉维持
  • input-enable:RX引脚必须使能输入,不然收不到数据

避坑指南:我曾经在调试CAN通信时,发现只能发不能收。查了半天,发现RX引脚没配input-enable。硬件工程师说“RX当然是输入啊”,但设备树里不写这一行,内核就不会把引脚配成输入模式。嗯,这个坑我替你们踩过了。

另外,sleep状态的配置也很重要。系统休眠时,把CAN引脚切回GPIO并输出低电平,可以降低功耗。如果不配sleep状态,系统休眠时引脚会保持浮空,既费电又不安全。

4.5 完整实战——把CAN0配通

好,咱们把上面所有知识点串起来,写一个完整的CAN0配置。假设MTK8676的CAN0挂载在APB总线上,基地址是0x1100A000。

/* 完整的CAN0设备树节点 */
can0: can@1100A000 {
    compatible = "mediatek,mt8676-can";
    reg = <0 0x1100A000 0 0x1000>;
    interrupts = <GIC_SPI 123 IRQ_TYPE_LEVEL_HIGH>;
    
    clocks = &topckgen CLK_TOP_CAN0_SEL,
             &topckgen CLK_TOP_CAN0_DIV;
    clock-names = "can_clk", "can_div_clk";
    assigned-clocks = &topckgen CLK_TOP_CAN0_SEL;
    assigned-clock-parents = &topckgen CLK_TOP_CAN0_PLL;
    assigned-clock-rates = <40000000>;
    
    pinctrl-names = "default", "sleep";
    pinctrl-0 = &can0_pins_default;
    pinctrl-1 = &can0_pins_sleep;
    
    /* CAN协议参数 */
    bus-clock = <40000000>;
    sjw = <1>;
    sample-point = <875>;
    
    status = "okay";
};

配完之后,怎么验证?我一般做三步检查:

  1. 看dmesg:内核启动时,CAN驱动会打印“mtk_can 1100A000.can: registered”之类的信息
  2. 看clk_summarycat /sys/kernel/debug/clk/clk_summary | grep can,确认时钟频率是40MHz
  3. 看pinmux状态cat /sys/kernel/debug/pinctrl/pinctrl-handles,确认引脚被正确配置

小技巧:如果CAN节点没起来,先别急着怀疑驱动。用ls /sys/bus/platform/devices/看看设备有没有被注册。如果没有,八成是设备树语法错了,或者compatible字符串没对上。

好了,设备树配置这块就讲到这里。说白了,就是告诉内核三件事:时钟怎么来、中断怎么用、引脚怎么接。把这三点搞清楚了,CAN节点就能跑起来。下一节咱们讲驱动移植,到时候会用到今天配的这些资源。