第四节:设备树中的pinctrl描述
好,咱们接着往下聊。上一节我们把pinctrl子系统的内部机制梳理了一遍,这一节就聚焦到设备树里,看看pinctrl到底是怎么描述的。
说实话,我刚接触设备树那会儿,看到pinctrl相关的节点也是一头雾水。什么pinctrl-0、pinctrl-names,还有那一堆pinmux、pinconf的配置,感觉就像在看天书。后来做项目踩了几个坑,才慢慢摸清楚门道。
4.1 pinctrl节点定义
先说说pinctrl节点本身。在设备树里,pinctrl节点通常放在soc节点下面,或者单独作为一个子节点。它的作用就是描述SoC内部那些pin controller的硬件信息。
我习惯这么写:
pinctrl: pinctrl@10000000 {
compatible = "vendor,soc-pinctrl";
reg = <0x10000000 0x1000>;
#pinctrl-cells = <2>;
/* 子节点:定义具体的引脚功能组 */
uart0_pins: uart0-pins {
function = "uart0";
groups = "uart0_tx", "uart0_rx";
};
i2c0_pins: i2c0-pins {
function = "i2c0";
pins = "GPIO0", "GPIO1";
bias-pull-up;
drive-strength = <2>;
};
};
这里有几个关键点:
- compatible:匹配驱动用的,必须和驱动里写的名字一致
- reg:pin controller的寄存器基地址和大小
- #pinctrl-cells:这个很重要,表示每个引脚描述需要几个cell。常见的是2个,一个表示引脚号,一个表示功能选择
核心要点:pinctrl节点本身不直接配置引脚,它只是提供一个"模板仓库"。真正的配置是在使用这些引脚的外设节点里引用的。
4.2 pinctrl状态
为什么要有pinctrl状态?说白了,一个外设在不同的工作场景下,引脚配置可能不一样。
举个例子,我做过一个项目,SD卡接口在正常工作时需要高速模式,引脚驱动能力要强;但在休眠时,为了省电,引脚要改成弱驱动甚至关闭。这就涉及到不同状态的切换。
设备树里用pinctrl-names来定义状态名称,用pinctrl-0、pinctrl-1来对应具体的配置:
&mmc0 {
pinctrl-names = "default", "sleep", "idle";
pinctrl-0 = <&mmc0_default_pins>;
pinctrl-1 = <&mmc0_sleep_pins>;
pinctrl-2 = <&mmc0_idle_pins>;
};
内核里,驱动可以通过pinctrl_select_state()来切换状态。比如在系统休眠时,内核会自动调用pinctrl_select_state(dev, "sleep")。
我的经验:状态名称最好用"default"、"sleep"、"active"这种语义明确的词。我曾经见过有人用"state0"、"state1",结果三个月后自己都忘了哪个是干嘛的。
4.3 pinctrl绑定
绑定,就是把pinctrl配置"挂"到具体的外设节点上。这个过程其实很简单,就是在你的外设节点里加上pinctrl-names和pinctrl-X属性。
来看一个完整的例子:
&uart0 {
status = "okay";
pinctrl-names = "default";
pinctrl-0 = <&uart0_pins>;
};
这里&uart0_pins就是我们在pinctrl节点里定义的那个子节点。内核在初始化uart0驱动时,会去解析这个绑定,然后调用pinctrl子系统来配置引脚。
嗯,这里要注意一点:pinctrl-0可以引用多个配置节点,用空格隔开就行:
pinctrl-0 = <&pinmux_uart0 &pinconf_uart0>;
这样做的目的是把mux功能和conf功能分开定义,方便复用。我个人比较推荐这种写法,清晰明了。
4.4 pinctrl配置示例
光说不练假把式,咱们来看一个完整的实战配置。假设我们要配置一个I2C控制器,引脚是GPIO0和GPIO1,需要上拉、驱动强度为4mA。
第一步,在pinctrl节点里定义引脚组:
pinctrl: pinctrl@10000000 {
compatible = "vendor,soc-pinctrl";
reg = <0x10000000 0x1000>;
#pinctrl-cells = <2>;
i2c0_pins_a: i2c0-pins-a {
pins = "GPIO0", "GPIO1";
function = "i2c0";
bias-pull-up;
drive-strength = <4>;
slew-rate = <1>;
};
i2c0_pins_b: i2c0-pins-b {
pins = "GPIO2", "GPIO3";
function = "i2c0";
bias-pull-up;
drive-strength = <2>;
};
};
第二步,在I2C控制器节点里引用:
&i2c0 {
status = "okay";
pinctrl-names = "default", "alt";
pinctrl-0 = <&i2c0_pins_a>;
pinctrl-1 = <&i2c0_pins_b>;
clock-frequency = <100000>;
};
你看,这里我定义了两套引脚配置。默认用GPIO0/1,备用方案用GPIO2/3。这在做硬件兼容设计时特别有用。
避坑指南:我曾经在一个项目里,把两个外设的pinctrl配置写成了同一个引脚组,结果两个设备同时工作时,引脚功能冲突,系统直接死机。排查了整整两天才发现问题。所以一定要检查引脚是否被复用。
4.5 知识体系总览
为了让你更直观地理解pinctrl在设备树中的整体结构,我画了一张图:
从这张图可以看得很清楚:pinctrl节点是"配置仓库",外设节点通过pinctrl-0等属性去"取货"。内核在启动时,会按照这个绑定关系,一步步把引脚配置到寄存器里。
好了,关于设备树中的pinctrl描述,核心内容就是这些。记住三个关键词:定义、状态、绑定。搞懂了这三个,设备树这块你就拿下了大半。
总结一下:pinctrl节点定义引脚功能组,pinctrl状态管理不同场景的配置,pinctrl绑定把配置挂到外设上。三者缺一不可。