4. pinctrl设备树绑定:pinctrl-names、pinctrl-0属性、默认状态与休眠状态
好,咱们今天聊点实在的。设备树里跟引脚控制最密切的,就是 pinctrl 这块的绑定。说白了,就是告诉内核:我这个设备在什么状态下,引脚该怎么配置。
很多刚接触设备树的兄弟,看到 pinctrl-names 和 pinctrl-0 这两个属性就懵了。其实没那么复杂,我带你捋一遍。
4.1 核心属性:pinctrl-names 和 pinctrl-0
先看一个最简单的例子:
uart0: serial@44e09000 {
compatible = "ti,omap3-uart";
reg = <0x44e09000 0x2000>;
interrupts = <72>;
pinctrl-names = "default";
pinctrl-0 = <&uart0_pins>;
};
这里 pinctrl-names 定义了一个名字叫 "default" 的状态。pinctrl-0 则指向了具体的引脚配置节点 uart0_pins。
关键点来了:
pinctrl-names是一个字符串列表,给每个状态起个名字pinctrl-0对应第一个状态(索引0)的引脚配置- 如果有多个状态,就用
pinctrl-1、pinctrl-2依次类推
重要: pinctrl-names 和 pinctrl-X 的索引必须一一对应。名字可以随便起,但顺序不能乱。
4.2 默认状态与休眠状态
实际项目中,我最常用的就是两个状态:default 和 sleep。
默认状态(default): 设备正常工作时用的引脚配置。比如 UART 的 TX/RX 引脚要设为功能模式,带上拉。
休眠状态(sleep): 系统进入低功耗模式时用的配置。这时候引脚可能要设为输入、下拉,或者干脆断开,省电。
来看一个完整的例子:
i2c0: i2c@44e0b000 {
compatible = "ti,omap4-i2c";
reg = <0x44e0b000 0x1000>;
interrupts = <70>;
pinctrl-names = "default", "sleep";
pinctrl-0 = <&i2c0_default_pins>;
pinctrl-1 = <&i2c0_sleep_pins>;
status = "okay";
};
这里定义了两种状态。内核在设备 probe 时会自动应用 default 状态。当系统要休眠时,内核会查找 sleep 状态并切换过去。
我的习惯: 每个外设节点都配两个状态。哪怕 sleep 状态只是把引脚设为高阻,也比不配强。省电效果很明显。
4.3 引脚配置节点的写法
刚才提到的 uart0_pins 和 i2c0_default_pins 这些节点,通常定义在 pinctrl 控制器下面:
&am33xx_pinmux {
uart0_pins: uart0_pins {
pinctrl-single,pins = <
0x170 (PIN_INPUT | MUX_MODE0) /* uart0_rxd */
0x174 (PIN_OUTPUT | MUX_MODE0) /* uart0_txd */
>;
};
i2c0_default_pins: i2c0_default_pins {
pinctrl-single,pins = <
0x188 (PIN_INPUT_PULLUP | MUX_MODE0) /* i2c0_sda */
0x18c (PIN_INPUT_PULLUP | MUX_MODE0) /* i2c0_scl */
>;
};
i2c0_sleep_pins: i2c0_sleep_pins {
pinctrl-single,pins = <
0x188 (PIN_INPUT_PULLDOWN | MUX_MODE7) /* i2c0_sda */
0x18c (PIN_INPUT_PULLDOWN | MUX_MODE7) /* i2c0_scl */
>;
};
};
注意看区别:
- 默认状态用了
PIN_INPUT_PULLUP,保证 I2C 总线空闲时是高电平 - 休眠状态用了
PIN_INPUT_PULLDOWN和MUX_MODE7(通常是 GPIO 模式),彻底断开功能
踩坑提醒: 我曾经在某个项目里,sleep 状态忘了配,结果系统休眠后 I2C 引脚还保持输出状态,跟其他设备打架,导致唤醒后总线卡死。嗯,从那以后我再也不敢偷懒了。
4.4 内核如何解析这些属性
你可能好奇,内核是怎么知道什么时候切换状态的?
其实流程很简单:
- 设备 probe 时,内核调用
pinctrl_bind_pins(),自动应用default状态 - 系统进入 suspend 时,内核遍历所有设备,调用
pinctrl_pm_select_sleep_state(),切换到sleep状态 - 系统 resume 时,再切回
default状态
你想想看,这个机制其实很优雅。设备驱动本身几乎不用关心引脚切换,全由 pinctrl 子系统代劳了。
4.5 更多状态:init、idle、active
除了 default 和 sleep,有些 SoC 还支持更多状态:
| 状态名 | 典型用途 | 触发时机 |
|---|---|---|
| default | 正常工作 | 设备 probe |
| sleep | 低功耗休眠 | 系统 suspend |
| init | 早期初始化 | probe 之前 |
| idle | 运行时空闲 | 运行时 PM |
| active | 高负载运行 | 动态切换 |
我个人建议,除非你的项目对功耗有极致要求,否则配好 default 和 sleep 两个状态就够了。状态太多反而容易搞混。
4.6 实战中的几个要点
最后分享几个我在项目中总结的经验:
- 命名规范: 我习惯用
xxx_default_pins和xxx_sleep_pins,一看就懂 - 复用检查: 同一个引脚不要被两个设备同时配置为输出,否则会打架
- 调试技巧: 如果引脚不工作,先检查
/sys/kernel/debug/pinctrl/下的信息,看看当前引脚状态对不对 - 兼容性: 有些老内核不支持
sleep状态自动切换,需要驱动里手动调用pinctrl_pm_select_sleep_state()
一句话总结: pinctrl-names 定义状态名称,pinctrl-X 定义具体配置。默认状态保证设备能工作,休眠状态保证省电。两者配合,才是完整的引脚管理方案。
这张图把整个流程串起来了。从设备节点出发,通过 pinctrl-names 和 pinctrl-X 属性,指向具体的引脚配置节点,最后由内核 pinctrl 子系统在合适的时机自动切换。嗯,就是这么回事。