Pinctrl子系统(一)
一、pinctrl子系统相关概念
1.1 什么是pinctrl子系统?
如上图所示,在芯片内部通常存在一个管理引脚功能复用的模块IOMUX:
- 如果我们想让
pinA
和pinB
用于SPI
功能,需要设置IOMUX
,配置这两个引脚连接到SPI
模块。 - 如果我们想让
pinA
和pinB
用于GPIO
功能,需要设置IOMUX
,配置这两个引脚连接到GPIO
模块。
📌 此处的
GPIO
模块与SPI
、IIC
、UART
等为并列关系,它与pinctrl
子系统的关系是,使用pinctrl
子系统将pinA
、pinB
等复用至GPIO
模块,然后才能将pinA
、pinB
用于GPIO
功能。
除了将引脚复用为某种功能外,有时候还需要配置引脚的电气属性
,如上拉、下拉、开漏
等等。
目前,市场上有很多的芯片厂商,不同厂商生产的芯片在引脚功能上都有差异,此外,一个芯片动辄上百个引脚,如果我们每次更换芯片都需要一个一个的去配置引脚,那需要耗费太多的精力。虽然各个厂商的芯片引脚功能都有差异,但在使用时也有着诸多的共性,如上面说到的功能复用、特性配置等。linux内核中基于这些共性,提供了一套代码来管理这些引脚,这套代码就是pinctrl subsystem
,主要实现以下功能:
- 枚举所有可以控制的
pin
并做唯一标识; - 将
pin
进行功能复用; - 配置这些引脚的
电气属性
,如上拉、下拉、开漏
等等。
1.3 pinctrl子系统的重要概念
1.3.1 pin controller device 和 client device
pinctrl subsystem
涉及到了两个对象:
pin controller device
:其主要目的是提供服务,可以用来复用引脚,配置引脚;client device
:作为客户,使用pin controller
提供的服务,声明自己需要使用哪些引脚的哪些功能以及怎么去配置它们,因此只需要在设备节点中描述与引脚相关的信息。
在设备树中,以上两个对象被定义成了两个设备节点,如下所示。
/* client device */
device{
pinctrl-names = "default","sleep" /* 1.该设备有default和sleep两种状态 */
pinctrl-0 = <state_0_node_a>; /* 2.第0个状态名字是default,对应引脚在pinctrl-0里定义 */
pinctrl-1 = <state_1_node_a>; /* 3.第1个状态名字是sleep,对应引脚在pinctrl-1里定义 */
};
/* pin controller device */
pincontroller{
state_0_node_a{
function = "uart0"; /* 1.复用为哪些功能 */
groups = "u0rxtx","uortscts"; /* 2. 用到哪些引脚 */
};
state_1_node_a{
groups = "u0rxtx","uortscts";
output-high; /* 3.配置成什么状态 */
};
};
1.3.2 pin state
如上1.3.1所示,在一个client device
中,有多个状态
,第一个状态名为default
,其对应的引脚定义在pinctrl-0
中;第二个状态名为sleep
,其对应的引脚定义在pinctrl-1
中。
1.3.3 pin group
以功能为依据,用于同一功能的多个引脚为一个pin group
。当然,一个设备可以用到多组引脚,比如A1
、A2
两组引脚,A1
组复用为F1
功能,A2
组复用为F2
功能。
1.3.4 pin bank
pin bank
可以理解为同一个GPIO
控制器下的一组GPIO
端口,以s3c2440为例,分为了9 个GPIO控制器,如下:
GPIO控制器 | GPIO端口名称 | GPIO端口数量 |
---|---|---|
GPIOA | GPA0~GPA24 | 25 |
GPIOB | GPB0~GPB10 | 11 |
GPIOC | GPC0~GPC15 | 16 |
GPIOD | GPD0~GPD15 | 16 |
GPIOE | GPE0~GPE15 | 16 |
GPIOF | GPF0~GPF7 | 8 |
GPIOG | GPG0~GPG15 | 16 |
GPIOG | GPG0~GPG15 | 16 |
GPIOH | GPH0~GPH10 | 11 |
GPIOJ | GPJ0~GPJ12 | 13 |
二、pinctrl子系统的使用
2.1 在设备树文件中定义pin controller device节点和client device节点
- 查找原理图,确定设备使用了哪些引脚。
- 生成
pin controller
的设备树信息——功能复用和特性配置。由于pincontroller
格式不统一,生成pincontroller
设备树信息有3种方法:- 使用图形化工具;
- 看设备树文档或参考设备树的例子;
- 阅读驱动代码。
- 生成
client device
的设备树信息,比如在I2C设备节点里定义"pinctrl-name
",“pinctrl-0
”,“pinctrl-1
”。
2.2 在client driver中使用pinctrl子系统
方法1:linux内核自动调用pinctrl子系统
pinctrl
子系统对于驱动工程师来说是透明的,我们在编写client driver
时基本上不需要管,当设备切换状态时(对应设备树中的pinctrl-names
的状态),对应的pinctrl
子节点就会被调用,当系统休眠时,也会去设置该设备sleep
状态对应的引脚,不需要我们自己去调用代码。
那么,linux内核是如何调用pinctrl子系统呢?在linux系统初始化时,由如下操作:
- 注册
pin controller device
; - 将设备节点转换为
platform_device
,或者其它结构体(比如i2c_client、spi_device
);
在将设备节点转换为platform_device
后,platform_device
会被注册进linux内核中,在注册的过程中,pinctrl
子系统被调用。
以uart0
驱动为例,其设备节点:
uart0: serial@50000000 {
compatible = "samsung,s3c2410-uart";
reg = <0x50000000 0x4000>;
interrupts = <1 28 0 4>, <1 28 1 4>;
status = "okay";
pinctrl-names = "default";
pinctrl-0 = <&uart0_data>;
};
serial@50000000
设备节点会被转换为platform_device
,并被注册到Linux
内核中,以便与其它驱动程序进行匹配和绑定。其中,platform_device
注册使用函数为platform_device_register
,函数调用流程如下:
platform_device_register
platform_device_add
device_add
bus_probe_device;
device_initial_probe
__device_attach
bus_for_each_drv(dev->bus, NULL, &data, __device_attach_driver); // 对于plarform_bus_type下的每一个driver, 调用__device_attach_driver
__device_attach_driver
driver_match_device(drv, dev);
drv->bus->match(dev, drv)// 调用platform_bus_type.match
driver_probe_device(drv, dev)
really_probe(dev, drv)
ret = pinctrl_bind_pins(dev); // 绑定设备使用的pinctrl,获取一个pinctrl_dev handle
pinctrl_get(dev);
create_pinctrl
pinctrl_init_done(dev);//配置设备使用的pin
drv->probe //执行client driver中的probe函数
其中,函数pinctrl_bind_pins
的内部实现如下:
int pinctrl_bind_pins(struct device *dev)
{
/*1.先get*/
dev->pins->p = devm_pinctrl_get(dev);
/*2.获取各种state下的pin*/
dev->pins->default_state = pinctrl_lookup_state(dev->pins->p, PINCTRL_STATE_DEFAULT); /*default的必须要有,否则直接退出*/
dev->pins->init_state = pinctrl_lookup_state(dev->pins->p, PINCTRL_STATE_INIT);
/*3.选中一个state进行设置*/
if (IS_ERR(dev->pins->init_state)) {
/*这里对clent的gpio设备树节点进行解析,并对硬件进行了设置*/
ret = pinctrl_select_state(dev->pins->p, dev->pins->default_state); /*如果有init state,就设置为init state,否则设置为default state, Qcom没有定义sleep state*/
} else {
ret = pinctrl_select_state(dev->pins->p, dev->pins->init_state);
}
#ifdef CONFIG_PM //R上也定义了这个值
dev->pins->sleep_state = pinctrl_lookup_state(dev->pins->p, PINCTRL_STATE_SLEEP);
dev->pins->idle_state = pinctrl_lookup_state(dev->pins->p, PINCTRL_STATE_IDLE);
#endif
return 0;
}
方法2:使用pinctrl子系统向client driver提供的API
上面说到pinctrl
子系统对驱动工程师来说是透明的,我们驱动中基本不用管,但是当我们非要自己在驱动中调用pinctrl
子系统时,可进行如下操作:
- 解析设备树,获取
pin controller
节点。
struct pinctrl *devm_pinctrl_get(struct device *dev)
- 在
pin controller
节点的中,获取各种state
的gpio配置。
struct pinctrl_state *pinctrl_lookup_state(struct pinctrl *p, const char *name)
- 将上面获取的指定
state
设置到硬件中。
int pinctrl_select_state(struct pinctrl *p, struct pinctrl_state *state)
三、总结
pin controller
节点在pinctrl
子系统的驱动中是没有被解析的,是在设备驱动(client driver
)中使用的时候才会去解析设备树,进行实际的硬件配置。若是只是设备树中定义了pin controller
和client device
,但是没有client driver
使用,则不会什么影响。- 一般是驱动自身,在
suspend
回调中设置为sleep state
,resume
时设置为avtive state
。平台设备驱动在probe()
时会自动设置为default state
,其它状态驱动在probe()
内可以由驱动工程师手动进行设置。