iTOP-STM32MP157开发板采用ST推出的双核cortex-A7+单核cortex-M4异构处理器,既可用Linux、又可以用于STM32单片机开发。开发板采用核心板+底板结构,主频650M、1G内存、8G存储,核心板采用工业级板对板连接器,高可靠,牢固耐用,可满足高速信号环境下使用。共240PIN,CPU功能全部引出:底板扩展接口丰富底板板载4G接口(选配)、千兆以太网、WIFI蓝牙模块HDMI、CAN、RS485、LVDS接口、温湿度传感器(选配)光环境传感器、六轴传感器、2路USB OTG、3路串口,CAMERA接口、ADC电位器、SPDIF、SDIO接口等
第五十四章 Pinctrl 子系统和 GPIO 子系统
本章导读
在学习了设备树之后,如果还按照裸板开发的方式配置寄存器来控制IO的方式太过于原始,Linux内核提供了pinctrl子系统和gpio子系统用于GPIO驱动,当然pinctrl子系统负责的就不仅仅是GPIO的驱动了而是所有pin脚的配置。pinctrl子系统是随着设备树的加入而加入的,依赖于设备树。GPIO子系统在之前的内核中也是存在的,但是pinctrl子系统的加入GPIO子系统也是有很大的改变,之前的GPIO子系统需要芯片厂商提供的mach文件,而加入设备树后,GPIO子系统使用设备树来实现。
54.1章节讲解了pinctrl子系统。
54.2章节讲解了GPIO子系统。
本章内容对应视频讲解链接(在线观看):
“pinctl和gpio子系统 (一) ” → https://www.bilibili.com/video/BV1Vy4y1B7ta?p=29
54.1 Pinctrl子系统
大多数SOC的PIN都是支持复用的,此外我们还需要配置 pin 的电气特性,比如上/下拉、速度、驱动能力等等。传统的配置 pin 的方式就是直接操作相应的寄存器,但是这种配置方式比较繁琐、而且容易出问题(比如 pin 功能冲突)。pinctrl 子系统就是为了解决这个问题而引入的,pinctrl 子系统主要工作内容如下
- 获取设备树中pin信息,管理系统中所有的可以控制的pin,在系统初始化的时候,枚举所有可以控制的pin,并标识这些pin。
- 根据获得到的pin信息来设置pin的复用功能,对于SOC而言,其引脚除了配置成普通的GPIO之外,若干个引脚还可以组成一个pin group,形成特定的功能。
- 根据获得到的pin信息来设置pin的电气特性,比如上下拉、速度、驱动能力。
对于我们使用者来说,只需要在设备树里面设置好某个pin的相关属性即可,其他的初始化工作均由pinctrl子系统来完成,pinctrl 子系统源码目录drivers/pinctrl。
不同soc厂家的pin controller的节点是不一样的,但是这些节点里都是把某些引脚复用成某些功能。如
NXP的pinctrl子系统如下所示:
三星pinctrl子系统如下所示:
瑞星微pinctrl子系统如下所示:
ST的pinctrl子系统如下所示:
不同soc厂家的pin controller的节点里面的属性都可以通过内核源码/Documentation/devicetree/bindings下的txt文档查看。
下面我们来看一下 pinctrl 子系统在 stm32mp157的设备树中是如何实现并使用的。
首先来到linux内核目录下,如图所示:
然后我们使用以下命令进入stm32mp151.dtsi文件中,找到 pinctrl: pin-controller@50002000节点,如下图所示:
vim arch/arm/boot/dts/stm32mp151.dtsi
部分截图如下图所示:
pinctrl: pin-controller@50002000 {
#address-cells = <1>;
#size-cells = <1>;
compatible = "st,stm32mp157-pinctrl";
ranges = <0 0x50002000 0xa400>;
interrupt-parent = <&exti>;
st,syscfg = <&exti 0x60 0xff>;
hwlocks = <&hsem 0 1>;
pins-are-numbered;
#address-cells 属性值为 1 和#size-cells 属性值为 1第 1818 行,
ranges 属性表示 STM32MP1 的 GPIO 相关寄存器起始地址,0x50002000 表示起始地址,0xa400 表示寄存器地址范围。
interrupt-parent 属性值为“&exti”,父中断为 exti。
然后在该内容的下方可以找到如下内容:
gpioa: gpio@50002000 {
gpio-controller;
#gpio-cells = <2>;
interrupt-controller;
#interrupt-cells = <2>;
reg = <0x0 0x400>;
clocks = <&rcc GPIOA>;
st,bank-name = "GPIOA";
status = "disabled";
};
..........................................................
这些是关于gpio相关的,我们在稍后的GPIO子系统相关小节中会进行说明。绑定文档 Documentation/devicetree/bindings/pinctrl/st,stm32-pinctrl.yaml 描述了如何在设备树中设置 STM32 的 PIN 信息,截取出的一段例程如下
pinctrl@60020000 {
usart1_pins_a: usart1-0 {
pins1 {
pinmux = <STM32_PINMUX('A', 9, AF7)>;
bias-disable;
drive-push-pull;
slew-rate = <0>;
};
pins2 {
pinmux = <STM32_PINMUX('A', 10, AF7)>;
bias-disable;
};
};
};
以上内容对usart1-0节点进行定义, usart1_pins_a是usart1-0的别名,然后对两个引脚使用 pinmux属性进行分别的功能复用。其中pinmux属性的宏定义在include/dt-bindings/pinctrl/stm32-pinfunc.h 文件中,具体内容如下
/* define Pins number*/
#define PIN_NO(port, line) (((port) - 'A') * 0x10 + (line))
#define STM32_PINMUX(port, line, mode) (((PIN_NO(port, line)) << 8) | (mode))
port:表示用那一组 GPIO。
line:表示这组 GPIO 的第几个引脚。
mode:表示当前引脚要做那种复用功能
pinmux = <STM32_PINMUX('A', 9, AF7)>;
在这里以pins1的 pinmux属性为例子,进行讲解。首先前面的A代表GPIOA组别,而9代表该组别的第9个io口,之后的AF7为对应的复用功能,我们打开iTOP-STM32MP157开发板\iTOP-STM32MP157开发板光盘资料\20220223\01_开发板硬件资料\官方数据手册\STM32MP157A&D 数据手册.pdf ,然后我们来到4 Pinouts, pin description and alternate functions小节,即引脚输出、引脚描述和功能复用,找到PA9相关的功能描述如下图所示:
在找到PA9功能之后,然后找到对应的AF7,可以发现对应的复用功能为USART1_TX。
而如何对我们的pinctrl子系统进行调用呢,我们将我们在st,stm32-pinctrl.yaml例子中没列举的内容进行展示,内容如下
usart1 {
pinctrl-0 = <&usart1_pins_a>;
pinctrl-names = "default";
};
我们只需要将对应的节点内容,添加到&pinctrl 节点下,即可,这里我们在内核移植章节中进行了演示。
接下来我们进行gpio子系统的讲解。
54.2 GPIO子系统
当我们使用pinctrl子系统将引脚的复用关系设置为GPIO以后,我们就可以使用GPIO子系统来操作我们的GPIO了,Linux 内核提供了 pinctrl 子系统和 gpio 子系统用于 GPIO 驱动。当然 pinctrl 子系统负责的就不仅仅是 GPIO 的驱动了而是所有 pin 脚的配置。pinctrl 子系统是随着设备树的加入而加入的,依赖于设备树。GPIO 子系统在之前的内核中也是存在的,但是 pinctrl 子系统的加入 GPIO 子系统也是有很大的改变。为什么他会发生非常大的变化呢?之前我们控制一个GPIO可以直接来操作我们的寄存器,还有一种方法是使用SOC厂家实现的配置函数,例如三星的配置函数为s3c_gpio_cfgpin等,这样带来的问题就是各家有各家的接口函数与实现方式,不但内核的代码复用率低而且开发者很难记住这么多的函数,如果要使用多种平台的话背函数都是很麻烦的,所以在引入设备树后对GPIO子系统进行了大的改造,使用设备树来实现并提供统一的接口。通过 GPIO 子系统功能要实现:
- 引脚功能的配置(设置为 GPIO,特殊功能,GPIO 的方向,设置为中断等)
- 实现软硬件的分离(分离出硬件差异,有厂商提供的底层支持;软件分层。驱动只需要调用接口 API 即可操作 GPIO)
- iommu 内存管理(直接调用宏即可操作 GPIO)
经过 GPIO 子系统,我们可以通过如下的方式来配置 GPIO
下面具体讲解一下这几个函数的用法:
1 gpio_request 函数
函数 | int gpio_request(unsigned gpio, const char *label) |
gpio | 要申请的 gpio 标号,使用 of_get_named_gpio 函数从设备树获取指定 GPIO 属性信息,此函数会返回这个 GPIO 的标号。 |
label | 给 gpio 设置个名字 |
返回值 | 0,申请成功;其他值,申请失败 |
功能 | gpio_request 函数用于申请一个 GPIO 管脚 |
2 gpio_free函数
函数 | void gpio_free(unsigned gpio) |
gpio | 要释放的 gpio 标号 |
返回值 | 无 |
功能 | 如果不使用某个 GPIO 了,那么就可以调用 gpio_free 函数进行释放。 |
3 gpio_direction_input 函数
函数 | int gpio_direction_input(unsigned gpio) |
gpio | 要设置为输入的 GPIO 标号 |
返回值 | 设置成功返回0;设置失败返回负值 |
功能 | 此函数用于设置某个 GPIO 为输入 |
4 gpio_direction_output 函数
函数 | int gpio_direction_output(unsigned gpio, int value) |
gpio | 要设置为输出的 GPIO 标号 |
value | GPIO 默认输出值 |
返回值 | 设置成功返回0;设置失败返回负值 |
功能 | 此函数用于设置某个 GPIO 为输出,并且设置默认输出值 |
5 gpio_get_value函数
函数 | int __gpio_get_value(unsigned gpio) |
gpio | 要获取的 GPIO 标号 |
返回值 | 成功返回GPIO值,失败返回负值 |
功能 | 此函数用于获取某个 GPIO 的值(0 或 1) |
6 gpio_set_value函数
函数 | void __gpio_set_value(unsigned gpio, int value) |
gpio | 要设置的 GPIO 标号 |
value | 要设置的值 |
返回值 | 无 |
功能 | 此函数用于设置某个 GPIO 的值 |
7 of_get_named_gpio 函数
函数 | int of_get_named_gpio(struct device_node *np,const char *propname,int index) |
np | 设备节点 |
propname | 包含要获取 GPIO 信息的属性名 |
index | 因为一个属性里面可能包含多个 GPIO,此参数指定要获取哪个 GPIO的编号,如果只有一个 GPIO 信息的话此参数为 0 |
返回值 | 成功返回到的 GPIO 编号,失败返回一个负数 |
功能 | 此函数获取 GPIO 编号,因为 Linux 内核中关于 GPIO 的 API 函数都要使用 GPIO 编号,此函数会将设备树中类似<&gpio1 3 GPIO_ACTIVE_LOW>的属性信息转换为对应的 GPIO 编号 |