Pinctrl子系统_04_Pinctrl子系统主要数据结构

引言

本节说明Pinctrl子系统中主要的数据结构,对这些数据结构有所了解,也就是对Pinctrl子系统有所了解了。

前面说过,要使用Pinctrl子系统,就需要去配置设备树。

以内核面向对象的思想,设备树可以分为两部分,一部分(左边)用来描述Controller,另一部分(右边)则是描述使用引脚(使用controller)的device

对于controller的部分,内核会抽象出一个 pinctrl_dev 结构体;对于 device 的部分,内核会抽象出一个 device 结构体,device 结构体中会有 pinctrl 方面的成员。

显然,这个 pinctrl 方面的成员肯定会和左边的 pinctrl_dev 结构体产生联系。

那么,它们之间是什么样的关系呢?

答:在我们了解完Pinctrl子系统的数据结构之后,他们之间的关系也就清晰了。

首先,在了解Pinctrl子系统的数据结构前,先回忆一下Pinctrl子系统的三大作用

  1. 引脚枚举与命名(Enumerating and naming ):这个pinctrl支持哪些引脚,这些引脚叫什么名字;
  2. 引脚复用(Multiplexing ):用作什么功能,比如用作GPIO、I2C或其他功能;
  3. 引脚配置(Configuration):配置具体的引脚属性,比如上拉、下拉、open drain、驱动强度等;

记住这三大作用,就可以比较形象的去理解相关的结构体了。

那么,在Pinctrl子系统中,是怎么去实现这三大作用的呢?

pinctrl_desc和pinctrl_dev

我们刚刚说,controller 的部分,内核会抽象出一个 pinctrl_dev 结构体,但是事实上我们并不需要自己构造出这个 pinctrl_dev 结构体,而是使用内核提供的注册接口(pinctrl_register函数),我们只需要提供一个pinctrl_desc,然后调用这个接口,接口的返回值就是一个指向 pinctrl_dev 结构体的指针

通过 pinctrl_devpinctrl_desc 这两个结构体,就可以描述一个 pincontroller

controller相关结构体说明

imx6ull 为例,来了解pinctrl子系统中,controller 主要的数据结构,看看代码中是如何通过这些数据结构,实现 pinctrl 的三大作用的。

以下是 imx6ull 的pinctrl节点:

在设备启动后,这个节点会被转换成一个平台设备,和对应的平台驱动匹配完成后,就会调用对应的 probe 函数。

probe函数的大致流程

根据 compatible 属性值("fsl,imx6ul-iomuxc"),可以找到对应的驱动文件是 pinctrl-imx6ul.c,对应的 probe 函数是 imx6ul_pinctrl_probe,imx6ul_pinctrl_probe中则会调用 imx_pinctrl_probe函数。

imx_pinctrl_probe 函数中,会定义一个结构体指针 imx_pinctrl_desc,指向一个 pinctrl_desc 结构体。

在之后的代码中,首先申请一段内存,用来保存 pinctrl_desc,然后填充 pinctrl_desc 结构体,最后调用 devm_pinctrl_register,进行注册。

三大作用的具体实现

上面是probe函数中相关操作的大致流程,下面来具体说一下,在 Pinctrl 子系统中,是怎么去实现上面说的三大作用(引脚枚举与命名,引脚复用,引脚配置)的。

实现的关键就在于 pinctrl_desc 结构体,下面依次说明。

引脚的枚举与命名

首先看第一个功能,引脚的枚举与命名。

需要注意一下,引脚的枚举与命名分为两种情况

  1. 单个引脚 的枚举与命名;
  2. 多个引脚 的枚举与命名;
单个引脚

单个引脚的枚举和命名,主要是通过 pinctrl_desc 结构体的 pins 成员和 npins 成员来实现的。

其中,pins 成员是一个结构体指针,指向一个 pinctrl_pin_desc 结构体,主要负责引脚的枚举与命名;而 npins 成员则是一个无符号的整型数据,用来记录引脚的总个数

在 probe 函数中,会对 pins 和 npins 赋值,大致流程如下:

其中,imx6ul_pinctrl_info 变量的类型是 imx_pinctrl_soc_info 结构体,他也有 pins 和 npins 成员:

可以看到,他的 pins 成员指向了一个 imx6ul_pinctrl_pads 变量,这个变量是一个结构体数组,我们稍后再说。

综上,相当于执行了:

imx_pinctrl_desc->pins = imx6ull_snvs_pinctrl_pads;

imx_pinctrl_desc->npins = ARRAY_SIZE(imx6ull_snvs_pinctrl_pads);

下面,看一下 imx6ul_pinctrl_pads 变量,他是一个结构体数组。通过对 IMX_PINCTRL_PIN 宏的分析,可以看到,这里主要定义了两个成员:

  1. number(第几个引脚,引脚的枚举)
  2. name (引脚的名字,引脚的命名)

 

总结一下,单个引脚的枚举与命名,主要的相关结构体是:

  1. pinctrl_pin_desc结构体;

多个引脚

上面说 pins 和 npins,他们是描述单个引脚。而在实际使用中,有时候会需要同时操作多个引脚group),比如i2c中我们要用到一组引脚,要如何同时操作多个引脚呢?

答:这个时候就要用到 pinctrl_ops 结构体了:

可以看到, pinctrl_ops结构体的成员全部都是函数指针,它们的功能如下:

struct pinctrl_ops {
    /* 返回已注册的group数
     *  - struct pinctrl_dev *pctldev:引脚控制设备结构体指针,表示引脚控制器设备。
     */
	int (*get_groups_count) (struct pinctrl_dev *pctldev);

    /* 返回指定group的名字
     *  - struct pinctrl_dev *pctldev:引脚控制设备结构体指针,表示引脚控制器设备。
     *  - unsigned selector:group选择器,表示选择哪个group。
     */
	const char *(*get_group_name) (struct pinctrl_dev *pctldev,
				       unsigned selector);

    /* 返回指定group的引脚数组,并在num_pins中返回数组大小。
     *  - struct pinctrl_dev *pctldev:引脚控制设备结构体指针,表示引脚控制器设备。
     *  - unsigned selector:group选择器,表示选择哪个group。
     *  - const unsigned **pins:指向存储引脚数组的指针的指针。
     *  - unsigned *num_pins:指向存储引脚数组大小的变量的指针。
     */
	int (*get_group_pins) (struct pinctrl_dev *pctldev,
			       unsigned selector,
			       const unsigned **pins,
			       unsigned *num_pins);

    /* 可选的debugfs钩子函数,用于在debugfs中为特定引脚提供每个设备的信息。
     *  - struct pinctrl_dev *pctldev:引脚控制设备结构体指针,表示引脚控制器设备。
     *  - struct seq_file *s:序列文件结构体指针,用于在debugfs中显示信息。
     *  - unsigned offset:偏移量,表示特定引脚的偏移量。
     */
	void (*pin_dbg_show) (struct pinctrl_dev *pctldev, struct seq_file *s,
			  unsigned offset);

    /* 解析设备树中的“引脚配置节点”,并为其创建映射表条目。这些通过`map`和`num_maps`输出参数返回。此函数是可选的,对于不支持设备树的引脚控制驱动程序可以省略。
     *  - struct pinctrl_dev *pctldev:引脚控制设备结构体指针,表示引脚控制器设备。
     *  - struct device_node *np_config:设备树中的引脚配置节点。
     *  - struct pinctrl_map **map:指向映射表指针的指针,用于返回映射表条目。
     *  - unsigned *num_maps:指向存储映射表条目数量的变量的指针。
     */
	int (*dt_node_to_map) (struct pinctrl_dev *pctldev,
			       struct device_node *np_config,
			       struct pinctrl_map **map, unsigned *num_maps);

    /* 释放通过`dt_node_to_map`创建的映射表条目。必须释放顶层`map`指针,以及映射表条目本身的任何动态分配成员。此函数是可选的,对于不支持设备树的引脚控制驱动程序可以省略。
     *  - struct pinctrl_dev *pctldev:引脚控制设备结构体指针,表示引脚控制器设备。
     *  - struct pinctrl_map *map:映射表指针,需要释放。
     *  - unsigned num_maps:映射表条目数量。
     */
	void (*dt_free_map) (struct pinctrl_dev *pctldev,
			     struct pinctrl_map *map, unsigned num_maps);
};

这里说明一下,pinctrl_ops结构体成员中,有一个很关键的函数指针 dt_node_to_map,他是用来处理设备树的,我们以后再说,这里先点一下。

所以,对于多个引脚(group),相关的结构体是:

  1. pinctrl_ops 结构体;

引脚复用

类似的,引脚的复用也是由一个结构体来实现:pinmux_ops

pinmux_ops 就是 "Pin Multiplexing Operations"的缩写,表示引脚复用操作。

可以看到,结构体内部主要也是一堆函数指针

其中,引脚的复用主要是通过 set_mux 成员来实现的,他也是一个函数指针:

这里目前只对set_mux成员做说明,后面如果有用到其他函数指针,到时候再补充。

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);

    /* 启用特定的muxing功能与特定的group。驱动程序无需确定启用此功能是否与该组中pin的其他用途冲突,这种冲突由pinmux子系统处理。
     *  - struct pinctrl_dev *pctldev: 指向pinctrl设备结构的指针,用于表示设置mux的pinctrl设备。
     *  - unsigned func_selector: 无符号整数,表示选择什么功能。
     *  - unsigned group_selector: 无符号整数,表示选择哪个group。
     */
	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;
};

引脚配置

我们还可以将一个或一组引脚,设置成不同的配置,比如上拉,下拉,open drain(开漏)等等。

这是怎么操作的呢?

答:同样是通过一个结构体:pinconf_ops

pinconf_ops 就是"Pin Configuration Options"的缩写,表示引脚的配置操作。

可以看到,结构体内部主要还是一堆函数指针。

这里主要说明以下四个函数指针:

  1. pin_config_get:获取某个pin的配置;
  2. pin_config_set:设置某个pin的配置;
  3. pin_config_group_get:获取某个group的配置;
  4. pin_config_group_set:设置某个group的配置;

注册pinctrl_dev

填充完 pinctrl_desc 结构体之后,调用 devm_pinctrl_register pinctrl_register,就可以根据 pinctrl_desc 构造出 pinctrl_dev,并且把 pinctrl_dev 放入链表

devm_pinctrl_register
    pinctrl_register
    	struct pinctrl_dev *pctldev;
		pctldev = kzalloc(sizeof(*pctldev), GFP_KERNEL);

		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);

		list_add_tail(&pctldev->node, &pinctrldev_list);

总结

综上,对于controller,涉及的结构体主要有5个:

  1. pinctrl_desc:用于描述一个特定的引脚控制器(pinctrl)的配置信息,包括该控制器管理的引脚数量、引脚的功能、引脚的默认状态等。
  2. pinctrl_dev:代表一个具体的引脚控制器设备,它与`pinctrl_desc`结构体相关联,用于在系统中表示和管理一个特定的引脚控制器。
  3. pinctrl_ops :定义了对引脚控制器进行操作的一组函数指针,包括引脚的配置、引脚的状态读取、引脚的状态设置等操作。
  4. pinmux_ops:定义了对引脚复用(pin multiplexing)进行操作的一组函数指针,用于配置引脚的不同功能模式,例如将一个引脚配置为GPIO模式或者特定外设的模式。
  5. pinconf_ops:定义了对引脚配置(pin configuration)进行操作的一组函数指针,用于设置和获取引脚的一些特定属性,例如引脚的电压、上下拉设置等。

device相关结构体说明

上面主要是对左边controller相关的结构体说明,下面来讨论一下右边device相关的结构体。

dev_pin_info 结构体

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

/* 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 结构体。在 device 结构体中会有一个 pins 成员,这个 pins 成员是一个结构体指针,指向一个 dev_pin_info 结构体。

dev_pin_info 结构体保存的就是这个 device 的 pinctrl 信息

下面是 dev_pin_info 结构体的定义:

可以看到,主要定义了:

  1. 一个指向 pinctrl 结构体的结构体指针p;
  2. 4个指向 pinctrl_state 结构体的结构体指针 default_state,init_state,sleep_state,idle_state。

结合device节点,分析理解一下 dev_pin_info 结构体,以下面的device节点为例:

右边的device节点中,定义了两种状态:

  1. 状态0:default(对应 controller 节点 state_0_node_a) ;
  2. 状态1:sleep(对应 controller 节点 state_1_node_a 和 state_1_node_b)。

那么,就会用这些节点,来构造 dev_pin_info 结构体中的 default_state sleep_state

可以看到,dev_pin_info 结构体中已经定义了 4 个pinctrl_state指针,如果要添加我们自定义的state,要怎么记录呢?

答:dev_pin_info 结构体中有一个 pinctrl 结构体,我们自定义的 state 就存放在这个 pinctrl 结构体中。

假设要添加一个自定义的state,名字叫做“plane”,意为飞行模式,那么节点会变成这样:

pinctrl 结构体中有一个 states 成员,这个成员就会以链表的形式保存所有state,根据状态的序号,依次分别是default,sleep,plane。

并且 dev_pin_info 结构体中原先就有定义的 default_state 和 sleep_state,他们也会指向 states 成员中保存的 default 和 sleep 状态信息。

综上,对应device节点,最重要的结构体当属 pinctrl_state 结构体。

当设备进入某种状态时,就要把引脚配置成对应的 state。

那么,我们如何构造 pinctrl_state 呢?

如何构造pinctrl_state?

以下图为例,device 节点中的 pinctrl-0(default状态) 使用的是 state_0_node_a 节点,那么自然就要根据 state_0_node_a 节点来构造出 default_state。

那么,怎么根据pinctrl节点构造state呢?

答:需要用到 pinctrl_ops 结构体中的 dt_node_to_map 成员了。

dt_node_to_map 就是 "device tree node ==》map",顾名思义,就是将设备树的节点转换成一系列的map结构体(即pinctrl_map结构体)的意思。

通过 dt_node_to_map,将 pinctrl 节点转换为 pinctrl_map,再由 pinctrl_map 转换为 pinctrl_setting,最后,pinctrl_setting 会被存入 pinctrl_state 中的 settings 链表。

 

那么,pinctrl_map pinctrl_setting 中,需要保存哪些信息呢?

答:对于 pinctrl 节点,他主要有两个作用:

  1. pin mux,引脚复用;
  2. pin cfg,引脚配置;

那么,显然,pinctrl_map pinctrl_setting 就需要将这两个信息(引脚的复用信息,引脚的配置信息)保存记录下来。

首先,看一下 pinctrl_map 结构体:

可以看到,pinctrl_map 内部有一个联合体(union)data,这个联合体有两个成员:

  1. pinctrl_map_mux,记录复用信息;
  2. pinctrl_map_configs,记录配置信息;

所以,pinctrl_map 既可以记录引脚的复用信息,也可以记录引脚的配置信息;

刚刚说了,pinctrl_map 还会转换出 pinctrl_setting,来看一下 pinctrl_setting 结构体:

可以看到,他也有一个联合体(union) data,并且这个data也有两个成员:

  1. pinctrl_setting_mux,记录复用信息;
  2. pinctrl_setting_configs,记录配置信息;

所以,与 pinctrl_map 一样,pinctrl_setting 也是既可以记录引脚的复用信息,也可以记录引脚的配置信息。

对比 pinctrl_map pinctrl_setting,可以发现两者高度类似:都可以保存引脚的复用信息,配置信息。

综上,我们知道了, 驱动程序会把 pinctrl子节点 转换成一系列(为什么说一系列?因为一个pinctrl子节点可能包含多个引脚)的 pinctrl_map pinctrl_setting 结构体,在 pinctrl_map pinctrl_setting 结构体中会保存引脚的配置信息,复用信息

并且 pinctrl_setting 结构体还会被存入 pinctrl_state,以后我们选择让这个设备进入某种状态时,就会根据这些setting,来设置那些引脚,选择引脚的功能,配置引脚的上下拉,驱动强度等等。

 

使用pinctr_setting

最后,我们来看一下 pinctrl_state 中的这一系列 setting,是如何被调用的,又是如何去配置引脚的

这主要会涉及 pinmux_ops 结构体中的 set_mux(设置复用)和 pinconf_ops 结构体中的 pin_config_set(设置引脚配置),pin_config_group_set(设置group配置)。

调用的流程如下:

really_probe
	pinctrl_bind_pins
		pinctrl_select_state
			/* 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);
                        switch (setting->type) {
                    	case PIN_MAP_TYPE_CONFIGS_PIN:
							ret = ops->pin_config_set(...);
                        break;
	                    case PIN_MAP_TYPE_CONFIGS_GROUP:
		                    ret = ops->pin_config_group_set(...);
                        break;
	                    default:
		                    return -EINVAL;
                        }
					break;
				default:
					ret = -EINVAL;
				    break;
			}		

这样,左右两边的结构体(controller和device)就产生了联系,当设备进入某种状态时,就可以将引脚设置为对应的配置。

当我们对上述的结构体都有了初步的了解之后,后面就可以开始进行更深入的分析了。

以上就是本节全部内容。

  • 8
    点赞
  • 25
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
pinctrl 子系统Linux 内核的一个子系统,用于管理和控制 SoC(System-on-a-Chip)上的引脚。在编写 pinctrl 子系统驱动时,需要完成以下几个步骤: 1. 定义 pinctrl 子系统设备树节点 在设备树定义 pinctrl 子系统节点,包括引脚组、引脚和功能等信息。这些信息将在驱动程序使用。 2. 注册 pinctrl 子系统 在驱动程序调用 pinctrl_register() 函数注册 pinctrl 子系统。此时,内核会根据设备树节点的信息创建 pinctrl 子系统的实例,并将其加入到内核的全局列表。 3. 实现 pinctrl 子系统驱动程序 实现 pinctrl 子系统驱动程序,包括 pinmux 和 pinconf 两个部分。 pinmux 部分负责选择引脚组的某个引脚,并将其配置为特定的模式(如输入、输出等)。 pinconf 部分负责配置引脚的其他属性,如电气特性、驱动能力等。 4. 注册 pinctrl 子系统驱动程序 在驱动程序调用 pinctrl_register_mappings() 函数注册 pinctrl 子系统驱动程序。这将使驱动程序与 pinctrl 子系统建立连接,并允许驱动程序向子系统发送命令。 5. 使用 pinctrl 子系统 在驱动程序使用 pinctrl 子系统,包括选择引脚组和引脚,配置引脚的模式和属性等。这些操作通过调用 pinctrl_select_state() 和 pinctrl_set_state() 等函数完成。 以上是编写 pinctrl 子系统驱动程序的基本步骤。需要注意的是,具体实现可能会因为硬件平台和需求的不同而有所不同。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值