Skip to content

LV020-pinctrl驱动分析

所有的东西都已经准备好了,包括寄存器地址和寄存器值, Linux 内核相应的驱动文件就会根据这些值来做相应的初始化。iomuxc 节点中 compatible 属性的值为“fsl, imx6ul-iomuxc”,在 Linux 内核中全局搜索“fsl, imx6ul-iomuxc”字符串就会找到对应的驱动文件。

一、pincontroller 的数据结构

1.1 pinctrl_dev

c
struct pinctrl_dev {
	struct list_head node;
	struct pinctrl_desc *desc;
	struct radix_tree_root pin_desc_tree;
	//......
};

这里我们主要关注这个 desc 指针,它是 struct pinctrl_desc 类型的。pincontroller 虽然是一个软件的概念,但是它背后是有硬件支持的,所以可以使用一个结构体来表示它:pinctrl_dev。怎么使用 pinctrl_descpinctrl_dev 来描述一个 pin controller?这两个结构体定义如下:

image-20260121110418237

1.2 pinctrl_desc

linux 中使用结构体 struct pinctrl_desc 来描述一个引脚控制器(pinctrl) 的属性和操作:

c
struct pinctrl_desc {
	const char *name;                   // 引脚控制器的名称
	const struct pinctrl_pin_desc *pins;// 引脚描述符数组
	unsigned int npins;                 // 引脚描述符数组的大小
	const struct pinctrl_ops *pctlops;  // 引脚控制操作函数指针
	const struct pinmux_ops *pmxops;    // 引脚复用操作函数指针
	const struct pinconf_ops *confops;  // 引脚配置操作函数指针
	struct module *owner;               // 拥有该结构体的模块
#ifdef CONFIG_GENERIC_PINCONF
	unsigned int num_custom_params;     // 自定义参数数量
	const struct pinconf_generic_params *custom_params; // 自定义参数数组
	const struct pin_config_item *custom_conf_items;    // 自定义配置项数组
#endif
};

引脚控制器是硬件系统中的一个组件, 用于管理和控制引脚的功能和状态。 struct pinctrl_desc 结构体的作用是提供一个统一的接口, 用于配置和管理引脚控制器的行为。

  • name:引脚控制器的名称, 用于标识引脚控制器的唯一性。

  • pins:引脚描述符数组, 是一个 struct pinctrl_pin_desc 类型的指针,用于描述引脚的属性和配置。 每个引脚描述符包含了引脚的名称、 编号、 模式等信息。

  • npins:表示引脚描述符数组中元素的数量, 用于确定引脚描述符数组的长度。

  • pctlops :指向引脚控制操作函数的指针, struct pinctrl_ops 类型,用于定义引脚控制器的操作接口。 通过这些操作函数, 可以对引脚进行配置、 使能、 禁用等操作。

  • pmxops:指向引脚复用操作函数的指针, struct pinmux_ops 类型,用于定义引脚的复用功能。 复用功能允许将引脚的功能切换为不同的模式, 以适应不同的设备需求。

  • pinconf_ops:指向引脚配置操作函数的指针, struct pinconf_ops 类型,用于定义引脚的其他配置选项。 这些配置选项可以包括引脚的上拉、 下拉配置、 电气特性等。

  • owner:指向拥有该引脚控制器结构体的模块的指针。 这个字段用于跟踪引脚控制器结构体的所有者。

  • num_custom_params: 表示自定义配置参数的数量, 用于描述引脚控制器的自定义配置参数。

  • custom_params: 指向自定义配置参数的指针,用于描述引脚控制器的自定义配置参数的属性。 自定义配置参数可以根据具体需求定义, 用于扩展引脚控制器的配置选项。

  • custom_conf_items: 指向自定义配置项的指针, 用于描述引脚控制器的自定义配置项的属性。 自定义配置项可以根据具体需求定义, 用于扩展引脚控制器的配置选项。

1.2.1 pinctrl_desc.pinctrl_pin_desc

c
struct pinctrl_pin_desc {
	unsigned number;
	const char *name;
	void *drv_data;
};

1.2.2 pinctrl_desc.pinctrl_ops

c
struct pinctrl_ops {
	//......
	int (*dt_node_to_map) (struct pinctrl_dev *pctldev,
			       struct device_node *np_config,
			       struct pinctrl_map **map, unsigned *num_maps);
	//......
};

重点是函数 pinctrl_desc.pinctrl_ops.dt_node_to_map,这个函数用于处理设备树中 pin controller 中的某个节点,可以通过该函数将 device_node 转换为一系列的 pinctrl_map

1.2.3 pinctrl_desc.pinmux_ops

c
struct pinmux_ops {
	//......
	int (*set_mux) (struct pinctrl_dev *pctldev, unsigned func_selector,
			unsigned group_selector);
	//......
};

pinctrl_desc.pinmux_ops 中需要关注的是 pinctrl_desc.pinmux_ops.set_mux 函数,这个函数用于配置引脚的复用。

1.2.4 pinctrl_desc.pinconf_ops

c
struct pinconf_ops {
#ifdef CONFIG_GENERIC_PINCONF
	bool is_generic;
#endif
	int (*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);
};

pinctrl_desc.pinconf_ops 中的函数是用于对引脚进行配置。

1.3 使用 pinctrl_desc 注册得到 pinctrl_dev

调用 devm_pinctrl_register()pinctrl_register(),就可以根据 pinctrl_desc 构造出 pinctrl_dev,并且把 pinctrl_dev 放入链表。其实 devm_pinctrl_register() 最后也是调用的 pinctrl_register(),我们来看一下 pinctrl_register()

c
struct pinctrl_dev *pinctrl_register(struct pinctrl_desc *pctldesc,
				    struct device *dev, void *driver_data)
{
	//......
	pctldev = pinctrl_init_controller(pctldesc, dev, driver_data);
	//......

}

内部会再调用 pinctrl_init_controller(),下面的删去了暂时不需要关心的内容。

c
static struct pinctrl_dev *
pinctrl_init_controller(struct pinctrl_desc *pctldesc, struct device *dev,
			void *driver_data)
{
	struct pinctrl_dev *pctldev;
    
	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;

	/* check core ops for sanity */
	ret = pinctrl_check_ops(pctldev);

	/* If we're implementing pinmuxing, check the ops for sanity */
	ret = pinmux_check_ops(pctldev);

	/* If we're implementing pinconfig, check the ops for sanity */
	ret = pinconf_check_ops(pctldev);

	/* Register all the pins */
	ret = pinctrl_register_pins(pctldev, pctldesc->pins, pctldesc->npins);

}

2. client 的数据结构

2.1 device

在设备树中,使用 pinctrl 时格式如下:

c
/* For a client device requiring named states */
device {
    pinctrl-names = "active", "idle";
    pinctrl-0 = <&state_0_node_a>;
    pinctrl-1 = <&state_1_node_a &state_1_node_b>;
};

设备节点要么被转换为 platform_device,或者其他结构体(比如 i2c_client),但是里面都会有一个 device 结构体,比如:

image-20260121111014925

2.2 device.dev_pin_info

每个 device 结构体里都有一个 dev_pin_info 结构体,用来保存设备的 pinctrl 信息:

c
struct dev_pin_info {
	struct pinctrl *p;// 引脚控制器指针
	struct pinctrl_state *default_state;// 默认状态指针
	struct pinctrl_state *init_state;   // 初始化状态指针
#ifdef CONFIG_PM
	struct pinctrl_state *sleep_state;// 睡眠状态指针(仅在支持电源管理时可用)
	struct pinctrl_state *idle_state; // 空闲状态指针(仅在支持电源管理时可用)
#endif
};

(1) struct pinctrl *p;: 引脚控制器指针。 该指针指向设备所使用的引脚控制器对象, 用于对设备的引脚进行控制和配置。

(2) struct pinctrl_state *default_state;: 默认状态指针。 该指针指向设备的默认引脚配置状态, 表示设备在正常操作时的引脚配置。

(3) struct pinctrl_state *init_state;: 初始化状态指针。 该指针指向设备的初始化引脚配置状态, 表示设备在初始化阶段的引脚配置。

(4) struct pinctrl_state *sleep_state;: 睡眠状态指针(仅在支持电源管理时可用) 。 该指针指向设备的引脚配置状态, 表示设备在进入睡眠状态时的引脚配置。

(5) struct pinctrl_state *idle_state;: 空闲状态指针(仅在支持电源管理时可用) 。 该指针指向设备的引脚配置状态, 表示设备在空闲状态时的引脚配置。

这个后面学习 pinctrl_bind_pins() 的时候会继续学习。

image-20260121111654599

2.2.1  device.dev_pin_info.pinctrl

假设芯片上有多个 pin controller,那么这个设备使用哪个 pin controller?这需要通过设备树来确定。

分析设备树,找到 pin controller,对于每个状态,比如 default、init,去分析 pin controller 中的设备树节点。然后使用 pin controller 的 pinctrl_desc.pinctrl_ops.dt_node_to_map 来处理设备树的 pinctrl 节点信息,得到一系列的 pinctrl_map,这些 pinctrl_map 放在 device.dev_pin_info.pinctrl.dt_maps 链表中,每个 pinctrl_map 都被转换为 pinctrl_setting,放在对应的 pinctrl_state.settings 链表中。

image-20210505182828324

2.2.2 pinctrl_mappinctrl_setting

设备引用 pin controller 中的某个节点时,这个节点会被转换为一系列的 pinctrl_map,转换为多少个 pinctrl_map,完全由具体的驱动决定。每个 pinctrl_map,又被转换为一个 pinctrl_setting

例如,设备节点里有:pinctrl-0 = <&state_0_node_a>,pinctrl-0 对应一个状态,会得到一个 pinctrl_state,state_0_node_a 节点被解析为一系列的 pinctrl_map,这一系列的 pinctrl_map 被转换为一系列的 pinctrl_setting,这些 pinctrl_setting 被放入 pinctrl_state 的 settings 链表

image-20210505182324076

2.2.4 使用 pinctrl_setting

函数调用过程如下:

c
really_probe()
	pinctrl_bind_pins()
		pinctrl_select_state()
    		pinctrl_commit_state()

pinctrl_commit_state() 中需要关注的部分如下:

c
			/* Apply all the settings for the new state */
			list_for_each_entry(setting, &state->settings, node) {
				switch (setting->type) {
				case PIN_MAP_TYPE_MUX_GROUP:
					ret = pinmux_enable_setting(setting);
							ret = ops->set_mux(...);
				break;
				case PIN_MAP_TYPE_CONFIGS_PIN:
				case PIN_MAP_TYPE_CONFIGS_GROUP:
					ret = pinconf_apply_setting(setting);
							ret = ops->pin_config_group_set(...);
					break;
				default:
					ret = -EINVAL;
				break;
			}
imgimg

3. 驱动源码分析

3.1 驱动入口与匹配表

3.1.1 imx6ul_pinctrl_init()

pinctrl-imx6ul.c - drivers/pinctrl/freescale/pinctrl-imx6ul.c 中有如下内容:

c
static int __init imx6ul_pinctrl_init(void)
{
	return platform_driver_register(&imx6ul_pinctrl_driver);
}
arch_initcall(imx6ul_pinctrl_init);

会发现,这也都是平台设备驱动。平台设备驱动结构体为 imx6ul_pinctrl_driver

c
static struct platform_driver imx6ul_pinctrl_driver = {
	.driver = {
		.name = "imx6ul-pinctrl",
		.of_match_table = of_match_ptr(imx6ul_pinctrl_of_match),
	},
	.probe = imx6ul_pinctrl_probe,
};

3.1.2 imx6ul_pinctrl_driver.of_match_table

接下来看一下这个 imx6ul_pinctrl_driver.of_match_table,它指向的是数组 imx6ul_pinctrl_of_match

c
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 */ }
};

之前学习设备树的时候说过了, of_device_id 里面保存着这个驱动文件的兼容性值,设备树中的 compatible 属性值会和 of_device_id 中的所有兼容性字符串比较,查看是否可以使用此驱动。 imx6ul_pinctrl_of_match 结构体数组一共有两个兼容性字符串, 分别为“fsl, imx6ul-iomuxc”和“fsl, imx6ull-iomuxc-snvs”,因此 如下所示的 iomuxc 节点会与此驱动匹配,之后 pinctrl-imx6ul.c 会完成 I.MX6ULL 的 PIN 配置工作。

c
iomuxc: iomuxc@20e0000 {
    compatible = "fsl,imx6ul-iomuxc";
    reg = <0x020e0000 0x4000>;
};

3.2 imx6ul_pinctrl_driver.imx6ul_pinctrl_probe()

当驱动匹配上之后,就会执行.probe 函数,在这里就是 imx6ul_pinctrl_probe()

c
static int imx6ul_pinctrl_probe(struct platform_device *pdev)
{
	const struct imx_pinctrl_soc_info *pinctrl_info;
	//进行驱动和设备树的匹配,匹配后获取相关信息
	pinctrl_info = of_device_get_match_data(&pdev->dev);
	if (!pinctrl_info)
		return -ENODEV;
	// 完成 pinctrl 子系统初始化
	return imx_pinctrl_probe(pdev, pinctrl_info);
}

后续就是我们就是要分析这个 imx_pinctrl_probe() 函数了。

image-20250330051104962

3.3.1 imx_pinctrl_parse_groups()

image-20250330051435182

设备树中的 mux_reg 和 conf_reg 值会保存在 info 参数中, input_reg、mux_mode、 input_val 和 config 值会保存在 grp 参数中。

  • 511 - 515 行:获取 mux_reg、 conf_reg、 input_reg、 mux_mode 和 input_val 值。
image-20250330051538198
  • 527 行:获取 config 值。
image-20250330051729390

3.3.2 pinctrl_register()

c
struct pinctrl_dev *pinctrl_register(struct pinctrl_desc *pctldesc,
				    struct device *dev, void *driver_data)
{
	//......
}

这个我们主要看一下结构体 struct pinctrl_desc ,它被用来描述一个引脚控制器(pinctrl) 的属性和操作:

c
struct pinctrl_desc {
	//......
	const struct pinctrl_ops *pctlops;  // 引脚控制操作函数指针
	const struct pinmux_ops *pmxops;    // 引脚复用操作函数指针
	const struct pinconf_ops *confops;  // 引脚配置操作函数指针
	//......
};

这三个“_ops”结构体指针非常重要!!!因为这三个结构体就是 PIN 控制器的“工具”,这三个结构体里面包含了很多操作函数,通过这些操作函数就可以完成对某一个 PIN 的配置。struct pinctrl_desc 结构体需要由用户提供,结构体里面的成员变量也是用户提供的。但是这个用户并不是我们这些使用芯片的程序员,而是半导体厂商,半导体厂商发布的 Linux 内核源码中已经把这些工作做完了。比如在 imx_pinctrl_probe() 函数中可以找到如下所示代码 :

c
int imx_pinctrl_probe(struct platform_device *pdev,
		      const struct imx_pinctrl_soc_info *info)
{
	//......
	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;
	imx_pinctrl_desc->pmxops = &imx_pmx_ops;
	imx_pinctrl_desc->confops = &imx_pinconf_ops;
	imx_pinctrl_desc->owner = THIS_MODULE;
	//......
    platform_set_drvdata(pdev, ipctl);
	ret = devm_pinctrl_register_and_init(&pdev->dev,
					     imx_pinctrl_desc, ipctl,
					     &ipctl->pctl);
}

其中用到了 imx_pctrl_opsimx_pmx_opsimx_pinconf_ops 这三个结构体定义。这里就不再继续分析了,这个系统的内容好多。这里一些函数我们后面再开一个小节学习。

3.3 imx_pctrl_ops.dt_node_to_map()

设备树(Device Tree) 中存放的是对硬件设备信息的描述, 包含了硬件设备的配置和连接信息, 例如在 pinctrl 节点中的引脚的配置和映射关系。imx_pctrl_ops.dt_node_to_map() 函数的作用就是根据设备树中的节点信息, 生成对应的引脚映射数组。 这个映射数组将描述硬件功能(如复用功能和配置信息) 与设备树中的引脚信息进行绑定。

3.4.1 pinctrl_map

先看一下 pinctrl_map

c
struct pinctrl_map {
    const char *dev_name;       // 设备名称
    const char *name;           // 映射名称
    enum pinctrl_map_type type; // 映射类型
    const char *ctrl_dev_name;  // 控制设备名称
    union {
        struct pinctrl_map_mux mux;         // 复用映射数据
        struct pinctrl_map_configs configs; // 配置映射数据
    } data;
};

该结构体用于在引脚控制器中定义引脚的映射关系。 通过映射类型的不同, 可以将引脚与具体的复用功能或配置信息关联起来,从而实现引脚的配置和控制 。

3.4.2 dt_node_to_map()

c
static int imx_dt_node_to_map(struct pinctrl_dev *pctldev,
			struct device_node *np,
			struct pinctrl_map **map, unsigned *num_maps)
{
	struct imx_pinctrl *ipctl = pinctrl_dev_get_drvdata(pctldev);// 获取引脚控制器的私有数据指针
	const struct group_desc *grp;// 引脚组指针
	struct pinctrl_map *new_map; // 新的引脚映射数组
	struct device_node *parent;  // 父节点指针
	int map_num = 1;             // 映射数量, 默认为 1
	int i, j;

	/*
	 * first find the group of this node and check if we need create
	 * config maps for pins
	 */
    /* 查找引脚组 */
	grp = imx_pinctrl_find_group_by_name(pctldev, np->name); // 根据节点名称查找对应的引脚组
	if (!grp) {
		dev_err(ipctl->dev, "unable to find group for node %s\n",
			np->name);
		return -EINVAL;
	}

	for (i = 0; i < grp->num_pins; i++) {
		struct imx_pin *pin = &((struct imx_pin *)(grp->data))[i];

		if (!(pin->config & IMX_NO_PAD_CTL))
			map_num++;
	}

	new_map = kmalloc_array(map_num, sizeof(struct pinctrl_map),
				GFP_KERNEL);
	if (!new_map)
		return -ENOMEM;

	*map = new_map;
	*num_maps = map_num;

	/* create mux map */
    /* 创建复用映射 */
	parent = of_get_parent(np);// 获取节点的父节点
	if (!parent) {
		kfree(new_map);
		return -EINVAL;
	}
	new_map[0].type = PIN_MAP_TYPE_MUX_GROUP; // 设置映射类型为复用映射
	new_map[0].data.mux.function = parent->name;// 复用功能名称为父节点的名称
	new_map[0].data.mux.group = np->name;// 引脚组名称为节点的名称
	of_node_put(parent);// 释放父节点的引用计数

	/* create config map */
    /* 创建配置映射 */
	new_map++;
	for (i = j = 0; i < grp->num_pins; i++) {
		struct imx_pin *pin = &((struct imx_pin *)(grp->data))[i];

		if (!(pin->config & IMX_NO_PAD_CTL)) {
			new_map[j].type = PIN_MAP_TYPE_CONFIGS_PIN;// 设置映射类型为配置映射
			new_map[j].data.configs.group_or_pin =
					pin_get_name(pctldev, pin->pin);// 引脚组或引脚名称为引脚组中的引脚名称
			new_map[j].data.configs.configs = &pin->config; // 配置信息数组为引脚组中该引脚的配置信息
			new_map[j].data.configs.num_configs = 1;
			j++;
		}
	}

	dev_dbg(pctldev->dev, "maps: function %s group %s num %d\n",
		(*map)->data.mux.function, (*map)->data.mux.group, map_num);

	return 0;
}

这个函数根据设备节点的信息创建引脚映射, 包括复用映射和配置映射。 复用映射用于将引脚组的功能与父节点的功能关联起来, 而配置映射用于将引脚的配置信息与引脚的名称关联起来。 这些映射将用于配置引脚控制器, 以确保引脚在系统中正确地配置和使用。 这个函数在设备树解析过程中被调用, 以便为每个设备节点创建相应的引脚映射。

3.4 imx_pmx_ops.imx_pmx_set()

c
static int imx_pmx_set(struct pinctrl_dev *pctldev, unsigned selector,
		       unsigned group)
{
    //......
}

这个函数会对引脚进行配置。这里就不详细去分析了。

3.5 pinctrl 是怎么被调用的?

我们的驱动基本不用管。当设备切换状态时,对应的 pinctrl 就会被调用。比如在 platform_device 和 platform_driver 的枚举过程中,流程如下:

09_pinctrl_really_probe

当系统休眠时,也会去设置该设备 sleep 状态对应的引脚,不需要我们自己去调用代码。非要自己调用,也有函数:

c
devm_pinctrl_get_select_default(struct device *dev); // 使用"default"状态的引脚
pinctrl_get_select(struct device *dev, const char *name); // 根据 name 选择某种状态的引脚
pinctrl_put(struct pinctrl *p); // 不再使用, 退出时调用

3.5.1 really_probe()

前面学习 platform_driver 的时候,我们知道当驱动和设备树匹配上后,就会调用 probe 函数,这个 probe 函数是这样被调用的:LV06-04-linux 设备模型-09-设备与驱动的匹配 | 苏木,这里学习 linux 设备模型的时候有了解过,是通过 really_probe() 最终调用了上面的.probe 函数,我们看下 really_probe()

c
static int really_probe(struct device *dev, struct device_driver *drv)
{
	//......
re_probe:
	dev->driver = drv;// 将设备的驱动程序指针设置为当前驱动

	/* If using pinctrl, bind pins now before probing */
    // 如果使用了 pinctrl, 在探测之前绑定引脚
	ret = pinctrl_bind_pins(dev);
	if (ret)
		goto pinctrl_bind_failed;

	ret = dma_configure(dev);// 绑定设备的引脚
	if (ret)
		goto probe_failed;

	if (driver_sysfs_add(dev)) { // 添加驱动程序的 sysfs 接口
		printk(KERN_ERR "%s: driver_sysfs_add(%s) failed\n",
			__func__, dev_name(dev));
		goto probe_failed;
	}
	//......
	if (dev->bus->probe) {// 如果总线的探测函数存在, 则调用总线的探测函数
		ret = dev->bus->probe(dev);
		if (ret)
			goto probe_failed;
	} else if (drv->probe) {
		ret = drv->probe(dev);
		if (ret)
			goto probe_failed;
	}
	//......
}

如果使用了 pinctrl 就会调用 第 9 行pinctrl_bind_pins() 函数进行设备引脚的绑定。

3.5.2 pinctrl_bind_pins()

c
struct dev_pin_info {
	struct pinctrl *p;// 引脚控制器指针
	struct pinctrl_state *default_state;// 默认状态指针
	struct pinctrl_state *init_state;   // 初始化状态指针
#ifdef CONFIG_PM
	struct pinctrl_state *sleep_state;// 睡眠状态指针(仅在支持电源管理时可用)
	struct pinctrl_state *idle_state; // 空闲状态指针(仅在支持电源管理时可用)
#endif
};

前面介绍了每个成员的含义,这里举个例子:

c
rk_485_ctl: rk-485-ctl {
    compatible = "topeet,rs485_ctl";
    gpios = <&gpio0 22 GPIO_ACTIVE_HIGH>;
    pinctrl-names = "default";
    pinctrl-0 = <&rk_485_gpio>;
};

&pinctrl {
    rk_485{
    	rk_485_gpio:rk-485-gpio {
        	rockchip,pins = <3 13 RK_FUNC_GPIO &pcfg_pull_none>;
        };
    };
};

其中第 4 行的 pinctrl-names 属性指定了设备所使用的引脚控制器为 default, 即第 5 行的 p inctrl-0, 而 pinctrl-0 的值为 pinctrl 节点中的 rk_485_gpio, 所以 struct pinctrl_state *default_st ate 这一默认状态结构体指针会用来存放 11 行的引脚复用信息, 设备树中的 pinctrl 节点会转换为 pinctrl_map 结构体, 那么 struct pinctrl_state *default_state 必然会跟 pinctrl_map 结构体建立联系, 那这个联系是如何建立的呢?

根据线索跳转到 pinctrl_bind_pins() 函数, 该函数定义如下:

c
int pinctrl_bind_pins(struct device *dev)
{
	int ret;

	if (dev->of_node_reused)// 检查设备是否重用了节点
		return 0;
	// 为设备的引脚分配内存空间
	dev->pins = devm_kzalloc(dev, sizeof(*(dev->pins)), GFP_KERNEL);
	if (!dev->pins)
		return -ENOMEM;
	// 获取设备的 pinctrl 句柄
	dev->pins->p = devm_pinctrl_get(dev);
	if (IS_ERR(dev->pins->p)) {
		dev_dbg(dev, "no pinctrl handle\n");
		ret = PTR_ERR(dev->pins->p);
		goto cleanup_alloc;
	}
	// 查找设备的默认 pinctrl 状态
	dev->pins->default_state = pinctrl_lookup_state(dev->pins->p,
					PINCTRL_STATE_DEFAULT);
	if (IS_ERR(dev->pins->default_state)) {
		dev_dbg(dev, "no default pinctrl state\n");
		ret = 0;
		goto cleanup_get;
	}
	// 查找设备的初始化 pinctrl 状态
	dev->pins->init_state = pinctrl_lookup_state(dev->pins->p,
					PINCTRL_STATE_INIT);
	if (IS_ERR(dev->pins->init_state)) {
		/* Not supplying this state is perfectly legal */
		dev_dbg(dev, "no init pinctrl state\n");

		ret = pinctrl_select_state(dev->pins->p,
					   dev->pins->default_state);
	} else {
		ret = pinctrl_select_state(dev->pins->p, dev->pins->init_state);
	}

	if (ret) {
		dev_dbg(dev, "failed to activate initial pinctrl state\n");
		goto cleanup_get;
	}

#ifdef CONFIG_PM
	/*
	 * If power management is enabled, we also look for the optional
	 * sleep and idle pin states, with semantics as defined in
	 * <linux/pinctrl/pinctrl-state.h>
	 */
	dev->pins->sleep_state = pinctrl_lookup_state(dev->pins->p,
					PINCTRL_STATE_SLEEP);
	if (IS_ERR(dev->pins->sleep_state))
		/* Not supplying this state is perfectly legal */
		dev_dbg(dev, "no sleep pinctrl state\n");

	dev->pins->idle_state = pinctrl_lookup_state(dev->pins->p,
					PINCTRL_STATE_IDLE);
	if (IS_ERR(dev->pins->idle_state))
		/* Not supplying this state is perfectly legal */
		dev_dbg(dev, "no idle pinctrl state\n");
#endif

	return 0;

	/*
	 * If no pinctrl handle or default state was found for this device,
	 * let's explicitly free the pin container in the device, there is
	 * no point in keeping it around.
	 */
cleanup_get:
	devm_pinctrl_put(dev->pins->p);
cleanup_alloc:
	devm_kfree(dev, dev->pins);
	dev->pins = NULL;

	/* Return deferrals */
	if (ret == -EPROBE_DEFER)
		return ret;
	/* Return serious errors */
	if (ret == -EINVAL)
		return ret;
	/* We ignore errors like -ENOENT meaning no pinctrl state */

	return 0;
}

会用就行,这里后面有机会再详细分析。

3.6 总结

下面这个图是讯为的 rk3568 开发板文档中的一张图,都差不多,帮助理解。

image-20250403100549998

4. 总结

pinctrl 的三大作用,有助于理解所涉及的数据结构:

  • (1)引脚枚举与命名(Enumerating and naming):包括单个引脚和各组引脚

  • (2)引脚复用(Multiplexing):比如用作 GPIO、I2C 或其他功能

  • (3)引脚配置(Configuration):比如上拉、下拉、open drain、驱动强度等

pinctrl 驱动程序的核心是构造一个 pinctrl_desc 结构体:

image-20260121110418237

4.1 作用 1:描述、获得引脚

分为 2 部分:描述、获得单个引脚某组引脚的信息。

image-20260121140153875

4.2 作用 2:引脚复用

用来把某组引脚(group)复用为某个功能(function)。

image-20260121140426319

4.3 作用 3:引脚配置

用来配置:某个引脚(pin)或某组引脚(group)。

image-20260121140617640

参考资料:

linux 内核驱动-pinctrl 子系统 - fuzidage - 博客园

NXP(恩智浦)官方提供的配置 i.MX 系列处理器引脚复用和功能的工具“i.MX Pins Tool”的安装及使用说明【我主要用于生成设备树节点描述语句】_imx pin tool-CSDN 博客

06_client端使用pinctrl过程的情景分析_基于IMX6ULL — Linux设备驱动开发教程中心