GPIO和Pinctrl子系统的使用

前言

使用直接操作寄存器的方法编写驱动,非常低效。Linux 下针对引脚有 2 个重要的子系统:GPIO、Pinctrl。

Pinctrl子系统

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

要想让 pinA、B 用于 GPIO,需要设置 IOMUX 让它们连接到 GPIO 模块;
要想让 pinA、B 用于 I2C,需要设置 IOMUX 让它们连接到 I2C 模块。
所以 GPIO、I2C 应该是并列的关系,它们能够使用之前,需要设置 IOMUX。有时候并不仅仅是设置 IOMUX,还要配置引脚,比如上拉、下拉、开漏等等。
现在的芯片动辄几百个引脚,在使用到 GPIO 功能时,让你一个引脚一个引脚去找对应的寄存器,这要
疯掉。
所以,要把引脚的复用、配置抽出来,做成 Pinctrl 子系统,给 GPIO、I2C 等模块使用。

1、 重要概念

1.1、pin controller

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

1.2、client device

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

上图中,左边是 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,位于 pincontroller 节点中。
第 1 种状态用到的引脚在 pinctrl-1 中定义,它是 state_1_node_a,位于 pincontroller 节点中。
当这个设备处于 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),比如上拉、下拉等。

2、代码中怎样引用pinctrl

这是透明的,我们的驱动基本不用管。当设备切换状态时,对应的 pinctrl 就会被调用。
比如在 platform_device 和 platform_driver 的枚举过程中,流程如下:

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

GPIO子系统

1.1 引入

要操作 GPIO 引脚,先把所用引脚配置为 GPIO 功能,这通过 Pinctrl 子系统来实现。
然后就可以根据设置引脚方向(输入还是输出)、读值──获得电平状态,写值──输出高低电平。
以前我们通过寄存器来操作 GPIO 引脚,即使 LED 驱动程序,对于不同的板子它的代码也完全不同。
当 BSP 工程师实现了 GPIO 子系统后,我们就可以:
a. 在设备树里指定 GPIO 引脚
b. 在驱动代码中:
使用 GPIO 子系统的标准函数获得 GPIO、设置 GPIO 方向、读取/设置 GPIO 值。
这样的驱动代码,将是单板无关的。

1.2 在设备树中指定引脚

在几乎所有 ARM 芯片中,GPIO 都分为几组,每组中有若干个引脚。所以在使用 GPIO 子系统之前,就要先确定:它是哪组的?组里的哪一个?
在设备树中,“GPIO 组”就是一个 GPIO Controller,这通常都由芯片厂家设置好。我们要做的是找到
它名字,比如“gpio1”,然后指定要用它里面的哪个引脚,比如<&gpio1 0>。
有代码更直观,下图是一些芯片的 GPIO 控制器节点,它们一般都是厂家定义好,在 xxx.dtsi 文件中:

我们暂时只需要关心里面的这 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 是芯片厂家的事,我们怎么引用某个引脚呢?在自己的设备节点中使用属性
“[-]gpios”,示例如下

1.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

常用函数:

有前缀“devm_”的含义是“设备资源管理”(Managed Device Resource),这是一种自动释放资源的机 制。它的思想是“资源是属于设备的,设备不存在时资源就可以自动释放”。 比如在 Linux 开发过程中,先申请了 GPIO,再申请内存;如果内存申请失败,那么在返回之前就需要 先释放 GPIO 资源。如果使用 devm 的相关函数,在内存申请失败时可以直接返回:设备的销毁函数会自动地释放已经申请了的 GPIO 资源。 建议使用“devm_”版本的相关函数。
  • 1
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值