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 子系统交互。
4.6 驱动注册流程
一个典型的 pinctrl 驱动注册流程如下:
- 定义引脚描述数组:填充
struct pinctrl_pin_desc数组,为每个引脚分配编号和名称。 - 实现三组 ops:根据硬件手册实现
pinctrl_ops、pinmux_ops和pinconf_ops中的所有回调函数。 - 填充 pinctrl_desc:将引脚数组、三组 ops 指针以及控制器名称填入
pinctrl_desc结构体。 - 注册 pinctrl 设备:调用
devm_pinctrl_register()或pinctrl_register()将控制器注册到内核。 - 添加 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。