pinctrl 子系统
分析总结 pinctrl 子系统框架结构,平台 mtk6771 内核版本 kernel-4.4 , 本文所有的分析均基于此版本。
¶一、pinctrl 子系统基本概念
引脚控制子系统(pin control subsystem),和设备模型一样是linux驱动最基础的系统之一。 对于一块 soc 的 cpu 上有很多引脚,驱动工程师需要根据其应用场景使其处于我们需要的状态,例如配置某个引脚为 gpio 或者 配置其为 i2c。对于不同的 cpu 其寄存器的地址往往是不一样的,比如 S3C2440 的 gpio 控制器的基地址为 0x53000000,而 mtk 的 gpio 地址为 0x10005000,而且寄存器的地址的位也表示不同的含义,而内核为了兼容不同的芯片于是创建出 pinctrl 子系统,该系统用于将板级信息从内核分离出来,对于真正的寄存器的操作,由 soc 厂家来完成(bsp工程师),而对于普通的驱动工程师来讲,我们调用内核给出的统一接口来设置对应的pin脚就行了。
¶1、pinctrl_dev
该结构是 pinctrl 子系统的核心结构,每一个 soc 都需要向内核注册一个 pinctrl_dev 来描述该 pinctl 子系统,它包含下面几部分内容。
- 注册到该子系统的 soc 的引脚控制器 pinctrl_desc
- 注册到该子系统的 pin 脚 pin_desc_tree
- 注册到该子系统的 pin 脚对应的 gpio_rang
- 注册到该子系统的 私有数据
- soc 的 pin 脚的默认状态以及,板子休眠时 pin 脚的状态,我看了一下 mtk 平台好像并没有用到这部分功能,不知道其他平台有没有用到这个功能。
1 | struct pinctrl_dev { |
¶2、pinctrl_desc
pinctrl_desc 表示引脚控制器,它是软件上抽象出来的概念,抽象这个结构是为了方便代码的编写,实际硬件并不存在这样的控制器,该结构描述对 pin 脚寄存器真正的操作接口,该接口通常由 soc 厂完成,一般情况一个 soc 只有一个 pinctrl_desc 结构,它包含了下面内容。
- soc 要处理的所有引脚的软件描述
- 获取每个(组)引脚的 pin 脚信息的操作接口 pctlops
- 每个(组)引脚的复用(pinmux)操作接口 pmxops
- 每个(组)引脚的电器特性(pinconfig)操作接口 confops
- 支持客制化的 pinconfig
1 | // 引脚控制器描述符,将其注册到引脚控制子系统 |
¶1) pinctrl_pin_desc
soc上有大量引脚,每一个引脚使用 pinctrl_pin_desc 来描述
1 | //内核中用其描述 pin 的信息 |
¶2) pinctrl_ops
对于引脚的使用,有时候一次性会使用到多个引脚,I2C接口会同时使用 2 个引脚,SPI 接口会同时使用 4 个引脚。需要以 group 为单位,访问控制多个 pin,这就是 pin groups。但是 mtk 平台采取的策略则是每一个 pin 就是一个 group。,而 pinctrl_ops 则用于获取对应 group 的 pin 脚信息。
1 | struct pinctrl_ops { |
需要注意的是该接口除了能够获取 pin group 信息之外,还有一个非常重要的回调接口 dt_node_to_map ,用于将设备树中的引脚配置转换为对应的 pinctrl_map。 该接口的实现通常由soc原厂实现。
¶3) pinmux
pinmux 表示引脚复,用这个概念就不做解释了。需要注意的是引脚复用和引脚的电器特性(pinconfig)并不是相同的概念, pinctrl子系统中引脚的复用类型用 func 来描述,例如,某 pin 可以复用为 i2c 也可复用为 spi,那么这个引脚则拥有两个 func。
1 | struct pinmux_ops { |
¶4) pinconf_ops
对于每一个引脚都有其特定的电器特性,如上拉、下拉、三态、强推挽输出等用 pinconfig 来描述,pinctrl子系统同样也给出了电器特性的操作函数回调接口。对于电器特性的操作既可以操作一个引脚,也可以操作一组引脚。对于 mtk 平台每个 pin 都是一个 group,因此只用到了 pin_config_group_get 和 pin_config_group_set 接口
1 | struct pinconf_ops { |
¶3、pinctrl
对于不同的设备的引脚状态统一由属于该设备的 pinctrl 统一管理。也就是说每一个设备都有一个属于自己的 pinctrl ,该 pinctrl 管理着该设备的 pinctrl_state。相关结构如下。
1 | struct pinctrl { |
¶1) pinctrl_state
用于描述设备上的 pin 脚所处的状态,一个设备上的 pin 脚可以有多种状态。
1 | struct pinctrl_state { |
每一个 pin 脚有两个状态, 引脚复用(pinmux),和引脚状态(pinconfig),这两种状态由 pinctrl_setting 来描述。
¶2) pinctrl_setting
一个 pinctrl_setting 只能表示一个引脚复用(pinmux)或者一组电器特性(pinconfig),通过 pinctrl_map_type 来区分当前的 pinctrl_setting 是描述 pinmux 还是描述 pinconfig,如果 pinctrl_map_type 为 PIN_MAP_TYPE_MUX_GROUP 表示该 setting 或 pinctrl_map 为引脚复用,如果为 PIN_MAP_TYPE_CONFIGS_GROUP 则表示 setting 或 pinctrl_map 为 pinconfig。
1 | struct pinctrl_setting { |
不过这个结构需要通过中间结构 pinctrl_map 转化而来,这个结构又通过 dts 配置的引脚参数创建而来。
¶3) pinctrl_map
该结构也是提供对应的索引,不过它提供的索引是以字符串的形式提供的。
1 | struct pinctrl_map { //三个 name 都是在 pinctrl_dt_to_map 函数获取,在 dt_remember_or_free_map 函数进行初始化。 |
¶二、mtk 平台寄存器的表示方式
如下图所示,mtk 平台的寄存器在内核中的数据组织方式如下所示:
¶三、pinctrl 子系统的系统框架
¶1、框架接口
pinctrl 子系统提供的服务并不复杂,主要提供了以下两个服务,向驱动工程师提供操作 pin 的接口也就是具体的设备要使用的接口,向 bsp 工程师提供对应的寄存器操作的接口。
¶2、内部实现
pinctrl 子系统提供的服务接口很简单,但它的内部实现还是有点复杂的。内核驱动都是基于设备模型来开发的,因此对引脚的操作都是基于设备来讲的。对于一个设备首先要做的就是配置其需要使用到的软硬件资源,当然其中就包括 pin 脚资源。而 pinctrl 子系统则需要解析处理我们配置的 pin 脚资源。当我们配置设备的引脚资源后 pinctrl 会在合适的时机来解析我们配置的引脚资源,什么时机是最合适的呢,显而易见设备和驱动匹配的时候。因为设备资源最终的使用者是驱动,当驱动匹配到设备的时候也就是要使用该资源的时候。当完成对资源的解析,我们只需要调用简单的接口就能使我们的设备上的 pin 脚处于我们需要的状态。
¶1) 配置设备的引脚资源
配置引脚资源有两种方式首先是使用设备树,这也是目前主流的方式。
1 | lcm_dev: lcm { |
上述 lcm 配置了两个 state ,他们分别是"lcm_1v8_en_low" 和 “lcm_1v8_en_high”.
¶2) 解析设备引脚资源的时机
当设备和驱动匹配的时候在 really_probe 函数中会调用 pinctrl_bind_pins 来解析并创建属于该设备的 pinctrl,匹配流程可以参考linux设备模型
1 | static int really_probe(struct device *dev, struct device_driver *drv) |
来看看这个函数做了什么
1 | int pinctrl_bind_pins(struct device *dev) |
这个函数解析 pin 脚信息并返回 pinctrl ,获取 default 以及其他默认state,这些state 在 include/linux/pinctrl/pinctrl-state.h 中提供
1 |
在 default 之后查找 init 的 state 如果不存在则设置为 default ,从代码也可以看出如果没有 default 也不会去获取后面的 state ,因此如果需要使用到 init、idle、sleep 这三个 state 必须配置 default state。由于解析过程涉及的函数比较多因此先给出内部数据结构的组织结构方便理解。
可以从数据结构中看出涉及到两个关键的数据 pinctrl_map 和 pinctrl_setting, 这两个数据结构是直接连接到外部的 pinmux 信息和 pinconfig 信息。
¶3) pinctrl 解析设备引脚资源
解析过程如下图所示
¶4) 源码分析
涉及到的代码相对比较多就不去排版了,每个函数都有注释可以作为一个手册阅读。
1 | // 1. 在 pinctrl_list 链表中查找属于本设备的 pinctrl |
1 | static struct pinctrl *create_pinctrl(struct device *dev) |
其实整个解析过程也是分为三步、part1-创建本设备的 pinctrl、part2-将 dts 转化为对应的 pinctrl_map、part3-最后是将 pinctrl_map 转换为pinctrl_setting
¶a. 创建 pinctrl_map
¶pinctrl_dt_to_map
1 | // 1. 从state = 0 开始,递增判断 pinctrl 所属的设备的的设备树节点是否存在 pinctrl-state ,不存在则直接返回 |
¶dt_to_map_one_config
1 | // 在 pinctrldev_list 中查找属于 gpio_lcm_pwr1v8_low 所在节点根节点的中的 pinctrl_dev, |
接下来是 mtk 平台的处理过程
¶mtk_pctrl_dt_node_to_map
1 | //对于 np_config(就是前面的 gpio_lcm_pwr1v8_low)的所有子节点调用 mtk_pctrl_dt_subnode_to_map, 从设备树中获取对应的配置,并创建对应的 pinctrl_map |
¶mtk_pctrl_dt_subnode_to_map
1 | //从设备树中获取对应的配置,并创建对应的 pinctrl_map |
¶mtk_pctrl_dt_node_to_map_func
1 | // 初始化 pinctrl_map 的 type 为 PIN_MAP_TYPE_MUX_GROUP, |
¶mtk_pctrl_is_function_valid
1 | //该 pin_num 的引脚复用 fnum 有效性检查 |
¶pinctrl_utils_add_map_configs
1 | //删除多余内存空间 |
¶pinconf_generic_parse_dt_config
1 | // 在设备节点 device_node 查找 dt_params 以及 pctldev->desc->custom_params 中的支持的引脚状态 |
¶parse_dt_cfg
1 | // 查询设备节点np中存在的 params[count] 数组中的属性, |
¶pinctrl_utils_reserve_map
1 | // 重新申请一片大小为 *num_maps + reserve 的内存空间,并将 map 的值复制过去, |
¶mtk_pctrl_find_group_by_pin
1 | //返回该 pin 脚所在的 group,mtk平台每一个 pin 就是一个 group |
¶dt_remember_or_free_map
1 | //进一步初始化 pinctrl_map, 并创建对应的 pinctrl_dt_map 映射 |
¶pinctrl_register_map
1 | // |
¶b. 将pinctrl_map转化为pinctrl_setting
¶add_setting
1 | static int add_setting(struct pinctrl *p, struct pinctrl_map const *map) |
¶create_state
1 | static struct pinctrl_state *create_state(struct pinctrl *p, |
¶pinmux_map_to_setting
1 | int pinmux_map_to_setting(struct pinctrl_map const *map, |
¶pinmux_func_name_to_selector
1 | static int pinmux_func_name_to_selector(struct pinctrl_dev *pctldev, |
¶pinctrl_get_group_selector
1 | //在所有的 group 中查找该 group 的下标 |
¶pinconf_map_to_setting
1 | //pinconf_map_to_setting 将 conifg 对应的 map 转化为setting |
¶pin_get_from_name
1 | //通过 name 返回对应的 pin 脚 |
¶四、pinctrl 子系统的使用
使用就比较简单了接口如下
1 | //获取当前设备的 pinctrl 句柄 |
mtk 平台可以参考这篇文章 mtk-GPIO设置与应用