第四节:设备树配置——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";
};
配完之后,怎么验证?我一般做三步检查:
- 看dmesg:内核启动时,CAN驱动会打印“mtk_can 1100A000.can: registered”之类的信息
- 看clk_summary:
cat /sys/kernel/debug/clk/clk_summary | grep can,确认时钟频率是40MHz - 看pinmux状态:
cat /sys/kernel/debug/pinctrl/pinctrl-handles,确认引脚被正确配置
小技巧:如果CAN节点没起来,先别急着怀疑驱动。用ls /sys/bus/platform/devices/看看设备有没有被注册。如果没有,八成是设备树语法错了,或者compatible字符串没对上。
好了,设备树配置这块就讲到这里。说白了,就是告诉内核三件事:时钟怎么来、中断怎么用、引脚怎么接。把这三点搞清楚了,CAN节点就能跑起来。下一节咱们讲驱动移植,到时候会用到今天配的这些资源。