0%

Linux驱动之Pinctrl子系统

Pinctrl概述

关于pinctrl主要可以归结为两类设置,其中一类是功能选择,即一组gpio是用于iic还是uart还是就作为普通gpio来用,另一类是gpio的特性配置,即上拉、下拉、驱动能力和速率的配置。而pinctrl主要负责这两类配置的管理工作。总结起来pinctrl主要完成以下三种功能:

  • 引脚枚举与命名(Enumerating and naming)
  • 引脚复用(Multiplexing):比如用作GPIO、I2C或其他功能
  • 引脚配置(Configuration):比如上拉、下来、open drain、驱动强度等

dts配置

以全志平台的写法如下(虚构的):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
uart0_pins: uart0-pins {
pins = "PA4", "PA5";
function = "uart0";
};
uart0_sleep_pins: uart0-pins {
pins = "PA4", "PA5";
function = "gpio";
};
...
&uart0 {
pinctrl-names = "default","sleep";
pinctrl-0 = <&uart0_pins>;
pinctrl-1 = <&uart0_sleep_pins>;
status = "okay";
};

pinctrl-0对应的pinctrl-names的第0个名称default,默认配置,也就是uart0在使用pinctrl的时候的默认配置,pinctrl-1对应的是pinctrl-names的第一个名称sleep,在系统进入休眠的时候会通过pinctrl将uart0的pin配置成gpio功能。

Pinctrl重要概念

在软件上将他们分为pin controller和client device两个设备,但是pinctrl一般是没有具体硬件的控制器的,就是gpio里的一些config和data寄存器,control是软件抽象的概念。

Pin Controller

pin controller和GPIO Controller不是一回事,前者控制的引脚可用于GPIO功能、I2C功能;后者只是把引脚配置为输入、输出等简单的功能。即先用pin controller把引脚配置为GPIO,再用GPIO Controler把引脚配置为输入或输出。

  • Pin State
    对于一个“client device”来说,比如对于一个UART设备,它有多个“状态”:default、sleep等,那对应的引脚也有这些状态。比如默认状态下,UART设备是工作的,那么所用的引脚就要复用为UART功能。在休眠状态下,为了省电,可以把这些引脚复用为GPIO功能;或者直接把它们配置输出高电平

  • Groups和Function
    一个设备会用到一个或多个引脚,这些引脚就可以归为一组(group)。这些引脚可以复用为某个功能:function

  • Multiplexing node和Configuration node
    可以用来描述复用信息:哪组(group)引脚复用为哪个功能(function) 可以用来描述配置信息:哪组(group)引脚配置为哪个设置功能(setting),比如上拉、下拉等

Client Device

具体到每个控制器一般都是要通过pin脚与外部连接的,这就需要使用soc的pin脚,那么这个控制器就是pinctrl的client device,具体的client device会通过pinctrl来将pin设置为相应的功能及配置。

数据结构

pinctrl的数据结构是比较复杂的,整体数据结构之间的关系如下:原图

上图是数据结构的整体结构图,主要分为三部分,下面我将分别介绍每一部分:

  • pin controller
    pin controller主要是描述该soc的所有引脚信息以及一系列操作函数集。该结构通过pinctrl_dev数据结构来描述,该结构体主要提供的功能是:将此引脚控制器的每个引脚描述符保存在radix_tree树中,通过struct pin_desc来描述该引脚控制器,pin_desc里有控制器的每个引脚配置信息以及访问及设置该引脚控制器的方法,也就是pctlops、pmxops和confops,分别是访问控制器下的pin和group的方法、设置复用功能和设置配置引脚的操作方法,这个一般由soc原厂来提供驱动

  • pinctrl_maps
    pinctrl_maps主要是描述板级配置信息,对于一块单板会使用的soc的不同控制器及不同配置,对于同一soc的iic0控制器在不同单板上可以接在不同的pin group上,这就是pin的管脚复用功能。在dts中如下:

    1
    2
    3
    4
    5
    6
    &uart0 {
    pinctrl-names = "default","sleep";
    pinctrl-0 = <&uart0_pins>;
    pinctrl-1 = <&uart0_sleep_pins>;
    status = "okay";
    };

    pinctrl-0和pinctrl-1等会被解析成一个个的pinctrl_maps然后通过pinctrl_register_mappings函数将每一个控制器用到的引脚信息这册到内核,实际上就是通过一个链表组织整个单板上控制器的引脚状态信息

  • pinctrl
    pinctrl主要是描述client设备管脚的状态和使用情况。例如对于uart、iic、spi等设备都需要通过pin与其他设备进行通信,那么就需要配置pin连接到对应的具体控制器以及配置pin的一些电器特性,例如上下拉,驱动能力速率等信息。该数据结构既然是client端的数据那么就需要挂入到设备驱动模型结构中,在上图可以看到该结构先是挂入到dev_pin_info结构,然后dev_pin_info又是struct device结构的成员,这是该结构的上半部分;下半部分是状态信息的描述了首先是挂接所有的状态例如default、sleep状态。然后每个状态下就是配置信息,这些配置信息来自上面介绍的pinctrl_maps结构体中的数据

实现

Pin Controller的注册

pin controller的注册主要调用吧如下函数:

1
2
struct pinctrl_dev *pinctrl_register(struct pinctrl_desc *pctldesc,
struct device *dev, void *driver_data)

该函数需要提供一个pinctrl_desc结构体,并返回一个pinctrl_dev结构体。由于函数调用关系比较简单这里就不画流程图了。这里最关键的是pinctrl_desc结构体:该结构体用于描述一个soc pin controller的信息,包含所支持的pin引脚的描述(每一个引脚的名称与index)、pin复用操作的接口(主要是struct pinmux_ops类型的变量,包含引脚的申请与释放、gpio引脚复用配置以及方向设置接口等)、pin引脚或group相关的引脚配置接口(包含引脚配置接口、引脚当前配置参数获取接口、group相关引脚的配置接口、debug接口等)、group相关的操作接口(获取group的个数、获取group的名称、获取group对应的引脚内容、从设备树设备节点中解析board pin描述信息并进行pinctrl map注册的接口dt_node_to_map)。然后pinctrl_register函数就是创建一个pinctrl_dev结构体然后设置相关成员变量并对pinctrl_desc结构体中提供的一些函数操作做检查(必须提供函数实现),再将pinctrl_dev链入pinctrldev_list全局链表中即可。

devm_pinctrl_register是pinctrl_register的资源管理版本,也是调用pinctrl_register实现的:

Pinctrl Map注册

pinctrl map的注册函数是pinctrl_register_mappings,如下:

1
2
int pinctrl_register_mappings(const struct pinctrl_map *maps,
unsigned num_maps)

该函数也比较简单,需要提供一个需要注册到内核的pinctrl_map结构体,然后pinctrl_register_mappings函数就对该结构体进行check,然后将其挂在pinctrl_maps链表上。

Bing过程

在前面提到了client里会通过设备树获得一个个的pinctrl_maps结构,而该结构是要根pin controller里的pin之间建立联系的,因为不同的soc pin脚的设计都是不一样的,其配置方法也不一样,而在控制器初始化阶段需要解析出该控制器所使用的pin脚然后与controller提供的pin脚建立联系通过controller的pin或group操作函数集来操作具体的pin配置为想要的功能。

在前面介绍设备驱动模型的时候有一张device注册过程的流程图,其中有一个细节就是在driver_probe_device的时候会调用really_probe函数,而really_probe函数又会调用pinctrl_bind_pins函数用来处理pinctrl信息,如下:原图

下面我们将其中的pinctrl处理过程补充完,pinctrl_bind_pins函数如下:原图

该函数主要完成以下工作:

  • 解析设备树,解析client设备的pinctrl状态信息,并创建相关结构
  • 通过设备树中的pinctrl的phandle找到controller配置中的pin,并将设备树中的每一个状态转换为map结构,这里由于不同的soc关于pinctrl的设备树写法不一致因此这里通过controller提供的回调函数dt_node_to_map转换为map结构(具体的soc有自己的实现自然知道转换方法)并将一系列的map转换为一系列的与map对应的setting结构
  • 对每一个map结构进行pinmux和pinconf的设置,同样设置函数也是由具体的controller提供实现,然后通过函数指针调用具体的实现

与gpio的关系

原则上gpio子系统是pinctrl的client用户,gpio和iic、spi这些一样会通过pinctrl来配置,但是实际上gpio和pinctrl之间有更为紧密的联系,如果通过soc的datasheet我们也可以了解到实际的soc中并没有具体的pin controller硬件,这个一般都在gpio的章节中介绍,与gpio的寄存器之间是密不可分的。这里关于gpio和pinctrl之间的关系可以参考窝窝科技的文章。

pinctrl和gpio之间的强耦合实现

首先还是看下相关数据结构,如下:原图

gpio和pinctrl通过gpio_pin_range建立映射关系,将gpio中的pin描述转换到pinctrl中的pin描述空间,从而将两者建立关联,这样gpio就可以不通过pinctrl提供的标准接口来设置pin的复用和配置了。gpio_pin_range这个描述关系通过dts文件来描述,这样就可以不通过代码将他们建立联系,写法如下:

1
2
3
4
5
6
qe_pio_e: gpio-controller@1460 {
#gpio-cells = <2>;
compatible = "fsl,qe-pario-bank-e", "fsl,qe-pario-bank";
gpio-controller;
gpio-ranges = <&pinctrl1 0 20 10>, <&pinctrl2 10 50 20>;
};

上面dts节点中的gpio-ranges关键字指定了两个gpio range:gpio controller(qe_pio_e)中的gpio0 ~ 9和pinctrl1中的pin20 ~ 29对应;gpio controller(qe_pio_e)中的gpio10 ~ 29和pinctrl2中的pin50 ~ 69对应。 而dts文件的解析过程如下:原图

以上过程将生成pinctrl_dev下由gpio_ranges组织成的链表。

下面以gpiod_get函数为例看一下gpio中具体是如何使用pinctrl中的接口的,gpiod_get函数流程如下:原图

该函数就是将gpio中的pin描述转换到pinctrl中的描述,进而找到该管脚在pinctrl中的描述然后调用pin controller提供的函数来设置引脚复用功能和配置引脚。

参考文献

https://blog.csdn.net/lickylin/article/details/106677781
https://blog.csdn.net/lickylin/article/details/106677851?spm=1001.2014.3001.5501
《韦东山老师的书籍和课程》
窝窝科技gpio子系统相关文章