什么是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中声明对 GENERIC_GPIO 的支持 (布尔型 true),并提供一个
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,你可使用以下断言:
如果编号不存在,则请求和释放GPIO的函数将拒绝执行相关操作(见下文)。其他编号也可能被拒绝,比如一个编号可能存在,但暂时在给定的板子上不可用。
一个平台是否支持多个GPIO控制器是平台特定的实现问题,就像是否可以在GPIO编号空间中有“空洞”和是否可以在运行时添加新的控制器一样。这些问题会影响其他事情,包括相邻的GPIO编号是否存在等。
使用 GPIO
对于一个GPIO,系统应该做的第一件事情就是通过 gpio_request() 函数分配他,见下文。
而接下来要做的是标识它的方向,这通常是在板级启动代码中为GPIO设置一个platform_device时做的。
返 回值为零代表成功,否则返回一个负的错误代码。这个返回值需要检查,因为get/set(获取/设置)函数调用没法返回错误,且有可能是配置错误。通常, 你应该在进程上下文中调用这些函数。然而,对于自旋锁安全的GPIO,在板子启动的早期、进程启动前使用他们也是可以的。
对于作为输出的GPIO,为其提供初始输出值,对于避免在系统启动期间出现信号毛刺是很有帮助的。
为了与传统的GPIO接口兼容, 在设置一个GPIO方向时,如果它还未被申请,则隐含了申请那个GPIO的操作(见下文)。这种兼容性正在从可选的gpiolib框架中移除。
如 果这个GPIO编码不存在或特定的GPIO不能用于那种模式,则方向设置可能失败。依赖启动固件来正确地设置方向通常是一个坏主意,因为它可能除了启动 Linux,并没有做更多的验证工作。(类似地, 板子的启动代码可能需要将这个复用的引脚设置为GPIO,并正确地配置上拉/下拉电阻。)
访问自旋锁安全的GPIO
大多数GPIO控制器可以通过内存读/写指令来访问。这些指令不会休眠,可以安全地在硬(非线程)中断例程和类似的上下文中完成。
对于那些用gpio_cansleep()测试总是返回失败的GPIO(见下文),使用以下的函数访问:
返回值是布尔值,零表示低电平,非零表示高电平。当读取一个输出引脚的值时,返回值应该是引脚上的值。这个值不总是和输出值相符,因为存在开漏输出信号和输出潜伏期的问题。
以 上的get/set函数不会对早期已经通过gpio_direction_*()报告“无效的GPIO”返回错误。此外,还需要注意的是并不是所有平台都 可以从输出引脚中读取数据的,那些引脚也不总是返回零。且对那些无法安全访问(可能会休眠)的GPIO(见下文)使用这些函数是错误的。
在 GPIO编号(还有输出、值)为常数的情况下,鼓励通过平台特定的实现来优化这两个函数来访问GPIO值。这种情况(读写一个硬件寄存器)下只需要几条指 令是很正常的,且无须自旋锁。这种优化函数比起那些在子程序上花费许多指令的函数可以使得模拟接口(译者注:例如GPIO模拟I2C、1-wire或 SPI)的应用(在空间和时间上都)更具效率。
访问可能休眠的GPIO
某些GPIO控制器必须通过基于总线(如I2C或SPI)的消息访问。读或写这些GPIO值的命令需要等待其信息排到队首才发送命令,再获得其反馈。期间需要休眠,这不能在IRQ例程(中断上下文)中执行。
支持此类GPIO的平台通过以下函数返回非零值来区分出这种GPIO。(此函数需要一个之前通过gpio_request分配到的有效的GPIO编号):
访问这样的GPIO需要一个允许休眠的上下文,例如线程IRQ处理例程,并用以上的访问函数替换那些没有cansleep()后缀的自旋锁安全访问函数。
除了这些访问函数可能休眠,且它们操作的GPIO不能在硬件IRQ处理例程中访问的事实,这些处理例程实际上和自旋锁安全的函数是一样的。
** 除此之外 ** 调用设置和配置此类GPIO的函数也必须在允许休眠的上下文中,因为它们可能也需要访问GPIO控制器芯片: (这些设置函数通常在板级启动代码或者驱动探测/断开代码中,所以这是一个容易满足的约束条件。)
声明和释放GPIO
为了有助于捕获系统配置错误,定义了两个函数。
将 无效的GPIO编码传递给gpio_request()会导致失败,申请一个已使用这个函数声明过的GPIO也会失败。gpio_request()的返 回值必须检查。你应该在进程上下文中调用这些函数。然而,对于自旋锁安全的GPIO,在板子启动的早期、进入进程之前是可以申请的。
这 个函数完成两个基本的目标。一是标识那些实际上已作为GPIO使用的信号线,这样便于更好地诊断;系统可能需要服务几百个潜在的GPIO,但是对于任何一 个给定的电路板通常只有一些被使用。另一个目的是捕获冲突,查明错误:如两个或更多驱动错误地认为他们已经独占了某个信号线,或是错误地认为移除一个管理 着某个已激活信号的驱动是安全的。也就是说,申请GPIO的作用类似一种锁机制。
某些平台可能也使用GPIO作为电源管理(例如通过关闭未使用芯片区和简单地关闭未使用时钟)激活信号。
注意:申请一个GPIO并没有以任何方式配置它,只不过标识那个GPIO处于使用状态。必须有另外的代码来处理引脚配置(如控制GPIO使用的引脚、上拉/下拉)。
并且注意在释放GPIO前,你必须停止使用它。
考虑到大多数情况下声明GPIO之后就会立即配置它们,所以定义了以下三个辅助函数:
这里 'flags' 当前定义可指定以下属性:
因为 GPIOF_INIT_* 仅有在配置为输出的时候才存在,所以有效的组合为:
将来这些标志可能扩展到支持更多的属性,比如开漏状态。
更进一步,为了更简单地声明/释放多个GPIO,'struct gpio'被引进来封装所有这三个领域:
一个典型的使用案例:
GPIO映射到IRQ
GPIO编号是无符号整数;IRQ编号也是。这些构成了两个逻辑上不同的命名空间(GPIO 0不一定使用IRQ 0)。你可以通过以下函数在它们之间实现映射:
它 们的返回值为对应命名空间的相关编号,或是负的错误代码(如果无法映射)。(例如,某些GPIO无法做为IRQ使用。)以下的编号错误是未经检测的:使用 一个未通过gpio_direction_input()配置为输入的GPIO编号,或者使用一个并非来源于gpio_to_irq()的IRQ编号。
这两个映射函数可能会在信号编号的加减计算过程上花些时间。它们不可休眠。
gpio_to_irq() 返回的非错误值可以传递给request_irq()或者free_irq()。它们通常通过板级特定的初始化代码存放到平台设备的IRQ资源中。注 意:IRQ触发选项是IRQ接口的一部分,比如IRQF_TRIGGER_FALLING,系统唤醒能力也是如此。
irq_to_gpio()返回的非错误值大多数通常可以被gpio_get_value()所使用,比如在IRQ是沿触发时初始化或更新驱动状态。注意某些平台不支持反映射,所以你应该尽量避免使用它。
模拟开漏信号
有 时在只有低电平信号作为实际驱动结果(译者注:多个输出连接于一点,逻辑电平结果为所有输出的逻辑与)的时候,共享的信号线需要使用“开漏”信号。(该术 语适用于CMOS管;而TTL用“集电极开路”。)一个上拉电阻使信号为高电平。这有时被称为“线与”。实际上,从负逻辑(低电平为真)的角度来看,这是 一个“线或”。
一个开漏信号的常见例子是共享的低电平使能IRQ信号线。此外,有时双向数据总线信号也使用漏极开路信号。
某些 GPIO 控制器直接支持开漏输出,还有许多不支持。当你需要开漏信号但你的硬件又不直接支持的时候,一个常用的方法是用任何即可作输入也可作输出的GPIO引脚来模拟:
LOW: gpio_direction_output(gpio, 0) ... 这代码驱动信号并覆盖上拉配置
HIGH: gpio_direction_input(gpio) ... 这代码关闭输出,所以上拉电阻(或其他的一些器件)控制了信号。
如 果你将信号线“驱动”为高电平,但是gpio_get_value(gpio)报告了一个低电平(在适当的上升时间后),你就可以知道是其他的一些组件将 共享信号线拉低了。这不一定是错误的。一个常见的例子就是I2C时钟的延长:一个需要较慢时钟的从设备延迟SCK的上升沿,而I2C主设备相应地调整其信 号传输速率。
这些公约忽略了什么?
这 些公约忽略的最大一件事就是引脚复用,因为这属于高度芯片特定的属性且没有可移植性。某个平台可能不需要明确的复用信息;有的对于任意给定的引脚可能只有 两个功能选项;有的可能每个引脚有八个功能选项;有的可能可以将几个引脚中的任何一个作为给定的GPIO。(是的,这些例子都来自于当前运行Linux的 系统。)
在 某些系统中,与引脚复用相关的是配置和使能集成的上、下拉模式。并不是所有平台都支持这种模式,或者不会以相同的方式来支持这种模式;且任何给定的电路板 可能使用外置的上拉(或下拉)电阻,这时芯片上的就不应该使用。(当一个电路需要5kOhm的拉动电阻,芯片上的100 kOhm电阻就不能做到。)同样的,驱动能力(2 mA vs 20 mA)和电压(1.8V vs 3.3V)是平台特定问题,就像模型一样在可配置引脚和GPIO之间(没)有一一对应的关系。
还 有其他一些系统特定的机制没有在这里指出,例如上述的输入去毛刺和线与输出选项。硬件可能支持批量读或写GPIO,但是那一般是配置相关的:对于处于同一 块区(bank)的GPIO。(GPIO通常以16或32个组成一个区块,一个给定的片上系统一般有几个这样的区块。)某些系统可以通过输出GPIO触发 IRQ,或者从并非以GPIO管理的引脚取值。这些机制的相关代码没有必要具有可移植性。
当前,动态定义GPIO并不是标准的,例如作为配置一个带有某些GPIO扩展器的附加电路板的副作用。
GPIO实现者的框架 (可选)
前面提到了, 有一个可选的实现框架,让平台使用相同的编程接口,更加简单地支持不同种类的GPIO控制器。这个框架称为"gpiolib"。
作为一个辅助调试功能,如果debugfs可用,就会有一个 /sys/kernel/debug/gpio 文件。通过这个框架,它可以列出所有注册的控制器,以及当前正在使用中的GPIO的状态。
控制器驱动: gpio_chip
在框架中每个 GPIO 控制器都包装为一个 "struct gpio_chip" ,他包含了该类型的每个控制器的常用信息:
也包含了来自device.platform_data的每个实例的数据:它第一个GPIO的编号和它可用的GPIO的数量。
实现 gpio_chip 的代码应该支持多控制器实例,可能使用驱动模型。那些代码要配置每个 gpio_chip,并发起 gpiochip_add()。卸载一个 GPIO 控制器很少见,但在必要的时候可以使用gpiochip_remove()。
大部分 gpio_chip 是一个实例特定结构体的一部分,而并不将GPIO接口单独暴露出来,比如编址、电源管理等。类似编解码器这样的芯片会有复杂的非GPIO状态。
任何一个debugfs信息导出方法通常应该忽略还未申请作为GPIO的信号线。他们可以使用 gpiochip_is_requested()测试,当这个GPIO已经申请过了就返回相关的标签,否则返回NULL。
平台支持
为 了支持这个框架,一个平台的 Kconfig 文件将会 "select"(选择) ARCH_REQUIRE_GPIOLIB 或 ARCH_WANT_OPTIONAL_GPIOLIB ,并让它的
它也应该提供一个ARCH_NR_GPIOS的定义值,这样可以更好地反映该平台GPIO的实际数量,节省静态表的空间。(这个定义值应该包含片上系统内建GPIO和GPIO扩展器中的数据。)
如果这些选项都没被选择,该平台就不通过GPIO-lib支持GPIO,且代码不可以被用户使能。
以下这些方法的实现可以直接使用框架代码,并总是通过gpio_chip调度:
这 些定义可以用更理想的实现方法替代,那就是使用经过逻辑优化的内联函数来访问基于特定片上系统的GPIO。例如,若引用GPIO的(寄存器地址)是常量 “12”,读取或设置它可能只需少则两或三个指令,且不会休眠。当这样的优化无法实现时,那些函数必须使用框架提供的代码,那就至少要几十条指令才可以实 现。对于用GPIO模拟的I/O接口, 如此精简指令是很有意义的。
对 于片上系统,平台特定代码为片上GPIO每个区(bank)定义并注册gpio_chip实例。那些GPIO应该根据芯片厂商的文档进行编码/标签,并直 接和电路板原理图对应。他们应该开始于零并终止于平台特定的限制。这些GPIO(代码)通常从arch_initcall()或者更早的地方集成进平台初 始化代码,使这些GPIO总是可用,且他们通常可以作为IRQ使用。
板级支持
对 于外部 GPIO 控制器(例如I2C或SPI扩展器、专用芯片、多功能器件、FPGA或CPLD),大多数常用板级特定代码都可以注册控制器设备,并保证他们的驱动知道 gpiochip_add()所使用的GPIO编号。他们的起始编号通常跟在平台特定的GPIO编号之后。
例如板级启动代码应该创建结构体指明芯片公开的GPIO范围,并使用platform_data将其传递给每个GPIO扩展器芯片。然后芯片驱动中的probe()例程可以将这个数据传递给gpiochip_add()。
初 始化顺序很重要。例如,如果一个设备依赖基于I2C的(扩展)GPIO,那么它的probe()例程就应该在那个GPIO有效以后才可以被调用。这意味着 设备应该在GPIO可以工作之后才可被注册。解决这类依赖的的一种方法是让这种gpio_chip控制器向板级特定代码提供setup()和 teardown()回调函数。一旦所有必须的资源可用之后,这些板级特定的回调函数将会注册设备,并可以在这些GPIO控制器设备变成无效时移除它们。
用户空间的Sysfs接口(可选)
使用"gpiolib"实现框架的平台可以选择配置一个GPIO的sysfs用户接口。这不同于debugfs接口,因为它提供的是对GPIO方向和值的控制,而不只显示一个GPIO的状态摘要。此外,它可以出现在没有调试支持的产品级系统中。
例 如,通过适当的系统硬件文档,用户空间可以知道GIOP #23控制Flash存储器的写保护(用于保护其中Bootloader分区)。产品的系统升级可能需要临时解除这个保护:首先导入一个GPIO,改变其 输出状态,然后在重新使能写保护前升级代码。通常情况下,GPIO #23是不会被触及的,并且内核也不需要知道他。
根据适当的硬件文档,某些系统的用户空间GPIO可以用于确定系统配置数据,这些数据是标准内核不知道的。在某些任务中,简单的用户空间GPIO驱动可能是系统真正需要的。
注意:标准内核驱动中已经存在通用的“LED和按键”GPIO任务,分别是:"leds-gpio" 和 "gpio_keys"。请使用这些来替代直接访问GPIO,因为集成在内核框架中的这类驱动比你在用户空间的代码更好。
Sysfs中的路径
在/sys/class/gpio中有3类入口:
除了这些标准的文件,还包含“device”符号链接。
控制接口是只写的:
GPIO信号的路径类似 /sys/class/gpio/gpio42/ (对于 GPIO #42来说),并有如下的读/写属性:
GPIO 控制器的路径类似 /sys/class/gpio/gpiochip42/ (对于从#42 GPIO开始实现控制的控制器),并有着以下只读属性:
大 多数情况下,电路板的文档应当标明每个GPIO的使用目的。但是那些编号并不总是固定的,例如在扩展卡上的GPIO会根据所使用的主板或所在堆叠架构中其 他的板子而有所不同。在这种情况下,你可能需要使用gpiochip节点(尽可能地结合电路图)来确定给定信号所用的GPIO编号。
从内核代码中导出
内核代码可以明确地管理那些已通过gpio_request()申请的GPIO的导出:
在一个内核驱动申请一个GPIO之后, 它可以通过gpio_export()使其在sysfs接口中可见。该驱动可以控制信号方向是否可修改。这有助于防止用户空间代码无意间破坏重要的系统状态。
这个明确的导出有助于(通过使某些实验更容易来)调试,也可以提供一个始终存在的接口,与文档配合作为一个板级支持包的一部分。
在 GPIO被导出之后,gpio_export_link()允许在sysfs文件系统的任何地方创建一个到这个GPIO sysfs节点的符号链接。这样驱动就可以通过一个描述性的名字,在sysfs中他们所拥有的设备下提供一个(到这个GPIO sysfs节点的)接口。
驱 动可以使用 gpio_sysfs_set_active_low() 来在用户空间隐藏电路板之间 GPIO 线的极性差异。这个仅对sysfs接口起作用。极性的改变可以在gpio_export()前后进行,且之前使能的轮询操作(poll(2))支持(上升 或下降沿)将会被重新配置来遵循这个设置。
一般gpio_request封装了mem_request(),起保护作用,最后要调用mem_free之类的。主要是告诉内核这地址被占用了。当其它地方调用同一地址的gpio_request就会报告错误,该地址已被申请。在/proc/mem应该会有地址占用表描述。
这种用法的保护作用前提是大家都遵守先申请再访问,有一个地方没遵守这个规则,这功能就失效了。好比进程互斥,必需大家在访问临界资源的时候都得先获取锁一样,其中一个没遵守约定,代码就废了。
其原型为 int gpio_request(unsigned gpio, const char *label)先说说其参数,gpio则为你要申请的哪一个管脚,label则是为其取一个名字。
其具体实现如下:
int gpio_request(unsigned gpio, const char *label)
{
struct gpio_desc *desc;//这个自己看源码
struct gpio_chip *chip;//这个自己看源码
int status = -EINVAL;
unsigned long flags;
spin_lock_irqsave(&gpio_lock, flags);//屏蔽中断
if (!gpio_is_valid(gpio))//判断是否有效,也就是参数的取值范围判断
goto done;
desc = &gpio_desc[gpio];//这个是关键gpio_desc为定义的一个全局的数组变量,这个函数的实值也就是,用gpio_desc里面的一个变量来表示数组中的这个元素
已经被申请了,而这个变量就是下面会看到的desc->flags。
chip = desc->chip;按理说这个这个全局的gpio_desc如果没有初始化的话,这个chip就为空了,随后就直接返回-EINVAL了。
if (chip == NULL)如果不为空继续往下走
goto done;
if (!try_module_get(chip->owner))
goto done;
/* NOTE: gpio_request() can be called in early boot,
* before IRQs are enabled, for non-sleeping (SOC) GPIOs.
*/
if (test_and_set_bit(FLAG_REQUESTED, &desc->flags) == 0) {这里测试并设置flags的第FLAG_REQUESTED位,如果没有被申请就返回该位的原值0,分析到这儿,
也差不多满足了我的个人要求。
desc_set_label(desc, label ? : "?");
status = 0;
} else {
status = -EBUSY;
module_put(chip->owner);
goto done;
}
if (chip->request) {
/* chip->request may sleep */
spin_unlock_irqrestore(&gpio_lock, flags);
status = chip->request(chip, gpio - chip->base);
spin_lock_irqsave(&gpio_lock, flags);
if (status < 0) {
desc_set_label(desc, NULL);
module_put(chip->owner);
clear_bit(FLAG_REQUESTED, &desc->flags);
}
}