欢迎光临
我们一直在努力

linux 中的 pinctrl 子系统

linux 中的 pinctrl 子系统

前置知识:Linux 设备模型、设备树(Device Tree)、GPIO 基本概念


1. 为什么需要 pinctrl?

1.1 SoC 引脚复用的现实问题

现代 SoC 的引脚(pin)数量远小于内部外设数量,因此绝大多数引脚都是多功能复用的:

  • 同一个物理引脚,既可以配置为 GPIO,也可以作为 I2C_SDA、SPI_CLK、UART_TX、PWM 输出等;
  • 每个引脚往往还带有电气属性配置:上拉/下拉(pull-up/down)、驱动强度(drive strength)、施密特触发、斜率控制、开漏(open-drain)等。

以一颗典型的 Cortex-A 级 SoC 为例,一个引脚可能有 4~8 种功能(mux function),而复用关系散布在数十个引脚上。

1.2 没有 pinctrl 之前的混乱

在 pinctrl 子系统出现之前(ARM Linux 早期),引脚配置存在几个典型问题:

  • 各平台代码重复:每个 mach-* 目录都有自己的一套引脚配置代码,接口不统一,无法复用。
  • 配置时机混乱:引脚配置写在板级文件(board file)里,与驱动代码割裂,驱动作者无法声明自己需要什么引脚状态。
  • 静态配置导致功耗浪费:所有引脚在开机时一次性配置好,即使外设休眠了,引脚仍保持活动状态,无法配合 runtime PM 省电。
  • GPIO 与复用功能冲突:引脚被复用为 I2C 后,又被其他驱动当 GPIO 请求,造成总线异常且难以定位。
  • 1.3 pinctrl 的定位

    pinctrl 子系统就是为解决上述问题而生的一个内核框架:

    • 向上(客户端驱动):提供统一接口,让驱动以"状态(state)"的抽象方式声明引脚需求,如 default、sleep;
    • 向下(SoC 驱动):定义 pinctrl 驱动的编写框架(pinctrl_desc、三组操作函数集);
    • 横向:与设备树绑定(引脚配置写在 DTS 里)、与 GPIO 子系统互通(gpio-ranges)、与 runtime PM 配合(引脚状态随设备电源切换)。

    一句话总结:pinctrl 负责"引脚复用(pinmux)+ 引脚电气配置(pinconf)"的统一管理,让引脚资源像时钟、电源一样成为可被设备树描述、被驱动申请的内核资源。


    2. 核心概念体系

    理解 pinctrl 的关键是把下面几个抽象层次分清:

    2.1 引脚控制器的功能划分

    pinctrl 子系统管理的能力分为两块:

    功能块英文职责典型配置项
    引脚复用 Pinmux 选择引脚连接到外设还是 GPIO function = i2c0 / gpio / spi1 …
    引脚配置 Pinconf 配置引脚的电气特性 bias-pull-up、drive-strength、slew-rate …

    有些 SoC 这两块在同一组寄存器里,有些则分开;pinctrl 框架允许驱动只实现其中一块(但大多数驱动两块都实现)。

    2.2 四个基本组织单位

    pinctrl 驱动把成千上万的引脚组织成四个层次的概念:

  • Pin(引脚):最小单位,每个 pin 有全局唯一编号,如 pin 0 ~ pin 287。
  • Group(引脚组):实现某个功能的一组 pin 的集合。例如 I2C0 需要 SDA + SCL 两个引脚,驱动里就定义一个 group(如 i2c0_xfer)包含这两个 pin。功能是挂在 group 上而不是单个 pin 上——这是与裸机思维最大的不同。
  • Function(功能):一个可选的复用功能,如 i2c0、gpio、spi1。一个 function 可以对应多个 group(例如 I2C0 可以从两组不同引脚引出,就有 i2c0_xfer 和 i2c0m1_xfer 两个 group 供板级选择)。
  • Map / State(映射/状态):设备树里一个 pinctrl 节点描述的"某客户端设备在某个状态下使用哪些 group + 什么电气配置"。在客户端视角这叫 state,如 default、sleep、idle。
  • 记忆模型:

    在这里插入图片描述

    上图要点:客户端设备以 state 名义引用若干 Group;每个 Group 对应一个 Function,并包含若干 Pin;每个 Pin 再附带 pinconf 电气配置。


    3. 整体架构

    3.1 分层视图

    在这里插入图片描述

    上图把 pinctrl 子系统从上到下分为四层:

  • Client:各类外设驱动,只感知 default / sleep / idle 等 state。
  • pinctrl core:统一维护 device / map / state,通过 pinctrl_ops、pinmux_ops、pinconf_ops 向驱动层派发。
  • SoC pinctrl driver:各平台具体实现,用 pinctrl_desc 描述全部引脚并操作寄存器。
  • Hardware:SoC 引脚控制器寄存器。
  • 此外,pinctrl 还与 GPIO 子系统、Runtime PM 存在横向关联。

    3.2 源码位置

    路径内容
    drivers/pinctrl/core.c pinctrl 核心:注册、map 解析、state 管理
    drivers/pinctrl/pinmux.c function/group 管理,复用冲突检查
    drivers/pinctrl/pinconf.c pinconf 配置分发与 debugfs
    drivers/pinctrl/devicetree.c 设备树 pinctrl-x 属性解析
    include/linux/pinctrl/ 核心头文件(consumer.h、machine.h、pinmux.h、pinconf.h 等)
    drivers/pinctrl/pinctrl-*.c 各 SoC 的 pinctrl 驱动
    Documentation/devicetree/bindings/pinctrl/ 各平台 pinctrl bindings 文档

    3.3 与相关子系统的关系

    • 与 GPIO 子系统:GPIO 引脚本质上也是"复用功能的一种"(function = gpio)。pinctrl 驱动通过 gpio-ranges 声明哪些 pin 对应哪些 GPIO 编号,gpiolib 在 gpio_request() 时回调 pinctrl_gpio_request(),防止"引脚已被复用为 I2C 却被当 GPIO 申请"的冲突。
    • 与设备模型:客户端驱动的 struct device 内部挂着 pins 指针;驱动核心在 probe 前会自动把设备切到 default 状态(自动调用,不需要驱动手写)。
    • 与 runtime PM:设备进入休眠时驱动把 state 切到 sleep,引脚进入低功耗配置(如下拉、输入高阻),唤醒时切回 default。

    4. 核心数据结构(内核视角)

    4.1 驱动侧:描述 SoC

    /* include/linux/pinctrl/pinctrl.h */
    struct pinctrl_desc {
    const char *name;
    const struct pinctrl_pin_desc *pins; /* 本 SoC 全部 pin 的表 */
    unsigned int npins;
    const struct pinctrl_ops *pctlops; /* 组/功能枚举 */
    const struct pinmux_ops *pmxops; /* 复用选择 */
    const struct pinconf_ops *confops; /* 电气配置 */
    struct module *owner;
    /* … */
    };

    注册入口:

    struct pinctrl_dev *devm_pinctrl_register(struct device *dev,
    struct pinctrl_desc *pctldesc,
    void *driver_data);

    4.2 三组操作函数集

    操作集职责关键回调
    struct pinctrl_ops 枚举 group/function,处理 DT 节点 get_groups_count、get_group_name、get_group_pins、dt_node_to_map、dt_free_map
    struct pinmux_ops 复用选择与 GPIO 互通 get_functions_count、set_mux(核心)、gpio_request_enable、strict
    struct pinconf_ops 电气配置读写 pin_config_get、pin_config_set、pin_config_group_set、is_generic

    其中 set_mux 是 pinctrl 驱动真正的"干活"函数:给定 function + group,写复用寄存器把引脚切到对应功能。

    4.3 客户端侧:抽象状态

    struct pinctrl; /* 一个客户端设备的 pinctrl 句柄 */
    struct pinctrl_state; /* 一个状态(如 default / sleep) */

    客户端通过 devm_pinctrl_get(dev) 拿到句柄,pinctrl_lookup_state(p, "sleep") 找到状态,pinctrl_select_state(p, s) 应用。设备树里 pinctrl-names 的每个名字对应一个 pinctrl_state,每个 state 内部是一组 map(功能映射 + 配置列表)。


    5. 设备树绑定:驱动开发者最常接触的部分

    5.1 服务端(pinctrl 控制器节点)

    /* 控制器本身 */
    pinctrl: pinctrl {
    compatible = "rockchip,rk3568-pinctrl";
    rockchip,grf = <&grf>;
    #address-cells = <2>;
    #size-cells = <2>;
    ranges;

    /* 各 bank 子节点,同时是 GPIO 控制器 */
    gpio0: gpio@fdd60000 {
    compatible = "rockchip,gpio-bank";
    reg = <0x0 0xfdd60000 0x0 0x100>;
    gpio-controller;
    #gpio-cells = <2>;
    /* … */
    };
    };

    5.2 引脚配置子节点(板级可复用的"引脚片段")

    &pinctrl {
    i2c0 {
    /* 一个引脚配置片段:group + 电气属性 */
    i2c0_xfer: i2c0-xfer {
    rockchip,pins =
    <0 RK_PB1 RK_FUNC_I2C0_SCL &pcfg_pull_up>,
    <0 RK_PB0 RK_FUNC_I2C0_SDA &pcfg_pull_up>;
    };
    };

    uart2 {
    uart2m0_xfer: uart2m0-xfer {
    rockchip,pins =
    <0 RK_PC7 RK_FUNC_UART2_TX_M0 &pcfg_pull_up>,
    <0 RK_PC6 RK_FUNC_UART2_RX_M0 &pcfg_pull_up>;
    };
    };

    /* 通用电气配置片段 */
    pcfg_pull_up: pcfg-pull-up {
    bias-pull-up;
    };
    pcfg_pull_none: pcfg-pull-none {
    bias-disable;
    };
    };

    注意两点:

    • 节点名采用 kebab-case(i2c0-xfer),label 采用 snake_case(i2c0_xfer)——这是 bindings 的通用约定;
    • 电气属性可以做成公共片段(pcfg_pull_up),被多个引脚配置引用复用。

    5.3 客户端引用方式

    &i2c0 {
    pinctrl-names = "default"; /* state 名字列表 */
    pinctrl-0 = <&i2c0_xfer>; /* 名字对应的 phandle 列表 */
    status = "okay";
    };

    &uart2 {
    pinctrl-names = "default", "sleep";
    pinctrl-0 = <&uart2m0_xfer>;
    pinctrl-1 = <&uart2m0_sleep>; /* 休眠时的引脚配置 */
    status = "okay";
    };

    解析规则(drivers/pinctrl/devicetree.c):

    • pinctrl-names 第 N 个名字 ↔ pinctrl-N 属性,一一对应;
    • 名字 default 是特殊的:设备模型在驱动 probe 之前会自动 select 它;
    • pinctrl-N 里可以挂多个 phandle(多个配置片段合并成一个 state)。

    5.4 常见 state 命名约定

    state 名语义内核自动处理?
    default 正常工作 是,probe 前自动 select
    sleep 休眠低功耗 否,驱动手动切换(配合 runtime/system PM)
    idle 空闲 否,较少用
    init probe 期间使用,之后释放 半自动(pinctrl_bind_pins 处理)

    5.5 各平台风格差异

    平台配置风格例子
    Rockchip 一个属性打包:<bank pin func &cfg-ref> rockchip,pins
    STM32 分离:pinmux 声明 mux + bias,电气属性单独写 pinmux、bias-pull-up、drive-push-pull
    i.MX (fsl) 紧凑数组:<mux_reg conf_reg input_reg mux_val input_val pad_val> fsl,pins
    通用 pinctrl-single 寄存器偏移 + 值的原始数组 pinctrl-single,pins

    虽然风格不同,但概念模型完全一致:都是"把一组 pin 设置成某 function + 某电气配置"。


    6. 客户端驱动如何使用 pinctrl

    6.1 最简单的情况:什么都不用写

    绝大多数驱动只需在 DTS 里写好 pinctrl-names = "default"; pinctrl-0 = <&xxx>; —— 设备模型(drivers/base/pinctrl.c 的 pinctrl_bind_pins())在 probe 前自动完成 get + select。这就是 i2c/spi/serial 驱动里看不到 pinctrl 代码的原因。

    6.2 需要动态切换的驱动

    #include <linux/pinctrl/consumer.h>

    struct my_dev {
    struct pinctrl *pctl;
    struct pinctrl_state *st_active;
    struct pinctrl_state *st_sleep;
    };

    static int my_probe(struct platform_device *pdev)
    {
    struct my_dev *m = /* … */;

    m->pctl = devm_pinctrl_get(&pdev->dev); /* 获取句柄 */
    if (IS_ERR(m->pctl))
    return PTR_ERR(m->pctl);

    m->st_active = pinctrl_lookup_state(m->pctl, "default");
    m->st_sleep = pinctrl_lookup_state(m->pctl, "sleep");
    /* … */
    }

    static int my_suspend(struct device *dev)
    {
    struct my_dev *m = dev_get_drvdata(dev);
    pinctrl_select_state(m->pctl, m->st_sleep); /* 引脚进入低功耗 */
    return 0;
    }

    static int my_resume(struct device *dev)
    {
    struct my_dev *m = dev_get_drvdata(dev);
    pinctrl_select_state(m->pctl, m->st_active);
    return 0;
    }

    6.3 客户端 API 一览

    API用途
    devm_pinctrl_get() 获取设备的 pinctrl 句柄(资源托管,推荐)
    pinctrl_lookup_state() 按名字查找 state
    pinctrl_select_state() 应用一个 state(做 mux + conf)
    devm_pinctrl_get_select_default() 一步到位:get + select “default”
    pinctrl_pm_select_default_state() / _sleep_state() / _idle_state() PM 语义封装,没有对应 state 时不报错
    pinctrl_gpio_request() / pinctrl_gpio_free() 把某个 GPIO 对应的 pin 切到 gpio 功能

    7. 编写一个 SoC 的 pinctrl 驱动(框架视角)

    新平台移植时的最小骨架:

    static const struct pinctrl_pin_desc foo_pins[] = {
    PINCTRL_PIN(0, "P0"), PINCTRL_PIN(1, "P1"), /* … 全部 pin */
    };

    static int foo_get_groups_count(struct pinctrl_dev *pctldev) { ... }
    static const char *foo_get_group_name(struct pinctrl_dev *pctldev, unsigned sel) { ... }
    static int foo_get_group_pins(struct pinctrl_dev *pctldev, unsigned sel,
    const unsigned **pins, unsigned *npins) { ... }
    static int foo_dt_node_to_map(struct pinctrl_dev *pctldev,
    struct device_node *np,
    struct pinctrl_map **map, unsigned *num_maps) { ... }

    static const struct pinctrl_ops foo_pctrl_ops = {
    .get_groups_count = foo_get_groups_count,
    .get_group_name = foo_get_group_name,
    .get_group_pins = foo_get_group_pins,
    .dt_node_to_map = foo_dt_node_to_map,
    .dt_free_map = pinctrl_utils_free_map,
    };

    static int foo_set_mux(struct pinctrl_dev *pctldev, unsigned func, unsigned group)
    {
    /* 查表找到 group 的 pins + 目标 function 编号,写复用寄存器 */
    return 0;
    }

    static const struct pinmux_ops foo_pmxops = {
    .get_functions_count = ... ,
    .get_function_name = ... ,
    .get_function_groups = ... ,
    .set_mux = foo_set_mux,
    .gpio_request_enable = ... ,
    .strict = true, /* 禁止对已复用 pin 再申请 GPIO */
    };

    static const struct pinconf_ops foo_confops = {
    .is_generic = true, /* 使用通用配置参数 */
    .pin_config_get = foo_pinconf_get,
    .pin_config_set = foo_pinconf_set,
    .pin_config_group_set = foo_pinconf_group_set,
    };

    static struct pinctrl_desc foo_desc = {
    .name = "foo-pinctrl",
    .pins = foo_pins,
    .npins = ARRAY_SIZE(foo_pins),
    .pctlops = &foo_pctrl_ops,
    .pmxops = &foo_pmxops,
    .confops = &foo_confops,
    .owner = THIS_MODULE,
    };

    static int foo_pinctrl_probe(struct platform_device *pdev)
    {
    /* 映射寄存器、初始化私有数据,然后: */
    return PTR_ERR_OR_ZERO(devm_pinctrl_register(&pdev->dev, &foo_desc, priv));
    }

    要点:

  • dt_node_to_map 是最花功夫的回调:把板级 DTS 的引脚片段翻译成内核 map。很多平台用 pinconf_generic_dt_node_to_map_group() 辅助,或干脆用 pinctrl-single 这类通用驱动免除自写。
  • .strict = true 强烈推荐:启用复用保护,pin 已被 mux 为外设功能时拒绝 GPIO 申请,能提前暴露大量板级配置错误。
  • GPIO 互通:pinctrl 驱动常和 GPIO 驱动是一对(一个 bank 一个 gpio_chip),通过 gpio-ranges 把两个子系统连起来。
  • 7.1 pinctrl-single:不想写驱动的替代方案

    对于"引脚配置就是往某偏移寄存器写值"的简单控制器,可直接用通用驱动 pinctrl-single:

    &pinmux {
    pinctrl-single,pins = <
    0x1a0 (PIN_INPUT_PULLUP | MUX_MODE2) /* uart0_rxd */
    0x1a4 (PIN_OUTPUT | MUX_MODE2) /* uart0_txd */
    >;
    };

    DTS 直接描述"偏移 + 值",内核里不用新增任何 C 代码——AM335x、OMAP、部分 RISC-V SoC 都是这种模式。


    8. 调试手段

    8.1 debugfs(首选)

    挂载 debugfs 后,/sys/kernel/debug/pinctrl/ 下每个 pinctrl 控制器一个目录:

    # 列出所有 pin 及其当前 owner/mux 状态
    cat /sys/kernel/debug/pinctrl/*/pins

    # 查看当前活跃的 mux 映射
    cat /sys/kernel/debug/pinctrl/*/pinmux-pins

    # 查看每个 function ↔ group 映射
    cat /sys/kernel/debug/pinctrl/*/pinmux-functions

    # 查看每个 pin 的电气配置(需要 pinconf debug 支持)
    cat /sys/kernel/debug/pinctrl/*/pinconf-pins

    pins 输出的典型形态:

    pin 32 (gpio1-0): device 3ff20000.i2c function i2c0 group i2c0-xfer
    pin 33 (gpio1-1): GPIO gpio1

    一行就能看到:引脚被谁占用、复用成什么功能、属于哪个 group——排查"引脚没反应"类问题第一刀就砍这里。

    8.2 动态调试

    echo 'file drivers/pinctrl/* +p' > /sys/kernel/debug/dynamic_debug/control

    8.3 常见故障速查表

    现象大概率原因排查动作
    probe 报 -EBUSY 申请 pinctrl 失败 同一组 pin 被两个设备同时引用 看 debugfs pins 里 owner 是谁
    外设无响应但无报错 group 选错(如用了 m0 引脚组,实际板子走 m1) 对照原理图核对 bank/pin 编号
    信号有但幅度/速率异常 drive-strength / slew-rate 不对 查 pinconf,调电气属性
    休眠唤醒后外设死掉 sleep state 切过去没切回来,或 sleep 配置写错 检查 PM 回调里的 select_state
    GPIO 操作影响外设 没开 .strict,GPIO 与 mux 冲突未被发现 pinctrl 驱动加 strict
    DTS 改了不生效 客户端节点没写 pinctrl-0 引用,或 label 拼错 检查编译出的 dtb(fdtdump)

    9. 与其他系统的横向对比

    维度裸机/RTOS 常见做法Linux pinctrl
    配置位置 散落在各驱动 init 函数里直接写寄存器 集中在设备树,驱动只声明"要什么状态"
    复用冲突 靠人工 review,运行期才发现 内核自动检查,申请时即报错
    功耗联动 手写 suspend 里重新配引脚 state 抽象 + PM 框架联动
    可移植性 换板子改一堆驱动代码 只改 DTS,驱动零改动

    对 RT-Thread 等 RTOS 而言,引脚配置通常是各 BSP 的 pinmux.c + 一个 rt_pin_* GPIO 框架,复用管理远弱于 Linux pinctrl——这也是 Linux 在复杂 SoC 上的结构性优势之一。


    10. 总结:一张图记住 pinctrl

    在这里插入图片描述

    整个流程可以概括为:设备树里的 控制器节点 定义引脚配置片段,客户端设备节点 通过 pinctrl-N = <&片段> 引用这些片段;内核解析后交给 pinctrl core 管理,再经 SoC pinctrl 驱动 的 ops 回调写入寄存器,最终在 SoC 硬件引脚 上生效。

    三句话收束:

  • pinctrl = pinmux(功能复用)+ pinconf(电气配置) 的统一资源管理框架;
  • 对驱动开发者,90% 的工作是在 DTS 里写对 group、function、电气属性和 state 引用;
  • 排障时 debugfs 的 pins / pinmux-pins 是最直接的事实来源。

  • 附录:常用配置属性速查(generic pinconf)

    DTS 属性含义
    bias-disable 关闭上下拉
    bias-pull-up / bias-pull-down 上拉 / 下拉
    bias-pull-pin-default 使用引脚默认上下拉
    drive-push-pull / drive-open-drain / drive-open-source 推挽 / 开漏 / 开源输出
    drive-strength = <mA>; 驱动电流强度
    input-enable / input-disable 输入使能
    input-schmitt-enable 施密特触发输入
    slew-rate = <n>; 边沿速率档位
    output-high / output-low 输出电平

    绑定文档:Documentation/devicetree/bindings/pinctrl/pincfg-node.yaml

    赞(0)
    未经允许不得转载:171主机测评 » linux 中的 pinctrl 子系统
    分享到: 更多 (0)

    评论 抢沙发

    • 昵称 (必填)
    • 邮箱 (必填)
    • 网址