往期内容
本专栏往期内容:
- Pinctrl子系统和其主要结构体引入
input子系统专栏:
- 专栏地址:input子系统
- input角度:I2C触摸屏驱动分析和编写一个简单的I2C驱动程序
– 末片,有往期内容观看顺序I2C子系统专栏:
- 专栏地址:IIC子系统
- 具体芯片的IIC控制器驱动程序分析:i2c-imx.c-CSDN博客
– 末篇,有往期内容观看顺序总线和设备树专栏:
- 专栏地址:总线和设备树
- 设备树与 Linux 内核设备驱动模型的整合-CSDN博客
– 末篇,有往期内容观看顺序
前言
在上一篇文章中对Pincontroller进行了简单的介绍(Pinctrl子系统和其主要结构体引入),以及对设备树也进行了示范,对Pincontroller的结构体进行的引入,也就是起了个开头,知道了是用pinctrl_dev
来指代一个Pincontroller,可以提到了可以通过提供一个pinctrl_desc
再去调用某个函数来实现一个pinctrl_dev
。那么接下来就进一步讲解。
那么接下来看看pinctrl_dev --> pinctrl_desc *desc --> pinctrl_ops、pinmux_ops、pinconf_ops
:
1. 作用1:描述、获得引脚 pinctrl_ops
使用pinctrl_ops来操作引脚,主要功能有二:
- 来取出某组的引脚:get_groups_count、get_group_pins
- 处理设备树中pin controller中的某个节点:dt_node_to_map,把device_node转换为一系列的pinctrl_map
\Linux-4.9.88\include\linux\pinctrl\pinctrl.hstruct pinctrl_ops {int (*get_groups_count) (struct pinctrl_dev *pctldev);const char *(*get_group_name) (struct pinctrl_dev *pctldev,unsigned selector);int (*get_group_pins) (struct pinctrl_dev *pctldev,unsigned selector,const unsigned **pins,unsigned *num_pins);void (*pin_dbg_show) (struct pinctrl_dev *pctldev, struct seq_file *s,unsigned offset);int (*dt_node_to_map) (struct pinctrl_dev *pctldev,struct device_node *np_config,struct pinctrl_map **map, unsigned *num_maps);void (*dt_free_map) (struct pinctrl_dev *pctldev,struct pinctrl_map *map, unsigned num_maps);
};
struct pinctrl_ops
结构体定义了全局引脚控制操作(pin control operations),这些操作需要由引脚控制器驱动程序实现。该结构体中的函数指针代表不同的操作接口,允许驱动程序通过这些接口与引脚控制子系统进行交互。
get_groups_count
: 返回当前已注册的引脚组(pin group)数量。该函数用于获取系统中引脚组的总数。get_group_name
: 返回特定引脚组的名称。通过组选择器selector
作为参数,驱动可以获取某个引脚组的名称。get_group_pins
: 返回与特定组选择器对应的引脚数组,并通过参数pins
和num_pins
返回该数组的指针和引脚数量。此函数用于查询引脚组包含的具体引脚。pin_dbg_show
: 可选的debugfs
显示挂钩,用于在调试文件系统(debugfs)中展示每个引脚的设备相关信息。它可以在调试时提供关于某个引脚的详细信息。dt_node_to_map
: 解析设备树(Device Tree)中的 “pin 配置节点” 并为其创建映射表项(mapping table entries)。这些映射表项通过map
和num_maps
输出参数返回。该函数是可选的,适用于支持设备树的引脚控制器驱动。dt_free_map
: 释放通过dt_node_to_map
创建的映射表项。顶层的map
指针需要释放,同时还要释放映射表项中动态分配的成员。该函数是可选的,对于不支持设备树的引脚控制器驱动,可以忽略此函数。
这些操作为引脚控制子系统提供了操作引脚组、解析设备树节点、调试显示等功能接口。
2. 作用2:引脚复用 pinmux_ops
把某一组引脚配置成某一功能,GPIO模式??或是其它模式
\Linux-4.9.88\include\linux\pinctrl\pinmux.h
struct pinmux_ops {int (*request) (struct pinctrl_dev *pctldev, unsigned offset);int (*free) (struct pinctrl_dev *pctldev, unsigned offset);int (*get_functions_count) (struct pinctrl_dev *pctldev);const char *(*get_function_name) (struct pinctrl_dev *pctldev,unsigned selector);int (*get_function_groups) (struct pinctrl_dev *pctldev,unsigned selector,const char * const **groups,unsigned *num_groups);int (*set_mux) (struct pinctrl_dev *pctldev, unsigned func_selector,unsigned group_selector);int (*gpio_request_enable) (struct pinctrl_dev *pctldev,struct pinctrl_gpio_range *range,unsigned offset);void (*gpio_disable_free) (struct pinctrl_dev *pctldev,struct pinctrl_gpio_range *range,unsigned offset);int (*gpio_set_direction) (struct pinctrl_dev *pctldev,struct pinctrl_gpio_range *range,unsigned offset,bool input);bool strict;
};
struct pinmux_ops
结构体定义了引脚复用(pinmux)操作,主要用于实现支持引脚复用功能的引脚控制器驱动程序。该结构体提供了一系列回调函数,允许引脚控制器子系统与引脚复用功能进行交互。
request
: 核心功能调用,用于判断特定的引脚是否可以被用于复用。在实际选择复用设置前,核心会调用该函数来请求引脚。驱动程序可以通过返回负值拒绝该请求。free
: 与request()
相反,释放被请求的引脚。这是在引脚复用完成后释放引脚的回调函数。get_functions_count
: 返回当前引脚复用驱动中可选择的复用功能数量。get_function_name
: 根据复用选择器selector
返回某个复用功能的名称。核心通过调用此函数来确定应将某个设备映射到哪个复用设置。get_function_groups
: 根据功能选择器selector
返回与该复用功能相关的引脚组名称数组,并通过groups
和num_groups
返回受影响的组及其数量。可以与pinctrl_ops
中的函数结合使用以获取受影响的引脚。set_mux
: 启用特定的复用功能与引脚组关联。该函数根据func_selector
选择特定的复用功能,并根据group_selector
选择一组引脚进行复用设置。驱动程序不需要检测复用冲突,系统会自动处理引脚冲突。gpio_request_enable
: 请求并启用某个引脚的 GPIO 模式。如果引脚控制器支持每个引脚单独复用为 GPIO 模式,则需要实现此函数。参数包括 GPIO 范围和在该范围内的引脚偏移量。gpio_disable_free
: 释放之前启用的 GPIO 复用设置,与gpio_request_enable
作用相反。gpio_set_direction
: 对于需要引脚复用的 GPIO 控制器,当 GPIO 配置为输入或输出时可能需要不同的设置。该函数允许控制引脚方向(输入或输出)。strict
: 如果为真,则不允许引脚同时作为 GPIO 和复用其他功能使用。在批准引脚请求之前,会严格检查gpio_owner
和mux_owner
以防止冲突。
pinmux_ops
提供了驱动与引脚复用子系统之间的交互接口,用于管理引脚复用、GPIO 配置、引脚组和功能的分配。
3. 作用3:引脚配置 pinconf_ops
上拉?下拉?等等
\Linux-4.9.88\include\linux\pinctrl\pinconf.h
struct pinconf_ops {
#ifdef CONFIG_GENERIC_PINCONFbool is_generic;
#endifint (*pin_config_get) (struct pinctrl_dev *pctldev,unsigned pin,unsigned long *config);int (*pin_config_set) (struct pinctrl_dev *pctldev,unsigned pin,unsigned long *configs,unsigned num_configs);int (*pin_config_group_get) (struct pinctrl_dev *pctldev,unsigned selector,unsigned long *config);int (*pin_config_group_set) (struct pinctrl_dev *pctldev,unsigned selector,unsigned long *configs,unsigned num_configs);int (*pin_config_dbg_parse_modify) (struct pinctrl_dev *pctldev,const char *arg,unsigned long *config);void (*pin_config_dbg_show) (struct pinctrl_dev *pctldev,struct seq_file *s,unsigned offset);void (*pin_config_group_dbg_show) (struct pinctrl_dev *pctldev,struct seq_file *s,unsigned selector);void (*pin_config_config_dbg_show) (struct pinctrl_dev *pctldev,struct seq_file *s,unsigned long config);
};
struct pinconf_ops
结构体定义了引脚配置操作,用于支持引脚配置功能的驱动程序实现。它包含一系列函数指针,这些函数用于获取和设置单个引脚及引脚组的配置。
is_generic
: (可选)如果引脚控制器希望使用通用接口,设置该标志以告诉框架其为通用接口。pin_config_get
: 获取特定引脚的配置。如果请求的配置在该控制器上不可用,返回-ENOTSUPP
;如果可用但被禁用,返回-EINVAL
。pin_config_set
: 配置单个引脚的设置。该函数接受引脚的配置参数和数量,允许驱动程序在特定引脚上设置多个配置。pin_config_group_get
: 获取特定引脚组的配置。该函数通过选择器返回整个引脚组的配置,便于一次性读取多个引脚的设置。pin_config_group_set
: 配置引脚组内所有引脚的设置。与pin_config_set
类似,但作用于整个引脚组。pin_config_dbg_parse_modify
: (可选)用于调试文件系统的功能,允许修改引脚配置。pin_config_dbg_show
: (可选)调试文件系统中的显示钩子,提供特定引脚的设备信息。pin_config_group_dbg_show
: (可选)调试文件系统中的显示钩子,提供特定引脚组的设备信息。pin_config_config_dbg_show
: (可选)调试文件系统中的显示钩子,解码并显示驱动程序的引脚配置参数。
4. 使用pinctrl_desc注册得到pinctrl_dev
分析的文件:
- drivers\pinctrl\freescale\pinctrl-imx6ul.c📎pinctrl-imx6ul.c
- pincontroller的驱动程序文件 。
pinctrl-imx.c
文件是 Linux 内核中针对 NXP(之前是 Freescale)i.MX 系列 SoC(系统芯片)的引脚控制器驱动程序实现。这驱动程序通常用于管理和配置 SoC 中的引脚,允许用户和其他系统组件以灵活的方式控制引脚的功能和行为。是一个更通用的驱动,支持多个 i.MX 系列的 SoC。提供了一些基础的、通用的功能或结构,其中关键的是imx_pinctrl_probe
函数
imx_pinctrl_probe
内部调用了核心层(core.c📎core.c)devm_pinctrl_register
(里面又调用了pinctrl_register
),就可以根据pinctrl_desc构造出pinctrl_dev,并且把pinctrl_dev放入链表:
\Linux-4.9.88\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" }; // 定义寄存器映射配置结构体struct device_node *dev_np = pdev->dev.of_node; // 获取设备的设备树节点struct pinctrl_desc *imx_pinctrl_desc; // 指向 pinctrl 描述符的指针struct device_node *np; // 设备树节点指针struct imx_pinctrl *ipctl; // 指向 i.MX 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; // 将设备指针赋值给 info 结构体// 检查是否有与 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); // 分配内存用于 ipctl 结构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); // 设置 pinctrl 名称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; // 设置 pinconf 操作imx_pinctrl_desc->owner = THIS_MODULE; // 设置模块拥有者// 调用设备树解析函数ret = imx_pinctrl_probe_dt(pdev, info);if (ret) {dev_err(&pdev->dev, "fail to probe dt properties\n"); // 打印错误信息return ret; // 返回错误}// 设置 ipctl 结构中的信息ipctl->info = info;ipctl->dev = info->dev;platform_set_drvdata(pdev, ipctl); // 将 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; // 返回成功
}
可以看到,在其probe函数中主要是去配置pinctrl_desc
,然后调用devm_pinctrl_register
去构建pinctrl_dev
:
\Linux-4.9.88\drivers\pinctrl\core.c:
struct pinctrl_dev *devm_pinctrl_register(struct device *dev,struct pinctrl_desc *pctldesc,void *driver_data)
{struct pinctrl_dev **ptr, *pctldev; // 定义指向 pinctrl_dev 的指针// 为资源管理分配内存,大小是指针大小,使用 devm (device managed) 内存管理机制ptr = devres_alloc(devm_pinctrl_dev_release, sizeof(*ptr), GFP_KERNEL); if (!ptr) // 检查内存分配是否成功return ERR_PTR(-ENOMEM); // 如果分配失败,返回内存不足错误// 调用 `pinctrl_register()` 注册 pinctrl 控制器pctldev = pinctrl_register(pctldesc, dev, driver_data);if (IS_ERR(pctldev)) { // 检查注册是否成功devres_free(ptr); // 如果注册失败,释放分配的资源return pctldev; // 返回错误指针}// 将注册的 pinctrl_dev 设备保存在资源管理指针中*ptr = pctldev;devres_add(dev, ptr); // 将该资源与设备关联,确保设备卸载时自动释放资源return pctldev; // 返回成功注册的 pinctrl_dev 设备
}
先是分配pinctrl_dev,然后调用pinctrl_register()
函数去注册pinctl控制器,来指代一个pincontroller, 核心工作是为设备分配并初始化 pinctrl_dev
结构体,并将其注册到 pin control 子系统中。它通过操作检查确保实现的功能完整,处理 pin 的注册和状态设置
/*** pinctrl_register() - 注册一个 pin 控制器设备* @pctldesc: 描述 pin 控制器的结构体* @dev: 父设备* @driver_data: 驱动程序的私有数据*/
struct pinctrl_dev *pinctrl_register(struct pinctrl_desc *pctldesc,struct device *dev, void *driver_data)
{struct pinctrl_dev *pctldev; // 定义 pin 控制器设备结构体指针int ret; // 用于保存函数返回值的变量// 检查输入参数的有效性,如果描述符为空或描述符的名字为空,返回错误指针if (!pctldesc)return ERR_PTR(-EINVAL);if (!pctldesc->name)return ERR_PTR(-EINVAL);// 分配内存为 pin 控制器设备结构体,并初始化为 0pctldev = kzalloc(sizeof(*pctldev), GFP_KERNEL);if (pctldev == NULL) {dev_err(dev, "failed to alloc struct pinctrl_dev\n"); // 输出分配失败的错误消息return ERR_PTR(-ENOMEM); // 返回内存不足的错误}// 初始化 pin 控制器设备结构体pctldev->owner = pctldesc->owner; // 赋值设备的所有者pctldev->desc = pctldesc; // 关联描述符pctldev->driver_data = driver_data; // 关联驱动私有数据INIT_RADIX_TREE(&pctldev->pin_desc_tree, GFP_KERNEL); // 初始化 `pin_desc_tree` 为 radix 树,用于描述 pinINIT_LIST_HEAD(&pctldev->gpio_ranges); // 初始化 GPIO 范围的链表pctldev->dev = dev; // 关联设备结构mutex_init(&pctldev->mutex); // 初始化互斥锁// 核查核心操作接口的完整性,确保驱动实现了必要的功能ret = pinctrl_check_ops(pctldev);if (ret) {dev_err(dev, "pinctrl ops lacks necessary functions\n"); // 输出缺少必要操作的错误消息goto out_err; // 如果失败,跳转到错误处理}// 如果实现了 pinmux 操作,检查其操作的完整性if (pctldesc->pmxops) {ret = pinmux_check_ops(pctldev);if (ret)goto out_err;}// 如果实现了 pinconfig 操作,检查其操作的完整性if (pctldesc->confops) {ret = pinconf_check_ops(pctldev);if (ret)goto out_err;}// 注册所有的 pindev_dbg(dev, "try to register %d pins ...\n", pctldesc->npins);ret = pinctrl_register_pins(pctldev, pctldesc->pins, pctldesc->npins); // 注册 pinsif (ret) {dev_err(dev, "error during pin registration\n"); // 输出 pin 注册失败的错误消息pinctrl_free_pindescs(pctldev, pctldesc->pins, pctldesc->npins); // 如果失败,释放 pin 资源goto out_err; // 跳转到错误处理}// 加入到全局的 pin 控制器设备列表mutex_lock(&pinctrldev_list_mutex);list_add_tail(&pctldev->node, &pinctrldev_list); // 将设备添加到设备列表的尾部mutex_unlock(&pinctrldev_list_mutex);// 获取 pinctrl 句柄pctldev->p = pinctrl_get(pctldev->dev);// 尝试查找并选择设备的默认和睡眠状态if (!IS_ERR(pctldev->p)) {// 查找并选择默认状态pctldev->hog_default = pinctrl_lookup_state(pctldev->p, PINCTRL_STATE_DEFAULT);if (IS_ERR(pctldev->hog_default)) {dev_dbg(dev, "failed to lookup the default state\n");} else {if (pinctrl_select_state(pctldev->p, pctldev->hog_default))dev_err(dev, "failed to select default state\n");}// 查找睡眠状态pctldev->hog_sleep = pinctrl_lookup_state(pctldev->p, PINCTRL_STATE_SLEEP);if (IS_ERR(pctldev->hog_sleep))dev_dbg(dev, "failed to lookup the sleep state\n");}// 初始化 debugfs 调试文件系统pinctrl_init_device_debugfs(pctldev);return pctldev; // 返回注册成功的 pin 控制器设备结构体指针out_err:mutex_destroy(&pctldev->mutex); // 销毁互斥锁kfree(pctldev); // 释放设备结构体内存return ERR_PTR(ret); // 返回错误指针
}
- 在drivers\pinctrl\freescale\pinctrl-imx6ul.c📎pinctrl-imx6ul.c中注册了平台驱动后,其
probe
先通过设备树匹配找到对应的pinctrl_info
,然后传递给通用的pinctrl-imx.c
的probe
函数。这确保了驱动能够正确识别和初始化针对特定硬件的引脚配置。 所以pinctrl-im6ull.c
是通用的。
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;return imx_pinctrl_probe(pdev, pinctrl_info); //这里就是上文中pinctrl-imx.c中定义的
}