4. pinctrl 驱动框架:pinctrl_desc、pinctrl_ops、pinmux_ops、pinconf_ops

pinctrl 子系统是 Linux 内核中负责管理引脚复用、电气属性配置和引脚控制的核心框架。其驱动模型围绕一个核心数据结构 pinctrl_desc 展开,并通过三组操作函数集(ops)分别实现引脚信息查询、复用功能配置和电气参数控制。理解这一框架是进行 GPIO 控制与中断调试的基础。

4.1 核心数据结构:pinctrl_desc

pinctrl_desc 是 pinctrl 驱动的“身份证”,它向内核描述一个引脚控制器的名称、支持的引脚数量、所属芯片以及三组操作函数指针。驱动开发者需要填充该结构体并调用 pinctrl_register() 将其注册到内核中。

成员 类型 说明
name const char * 引脚控制器名称,用于设备树匹配和调试
pins const struct pinctrl_pin_desc * 引脚描述数组,定义每个引脚的编号和名称
npins unsigned int 引脚总数
pctlops const struct pinctrl_ops * 引脚控制器操作集,提供引脚枚举和组查询
pmxops const struct pinmux_ops * 引脚复用操作集,负责功能选择和复用冲突检查
confops const struct pinconf_ops * 引脚配置操作集,处理上下拉、驱动强度等电气参数
owner struct module * 所属模块,通常设置为 THIS_MODULE
关键点: 一个完整的 pinctrl 驱动必须同时实现 pctlops、pmxops 和 confops 三组操作,缺一不可。即使某些操作为空函数,也需要提供占位指针。

4.2 pinctrl_ops:引脚控制器操作

pinctrl_ops 负责向 pinctrl 核心层提供引脚控制器的基本信息,包括引脚枚举、引脚组查询以及设备树中的引脚映射解析。这是框架识别硬件引脚布局的入口。

struct pinctrl_ops {
    int (*get_groups_count)(struct pinctrl_dev *pctldev);
    const char *(*get_group_name)(struct pinctrl_dev *pctldev,
                                  unsigned int selector);
    int (*get_group_pins)(struct pinctrl_dev *pctldev,
                          unsigned int selector,
                          const unsigned int **pins,
                          unsigned int *num_pins);
    void (*pin_dbg_show)(struct pinctrl_dev *pctldev,
                         struct seq_file *s,
                         unsigned int offset);
    int (*dt_node_to_map)(struct pinctrl_dev *pctldev,
                          struct device_node *np,
                          struct pinctrl_map **map,
                          unsigned int *num_maps);
    void (*dt_free_map)(struct pinctrl_dev *pctldev,
                        struct pinctrl_map *map,
                        unsigned int num_maps);
};
  • get_groups_count / get_group_name / get_group_pins:提供引脚组(pin group)的枚举能力。一个引脚组可以包含一个或多个物理引脚,用于实现特定功能(如 I2C 总线需要 SDA 和 SCL 两个引脚)。
  • dt_node_to_map / dt_free_map:将设备树中的 pinctrl 节点解析为内核可用的 pinctrl_map 结构。这是设备树与驱动代码之间的桥梁。
  • pin_dbg_show:调试接口,在 /sys/kernel/debug/pinctrl 中显示引脚状态。
调试技巧: 当引脚复用不生效时,首先检查 dt_node_to_map 是否正确解析了设备树中的 pinctrl-0 和 pinctrl-names 属性。可以通过 cat /sys/kernel/debug/pinctrl/*/pins 查看当前引脚映射状态。

4.3 pinmux_ops:引脚复用操作

pinmux_ops 是 pinctrl 框架中最核心的操作集,它负责将物理引脚切换到指定的功能模式(如 GPIO、UART、I2C 等)。复用功能的正确配置直接决定了外设能否正常工作。

struct pinmux_ops {
    int (*request)(struct pinctrl_dev *pctldev,
                   unsigned int offset);
    int (*free)(struct pinctrl_dev *pctldev,
                unsigned int offset);
    int (*get_functions_count)(struct pinctrl_dev *pctldev);
    const char *(*get_function_name)(struct pinctrl_dev *pctldev,
                                     unsigned int selector);
    int (*get_function_groups)(struct pinctrl_dev *pctldev,
                               unsigned int selector,
                               const char * const **groups,
                               unsigned int *num_groups);
    int (*set_mux)(struct pinctrl_dev *pctldev,
                   unsigned int function_selector,
                   unsigned int group_selector);
    int (*gpio_request_enable)(struct pinctrl_dev *pctldev,
                               struct pinctrl_gpio_range *range,
                               unsigned int offset);
    void (*gpio_disable_free)(struct pinctrl_dev *pctldev,
                              struct pinctrl_gpio_range *range,
                              unsigned int offset);
    int (*gpio_set_direction)(struct pinctrl_dev *pctldev,
                              struct pinctrl_gpio_range *range,
                              unsigned int offset,
                              bool input);
};
  • set_mux:核心函数,将指定的功能(function)应用到指定的引脚组(group)上。例如将 UART0_TX 功能复用到 GPIO1_12 引脚。
  • gpio_request_enable / gpio_disable_free / gpio_set_direction:GPIO 与 pinctrl 的交互接口。当 GPIO 驱动请求一个引脚时,pinctrl 需要将该引脚从复用功能切换回 GPIO 模式,并设置方向。
  • request / free:引脚占用与释放的回调,用于实现引脚独占访问控制。
常见错误: 如果某个外设驱动在 probe 时返回 -EBUSY,通常是因为引脚已经被其他功能占用。检查 request 回调是否实现了引脚冲突检测,以及设备树中是否存在重复的 pinctrl 配置。

4.4 pinconf_ops:引脚配置操作

pinconf_ops 负责管理引脚的电气特性,包括上下拉电阻、驱动强度、施密特触发器、 slew rate 等参数。这些配置直接影响信号质量和功耗。

struct pinconf_ops {
    int (*pin_config_get)(struct pinctrl_dev *pctldev,
                          unsigned int offset,
                          unsigned long *config);
    int (*pin_config_set)(struct pinctrl_dev *pctldev,
                          unsigned int offset,
                          unsigned long config);
    int (*pin_config_group_get)(struct pinctrl_dev *pctldev,
                                unsigned int selector,
                                unsigned long *config);
    int (*pin_config_group_set)(struct pinctrl_dev *pctldev,
                                unsigned int selector,
                                unsigned long config);
    int (*pin_config_dbg_show)(struct pinctrl_dev *pctldev,
                               struct seq_file *s,
                               unsigned int offset);
    void (*pin_config_group_dbg_show)(struct pinctrl_dev *pctldev,
                                      struct seq_file *s,
                                      unsigned int selector);
};
  • pin_config_set / pin_config_get:单个引脚的配置读写接口。参数 config 是一个编码后的 unsigned long 值,通常包含参数类型和数值。
  • pin_config_group_set / pin_config_group_get:批量配置一组引脚,提高效率。
  • pin_config_dbg_show:调试输出,在 debugfs 中显示每个引脚的当前配置状态。
参数编码约定: 内核提供了 PIN_CONFIG_BIAS_PULL_UP、PIN_CONFIG_DRIVE_STRENGTH 等标准宏定义引脚配置参数。驱动应使用 pinconf_to_config_param() 和 pinconf_to_config_argument() 进行编解码,确保跨平台兼容性。

4.5 驱动框架整体架构

下图展示了 pinctrl 驱动框架中 pinctrl_desc 与三组 ops 之间的关系,以及它们如何与 pinctrl 核心层、GPIO 子系统交互。

pinctrl 驱动框架架构图 pinctrl_desc pinctrl_ops pinmux_ops pinconf_ops pinctrl_ops 回调 • get_groups_count / get_group_name • get_group_pins • dt_node_to_map / dt_free_map pinmux_ops 回调 • set_mux(核心复用函数) • gpio_request_enable • gpio_set_direction pinconf_ops 回调 • pin_config_set / pin_config_get • pin_config_group_set • pin_config_dbg_show pinctrl 核心层 GPIO 子系统 硬件引脚控制器(SoC 特定实现)

4.6 驱动注册流程

一个典型的 pinctrl 驱动注册流程如下:

  1. 定义引脚描述数组:填充 struct pinctrl_pin_desc 数组,为每个引脚分配编号和名称。
  2. 实现三组 ops:根据硬件手册实现 pinctrl_ops、pinmux_ops 和 pinconf_ops 中的所有回调函数。
  3. 填充 pinctrl_desc:将引脚数组、三组 ops 指针以及控制器名称填入 pinctrl_desc 结构体。
  4. 注册 pinctrl 设备:调用 devm_pinctrl_register() 或 pinctrl_register() 将控制器注册到内核。
  5. 添加 GPIO 范围:如果该控制器也支持 GPIO 功能,需要调用 gpiochip_add_pin_range() 建立 GPIO 编号与引脚编号的映射关系。
static const struct pinctrl_pin_desc my_pins[] = {
    PINCTRL_PIN(0, "GPIO0"),
    PINCTRL_PIN(1, "GPIO1"),
    PINCTRL_PIN(2, "GPIO2"),
    /* ... */
};

static struct pinctrl_desc my_pinctrl_desc = {
    .name   = "my-pinctrl",
    .pins   = my_pins,
    .npins  = ARRAY_SIZE(my_pins),
    .pctlops = &my_pctlops,
    .pmxops  = &my_pmxops,
    .confops = &my_confops,
    .owner  = THIS_MODULE,
};

static int my_pinctrl_probe(struct platform_device *pdev)
{
    struct pinctrl_dev *pctldev;

    pctldev = devm_pinctrl_register(&pdev->dev, &my_pinctrl_desc, NULL);
    if (IS_ERR(pctldev))
        return PTR_ERR(pctldev);

    /* 注册 GPIO 范围 */
    pinctrl_add_gpio_range(pctldev, &my_gpio_range);

    return 0;
}
推荐实践: 优先使用 devm_pinctrl_register() 而非 pinctrl_register(),前者支持设备资源管理,在驱动卸载时自动注销,避免资源泄漏。

4.7 调试与验证

注册完成后,可以通过以下方式验证 pinctrl 驱动是否正确工作:

调试方法 命令/路径 验证内容
查看注册的控制器 /sys/kernel/debug/pinctrl/pinctrl-handles 确认控制器名称和引脚数量正确
查看引脚状态 /sys/kernel/debug/pinctrl/*/pins 检查每个引脚的当前复用功能和配置参数
查看引脚组 /sys/kernel/debug/pinctrl/*/pinmux-pins 确认引脚组与功能的映射关系
查看 GPIO 范围 /sys/kernel/debug/gpio 验证 GPIO 编号与 pinctrl 引脚的对应关系
注意事项: 如果 /sys/kernel/debug/pinctrl 目录为空,说明内核未开启 CONFIG_DEBUG_FS 或 pinctrl 驱动注册失败。检查 dmesg 中的错误信息,确认 pinctrl_register 返回值是否为 NULL。