4. pinctrl设备树绑定:pinctrl-names、pinctrl-0属性、默认状态与休眠状态

好,咱们今天聊点实在的。设备树里跟引脚控制最密切的,就是 pinctrl 这块的绑定。说白了,就是告诉内核:我这个设备在什么状态下,引脚该怎么配置。

很多刚接触设备树的兄弟,看到 pinctrl-namespinctrl-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-1pinctrl-2 依次类推

重要: pinctrl-namespinctrl-X 的索引必须一一对应。名字可以随便起,但顺序不能乱。

4.2 默认状态与休眠状态

实际项目中,我最常用的就是两个状态:defaultsleep

默认状态(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_pinsi2c0_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_PULLDOWNMUX_MODE7(通常是 GPIO 模式),彻底断开功能

踩坑提醒: 我曾经在某个项目里,sleep 状态忘了配,结果系统休眠后 I2C 引脚还保持输出状态,跟其他设备打架,导致唤醒后总线卡死。嗯,从那以后我再也不敢偷懒了。

4.4 内核如何解析这些属性

你可能好奇,内核是怎么知道什么时候切换状态的?

其实流程很简单:

  1. 设备 probe 时,内核调用 pinctrl_bind_pins(),自动应用 default 状态
  2. 系统进入 suspend 时,内核遍历所有设备,调用 pinctrl_pm_select_sleep_state(),切换到 sleep 状态
  3. 系统 resume 时,再切回 default 状态

你想想看,这个机制其实很优雅。设备驱动本身几乎不用关心引脚切换,全由 pinctrl 子系统代劳了。

4.5 更多状态:init、idle、active

除了 defaultsleep,有些 SoC 还支持更多状态:

状态名 典型用途 触发时机
default 正常工作 设备 probe
sleep 低功耗休眠 系统 suspend
init 早期初始化 probe 之前
idle 运行时空闲 运行时 PM
active 高负载运行 动态切换

我个人建议,除非你的项目对功耗有极致要求,否则配好 defaultsleep 两个状态就够了。状态太多反而容易搞混。

4.6 实战中的几个要点

最后分享几个我在项目中总结的经验:

  • 命名规范: 我习惯用 xxx_default_pinsxxx_sleep_pins,一看就懂
  • 复用检查: 同一个引脚不要被两个设备同时配置为输出,否则会打架
  • 调试技巧: 如果引脚不工作,先检查 /sys/kernel/debug/pinctrl/ 下的信息,看看当前引脚状态对不对
  • 兼容性: 有些老内核不支持 sleep 状态自动切换,需要驱动里手动调用 pinctrl_pm_select_sleep_state()

一句话总结: pinctrl-names 定义状态名称,pinctrl-X 定义具体配置。默认状态保证设备能工作,休眠状态保证省电。两者配合,才是完整的引脚管理方案。

pinctrl 设备树绑定核心逻辑 设备节点 pinctrl-names "default", "sleep" pinctrl-0 / pinctrl-1 &xxx_pins status "okay" / "disabled" default 引脚配置 功能模式、上拉/下拉 sleep 引脚配置 高阻、下拉、GPIO模式 内核 pinctrl 子系统自动处理 probe → default | suspend → sleep | resume → default 设备节点 绑定属性 引脚配置 内核处理

这张图把整个流程串起来了。从设备节点出发,通过 pinctrl-namespinctrl-X 属性,指向具体的引脚配置节点,最后由内核 pinctrl 子系统在合适的时机自动切换。嗯,就是这么回事。

专注资料整理