引言
本节说明Pinctrl子系统中主要的数据结构,对这些数据结构有所了解,也就是对Pinctrl子系统有所了解了。
前面说过,要使用Pinctrl子系统,就需要去配置设备树。
以内核面向对象的思想,设备树可以分为两部分,一部分(左边)用来描述Controller,另一部分(右边)则是描述使用引脚(使用controller)的device。
对于controller的部分,内核会抽象出一个 pinctrl_dev 结构体;对于 device 的部分,内核会抽象出一个 device 结构体,device 结构体中会有 pinctrl 方面的成员。
显然,这个 pinctrl 方面的成员肯定会和左边的 pinctrl_dev 结构体产生联系。
那么,它们之间是什么样的关系呢?
答:在我们了解完Pinctrl子系统的数据结构之后,他们之间的关系也就清晰了。
首先,在了解Pinctrl子系统的数据结构前,先回忆一下Pinctrl子系统的三大作用:
- 引脚枚举与命名(Enumerating and naming ):这个pinctrl支持哪些引脚,这些引脚叫什么名字;
- 引脚复用(Multiplexing ):用作什么功能,比如用作GPIO、I2C或其他功能;
- 引脚配置(Configuration):配置具体的引脚属性,比如上拉、下拉、open drain、驱动强度等;
记住这三大作用,就可以比较形象的去理解相关的结构体了。
那么,在Pinctrl子系统中,是怎么去实现这三大作用的呢?
pinctrl_desc和pinctrl_dev
我们刚刚说,controller 的部分,内核会抽象出一个 pinctrl_dev 结构体,但是事实上我们并不需要自己构造出这个 pinctrl_dev 结构体,而是使用内核提供的注册接口(pinctrl_register函数),我们只需要提供一个pinctrl_desc,然后调用这个接口,接口的返回值就是一个指向 pinctrl_dev 结构体的指针。
通过 pinctrl_dev 和 pinctrl_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 结构体,下面依次说明。
引脚的枚举与命名
首先看第一个功能,引脚的枚举与命名。
需要注意一下,引脚的枚举与命名分为两种情况:
- 单个引脚 的枚举与命名;
- 多个引脚 的枚举与命名;
单个引脚
单个引脚的枚举和命名,主要是通过 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 宏的分析,可以看到,这里主要定义了两个成员:
- number(第几个引脚,引脚的枚举) ;
- name (引脚的名字,引脚的命名);
总结一下,单个引脚的枚举与命名,主要的相关结构体是:
- 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),相关的结构体是:
- 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"的缩写,表示引脚的配置操作。
可以看到,结构体内部主要还是一堆函数指针。
这里主要说明以下四个函数指针:
- pin_config_get:获取某个pin的配置;
- pin_config_set:设置某个pin的配置;
- pin_config_group_get:获取某个group的配置;
- pin_config_group_set:设置某个group的配置;
注册pinctrl_dev
填充完 pinctrl_desc 结构体之后,调用 devm_pinctrl_register 或 pinctrl_register,就可以根据 pinctrl_desc 构造出 pinctrl_dev,并且把 pinctrl_dev 放入链表:
devm_pinctrl_registerpinctrl_registerstruct 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个:
- pinctrl_desc:用于描述一个特定的引脚控制器(pinctrl)的配置信息,包括该控制器管理的引脚数量、引脚的功能、引脚的默认状态等。
- pinctrl_dev:代表一个具体的引脚控制器设备,它与`pinctrl_desc`结构体相关联,用于在系统中表示和管理一个特定的引脚控制器。
- pinctrl_ops :定义了对引脚控制器进行操作的一组函数指针,包括引脚的配置、引脚的状态读取、引脚的状态设置等操作。
- pinmux_ops:定义了对引脚复用(pin multiplexing)进行操作的一组函数指针,用于配置引脚的不同功能模式,例如将一个引脚配置为GPIO模式或者特定外设的模式。
- 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 结构体的定义:
可以看到,主要定义了:
- 一个指向 pinctrl 结构体的结构体指针p;
- 4个指向 pinctrl_state 结构体的结构体指针 default_state,init_state,sleep_state,idle_state。
结合device节点,分析理解一下 dev_pin_info 结构体,以下面的device节点为例:
右边的device节点中,定义了两种状态:
- 状态0:default(对应 controller 节点 state_0_node_a) ;
- 状态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 节点,他主要有两个作用:
- pin mux,引脚复用;
- pin cfg,引脚配置;
那么,显然,pinctrl_map 和 pinctrl_setting 就需要将这两个信息(引脚的复用信息,引脚的配置信息)保存记录下来。
首先,看一下 pinctrl_map 结构体:
可以看到,pinctrl_map 内部有一个联合体(union)data,这个联合体有两个成员:
- pinctrl_map_mux,记录复用信息;
- pinctrl_map_configs,记录配置信息;
所以,pinctrl_map 既可以记录引脚的复用信息,也可以记录引脚的配置信息;
刚刚说了,pinctrl_map 还会转换出 pinctrl_setting,来看一下 pinctrl_setting 结构体:
可以看到,他也有一个联合体(union) data,并且这个data也有两个成员:
- pinctrl_setting_mux,记录复用信息;
- 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_probepinctrl_bind_pinspinctrl_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)就产生了联系,当设备进入某种状态时,就可以将引脚设置为对应的配置。
当我们对上述的结构体都有了初步的了解之后,后面就可以开始进行更深入的分析了。
以上就是本节全部内容。