嵌入式linux/鸿蒙开发板(IMX6ULL)开发(三十六)GPIO和Pinctrl子系统的使用(上)

简介: 嵌入式linux/鸿蒙开发板(IMX6ULL)开发(三十六)GPIO和Pinctrl子系统的使用

1.GPIO和Pinctrl子系统的使用


参考文档:

a. 内核 Documentation\devicetree\bindings\Pinctrl\ 目录下:Pinctrl-bindings.txt


b. 内核 Documentation\gpio 目录下: Pinctrl-bindings.txt


c. 内核 Documentation\devicetree\bindings\gpio 目录下: gpio.txt


注意:本章的重点在于“使用”,深入讲解放在“驱动大全”的视频里。

前面的视频,我们使用直接操作寄存器的方法编写驱动。这只是为了让大家掌握驱动程序的本质,在实际开发过程中我们可不这样做,太低效了!如果驱动开发都是这样去查找寄存器,那我们就变成“寄存器工程师”了,即使是做单片机的都不执着于裸写寄存器了。


Linux下针对引脚有2个重要的子系统:GPIO、Pinctrl。


1.1 Pinctrl子系统重要概念


1.1.1 引入


无论是哪种芯片,都有类似下图的结构:

1670921509137.jpg

要想让pinA、B用于GPIO,需要设置IOMUX让它们连接到GPIO模块;

要想让pinA、B用于I2C,需要设置IOMUX让它们连接到I2C模块。

所以GPIO、I2C应该是并列的关系,它们能够使用之前,需要设置IOMUX。有时候并不仅仅是设置IOMUX,还要配置引脚,比如上拉、下拉、开漏等等。


现在的芯片动辄几百个引脚,在使用到GPIO功能时,让你一个引脚一个引脚去找对应的寄存器,这要疯掉。术业有专攻,这些累活就让芯片厂家做吧──他们是BSP工程师。我们在他们的基础上开发,我们是驱动工程师。开玩笑的,BSP工程师是更懂他自家的芯片,但是如果驱动工程师看不懂他们的代码,那你的进步也有限啊。


所以,要把引脚的复用、配置抽出来,做成Pinctrl子系统,给GPIO、I2C等模块使用。 BSP工程师要做什么?看下图:

1670921519577.jpg

等BSP工程师在GPIO子系统、Pinctrl子系统中把自家芯片的支持加进去后,我们就可以非常方便地使用这些引脚了:点灯简直太简单了。

等等,GPIO模块在图中跟I2C不是并列的吗?干嘛在讲Pinctrl时还把GPIO子系统拉进来?

大多数的芯片,没有单独的IOMUX模块,引脚的复用、配置等等,就是在GPIO模块内部实现的。

在硬件上GPIO和Pinctrl是如此密切相关,在软件上它们的关系也非常密切。 所以这2个子系统我们一起讲解。


1.1.2 重要概念


设备树开始学习Pintrl会比较容易。

主要参考文档是:内核Documentation\devicetree\bindings\pinctrl\pinctrl-bindings.txt


这会涉及2个对象:pin controller、client device。

前者提供服务:可以用它来复用引脚、配置引脚。

后者使用服务:声明自己要使用哪些引脚的哪些功能,怎么配置它们。


a. pin controller:


在芯片手册里你找不到pin controller,它是一个软件上的概念,你可以认为它对应IOMUX──用来复用引脚,还可以配置引脚(比如上下拉电阻等)。 注意,pin controller和GPIO Controller不是一回事,前者控制的引脚可用于GPIO功能、I2C功能;后者只是把引脚配置为输入、输出等简单的功能。即先用pin controller把引脚配置为GPIO,再用GPIO Controler把引脚配置为输入或输出。


b. client device


“客户设备”,谁的客户?Pinctrl系统的客户,那就是使用Pinctrl系统的设备,使用引脚的设备。它在设备树里会被定义为一个节点,在节点里声明要用哪些引脚。

下面这个图就可以把几个重要概念理清楚:

1670921539741.jpg

上图中,左边是pin controller节点,右边是client device节点:


a. pin state:

对于一个“client device”来说,比如对于一个UART设备,它有多个“状态”:default、sleep等,那对应的引脚也有这些状态。 怎么理解?

比如默认状态下,UART设备是工作的,那么所用的引脚就要复用为UART功能。

在休眠状态下,为了省电,可以把这些引脚复用为GPIO功能;或者直接把它们配置输出高电平。

上图中,pinctrl-names里定义了2种状态:default、sleep。

第0种状态用到的引脚在pinctrl-0中定义,它是state_0_node_a,位于pin controller节点中。

第1种状态用到的引脚在pinctrl-1中定义,它是state_1_node_a,位于pin controller节点中。

当这个设备处于default状态时,pinctrl子系统会自动根据上述信息把所用引脚复用为uart0功能。

当这这个设备处于sleep状态时,pinctrl子系统会自动根据上述信息把所用引脚配置为高电平。


b. groups和function:

一个设备会用到一个或多个引脚,这些引脚就可以归为一组(group);

这些引脚可以复用为某个功能:function。

当然:一个设备可以用到多组引脚,比如A1、A2两组引脚,A1组复用为F1功能,A2组复用为F2功能。


c. Generic pin multiplexing node和Generic pin configuration node

在上图左边的pin controller节点中,有子节点或孙节点,它们是给client device使用的。

可以用来描述复用信息:哪组(group)引脚复用为哪个功能(function);

可以用来描述配置信息:哪组(group)引脚配置为哪个设置功能(setting),比如上拉、下拉等。


注意:pin controller节点的格式,没有统一的标准!!!!每家芯片都不一样。 甚至上面的group、function关键字也不一定有,但是概念是有的。


1.1.3 示例

1670921549725.jpg


1.1.4 代码中怎么引用pinctrl


这是透明的,我们的驱动基本不用管。当设备切换状态时,对应的pinctrl就会被调用。

比如在platform_device和platform_driver的枚举过程中,流程如下:

1670921564305.jpg

当系统休眠时,也会去设置该设备sleep状态对应的引脚,不需要我们自己去调用代码。


非要自己调用,也有函数:

devm_pinctrl_get_select_default(struct device *dev);      // 使用"default"状态的引脚
pinctrl_get_select(struct device *dev, const char *name); // 根据name选择某种状态的引脚
pinctrl_put(struct pinctrl *p);   // 不再使用, 退出时调用


1.2 GPIO子系统重要概念


1.2.1 引入


要操作GPIO引脚,先把所用引脚配置为GPIO功能,这通过Pinctrl子系统来实现。


然后就可以根据设置引脚方向(输入还是输出)、读值──获得电平状态,写值──输出高低电平。

以前我们通过寄存器来操作GPIO引脚,即使LED驱动程序,对于不同的板子它的代码也完全不同。

当BSP工程师实现了GPIO子系统后,我们就可以:

a. 在设备树里指定GPIO引脚

b. 在驱动代码中:使用GPIO子系统的标准函数获得GPIO、设置GPIO方向、读取/设置GPIO值。

这样的驱动代码,将是单板无关的。


1.2.2 在设备树中指定引脚


在几乎所有ARM芯片中,GPIO都分为几组,每组中有若干个引脚。所以在使用GPIO子系统之前,就要先确定:它是哪组的?组里的哪一个?

在设备树中,“GPIO组”就是一个GPIO Controller,这通常都由芯片厂家设置好。我们要做的是找到它名字,比如“gpio1”,然后指定要用它里面的哪个引脚,比如<&gpio10>。 有代码更直观,下图是一些芯片的GPIO控制器节点,它们一般都是厂家定义好,在xxx.dtsi文件中:

1670921597365.jpg

我们暂时只需要关心里面的这2个属性:

gpio-controller;
#gpio-cells = <2>;


“gpio-controller”表示这个节点是一个GPIO Controller,它下面有很多引脚。 “#gpio-cells = <2>”表示这个控制器下每一个引脚要用2个32位的数(cell)来描述。

为什么要用2个数?

其实使用多个cell来描述一个引脚,这是GPIO Controller自己决定的。比如可以用其中一个cell来表示那是哪一个引脚,用另一个cell来表示它是高电平有效还是低电平有效,甚至还可以用更多的cell来示其他特性。

普遍的用法是,用第1个cell来表示哪一个引脚,用第2个cell来表示有效电平:

GPIO_ACTIVE_HIGH : 高电平有效
GPIO_ACTIVE_LOW  :  低电平有效


定义GPIO Controller是芯片厂家的事,我们怎么引用某个引脚呢?

在自己的设备节点中使用属性"[<name>-]gpios",示例如下:

1670921620515.jpg

上图中,可以使用gpios属性,也可以使用name-gpios属性。


1.2.3 在驱动代码中调用GPIO子系统


在设备树中指定了GPIO引脚,在驱动代码中如何使用? 也就是GPIO子系统的接口函数是什么?

GPIO子系统有两套接口:基于描述符的(descriptor-based)、老的(legacy)。前者的函数都有前缀“gpiod_”,它使用gpio_desc结构体来表示一个引脚;后者的函数都有前缀“gpio_”,它使用一个整数来表示一个引脚。


要操作一个引脚,首先要get引脚,然后设置方向,读值、写值。


驱动程序中要包含头文件,

#include <linux/gpio/consumer.h>   // descriptor-based


#include <linux/gpio.h>            // legacy


下表列出常用的函数:

1670921651705.jpg

1670921657994.jpg

有前缀“devm_”的含义是“设备资源管理”(Managed Device Resource),这是一种自动释放资源的机制。它的思想是“资源是属于设备的,设备不存在时资源就可以自动释放”。

比如在Linux开发过程中,先申请了GPIO,再申请内存;如果内存申请失败,那么在返回之前就需要先释放GPIO资源。如果使用devm的相关函数,在内存申请失败时可以直接返回:设备的销毁函数会自动地释放已经申请了的GPIO资源。


建议使用“devm_”版本的相关函数。

举例,假设备在设备树中有如下节点:

foo_device {
  compatible = "acme,foo";
  ...
  led-gpios = <&gpio 15 GPIO_ACTIVE_HIGH>, /* red */
      <&gpio 16 GPIO_ACTIVE_HIGH>, /* green */
      <&gpio 17 GPIO_ACTIVE_HIGH>; /* blue */
  power-gpios = <&gpio 1 GPIO_ACTIVE_LOW>;
};


那么可以使用下面的函数获得引脚:

struct gpio_desc *red, *green, *blue, *power;
red = gpiod_get_index(dev, "led", 0, GPIOD_OUT_HIGH);
green = gpiod_get_index(dev, "led", 1, GPIOD_OUT_HIGH);
blue = gpiod_get_index(dev, "led", 2, GPIOD_OUT_HIGH);
power = gpiod_get(dev, "power", GPIOD_OUT_HIGH);


要注意的是,gpiod_set_value设置的值是“逻辑值”,不一定等于物理值。

什么意思?

1670921682973.jpg

旧的“gpio_”函数没办法根据设备树信息获得引脚,它需要先知道引脚号。 引脚号怎么确定? 在GPIO子系统中,每注册一个GPIO Controller时会确定它的“base number”,那么这个控制器里的第n号引脚的号码就是:base number + n。

但是如果硬件有变化、设备树有变化,这个base number并不能保证是固定的,应该查看sysfs来确定base number。

相关文章
|
2月前
|
Ubuntu Linux 开发者
Ubuntu20.04搭建嵌入式linux网络加载内核、设备树和根文件系统
使用上述U-Boot命令配置并启动嵌入式设备。如果配置正确,设备将通过TFTP加载内核和设备树,并通过NFS挂载根文件系统。
125 15
|
3月前
|
存储 监控 Linux
嵌入式Linux系统编程 — 5.3 times、clock函数获取进程时间
在嵌入式Linux系统编程中,`times`和 `clock`函数是获取进程时间的两个重要工具。`times`函数提供了更详细的进程和子进程时间信息,而 `clock`函数则提供了更简单的处理器时间获取方法。根据具体需求选择合适的函数,可以更有效地进行性能分析和资源管理。通过本文的介绍,希望能帮助您更好地理解和使用这两个函数,提高嵌入式系统编程的效率和效果。
134 13
|
7月前
|
NoSQL Linux C语言
嵌入式GDB调试Linux C程序或交叉编译(开发板)
【8月更文挑战第24天】本文档介绍了如何在嵌入式环境下使用GDB调试Linux C程序及进行交叉编译。调试步骤包括:编译程序时加入`-g`选项以生成调试信息;启动GDB并加载程序;设置断点;运行程序至断点;单步执行代码;查看变量值;继续执行或退出GDB。对于交叉编译,需安装对应架构的交叉编译工具链,配置编译环境,使用工具链编译程序,并将程序传输到开发板进行调试。过程中可能遇到工具链不匹配等问题,需针对性解决。
278 3
|
7月前
|
传感器 人工智能 网络协议
:嵌入式 Linux 及其用途
【8月更文挑战第24天】
275 0
|
25天前
|
Linux
Linux系统之whereis命令的基本使用
Linux系统之whereis命令的基本使用
63 23
Linux系统之whereis命令的基本使用
|
4月前
|
Linux 网络安全 数据安全/隐私保护
Linux 超级强大的十六进制 dump 工具:XXD 命令,我教你应该如何使用!
在 Linux 系统中,xxd 命令是一个强大的十六进制 dump 工具,可以将文件或数据以十六进制和 ASCII 字符形式显示,帮助用户深入了解和分析数据。本文详细介绍了 xxd 命令的基本用法、高级功能及实际应用案例,包括查看文件内容、指定输出格式、写入文件、数据比较、数据提取、数据转换和数据加密解密等。通过掌握这些技巧,用户可以更高效地处理各种数据问题。
407 8
|
4月前
|
监控 Linux
如何检查 Linux 内存使用量是否耗尽?这 5 个命令堪称绝了!
本文介绍了在Linux系统中检查内存使用情况的5个常用命令:`free`、`top`、`vmstat`、`pidstat` 和 `/proc/meminfo` 文件,帮助用户准确监控内存状态,确保系统稳定运行。
1252 6
|
12天前
|
缓存 Ubuntu Linux
Linux中yum、rpm、apt-get、wget的区别,yum、rpm、apt-get常用命令,CentOS、Ubuntu中安装wget
通过本文,我们详细了解了 `yum`、`rpm`、`apt-get`和 `wget`的区别、常用命令以及在CentOS和Ubuntu中安装 `wget`的方法。`yum`和 `apt-get`是高层次的包管理器,分别用于RPM系和Debian系发行版,能够自动解决依赖问题;而 `rpm`是低层次的包管理工具,适合处理单个包;`wget`则是一个功能强大的下载工具,适用于各种下载任务。在实际使用中,根据系统类型和任务需求选择合适的工具,可以大大提高工作效率和系统管理的便利性。
76 25
|
10天前
|
缓存 Linux
Linux查看内存命令
1. free free命令是最常用的查看内存使用情况的命令。它显示系统的总内存、已使用内存、空闲内存和交换内存的总量。 free -h • -h 选项:以易读的格式(如GB、MB)显示内存大小。 输出示例: total used free shared buff/cache available Mem: 15Gi 4.7Gi 4.1Gi 288Mi 6.6Gi 9.9Gi Swap: 2.0Gi 0B 2.0Gi • to
25 2
|
2月前
|
网络协议 Unix Linux
深入解析:Linux网络配置工具ifconfig与ip命令的全面对比
虽然 `ifconfig`作为一个经典的网络配置工具,简单易用,但其功能已经不能满足现代网络配置的需求。相比之下,`ip`命令不仅功能全面,而且提供了一致且简洁的语法,适用于各种网络配置场景。因此,在实际使用中,推荐逐步过渡到 `ip`命令,以更好地适应现代网络管理需求。
51 11