i.MX8MM处理器采用了先进的14LPCFinFET工艺,提供更快的速度和更高的电源效率;四核Cortex-A53,单核Cortex-M4,多达五个内核 ,主频高达1.8GHz,2G DDR4内存、8G EMMC存储。千兆工业级以太网、MIPI-DSI、USB HOST、WIFI/BT、4G模块、CAN、RS485等接口一应俱全。H264、VP8视频硬编码,H.264、H.265、VP8、VP9视频硬解码,并提供相关历程,支持8路PDM接口、5路SAI接口、2路Speaker。系统支持Android9.0(支持获取root限)Linux4.14.78+Qt5.10.1、Yocto、Ubuntu20、Debian9系统。适用于智能充电桩,物联网,工业控制,医疗,智能交通等,可用于任何通用工业和物联网应用、
【公众号】迅为电子
【粉丝群】258811263
第五十四章 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子系统如下所示:
不同soc厂家的pin controller的节点里面的属性都可以通过Linux源码/Documentation/devicetree/bindings下的txt文档查看。
54.1.1 配置pinctrl
下面我们来看一下 pinctrl 子系统在 imx8mm 的设备树中是如何实现并使用的。要使用pinctl子系统,我们需要在设备树里面配置引脚的信息。举个例子来说,我们在8M的设备树文件itop8mm-evk.dtsi中找到一个iomux的节点,如下图所示:
cpp
panel@0 {
compatible = "itop_mipi_screen";
reg = <0>;
pinctrl-0 = <&pinctrl_mipi_dsi_en>;
reset-gpio = <&gpio1 8 GPIO_ACTIVE_HIGH>;
dsi-lanes = <4>;
video-mode = <2>; /* 0: burst mode
* 1: non-burst mode with sync event
* 2: non-burst mode with sync pulse
*/
panel-width-mm = <68>;
panel-height-mm = <121>;
status = "okay";/*"okay";*/
&iomuxc
{
pinctrl_mipi_dsi_en: mipi_dsi_en {
fsl,pins = <
MX8MM_IOMUXC_GPIO1_IO08_GPIO1_IO8 0x16
>;
};
......
};
iomuxc 节点就是 I.MX8MM的 IOMUXC 外设对应的节点,不同的外设使用的PIN不同,配置也不同
GPIO1_IO08的配置信息如下所示:
MX8MM_IOMUXC_GPIO1_IO08_GPIO1_IO8 0x16
这个引脚的配置部分分为俩部分:MX8MM_IOMUXC_GPIO1_IO08_GPIO1_IO8和0x16
我们接下来看看这俩部分是代表了什么含义,对于一个引脚来说,配置主要包括俩方面,一个是设置这个引脚的复用功能,一个是设置这个引脚的电器特性,所以我们猜想这俩个部分MX8MM_IOMUXC_GPIO1_IO08_GPIO1_IO8和0x16,一个是设置GPIO1_IO08的复用功能,一个是用来设置GPIO1_IO08的电气特性。
首先来看一下MX8MM_IOMUXC_GPIO1_IO08_GPIO1_IO8,这是一个宏定义,定义在
linux/uboot-imx/arch/arm/dts/include/dt-bindings/pinctrl/pins-imx8mm.h文件中。设备树文件会引用这个头文件。MX8MM_IOMUXC_GPIO1_IO08_GPIO1_IO8的宏定义内容如下所示:
如上图所示,一共有五个以"IMX8MM_IOMUXC_GPIO1_IO08_"开头的宏定义,通过查找imx8MM 的参考手册,这几个宏定义分别对应GPIO1_IO08这个引脚的8个复用IO,如下图所示:
如下所示,MX8MM_IOMUXC_GPIO1_IO08_GPIO1_IO8表示将GPIO1_IO8这个IO复用为GPIO1_IO8。此宏定义后面有5个数字,也就是这个宏定义具体的值。
#define MX8MM_IOMUXC_GPIO1_IO08_GPIO1_IO8 0x048 0x2B0 0x000 0x0 0x0
- 0x0 48:mux_reg 复用寄存器的偏移地址,设备树中的 iomuxc 节点就是 IOMUXC 外设对应的节点, 根据其 reg 属性可知 IOMUXC 外设寄存器起始地址为 0x30330000 。因此
0x30330000+0x0048=0x30330048,MX8MM_IOMUXC_GPIO1_IO08_GPIO1_IO8 寄存器地址正好是 0x30330048,
如图所示:
因此可知, 0x30330000+mux_reg 就是 PIN 的复用寄存器地址。
- 0x0 2B0:conf_reg 寄存器偏移地址,和 mux_reg 一样,0x30330000+0x02B0=0x303302B0,这个就是寄存器IOMUXC_SW_PAD_CTL_PAD_GPIO1_IO08的地址。
- 0x000:input_reg 寄存器偏移地址,有些外设有 input_reg 寄存器,有 input_reg 寄存器的外设需要配置 input_reg 寄存,没有的话就不需要设置.
- 0x0:mux_reg 寄存器值,用于设置 IO 复用模式,这里设置为 0x0,就相当于设置复用功能为
ENET1_RX_EN。
0x0:input_reg 寄存器值,在这里无效。
这就是宏MX8MM_IOMUXC_GPIO1_IO08_GPIO1_IO8的含义,看的比较仔细的同学应该会发现并没有 conf_reg 寄存器的值, config_reg 寄存器是设置一个 PIN 的电气特性的,这么重要的寄存器怎么没有值呢?0x16就是config_reg寄存器的值,这个值是由客户来设置的,通过设置这个值可以设置一个IO的上下拉,驱动能力,和速度。在这里就相当于设置寄存器MX8MM_IOMUXC_GPIO1_IO08_GPIO1_IO8的值为0x16
MX8MM_IOMUXC_GPIO1_IO08_GPIO1_IO8 0x16
54.1.2 调用 pinctrl
调用 pinctrl 一般在设备树中进行,在上一小节的举例中也体现出了如何调用 pinctrl。下面在来回顾一下
在这个例子中我们只关注panel设备节点下的"pinctrl-0"两条语句,这句的意思就是引用在 iomuxc 中配置的 pinctrl 节点。
因此总结一下,想要配置某个外设而需要配置某一个引脚为 GPIO 时,一般的配置流程是这样的:
- 在 IOMUXC/pinctrl 中对某一个引脚进行配置,两个部分(复用、初始化),在外设节点中调用
- 在驱动中获取设备节点以及 GPIO
- 对GPIO 进行配置
怎么在代码里面使用pin controller里面定义好的节点?我们来举几个小例子。
例1:
pinctrl-names = "default";
pinctrl-0 = <&pinctrl_hog_1>;
- pinctrl-names = "default"; 设备的状态,可以有多个状态,default为状态0
- pinctrl-0 = <&pinctrl_hog_1>;第0个状态所对应的引脚配置,也就是default状态对应的引脚在pin controller里面定义好的节点pinctrl_hog_1里面的管脚配置。
例2:
pinctrl-names = "default","wake up";
pinctrl-0 = <&pinctrl_hog_1>;
pinctrl-1 = <&pinctrl_hog_2>;
- pinctrl-names = "default","wake up"; 设备的状态,可以有多个状态,default为状态0,wake up为状态1
- pinctrl-0 = <&pinctrl_hog_1>;第0个状态所对应的引脚配置,也就是default状态对应的引脚在pin controller里面定义好的节点pinctrl_hog_1里面的管脚配置
- pinctrl-1 = <&pinctrl_hog_2>;第1个状态所对应的引脚配置,也就是wake up状态对应的引脚在pin controller里面定义好的节点pinctrl_hog_2里面的管脚配置
例3:
pinctrl-names = "default";
pinctrl-0 = <&pinctrl_hog_1
&pinctrl_hog_2>;
- pinctrl-names = "default"; 设备的状态,可以有多个状态,default为状态0
- pinctrl-0 = <&pinctrl_hog_1 &pinctrl_hog_2>;第0个状态所对应的引脚配置,也就是default状态对应的引脚在pin controller里面定义好的节点,对应pinctrl_hog_1和pinctrl_hog_2这俩个节点的管脚配置。
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
cpp
gpio_request(leddev.led0, "led0");
gpio_direction_output(leddev.led0, 1);
gpio_set_value(leddev.led0, 0);
gpio_direction_input(key.irqkeydesc[0].gpio);
gpio_get_value(keydesc->gpio);
gpio_free(leddev.led0);
下面具体讲解一下这几个函数的用法:
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 编号 |