Linux驱动开发实战(八):Pinctrl驱动中pins和npins的传递流程以及引脚状态记录详解
文章目录
- Linux驱动开发实战(八):Pinctrl驱动中pins和npins的传递流程以及引脚状态记录详解
- 前言
- 一、数据传递流程详解
- 1.1 定义阶段
- 1.2 匹配与初始化阶段
- 1.3 注册阶段
- 1.4 图解
- 二、数据匹配
- 三、基数树(Radix Tree)获取引脚状态
- 初始化和注册
- 作用
- 参数解析
- 核心代码逻辑
- pinctrl 子系统初始化的基石
- 代码逻辑分解
- 参数合法性检查
- 内存分配与基础初始化
- 数据结构初始化
- 操作集合法性校验
- 引脚注册
- 小结
- pinctrl_register_pins:引脚批量注册函数
- 函数定位与调用关系
- 参数解析
- 执行流程
- 底层操作 pinctrl_register_one_pin(单个引脚注册)
- 应用场景
- 图解
- 总结
前言
本文将深入剖析i.MX6UL系列处理器的pinctrl驱动中,pins和npins这两个关键数据的传递流程以及获取引脚状态和注册引脚,我们可以更好地理解Linux内核中pinctrl子系统的工作机制,为后续的驱动开发打下坚实基础。
一、数据传递流程详解
1.1 定义阶段
首先,驱动中通过枚举定义了所有的引脚:
enum imx6ul_pads {MX6UL_PAD_RESERVE0 = 0,MX6UL_PAD_RESERVE1 = 1,MX6UL_PAD_GPIO1_IO00 = 2,MX6UL_PAD_GPIO1_IO01 = 3,// ... 更多引脚定义
};
然后,使用IMX_PINCTRL_PIN宏创建引脚描述数组:
static const struct pinctrl_pin_desc imx6ul_pinctrl_pads[] = {IMX_PINCTRL_PIN(MX6UL_PAD_RESERVE0),IMX_PINCTRL_PIN(MX6UL_PAD_RESERVE1),IMX_PINCTRL_PIN(MX6UL_PAD_GPIO1_IO00),IMX_PINCTRL_PIN(MX6UL_PAD_GPIO1_IO01),// ... 更多引脚
};
其中,IMX_PINCTRL_PIN宏的定义通常如下:
#define IMX_PINCTRL_PIN(pin) \{ .number = pin, .name = #pin, }
这个宏将枚举值转换为struct pinctrl_pin_desc类型的结构体,包含引脚的编号和名称。
接下来,定义控制器信息结构体:
static struct imx_pinctrl_soc_info imx6ul_pinctrl_info = {.pins = imx6ul_pinctrl_pads,.npins = ARRAY_SIZE(imx6ul_pinctrl_pads),// ... 其他字段
};
在这里,.pins字段指向引脚描述数组,.npins字段通过ARRAY_SIZE宏获取数组大小,即引脚的数量。
1.2 匹配与初始化阶段
在驱动的匹配阶段,通过设备树匹配表将设备树节点与控制器信息关联:
static const struct of_device_id imx6ul_pinctrl_of_match[] = {{ .compatible = "fsl,imx6ul-iomuxc", .data = &imx6ul_pinctrl_info },{ .compatible = "fsl,imx6ull-iomuxc-snvs", .data = &imx6ull_snvs_pinctrl_info },{ /* sentinel */ }
};
MODULE_DEVICE_TABLE(of, imx6ul_pinctrl_of_match);
当驱动的probe函数被调用时,通过匹配获取控制器信息:
static int imx6ul_pinctrl_probe(struct platform_device *pdev)
{const struct of_device_id *match;struct imx_pinctrl_soc_info *pinctrl_info;match = of_match_device(imx6ul_pinctrl_of_match, &pdev->dev);if (!match)return -ENODEV;pinctrl_info = (struct imx_pinctrl_soc_info *)match->data;// 此时 pinctrl_info->pins 指向 imx6ul_pinctrl_pads// pinctrl_info->npins 等于 ARRAY_SIZE(imx6ul_pinctrl_pads)return imx_pinctrl_probe(pdev, pinctrl_info);
}
在这个函数中,首先通过of_match_device获取匹配的设备树节点和关联的数据,然后从match->data中获取控制器信息结构体指针。
通过 of_match_device 获取匹配的设备树节点信息
直接返回该匹配项的 data 成员(即绑定到该设备的驱动私有数据)
1.3 注册阶段
static int imx6ul_pinctrl_probe(struct platform_device *pdev)
{const struct imx_pinctrl_soc_info *pinctrl_info;const struct of_device_id *match;pinctrl_info = of_device_get_match_data(&pdev->dev);if (!pinctrl_info)return -ENODEV;match = of_match_device(imx6ul_pinctrl_of_match, &pdev->dev);if (!match)return -ENODEV;pinctrl_info = (struct imx_pinctrl_soc_info *) match->data;return imx_pinctrl_probe(pdev, pinctrl_info);
}
在这个函数中,将控制器信息中的pins和npins赋值给pinctrl描述符:
1.4 图解
跟上一讲一样以MX6UL_PAD_UART1_TX_DATA为例
数据手册里面的偏移地址84跟上面的引脚值是对应上的
MX6UL_PAD_UART1_TX_DATA = 33,
33*4=132
十六进制就是84
通过这个流程,引脚名称和数量信息最终被传递给内核的 pinctrl 子系统,从而使系统能够识别和操作这些引脚。
二、数据匹配
在device.c中
const void *of_device_get_match_data(const struct device *dev)
{
const struct of_device_id *match;match = of_match_device(dev->driver->of_match_table, dev);
if (!match)return NULL;return match->data;
}
这个函数 of_device_get_match_data 与前面描述的 i.MX6UL pinctrl 驱动中的数据传递流程有着密切的关联。它实际上是一个辅助函数,用于简化在设备驱动中获取匹配数据的过程。
const struct of_device_id *of_match_device(const struct of_device_id *matches,
const struct device *dev)
{
if ((!matches) || (!dev->of_node))
return NULL;
return of_match_node(matches, dev->of_node);
}
EXPORT_SYMBOL(of_match_device);
of_match_device 函数是设备驱动和设备树之间的桥梁。它的具体功能和工作原理如下:
-
函数目的:of_match_device 根据设备关联的设备树节点,在驱动的兼容性表中查找匹配项。
-
参数:
- matches:指向 struct of_device_id 结构体数组的指针(驱动的兼容性表)
- dev:需要匹配的设备结构体
- 工作流程:
- 首先检查 matches 表和设备的 of_node(设备树节点)是否都存在
- 如果任何一个为 NULL,则返回 NULL(无法进行匹配)
- 否则调用 of_match_node() 函数为给定的设备树节点查找 matches 表中的第一个匹配项
- 与 i.MX6UL pinctrl 流程的关联:
- 在前面的例子中,这个函数在 imx6ul_pinctrl_probe 函数中被用来查找 imx6ul_pinctrl_of_match 表中哪一项与设备树节点对应
- 匹配到的表项包含一个 .data 指针,指向适当的 imx_pinctrl_soc_info 结构体(该结构体包含 pins 和 npins 信息)
完整的数据流程是:
- 设备树节点(如 “fsl,imx6ul-iomuxc”)与 imx6ul_pinctrl_of_match 中的兼容性字符串进行匹配
- of_match_device 找到这个匹配项
- 驱动获取关联的 .data(即 &imx6ul_pinctrl_info)
- 这个结构体包含 pins 指针(指向 imx6ul_pinctrl_pads)和 npins 值
- 这些信息随后通过 imx_pinctrl_probe 传递给 pinctrl 子系统
三、基数树(Radix Tree)获取引脚状态
之前我们讲到如何获得引脚数量和引脚编号,现在讲如何获取引脚的状态,记录管脚的复用状态(如 GPIO 或 UART 模式)、电气特性(如上拉/下拉)等
在drivers/pinctrl/core.c中
初始化和注册
int pinctrl_register_and_init(struct pinctrl_desc *pctldesc,
struct device *dev, void *driver_data,
struct pinctrl_dev **pctldev)
{
struct pinctrl_dev *p;p = pinctrl_init_controller(pctldesc, dev, driver_data);
if (IS_ERR(p))return PTR_ERR(p);/** pinctrl_start() 会通过 create_pinctrl() 调用驱动中的函数,* 至少包括 dt_node_to_map()(用于解析设备树中的管脚配置映射)。* 因此需要确保 pctldev 在控制器驱动操作前正确初始化。*/
*pctldev = p;return 0;
}
EXPORT_SYMBOL_GPL(pinctrl_register_and_init);
注释解释
- dt_node_to_map()
设备树(Device Tree)中管脚配置(如复用功能、电气特性)的解析函数,需要依赖已初始化的 pctldev 结构体 - 延迟注册设计
允许先初始化控制器实例,再通过后续流程(如设备树解析)补充配置,最后完成完整注册
作用
-
初始化核心控制器
通过 pinctrl_init_controller() 创建并初始化一个 pinctrl_dev 结构体
(包含管脚控制器的操作函数集、基数树管理的管脚状态等)下面会细讲 -
延迟注册
仅完成初始化但不立即注册到内核框架中,允许后续补充设备树映射等操作
(实际注册可能由后续的 pinctrl_register() 完成) -
提供控制器句柄
通过输出参数 pctldev 返回初始化后的控制器实例,供驱动后续操作使用
参数解析
- pctldesc:管脚控制器描述符,包含操作函数集(struct pinctrl_ops)、管脚命名规则、基数树配置等
- dev:关联的物理设备结构(通常为 platform_device)
- driver_data:驱动私有数据,透传给控制器初始化函数
- pctldev:输出参数,返回初始化后的 pinctrl_dev 实例指针
核心代码逻辑
struct pinctrl_dev *p;// 1. 调用初始化函数创建控制器实例
p = pinctrl_init_controller(pctldesc, dev, driver_data);// 2. 错误检查(内核常用模式)
if (IS_ERR(p))return PTR_ERR(p);// 3. 将初始化后的控制器实例通过输出参数返回
*pctldev = p;return 0;
pinctrl 子系统初始化的基石
static struct pinctrl_dev *
pinctrl_init_controller(struct pinctrl_desc *pctldesc, struct device *dev,
void *driver_data)
{
struct pinctrl_dev *pctldev;
int ret;if (!pctldesc)return ERR_PTR(-EINVAL);
if (!pctldesc->name)return ERR_PTR(-EINVAL);pctldev = kzalloc(sizeof(*pctldev), GFP_KERNEL);
if (!pctldev)return ERR_PTR(-ENOMEM);/* Initialize pin control device struct */
pctldev->owner = pctldesc->owner;
pctldev->desc = pctldesc;
pctldev->driver_data = driver_data;
INIT_RADIX_TREE(&pctldev->pin_desc_tree, GFP_KERNEL);
#ifdef CONFIG_GENERIC_PINCTRL_GROUPS
INIT_RADIX_TREE(&pctldev->pin_group_tree, GFP_KERNEL);
#endif
#ifdef CONFIG_GENERIC_PINMUX_FUNCTIONS
INIT_RADIX_TREE(&pctldev->pin_function_tree, GFP_KERNEL);
#endif
INIT_LIST_HEAD(&pctldev->gpio_ranges);
INIT_LIST_HEAD(&pctldev->node);
pctldev->dev = dev;
mutex_init(&pctldev->mutex);/* check core ops for sanity */
ret = pinctrl_check_ops(pctldev);
if (ret) {dev_err(dev, "pinctrl ops lacks necessary functions\n");goto out_err;
}/* If we're implementing pinmuxing, check the ops for sanity */
if (pctldesc->pmxops) {ret = pinmux_check_ops(pctldev);if (ret)goto out_err;
}/* If we're implementing pinconfig, check the ops for sanity */
if (pctldesc->confops) {ret = pinconf_check_ops(pctldev);if (ret)goto out_err;
}/* Register all the pins */
dev_dbg(dev, "try to register %d pins ...\n", pctldesc->npins);
ret = pinctrl_register_pins(pctldev, pctldesc->pins, pctldesc->npins);
if (ret) {dev_err(dev, "error during pin registration\n");pinctrl_free_pindescs(pctldev, pctldesc->pins,pctldesc->npins);goto out_err;
}return pctldev;
查看全部
out_err:
mutex_destroy(&pctldev->mutex);
kfree(pctldev);
return ERR_PTR(ret);
}
- 创建控制器实例
为 pinctrl_dev 分配内存并初始化其核心数据结构 - 操作集合法性校验
确保驱动提供的 pinctrl_ops、pinmux_ops、pinconf_ops 函数集符合内核要求 - 注册物理引脚
将 pinctrl_desc 中定义的引脚描述符(struct pinctrl_pin_desc)注册到基数树中管理
代码逻辑分解
参数合法性检查
if (!pctldesc)return ERR_PTR(-EINVAL);
if (!pctldesc->name)return ERR_PTR(-EINVAL);
- 必要性校验
确保传入的 pctldesc 非空且包含有效的名称(name 字段),避免后续操作出现空指针
内存分配与基础初始化
pctldev = kzalloc(sizeof(*pctldev), GFP_KERNEL);
pctldev->owner = pctldesc->owner;
pctldev->desc = pctldesc;
pctldev->driver_data = driver_data;
pctldev->dev = dev;
- 内存分配
使用 kzalloc 分配归零内存,确保结构体初始状态确定 - 关键字段赋值
关联控制器描述符(desc)、设备指针(dev)、驱动私有数据(driver_data)
数据结构初始化
INIT_RADIX_TREE(&pctldev->pin_desc_tree, GFP_KERNEL);
#ifdef CONFIG_GENERIC_PINCTRL_GROUPS
INIT_RADIX_TREE(&pctldev->pin_group_tree, GFP_KERNEL);
#endif
#ifdef CONFIG_GENERIC_PINMUX_FUNCTIONS
INIT_RADIX_TREE(&pctldev->pin_function_tree, GFP_KERNEL);
#endif
- 基数树初始化
- pin_desc_tree:存储所有引脚的基础描述(名称、编号等)
- pin_group_tree(条件编译):管理引脚分组(如 GPIO Bank)
- pin_function_tree(条件编译):管理引脚复用功能(如 UART、SPI)
- 设计意图
基数树提供高效的动态键值查找能力,适合快速访问海量引脚的状态信息
操作集合法性校验
ret = pinctrl_check_ops(pctldev); // 检查 pinctrl_ops 基本操作
if (pctldesc->pmxops) // 如果支持 pinmuxret = pinmux_check_ops(pctldev); // 检查 pinmux_ops 操作集
if (pctldesc->confops) // 如果支持 pinconfret = pinconf_check_ops(pctldev); // 检查 pinconf_ops 操作集
- 核心校验逻辑
- pinctrl_check_ops:确保必须的 get_groups_count、get_group_name 等函数存在
- pinmux_check_ops:验证 get_functions_count、get_function_name 等
- pinconf_check_ops:确认 pin_config_get、pin_config_set 等配置接口有效
- 防御性编程
避免驱动未实现必要接口导致内核崩溃
引脚注册
ret = pinctrl_register_pins(pctldev, pctldesc->pins, pctldesc->npins);
if (ret) {pinctrl_free_pindescs(...); // 回滚:释放已注册的引脚描述符goto out_err;
}
- 核心注册流程
遍历 pctldesc->pins 数组,为每个引脚:
- 分配 struct pin_desc 结构体
- 将引脚名称和编号插入 pin_desc_tree 基数树
- 初始化引脚的互斥锁和状态标记
- 错误回滚机制
注册失败时释放已分配的资源,避免内存泄漏
小结
这两个函数共同构成了 pinctrl 控制器的初始化流程:
pinctrl_init_controller 是技术实现的「建造者」,负责具体施工
pinctrl_register_and_init 是流程控制的「协调者」,负责接口封装和阶段衔接
调用如下
// 高层接口(驱动直接调用)
int pinctrl_register_and_init(...) {// 调用底层实现p = pinctrl_init_controller(...);if (IS_ERR(p))return PTR_ERR(p); // 错误码转换*pctldev = p; // 返回控制器实例return 0;
}
pinctrl_register_pins:引脚批量注册函数
上面也有讲过这个函数,在其他函数里面调用,但是我觉得这个函数比较重要,所以还是要再讲讲
pinctrl_register_pins 是 Linux 内核中 pinctrl 子系统的 引脚批量注册函数,其核心作用是将一组物理引脚(pinctrl_pin_desc)注册到指定的管脚控制器(pinctrl_dev)中
函数定位与调用关系
- 调用位置
由 pinctrl_init_controller 调用,完成控制器初始化流程中的 引脚注册阶段
// 在 pinctrl_init_controller 中:
ret = pinctrl_register_pins(pctldev, pctldesc->pins, pctldesc->npins);
- 设计目的
提供一种 批量注册机制,避免驱动逐个注册引脚的低效操作
参数解析
执行流程
- 循环遍历引脚数组
for (i = 0; i < num_descs; i++) {ret = pinctrl_register_one_pin(pctldev, &pins[i]);if (ret)return ret; // 严格错误处理
}
- 对每个引脚调用 pinctrl_register_one_pin 执行单引脚注册
- 原子化错误处理
任意单个引脚注册失败立即终止流程,返回错误码
底层操作 pinctrl_register_one_pin(单个引脚注册)
1、分配引脚描述符
struct pin_desc *pd = kzalloc(sizeof(*pd), GFP_KERNEL);
为每个引脚分配独立的 pin_desc 结构体
2、填充元数据
pd->pctldev = pctldev; // 关联控制器
pd->pin = pins[i].number; // 记录物理引脚编号
pd->name = kstrdup(pins[i].name, GFP_KERNEL); // 复制名称
mutex_init(&pd->mutex); // 初始化互斥锁
INIT_LIST_HEAD(&pd->functions); // 初始化复用功能链表
3、插入基数树
radix_tree_insert(&pctldev->pin_desc_tree, pd->pin, pd);
- 将引脚按编号插入控制器的 pin_desc_tree 基数树,实现 O(1) 时间复杂度查找
应用场景
// 驱动定义引脚描述(示例)
static const struct pinctrl_pin_desc my_soc_pins[] = {PINCTRL_PIN(0, "gpio0"),PINCTRL_PIN(1, "uart0_rx"),PINCTRL_PIN(2, "uart0_tx"),
};// 控制器描述符
static struct pinctrl_desc my_pinctrl_desc = {.name = "my-pinctrl",.pins = my_soc_pins,.npins = ARRAY_SIZE(my_soc_pins),// 其他操作集...
};// 初始化流程
pinctrl_register_pins(pctldev, my_pinctrl_desc.pins, my_pinctrl_desc.npins);
图解
总结
本章节主要讲了,如何获取pin和npin还获取引脚的状态,后面讲了如何注册引脚。通过分析 pinctrl 子系统的初始化流程,我们深入理解了 Linux 内核如何以 高效、安全 的方式管理硬件资源。掌握这些核心函数的设计思想,不仅能帮助开发者我们编写稳定的驱动程序,还能为调试复杂硬件问题提供理论依据。