往期内容
本专栏往期内容:
- Pinctrl子系统和其主要结构体引入
- Pinctrl子系统pinctrl_desc结构体进一步介绍
- Pinctrl子系统中client端设备树相关数据结构介绍和解析
input子系统专栏:
- 专栏地址:input子系统
- input角度:I2C触摸屏驱动分析和编写一个简单的I2C驱动程序
– 末片,有往期内容观看顺序I2C子系统专栏:
- 专栏地址:IIC子系统
- 具体芯片的IIC控制器驱动程序分析:i2c-imx.c-CSDN博客
– 末篇,有往期内容观看顺序总线和设备树专栏:
- 专栏地址:总线和设备树
- 设备树与 Linux 内核设备驱动模型的整合-CSDN博客
– 末篇,有往期内容观看顺序
前言
Linux 4.x内核文档
- Documentation\pinctrl.txt📎pinctrl.txt
- Documentation\devicetree\bindings\pinctrl\pinctrl-bindings.txt📎pinctrl-bindings.txt
- arch/arm/boot/dts/imx6ull-14x14-evk.dts
- arch/arm/boot/dts/100ask_imx6ull-14x14.dts
- drivers\pinctrl\freescale\pinctrl-imx6ul.c📎pinctrl-imx6ul.c
- drivers\pinctrl\freescale\pinctrl-imx.c📎pinctrl-imx.c
主要讲解pincontroller中设备树是如何去定义的,以及其驱动程序是如何去对设备树中节点的相关引脚去进行解析、获取相关信息并存储进imx_pinctrl_soc_info
结构体当中。
1.设备树
对应的驱动程序:drivers\pinctrl\freescale\pinctrl-imx6ul.c📎pinctrl-imx6ul.c,这要是涉及具体的单板,内部还是会调用到pinctrl-imx.c中提供的通用的函数,具体往下面看。
2.驱动代码执行流程
主要函数是pinrctrl-imx6ull.c中的imx6ul_pinctrl_probe
:
drivers\pinctrl\freescale\pinctrl-imx6ul.c:
static int imx6ul_pinctrl_probe(struct platform_device *pdev)
{const struct of_device_id *match;struct imx_pinctrl_soc_info *pinctrl_info;// 1. 使用设备树匹配表(imx6ul_pinctrl_of_match)查找是否有匹配的设备节点//通过 of_match_device 函数,将设备树中定义的设备节点与 pdev->dev 设备的 compatible 属性进行匹配。match = of_match_device(imx6ul_pinctrl_of_match, &pdev->dev);// 2. 如果没有找到匹配项,返回 -ENODEV,表示没有此设备if (!match)return -ENODEV;// 3. 取出匹配的设备信息数据(即 pinctrl 的配置信息),并将它转换成合适的数据结构类型//match->data 存储了当前 SoC 的 pinctrl 信息,如引脚映射表等内容。通过类型转换将其解析为 struct imx_pinctrl_soc_info 类型,便于后续操作。pinctrl_info = (struct imx_pinctrl_soc_info *) match->data;// 4. 调用 imx_pinctrl_probe 函数,初始化 pinctrl,并将其注册到系统中//将平台设备 pdev 和其 pinctrl 信息 pinctrl_info 传入 imx_pinctrl_probe,完成引脚控制器的初始化。return imx_pinctrl_probe(pdev, pinctrl_info);
}
这个 imx6ul_pinctrl_probe
函数是 IMX6UL(NXP i.MX 6UL系列处理器)的引脚控制器(pinctrl)驱动程序中的探测函数,用于在内核加载时初始化该设备的引脚控制功能。此函数通过匹配设备树中的节点,将设备的 pinctrl
配置信息初始化并注册到系统中。
其中imx_pinctrl_probe
,这个函数才是主要的。
drivers\pinctrl\freescale\pinctrl-imx.c:int imx_pinctrl_probe(struct platform_device *pdev,struct imx_pinctrl_soc_info *info)
{struct regmap_config config = { .name = "gpr" }; // 注册映射配置,名称为 "gpr"struct device_node *dev_np = pdev->dev.of_node; // 获取设备树节点struct pinctrl_desc *imx_pinctrl_desc; // 用于描述 pinctrl 的结构体struct device_node *np; // 临时设备节点指针struct imx_pinctrl *ipctl; // IMX pinctrl 控制器实例struct resource *res; // 存储资源信息struct regmap *gpr; // 注册映射指针int ret, i; // 返回值和循环计数器// 检查传入的 pinctrl 信息是否有效if (!info || !info->pins || !info->npins) {dev_err(&pdev->dev, "wrong pinctrl info\n"); // 打印错误信息return -EINVAL; // 返回无效参数错误}info->dev = &pdev->dev; // 将设备指针保存到信息结构中// 如果存在 GPR 兼容性,则获取 GPR 的注册映射if (info->gpr_compatible) {gpr = syscon_regmap_lookup_by_compatible(info->gpr_compatible);if (!IS_ERR(gpr))regmap_attach_dev(&pdev->dev, gpr, &config); // 附加设备到注册映射}// 为驱动创建状态持有者等结构体ipctl = devm_kzalloc(&pdev->dev, sizeof(*ipctl), GFP_KERNEL); // 分配内存if (!ipctl)return -ENOMEM; // 如果内存分配失败,返回错误// 检查是否使用 SCU(系统控制单元)if (!(info->flags & IMX8_USE_SCU)) {// 为每个引脚分配寄存器数组内存info->pin_regs = devm_kmalloc(&pdev->dev, sizeof(*info->pin_regs) *info->npins, GFP_KERNEL);if (!info->pin_regs)return -ENOMEM; // 内存分配失败,返回错误// 初始化引脚寄存器结构体for (i = 0; i < info->npins; i++) {info->pin_regs[i].mux_reg = -1; // 初始化复用寄存器info->pin_regs[i].conf_reg = -1; // 初始化配置寄存器}// 获取设备的内存资源res = platform_get_resource(pdev, IORESOURCE_MEM, 0);ipctl->base = devm_ioremap_resource(&pdev->dev, res); // 映射资源到内存if (IS_ERR(ipctl->base))return PTR_ERR(ipctl->base); // 映射失败,返回错误// 检查设备树中是否存在 "fsl,input-sel" 属性if (of_property_read_bool(dev_np, "fsl,input-sel")) {np = of_parse_phandle(dev_np, "fsl,input-sel", 0); // 解析设备树句柄if (!np) {dev_err(&pdev->dev, "iomuxc fsl,input-sel property not found\n"); // 错误信息return -EINVAL; // 返回无效参数错误}ipctl->input_sel_base = of_iomap(np, 0); // 映射输入选择寄存器of_node_put(np); // 释放设备节点if (!ipctl->input_sel_base) {dev_err(&pdev->dev,"iomuxc input select base address not found\n"); // 错误信息return -ENOMEM; // 内存不足错误}}}// 为 pinctrl 描述结构体分配内存imx_pinctrl_desc = devm_kzalloc(&pdev->dev, sizeof(*imx_pinctrl_desc),GFP_KERNEL);if (!imx_pinctrl_desc)return -ENOMEM; // 内存分配失败,返回错误// 初始化 pinctrl 描述结构体imx_pinctrl_desc->name = dev_name(&pdev->dev); // 设置设备名称imx_pinctrl_desc->pins = info->pins; // 设置引脚信息imx_pinctrl_desc->npins = info->npins; // 设置引脚数量imx_pinctrl_desc->pctlops = &imx_pctrl_ops; // 设置 pinctrl 操作imx_pinctrl_desc->pmxops = &imx_pmx_ops; // 设置 pinmux 操作imx_pinctrl_desc->confops = &imx_pinconf_ops; // 设置 pin 配置操作imx_pinctrl_desc->owner = THIS_MODULE; // 设置模块所有者// 通过设备树初始化 pinctrl 相关属性ret = imx_pinctrl_probe_dt(pdev, info);if (ret) {dev_err(&pdev->dev, "fail to probe dt properties\n"); // 错误信息return ret; // 返回错误}// 保存信息到 pinctrl 实例中ipctl->info = info; // 保存 pinctrl 信息ipctl->dev = info->dev; // 保存设备指针platform_set_drvdata(pdev, ipctl); // 设置平台驱动数据// 注册 pinctrl 驱动ipctl->pctl = devm_pinctrl_register(&pdev->dev,imx_pinctrl_desc, ipctl);if (IS_ERR(ipctl->pctl)) {dev_err(&pdev->dev, "could not register IMX pinctrl driver\n"); // 错误信息return PTR_ERR(ipctl->pctl); // 返回错误}// 初始化成功信息dev_info(&pdev->dev, "initialized IMX pinctrl driver\n");return 0; // 返回成功
}
3.描述、获得引脚(解析设备树)
3.1 单个引脚
/sys/kernel/debug/pinctrl/20e0000.iomuxc]# cat pins
这种控制器支持哪些引脚在代码中就已经写死了,而比如某个芯片中的I2C模块既支持第A组中的引脚,也支持第B组中的引脚(又或者是A组引脚既支持I2C又支持GPIO模块),这些组的引脚则通过pinctrl_ops来描述,具体看下面一点
3.2 某组引脚
在imx6ull中,组引脚的信息是在设备树中进行构造的,而在stm157中则是在代码中构造写死的
比较长,可以看下面图就,建议放大观看。
某组引脚中,有哪些引脚?这要分析设备树:imx_pinctrl_probe_dt。
[root@100ask:/sys/kernel/debug/pinctrl/20e0000.iomuxc]# cat pingroups
下面是详细的解析代码:
drivers\pinctrl\freescale\pinctrl-imx6ul.c:int imx_pinctrl_probe(struct platform_device *pdev,struct imx_pinctrl_soc_info *info)
{struct regmap_config config = { .name = "gpr" }; // 注册映射配置,名称为 "gpr"struct device_node *dev_np = pdev->dev.of_node; // 获取设备树节点struct pinctrl_desc *imx_pinctrl_desc; // 用于描述 pinctrl 的结构体// 。。。。。。。。。。。。。。// 为 pinctrl 描述结构体分配内存imx_pinctrl_desc = devm_kzalloc(&pdev->dev, sizeof(*imx_pinctrl_desc),GFP_KERNEL);if (!imx_pinctrl_desc)return -ENOMEM; // 内存分配失败,返回错误// 初始化 pinctrl 描述结构体imx_pinctrl_desc->name = dev_name(&pdev->dev); // 设置设备名称imx_pinctrl_desc->pins = info->pins; // 设置引脚信息imx_pinctrl_desc->npins = info->npins; // 设置引脚数量imx_pinctrl_desc->pctlops = &imx_pctrl_ops; // 设置 pinctrl 操作imx_pinctrl_desc->pmxops = &imx_pmx_ops; // 设置 pinmux 操作imx_pinctrl_desc->confops = &imx_pinconf_ops; // 设置 pin 配置操作imx_pinctrl_desc->owner = THIS_MODULE; // 设置模块所有者// 通过设备树初始化 pinctrl 相关属性ret = imx_pinctrl_probe_dt(pdev, info);if (ret) {dev_err(&pdev->dev, "fail to probe dt properties\n"); // 错误信息return ret; // 返回错误}//。。。。。。。。。。。。。。。。。
}
ret = imx_pinctrl_probe_dt(pdev, info);
,设备树中组引脚的解析和信息提取主要是在该函数中。其中参2为info
,为struct imx_pinctrl_soc_info *
类型的,解析该函数钱需要讲解一下该结构体
3.2.1 imx_pinctrl_soc_info
结构体
解析后的引脚的组信息会存放在info中
\Linux-4.9.88\drivers\pinctrl\freescale\pinctrl-imx.h:
// 用于描述 IMX 平台的 Pin 控制器的结构体
struct imx_pinctrl_soc_info {struct device *dev; // 设备指针,指向使用该 pinctrl 的设备const struct pinctrl_pin_desc *pins; // 指向描述每个 pin 的数组,包含每个引脚的详细信息unsigned int npins; // 引脚数量,表示 pins 数组中的元素数struct imx_pin_reg *pin_regs; // 指向该 SoC 使用的寄存器地址映射结构struct imx_pin_group *groups; // 描述各个引脚组的数组指针,每个组包含一组引脚配置unsigned int ngroups; // 引脚组数量,即 groups 数组的元素数量unsigned int group_index; // 引脚组的索引,用于指示当前配置的组struct imx_pmx_func *functions; // 指向支持的引脚复用功能的数组指针unsigned int nfunctions; // 支持的复用功能数量,即 functions 数组的元素数量unsigned int flags; // 标志位,用于存储当前 pin 控制器的特殊设置或配置const char *gpr_compatible; // 指向字符串,用于设备树兼容性匹配 (GPR) 的信息/* 当存在共享 MUX 和 CONF 寄存器的情况时,下面的字段用于控制 */unsigned int mux_mask; // MUX_MODE 的掩码值,用于提取和设置特定位u8 mux_shift; // MUX_MODE 的位移值,用于确定 mux_mask 应应用的位位置u32 ibe_bit; // 用于配置输入缓冲区使能 (Input Buffer Enable) 的位掩码u32 obe_bit; // 用于配置输出缓冲区使能 (Output Buffer Enable) 的位掩码
};
下面是其相关成员的介绍:
struct imx_pin_group *groups
: 在 i.MX 平台中定义一个引脚组的集合,该集合中的引脚通常是具有特定功能需求或用途相关的。例如,某些引脚组用于 UART 功能,而另一些用于 SPI、I2C 等功能。通过这种分组,便于对相关引脚的统一配置和管理,同时可以通过pinctrl
框架将一组引脚配置为同一功能,方便驱动和上层应用进行访问和配置。 这是一个数据指针类型的,也就是说引脚有多少组就该结构体有多少个
/*** struct imx_pin_group - 描述一个 i.MX 平台的引脚组(pin group)* @name: 引脚组名称* 用于标识该引脚组的名称(字符指针),通常用于调试或配置时参考。* * @npins: 引脚组中的引脚数量* 表示引脚组中的引脚数,即 .pins 数组中的元素数量。* 可以根据该值循环遍历 pins 数组中的元素。* * @pin_ids: 引脚组中每个引脚的 ID 数组* 一个存储每个引脚 ID 的数组(unsigned int* 类型),* 是 `pinctrl` 子系统所要求维护的,用于将每个引脚与其特定 ID 进行关联。* 引脚 ID 可以帮助查找和操作每个引脚的具体配置。* * @pins: 引脚数组* 一个 `struct imx_pin` 类型的数组,存储该引脚组中每个引脚的具体信息。* 该数组的每个元素对应引脚组中的一个引脚,其结构中可能包括引脚的复用、* 功能、驱动强度等详细配置。*/
struct imx_pin_group {const char *name; // 引脚组的名称,用于标识该组unsigned npins; // 引脚组中包含的引脚数量unsigned int *pin_ids; // 引脚 ID 数组,用于引用每个引脚的唯一标识struct imx_pin *pins; // 引脚数组,存储每个引脚的详细配置
};
其中imx_pin
结构体,存储每个引脚的详细配置,它是数组类型。 通过 struct imx_pin
,可以配置一个引脚的复用模式、电气特性等参数,适配不同的硬件平台(如基于内存映射或 SCU 管理的硬件)。imx_pin_memmap
和 imx_pin_scu
为不同硬件平台提供了所需的配置结构体,确保在驱动开发中可以灵活地配置引脚功能和特性:
/*** struct imx_pin - 描述单个 i.MX 平台的引脚配置* @pin: 引脚的编号或 ID* 表示该引脚在整个引脚控制器(pinctrl)系统中的唯一标识符,* 用于在设置时引用特定的引脚。** @pin_conf: 引脚的配置信息,具体使用哪种结构视硬件平台而定* 使用共用体(union)来存储引脚配置,有两种配置结构体可选:* 1. `imx_pin_memmap` - 内存映射的配置(适用于较旧的 i.MX 硬件)* 2. `imx_pin_scu` - SCU 配置(适用于较新的 SCU 管理的 i.MX 硬件)*/
struct imx_pin {unsigned int pin; // 引脚编号或 IDunion {struct imx_pin_memmap pin_memmap; // 内存映射的引脚配置struct imx_pin_scu pin_scu; // SCU 管理的引脚配置} pin_conf; // 选择合适的引脚配置结构
};/*** struct imx_pin_memmap - 描述基于内存映射的引脚配置(较旧的硬件平台)* @mux_mode: 复用模式* 设置引脚的功能复用模式,例如将该引脚配置为 UART、SPI、GPIO 等。* 不同的值对应不同的功能或信号通道。** @input_reg: 输入控制寄存器* 引脚的输入控制寄存器(16 位),用于配置引脚的输入特性,* 比如输入延迟或电平触发控制。** @input_val: 输入寄存器的值* 具体的输入控制值,决定输入配置的具体参数。设置引脚的输入配置时参考此值。** @config: 引脚的通用配置寄存器* 该寄存器的值用于设置引脚的电气特性和行为,如驱动强度、上拉/下拉电阻等。*/
struct imx_pin_memmap {unsigned int mux_mode; // 引脚复用模式u16 input_reg; // 输入控制寄存器unsigned int input_val; // 输入寄存器值unsigned long config; // 通用配置寄存器,用于电气特性设置
};/*** struct imx_pin_scu - 描述基于 SCU 管理的引脚配置(较新硬件平台)* @mux: 复用模式寄存器* 类似于 mux_mode,但用于 SCU 管理的硬件上。不同值代表不同功能复用。** @config: 通用配置寄存器* 用于设置引脚的电气特性和配置选项,包括驱动强度、上拉/下拉等。* 与 SCU 兼容的配置选项通常通过该寄存器控制。*/
struct imx_pin_scu {unsigned long mux; // 复用模式寄存器,SCU 管理的硬件专用unsigned long config; // 通用配置寄存器,SCU 兼容的电气特性设置
};
- 以该组引脚为例子,其中一个印记哦MX6UL_PAD_UART1_RTS_B_GPIO1_IO19 0X17059,最后就是会被解析成
imx_pin_memmap
- 回到
imx_pinctrl_soc_info
结构体,接下来讲一下其成员struct imx_pmx_func *functions
,imx_pmx_func
结构体用于定义 i.MX 平台的 pinmux 功能(pin multiplexing function)。在嵌入式系统中,一个引脚通常具有多种复用功能(如 GPIO、I2C、SPI 等),通过定义imx_pmx_func
,驱动程序可以描述每个特定功能所需要的引脚组,从而在硬件和软件层面实现该功能。
/*** struct imx_pmx_func - 描述 i.MX 平台的 pinmux 功能* @name: 该功能的名称* 此字段用于标识特定的引脚复用(pinmux)功能。例如,可以命名为* `i2c_func`、`spi_func` 等,以表示与不同外设关联的功能名称。** @groups: 关联的引脚组* 指向引脚组名称的字符串数组,表示执行该功能所需的引脚组。每个引脚组* 可以包含多个引脚,用于实现该功能的完整硬件连接。** @num_groups: 引脚组的数量* 该字段指定 `groups` 数组中的引脚组数量,用于迭代处理数组中的每个引脚组。*/
struct imx_pmx_func {const char *name; // 功能的名称,标识具体的 pinmux 功能const char **groups; // 对应的引脚组,指向该功能相关的引脚组名称数组unsigned num_groups; // 引脚组的数量,指定 `groups` 数组中的元素个数
};
3.2.2 结构体关联图
3.2.3 回到函数解析
用到的设备树示例:
\Linux-4.9.88\drivers\pinctrl\freescale\pinctrl-imx.c
回到上文提到的imx_pinctrl_probe
函数中,其中调用了imx_pinctrl_probe_dt
函数,下面对该函数拆开来讲解:该设备树解析函数 imx_pinctrl_probe_dt
是用于解析 i.MX 平台的设备树节点(Device Tree Node),并从中获取引脚控制的配置信息,存储在 imx_pinctrl_soc_info
结构体中。下面逐步解析这段代码如何对应到您设备树的内容。
static int imx_pinctrl_probe_dt(struct platform_device *pdev,struct imx_pinctrl_soc_info *info)
{struct device_node *np = pdev->dev.of_node; // 获取设备树节点struct device_node *child;u32 nfuncs = 0;u32 i = 0;bool flat_funcs;if (!np)return -ENODEV;
np = pdev->dev.of_node;
:获取当前设备(即pdev
)在设备树中的节点(对应您设备树中的imx6ul-evk
节点)。
- 判断函数结构
flat_funcs = imx_pinctrl_dt_is_flat_functions(np); // 检查函数结构if (flat_funcs) {nfuncs = 1;} else {nfuncs = of_get_child_count(np);if (nfuncs <= 0) {dev_err(&pdev->dev, "no functions defined\n");return -EINVAL;}}
imx_pinctrl_dt_is_flat_functions(np)
:检查函数是否以“平面”方式定义。如果flat_funcs
为真,则表示函数定义是平面的,例如,所有引脚组在一个层级中定义,而不再划分不同的子节点。nfuncs = of_get_child_count(np);
:如果不是平面结构,计算np
节点的子节点数,代表有多少个不同的功能模块(对应imx6ul-evk
下的子节点,如hdmigrp
、hoggrp-1
、enetgrp
等每个模块分别代表一个功能组)。- 其实就是看设备树节点中有没有"fsl,pin",以上面的设备树为例子是有的,因此是非平面结构体,其解析方式的函数如下:
static bool imx_pinctrl_dt_is_flat_functions(struct device_node *np)
{struct device_node *function_np; // 用于存储 np 节点的子节点struct device_node *pinctrl_np; // 用于存储 function_np 节点的子节点for_each_child_of_node(np, function_np) { // 遍历 np 的每个直接子节点if (of_property_read_bool(function_np, "fsl,pins"))return true; // 如果直接子节点包含 "fsl,pins" 属性,返回 true// 遍历 function_np 的每个子节点for_each_child_of_node(function_np, pinctrl_np) {if (of_property_read_bool(pinctrl_np, "fsl,pins"))return false; // 如果找到 "fsl,pins" 在 function_np 的子节点中,返回 false}}return true; // 如果遍历完没有发现嵌套结构,返回 true
}
- 分配内存空间
info->nfunctions = nfuncs;info->functions = devm_kzalloc(&pdev->dev, nfuncs * sizeof(struct imx_pmx_func),GFP_KERNEL);if (!info->functions)return -ENOMEM;info->group_index = 0;if (flat_funcs) {info->ngroups = of_get_child_count(np);} else {info->ngroups = 0;for_each_child_of_node(np, child)info->ngroups += of_get_child_count(child);}
info->nfunctions = nfuncs;
:将功能数量保存到imx_pinctrl_soc_info
的nfunctions
字段。info->functions
分配内存用于保存各功能的信息。info->ngroups
:若为平面结构,则直接获取节点np
的子节点数;若非平面结构,遍历每个子节点,统计所有子节点的引脚组数量。
3.解析函数与引脚组
info->groups = devm_kzalloc(&pdev->dev, info->ngroups * sizeof(struct imx_pin_group),GFP_KERNEL);if (!info->groups)return -ENOMEM;if (flat_funcs) {imx_pinctrl_parse_functions(np, info, 0);} else {for_each_child_of_node(np, child)imx_pinctrl_parse_functions(child, info, i++);}return 0;
}
-
info->groups
:分配用于保存引脚组信息的内存。 -
imx_pinctrl_parse_functions
:根据是否为平面结构,选择性地解析功能:- 如果是平面结构,则直接解析
np
下的所有引脚组。 - 如果非平面结构,则遍历
np
的每个子节点(即imx6ul-evk
下的各功能组节点),分别解析。
- 如果是平面结构,则直接解析
4.总结
该函数imx_pinctrl_probe_dt
主要是根据 imx6ul-evk
下的节点结构来设置功能和引脚组信息,并将这些信息填充到 imx_pinctrl_soc_info
结构体中,便于后续引脚复用的配置。
图中 hoggrp-1
、hdmigrp
等子节点被解析为各个功能组,fsl,pins
属性用于指定每个功能组内具体的引脚配置。
4.引脚复用和配置
引脚复用和引脚配置在 client端使用pinctrl过程的情景分析 中讲解,也就是下一章节,一般是由client的驱动程序来去调用相关函数实现的。