最近学习linux驱动中遇到一些gpio接口,便开始查找资料学习,在linux内核文档Documentation/gpio.txt中感觉里面写的挺不错的,就拿来与大家分享。
什么是GPIO
“通用输入/输出口”(GPIO)是一个灵活的由软件控制的数字信号。他们可由多种芯片提供,且对于从事嵌入式和定制硬件的 Linux 开发者来说是比较熟悉。每个GPIO 都代表一个连接到特定引脚或球栅阵列(BGA)封装中“球珠”的一个位。电路板原理图显示了 GPIO 与外部硬件的连接关系。驱动可以编写成通用代码,以使板级启动代码可传递引脚配置数据给驱动。
片上系统 (SOC) 处理器对 GPIO 有很大的依赖。在某些情况下,每个非专用引脚都可配置为 GPIO,且大多数芯片都最少有一GPIO。可编程逻辑器件(类似 FPGA) 可以方便地提供 GPIO。像电源管理和音频编解码器这样的多功能芯片经常留有一些这样的引脚来帮助那些引脚匮乏的 SOC。同时还有通过 I2C 或 SPI 串行总线连接的“GPIO扩展器”芯片。大多数 PC 的南桥有一些拥有 GPIO 能力的引脚 (只有BIOS固件才知道如何使用他们)。
GPIO 的实际功能因系统而异。通常用法有:
- 输出值可写 (高电平=1,低电平=0)。一些芯片也有如何驱动这些值的选项,例如只允许输出一个值、支持“线与”及其他取值类似的模式(值得注意的是“开漏”信号)
- 输入值可读(1、0)。一些芯片支持引脚在配置为“输出”时回读,这对于类似“线与”的情况(以支持双向信号)是非常有用的。GPIO 控制器可能有输入去毛刺/消抖逻辑,这有时需要软件控制。
- 输入通常可作为 IRQ 信号,一般是沿触发,但有时是电平触发。这样的 IRQ 可能配置为系统唤醒事件,以将系统从低功耗状态下唤醒。
- 通常一个 GPIO 根据不同产品电路板的需求,可以配置为输入或输出,也有仅支持单向的。
- 大部分 GPIO 可以在持有自旋锁时访问,但是通常由串行总线扩展的 GPIO不允许持有自旋锁。但某些系统也支持这种类型。对于给定的电路板,每个 GPIO 都用于某个特定的目的,如监控 MMC/SD 卡的插入/移除、检测卡的写保护状态、驱动 LED、配置收发器、模拟串行总线、复位硬件看门狗、感知开关状态等等。
GPIO 公约
注意,这个叫做“公约”,因为这不是强制性的,不遵循这个公约是无伤大雅的,因为此时可移植性并不重要。GPIO 常用于板级特定的电路逻辑,甚至可能随着电路板的版本而改变,且不可能在不同走线的电路板上使用。仅有在少数功能上才具有可移植性,其他功能是平台特定。这也是由于“胶合”的逻辑造成的。此外,这不需要任何的执行框架,只是一个接口。某个平台可能通过一个简单地访问芯片寄存器的内联函数来实现它,其他平台可能通过委托一系列不同的GPIO控制器的抽象函数来实现它。(有一些可选的代码能支持这种策略的实现,本文档后面会介绍,但作为 GPIO 接口的客户端驱动程序必须与它的实现无关。)也就是说,如果在他们的平台上支持这个公约,驱动应尽可能的使用它。
同时,平台必须在Kconfig中选择ARCH_REQUIRE_GPIOLIB或者ARCH_WANT_OPTIONAL_GPIOLIB选项。那些调用标准 GPIO 函数的驱动应该在 Kconfig 入口中声明依赖GENERIC_GPIO。
当驱动包含文件:
#include <linux/gpio.h>
则 GPIO 函数是可用,无论是“真实代码”还是经优化过的语句。如果你遵守
这个公约,当你的代码完成后,对其他的开发者来说会更容易看懂和维护。
注意,这些操作包含所用平台的 I/O 屏障代码,驱动无须显式地调用他们。
标识 GPIO
GPIO 是通过无符号整型来标识的,范围是 0 到 MAX_INT。保留“负”数用于其他目的,例如标识信号“在这个板子上不可用”或指示错误。未接触底层硬件的代码会忽略这些整数。
平台会定义这些整数的用法,且通常使用 #define 来定义 GPIO,这样板级特定的启动代码可以直接关联相应的原理图。相对来说,驱动应该仅使用启动代码传递过来的 GPIO 编号,用platform_data 保存板级特定引脚配置数据 (同时还有其他须要的板级特定数据),避免可能出现的问题。
例如一个平台使用编号 32-159 来标识 GPIO,而在另一个平台使用编号0-63标识一组 GPIO 控制器,64-79标识另一类 GPIO 控制器,且在一个含有FPGA 的特定板子上使用 80-95。编号不一定要连续,那些平台中,也可以使用编号2000-2063来标识一个 I2C 接口的 GPIO 扩展器中的 GPIO。
如果你要初始化一个带有无效 GPIO 编号的结构体,可以使用一些负编码(如”-EINVAL”),那将使其永远不会是有效。来测试这样一个结构体中的编号是否关联一个 GPIO,你可使用以下断言:
int gpio_is_valid(int number);
如果编号不存在,则请求和释放 GPIO 的函数将拒绝执行相关操作(见下文)。其他编号也可能被拒绝,比如一个编号可能存在,但暂时在给定的电路上不可用。
一个平台是否支持多个 GPIO 控制器为平台特定的实现问题,就像是否可以在 GPIO 编号空间中有“空洞”和是否可以在运行时添加新的控制器一样。这些问题会影响其他事情,包括相邻的 GPIO 编号是否存在等。
使用 GPIO
对于一个 GPIO,系统应该做的第一件事情就是通过 gpio_request()
函数分配它,见下文。
接下来是设置I/O方向,这通常是在板级启动代码中为所使用的 GPIO 设置platform_device
时完成。
/* 设置为输入或输出, 返回 0 或负的错误代码 */
int gpio_direction_input(unsigned gpio);
int gpio_direction_output(unsigned gpio, int value);
返回值为零代表成功,否则返回一个负的错误代码。这个返回值需要检查,因为get/set(获取/设置)函数调用没法返回错误,且有可能是配置错误。
通常,你应该在进程上下文中调用这些函数。然而,对于自旋锁安全的 GPIO,在板子启动的早期、进程启动前使用他们也是可以的。
对于作为输出的 GPIO,为其提供初始输出值,对于避免在系统启动期间出现信号毛刺是很有帮助的。
为了与传统的 GPIO 接口兼容, 在设置一个 GPIO 方向时,如果它还未被申请,则隐含了申请那个 GPIO 的操作(见下文)。这种兼容性正在从可选的 gpiolib框架中移除。
如果这个 GPIO 编码不存在,或者特定的 GPIO 不能用于那种模式,则方向设置可能失败。依赖启动固件来正确地设置方向通常是一个坏主意,因为它可能除了启动Linux,并没有做更多的验证工作。(同理, 板子的启动代码可能需要将这个复用的引脚设置为 GPIO,并正确地配置上拉/下拉电阻。)
访问自旋锁安全的 GPIO
大多数 GPIO 控制器可以通过内存读/写指令来访问。这些指令不会休眠,可以安全地在硬(非线程)中断例程和类似的上下文中完成。
对于那些用 gpio_cansleep()测试总是返回失败的 GPIO(见下文),使用以下的函数访问: