【北京迅为】《i.MX8MM嵌入式Linux开发指南》-第三篇 嵌入式Linux驱动开发篇-第五十四章 Pinctrl 子系统和 GPIO 子系统

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 编号 |

相关推荐
长弓聊编程14 分钟前
Linux系统使用valgrind分析C++程序内存资源使用情况
linux·c++
cherub.21 分钟前
深入解析信号量:定义与环形队列生产消费模型剖析
linux·c++
梅见十柒44 分钟前
wsl2中kali linux下的docker使用教程(教程总结)
linux·经验分享·docker·云原生
Koi慢热1 小时前
路由基础(全)
linux·网络·网络协议·安全
日晨难再1 小时前
嵌入式:STM32的启动(Startup)文件解析
stm32·单片机·嵌入式硬件
传而习乎1 小时前
Linux:CentOS 7 解压 7zip 压缩的文件
linux·运维·centos
我们的五年1 小时前
【Linux课程学习】:进程程序替换,execl,execv,execlp,execvp,execve,execle,execvpe函数
linux·c++·学习
yufengxinpian1 小时前
集成了高性能ARM Cortex-M0+处理器的一款SimpleLink 2.4 GHz无线模块-RF-BM-2340B1
单片机·嵌入式硬件·音视频·智能硬件
IT果果日记1 小时前
ubuntu 安装 conda
linux·ubuntu·conda
Python私教2 小时前
ubuntu搭建k8s环境详细教程
linux·ubuntu·kubernetes