概况
众所周知,每颗SoC都会有很多的pin,除了具有特殊作用的,比如电源、地等pin,其他的pin一般都会**“身兼数职”**,比如,一个pin既可以当做GPIO来使用,也可以用作UART的TX,又或者是SPI的MOSI;而且,这些pin往往具有不同的能力,比如,上拉、下拉、不同的驱动能力等等。
pinctrl子系统就是用来完成上述功能而设计的,pinctrl对下管理SoC所有的pin,对上提供配置SoC所有pin的接口。通常,再将linux系统移植到一款新SoC上时,其中一项工作,就是将所有pin注册到pinctrl子系统中,并且为配置这些pin提供相应接口。
pinctrl子系统Linux内核的位置,由于本篇文章,主要介绍pinctrl的基本配置、使用方式,如果你想更多的了解pinctrl,请参考最为权威的内核文档,这是内核5.18中关于pinctrl子系统的介绍。可以参考我之前写过的文章。
作用
pinctrl子系统作为嵌入式Linux的重要组成部分,其一般具有如下几项作用:
- 枚举、命名SoC所有的pin;
- pin分组管理功能;对于某些接口比如spi或者i2c,其一般需要多个pin配合使用,并且对于同一个外设控制器,其可能会有多组pin的组合。比如,i2c0控制器,其引脚组合可能是{21,22},也可能是{31,32}。pinctrl子系统需要具有管理这些pin组的能力。
- 提供pin复用配置;
- 提供配置pin特殊能力的接口,比如,上拉、下拉、推挽输出、开漏输出、负载驱动能力配置等等。
pinctrl会从系统全局的角度,管理所有的pin,提供pin互斥访问的服务,即,每个pin同一时间只能被一个consumer使用,并且只能配置成一种功能。
在进行驱动开发时,大到动辄有十多条控制信号线的网卡芯片,小到只有一个GPIO的led或者按键,谨记,第一件事就是配置外设所使用的pin的复用功能和IO特性。 就比如,我们使用一个pin来控制一个LED,我们要做的配置如下:
- pin复用为GPIO。
- IO为输出模式。
- 根据实际硬件电路,确定是否启用上拉、下拉,以及所需要的驱动能力。
架构
pinctrl子系统架构图表示,其提供两套接口,
- 对下,为各种硬件平台提供pins定义、控制方式注册接口。
- 对上,为各种驱动提供操作pins的接口,比如,GPIO基本控制接口,GPIO子系统与pinctrl子系统交互接口。
顶层接口
- 定义pinctrl硬件,实际上pinctrl硬件就是一片内存寄存器,这些寄存器可以配置pins,完成诸如复用(multiplex)、上/下拉配置(bias)、负载能力(load capacitance)、驱动能力等。
- 定义SoC所有的pin,pads, fingers, balls都是同一个意思,表示某一个pin,每个pinctrl硬件所控制的pins,属于同一个命名空间,范围是0~maxpins,它会略过不存在的pin,所以,这个空间可能是不连续的。
每个pinctrl硬件在初始化的时候,都会向pinctrl framework注册其所涉及的所有pins的描述符。这里以一个PGA(Pin Grid Array)封装的SoC为例,该SoC所有pins的排列方式如下:
A B C D E F G H
8 o o o o o o o o
7 o o o o o o o o
6 o o o o o o o o
5 o o o o o o o o
4 o o o o o o o o
3 o o o o o o o o
2 o o o o o o o o
1 o o o o o o o o
下面的代码枚举了上面SoC中所有的pins,宏PINCTRL_PIN用于定义每个pin的number和名字,number是系统唯一的。
注意:以上面SoC为例,这里pin的定义顺序是从0到63,这个顺序不是唯一的,它依赖于几个方面1)pingctrl硬件寄存器的布局;2)与GPIO numer映射时是否简单;
#include <linux/pinctrl/pinctrl.h>
const struct pinctrl_pin_desc foo_pins[] = {
PINCTRL_PIN(0, "A8"),
PINCTRL_PIN(1, "B8"),
PINCTRL_PIN(2, "C8"),
...
PINCTRL_PIN(61, "F1"),
PINCTRL_PIN(62, "G1"),
PINCTRL_PIN(63, "H1"),
};
static struct pinctrl_desc foo_desc = {
.name = "foo",
.pins = foo_pins,
.npins = ARRAY_SIZE(foo_pins),
.owner = THIS_MODULE,
};
int __init foo_probe(void)
{
int error;
struct pinctrl_dev *pctl;
error = pinctrl_register_and_init(&foo_desc, <PARENT>,
NULL, &pctl);
if (error)
return error;
return pinctrl_enable(pctl);
}
GPIO子系统与pinctrl子系统的关系
GPIO子系统控制硬件的操作,最终都是通过pinctrl实现的。但是,这两套系统都有自己的命名空间,比如,GPIO子系统是按照block来划分pin的,类似于blockA、blockB等等,而pinctrl子系统,是按照SoC的pin的序号,顺序定义的,所以,两个系统pin的索引不是一一对应的,这就需要一个map来进行映射。比如,下面的代码:
struct gpio_chip chip_a;
struct gpio_chip chip_b;
static struct pinctrl_gpio_range gpio_range_a = {
.name = "chip a",
.id = 0,
.base = 32,
.pin_base = 32,
.npins = 16,
.gc = &chip_a;
};
static struct pinctrl_gpio_range gpio_range_b = {
.name = "chip b",
.id = 0,
.base = 48,
.pin_base = 64,
.npins = 8,
.gc = &chip_b;
};
{
struct pinctrl_dev *pctl;
...
pinctrl_add_gpio_range(pctl, &gpio_range_a);
pinctrl_add_gpio_range(pctl, &gpio_range_b);
}
- chip_a和chip_b是GPIO子系统的两个block;
- pinctrl_gpio_range结构用于定义两个子系统pin的映射关系;
- base表示某一block中pin的起始序号,pin_base表示其在pinctrl子系统中起始序号,bpins表示block中pin的个数;
下面是就是两个两个子系统pin映射关系:
chip a:
- GPIO range : [32 .. 47]
- pin range : [32 .. 47]
chip b:
- GPIO range : [48 .. 55]
- pin range : [64 .. 71]
注意,上述表示,两个子系统中pin是线性映射的,如果是非线性或随机的呢?请看下面的代码:
static const unsigned range_pins[] = {
14, 1, 22, 17, 10, 8, 6, 2 };
static struct pinctrl_gpio_range gpio_range = {
.name = "chip",
.id = 0,
.base = 32,
.pins = &range_pins,
.npins = ARRAY_SIZE(range_pins),
.gc = &chip;
};
注意,用pins代替了线性映射的pin_base,而range_pins中的pin都是随机的。
**结论:**不管是线性映射还是随机映射,都要注意,对于同一个pin,在两个系统中的number,很有可能是不一样的!
GPIO子系统在控制某一pin时,通过两个系统pins的映射关系,转换为pinctrl中的pin number,从而控制硬件。