Linux驱动开发实战(八):Pinctrl驱动中pins和npins的传递流程以及引脚状态记录详解

embedded/2025/3/22 9:51:02/

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 函数是设备驱动和设备树之间的桥梁。它的具体功能和工作原理如下:

  1. 函数目的:of_match_device 根据设备关联的设备树节点,在驱动的兼容性表中查找匹配项。

  2. 参数

  • matches:指向 struct of_device_id 结构体数组的指针(驱动的兼容性表)
  • dev:需要匹配的设备结构体
  1. 工作流程
  • 首先检查 matches 表和设备的 of_node(设备树节点)是否都存在
  • 如果任何一个为 NULL,则返回 NULL(无法进行匹配)
  • 否则调用 of_match_node() 函数为给定的设备树节点查找 matches 表中的第一个匹配项
  1. 与 i.MX6UL pinctrl 流程的关联
  • 在前面的例子中,这个函数在 imx6ul_pinctrl_probe 函数中被用来查找 imx6ul_pinctrl_of_match 表中哪一项与设备树节点对应
  • 匹配到的表项包含一个 .data 指针,指向适当的 imx_pinctrl_soc_info 结构体(该结构体包含 pins 和 npins 信息)
    完整的数据流程是:
  1. 设备树节点(如 “fsl,imx6ul-iomuxc”)与 imx6ul_pinctrl_of_match 中的兼容性字符串进行匹配
  2. of_match_device 找到这个匹配项
  3. 驱动获取关联的 .data(即 &imx6ul_pinctrl_info
  4. 这个结构体包含 pins 指针(指向 imx6ul_pinctrl_pads)和 npins
  5. 这些信息随后通过 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 结构体
  • 延迟注册设计
    允许先初始化控制器实例,再通过后续流程(如设备树解析)补充配置,最后完成完整注册

作用

  1. 初始化核心控制器
    通过 pinctrl_init_controller() 创建并初始化一个 pinctrl_dev 结构体
    (包含管脚控制器的操作函数集、基数树管理的管脚状态等)下面会细讲

  2. 延迟注册
    仅完成初始化但不立即注册到内核框架中,允许后续补充设备树映射等操作
    (实际注册可能由后续的 pinctrl_register() 完成)

  3. 提供控制器句柄
    通过输出参数 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);
}
  1. 创建控制器实例
    pinctrl_dev 分配内存并初始化其核心数据结构
  2. 操作集合法性校验
    确保驱动提供的 pinctrl_ops、pinmux_ops、pinconf_ops 函数集符合内核要求
  3. 注册物理引脚
    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
  1. 基数树初始化
  • pin_desc_tree:存储所有引脚的基础描述(名称、编号等)
  • pin_group_tree(条件编译):管理引脚分组(如 GPIO Bank)
  • pin_function_tree(条件编译):管理引脚复用功能(如 UART、SPI)
  1. 设计意图
    基数树提供高效的动态键值查找能力,适合快速访问海量引脚的状态信息
操作集合法性校验
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 操作集
  1. 核心校验逻辑
  • 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 等配置接口有效
  1. 防御性编程
    避免驱动未实现必要接口导致内核崩溃
引脚注册
ret = pinctrl_register_pins(pctldev, pctldesc->pins, pctldesc->npins);
if (ret) {pinctrl_free_pindescs(...);  // 回滚:释放已注册的引脚描述符goto out_err;
}
  • 核心注册流程

遍历 pctldesc->pins 数组,为每个引脚:

  1. 分配 struct pin_desc 结构体
  2. 将引脚名称和编号插入 pin_desc_tree 基数树
  3. 初始化引脚的互斥锁和状态标记
  • 错误回滚机制
    注册失败时释放已分配的资源,避免内存泄漏
小结

这两个函数共同构成了 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);
  • 设计目的
    提供一种 批量注册机制,避免驱动逐个注册引脚的低效操作
参数解析

在这里插入图片描述

执行流程
  1. 循环遍历引脚数组
for (i = 0; i < num_descs; i++) {ret = pinctrl_register_one_pin(pctldev, &pins[i]);if (ret)return ret; // 严格错误处理
}
  • 对每个引脚调用 pinctrl_register_one_pin 执行单引脚注册
  1. 原子化错误处理
    任意单个引脚注册失败立即终止流程,返回错误码
底层操作 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 内核如何以 高效、安全 的方式管理硬件资源。掌握这些核心函数的设计思想,不仅能帮助开发者我们编写稳定的驱动程序,还能为调试复杂硬件问题提供理论依据。


http://www.ppmy.cn/embedded/174655.html

相关文章

【读点论文】Chain Replication for Supporting High Throughput and Availability

在分布式系统中&#xff0c;强一致性往往和高可用、高吞吐是矛盾的。比如传统的关系型数据库&#xff0c;其保证了强一致性&#xff0c;但往往牺牲了可用性和吞吐量。而像 NoSQL 数据库&#xff0c;虽然其吞吐量、和扩展性很高&#xff0c;但往往只支持最终一致性&#xff0c;无…

微服务分层架构详解:表示层、应用层与基础设施层的协同工作

微服务分层架构详解&#xff1a;表示层、应用层与基础设施层的协同工作 文章目录 微服务分层架构详解&#xff1a;表示层、应用层与基础设施层的协同工作1. 表示层&#xff08;Presentation Layer&#xff09;1.1 表示层的作用1.2 技术选型1.3 表示层的挑战 2. 应用层&#xff…

【从零开始学习计算机科学与技术】系统工程概论(四)系统仿真 与 系统评估

【从零开始学习计算机科学与技术】系统工程概论(四)系统仿真 与 系统评估 系统仿真系统仿真的工作流程应用系统动态学模型的步骤系统与反馈反馈系统反馈回路SD结构模型化的表示因果关系图流图流图的符号流图-流图绘制程序和方法基本反馈回路的DYNAMO仿真分析基本DYNAMO方程系…

深入理解MySQL日志机制

目录 1. MySQL日志概述 2. 错误日志&#xff08;Error Log&#xff09; 2.1 错误日志的作用 2.2 错误日志的配置 2.3 查看错误日志 3. 二进制日志&#xff08;Binary Log&#xff09; 3.1 二进制日志的作用 3.2 二进制日志的配置 3.3 查看二进制日志 3.4 二进制日志的…

Flume实战:Kafka Channel的使用配置场景

概述 使用Flume采集数据时&#xff0c;我们可能会遇到各种场景&#xff0c;一个数据采集任务的标准配置都是Source->Channel->Sink。对于Channel组件的选择常用的有Memory Channel、File Channel。而我们都知道&#xff0c;Kafka组件在大数据平台的使用过程中是一个非常…

【JavaEE进阶】Linux常用命令

目录 &#x1f343;前言 &#x1f334;pwd 与 ls &#x1f6a9;pwd &#x1f6a9;ls &#x1f38d;cd &#x1f332;mkdir与touch &#x1f6a9;mkdir &#x1f6a9;touch &#x1f340;cat与rm &#x1f6a9;cat &#x1f6a9;rm &#x1f38b;vim &#x1f6a9;…

C++ 各种map对比

文章目录 特点比较1. std::map2. std::unordered_map3. std::multimap4. std::unordered_multimap5. hash_map&#xff08;SGI STL 扩展&#xff09; C 示例代码代码解释 特点比较 1. std::map 底层实现&#xff1a;基于红黑树&#xff08;一种自平衡的二叉搜索树&#xff09…

C# 集合(Collection)详解以及区别

C# 集合&#xff08;Collection&#xff09;是用于数据存储和检索的核心数据结构&#xff0c;支持动态内存管理、多种数据组织形式及高效操作。以下是其核心特性和分类&#xff1a; 一、集合的核心作用 ‌1、动态扩展‌ 集合可动态调整容量&#xff08;如 List&#xff09;&am…