GPIO接口解析【转】-阿里云开发者社区

开发者社区> 开发与运维> 正文
登录阅读全文

GPIO接口解析【转】

简介:
本文提供了一个linux下访问GPIO的约定的概述。
这些调用使用gpio_* 命名前缀。没有别的调用会使用这个前缀或是相关的__gpio_*前缀。
 
什么是GPIO?
===============
GPIO----“通用目的输入/输出端口”----是一个灵活的软件控制的数字信号。许多种类的芯片都会提供,嵌入式linux开发者和硬件定制者会对此比较熟悉。每个GPIO提供一位与特定的管脚(或是“球”,BGA(Ball Grid Array)封装下)相连。单板电路图会显示外部硬件与GPIOs的连接关系。GPIO驱动可写成通用的,便于单板setup代码可以将这些管脚配置数据传递给驱动。
 
SOC处理器非常依赖于GPIO。某些情况下,普通管脚可以被配置为GPIO。大多数芯片至少拥有几组类似的GPIO。可编程逻辑器件(如FPGAs)可以很容易提供GPIO。一些多功能芯片,如电源管理、声音解码等经常具有一些这样的管脚来弥补SOC芯片上面管脚的不足。同样,也存在一些GPIO扩展芯片,连接用于I2C或是SPI串行总线。多数PC南桥拥有几组GPIO兼容的管脚(仅有BIOS固件知道如何使用它们)。
 
 
不同系统间的GPIO的确切作用不同。通用常有下面几种:
----输出值可写(高=1,低=0)。一些芯片也可以选择驱动这些值的方式,以便支持“线-或”或类似方案(开漏信号线)。
 
----输入值可读(1,0)。一些芯片支持输出管脚回读,这在线或的情况下非常有用(以支持双向信号线)。GPIO控制器可能具有一个输入防故障/防反跳逻辑,有时还会有软件控制。
----输入经常被用作中断信号,通常是边沿触发,但也有可能是电平触发。这些中断可以配置为系统唤醒事件,从而将系统从低功耗模式唤醒。
 
 
----一个GPIO经常被配置为输入/输出双向,根据不同的产品单板需求,但也存在单向的情况。
 
----大多是GPIO可以在获取到spinlock自旋锁时访问,但那些通过串行总线访问的通常不能如此操作(休眠的原因)。一些系统中会同时存在这两种形式的GPIO。
 
----在一个给定单板上,每个GPIO用于一个特定的目的,如监控MMC/SD卡的插入/移除,检查卡写保护状态,驱动LED,配置发送器,串行总线位拆,触发一个硬件看门狗,触发一个开关之类的。
 
 
GPIO约定
================
 
注意,它被称为一个约定,因为你不是必须要遵守它。如果你不使用此种方式操作,也不会遭到任何惩罚。存在一些可移植性不是关键的应用场景:GPIO经常用作板级胶合逻辑,这些逻辑甚至在单板的不同版本之间都会改变,且不能用在那些连接不同的单板上。只有极少通用的标准功能可以是可移植的。其余的特性是平台特有的,且对于胶合逻辑是关键(且危险)的。
 
另外,这不需要任何实现框架,只是一个接口。一个平台可以将它实现为一个简单的inline函数来存取芯片寄存器,另一个可能通过一个多个不同GPIO控制器的抽象委托实现。
(有一些可选的代码支持这种实施策略,这在本文后面会讲到,但担任客户的GPIO接口驱动不要关心他是如何实现的。)
 
也就是说,如果平台支持约定,驱动应当尽可能使用它。平台必须在Kconfig中声明GENERIC_GPIO来支持,并且提供一个<asm/gpio.h>文件。那些不能离开标准GPIO调用的驱动应该具有一个依赖于GENERIC_GPIO的条目。要使GPIO调用有效,无论是“真实代码”或是作为“optimized-away stubs”,驱动需要使用包含文件:
#include <linux/gpio.h>
 
如果你坚持此约定,这样别的开发者理解你的代码会比较容易且可以帮助维护它。
 
注意:在那些需要的平台上,这些操作包括I/0操作间隔(barriers);驱动不需要显式添加它们。
 
标识GPIO
-----------------
GPIO使用一个无符号整型数进行标识,范围0到MAX_INT。那些保留的“negative”(负)数用于其他的目的,如标识信号为“在此单板上无效”,或是指出错误。那些不涉及到基本硬件操作的代码将这些整型数视为不透明的。
 
平台定义了它们如何使用这些接口,并且通常为每个GPIO线使用#define宏定义符号,以便单板的启动代码与相关设计直接保持一致。与此相反,驱动应该只使用从setup代码传递给他们的GPIO号码,使用platform_data来保存单板特定的管脚配置数据(与其它所需的单板特定数据一起)。这避免了移植问题。
 
例如:一个平台给GPIOs使用号码32-159,同时另一个使用0-63支持一个GPIO控制器集合,64-79支持另一个类型的GPIO控制器,且在一个特定的单板上80-95支持一个FPGA。号码不必是连续的,这些单板也可以使用2000-2063来标识一组用于I2CGPIO扩展
 
如果你想要使用一个无效的GPIO号码初始化一个结构体,使用一些负数(可以为“-EINVAL”),它将永远不会有效。为了测试来自这样一个结构的这样一个号码是否能够引用一个GPIO,你需要使用:
int gpio_is_valid(int number);
 
一个无效的号码将被调用(可以是申请或释放GPIO)拒绝。别的号码也可能被拒绝。例如,一个号码可能是有效的,但在给定的单板上临时未使用。
 
平台是否支持多个GPIO控制器是平台特定实现的关键,同样是否支持GPIO号码空间的“空洞”,是否支持在运行时增加新控制器也是关键。这些关键会影响多个事情,包括相邻的GPIO号码是否都有效。
 
 
使用GPIOs
-----------
 
要使用GPIO,系统首先要分配一个GPIO,使用gpio_request() 为系统分配一个GPIO。
 
 
接下来要做的一件事是标示GPIO的方向,通常在使用GPIO建立一个platform_device时(位于单板的setup代码中):
/* set as input or output, returning 0 or negative errno */
int gpio_direction_input(unsigned gpio);
int gpio_direction_output(unsigned gpio, int value);
 
 
返回0标示成功,或是一个负的errno错误码。它应该被检查,因为get/set调用没有错误返回,且可能会有错误配置。你通常应该在线程上下文中使用这些调用。虽然如此,对于spinlock-safe的GPIO,在tasking使能之前使用也是可以的,作为一个早期的单板建立。
 
对于输出GPIO,value参数提供了初始输出值。这有助于避免系统启动过程中的信号干扰。
 
为了与GPIO早期的接口兼容,设置一个GPIO的方向,隐性要求申请GPIO。这个兼容性从可选的gpiolib架构中移除了。
 
 
如果GPIO号码无效或是指定的GPIO不能使用对应模式操作的话,设置方向会失败。依靠boot固件设置好GPIO的方向通常不是一个好主意,因为boot的功能可能没有通过验证(除了boot linux)。(类似的,单板setup代码可能需要将管脚复用为一个GPIO,和配置为合适的上拉/下拉。)
 
 
Spinlock-Safe GPIO访问
-------------------------
 
大多数GPIO控制器可以使用内存读写指令访问。它们不需要休眠,且可以从内部硬件中断处理(非线程)和类似的上下文环境安全完成。
 
使用下列调用访问这些GPIO,此时gpio_cansleep将总是返回错误
/* GPIO INPUT:  return zero or nonzero */
int gpio_get_value(unsigned gpio);
 
/* GPIO OUTPUT */
void gpio_set_value(unsigned gpio, int value);
 
 
其中,value是一个布尔型参数,零表示低,非零表示高。当读一个输出管脚的值时,返回的值应该是在管脚上看到的值...这并不总是与指定输出值相匹配的,因为存在开漏信号和输出延迟问题。
 
get/set调用没有错误返回,因为“无效GPIO”应该已经由gpio_direction_*()提早报告了。虽然如此,并非所有的平台都可以读取输出管脚的值,那些不能读的应该总是返回零。同时,对那些可能导致睡眠的GPIO使用这些接口是一个错误。
 
平台的特定实现被鼓励优化这两个调用以获取GPIO值。在那些GPIO号码是常量的情况下,它们通常只需一对指令(读或写一个硬件寄存器)访问,且不需要spinlock。这样的优化可以使位拆分应用更有效率(在时间和空间上)(相比较于花费一堆指令在子例程调用来说)。
 
 
可能睡眠的GPIO访问
--------------------------
 
一些GPIO控制器必须使用基于消息的总线如I2C和SPI来进行访问。读写这些GPIO的命令需要等待到达发送队列的开始和获取它的响应。这需要休眠,且不能从内部中断处理函数中完成。
 
对于这种GPIO调用gpio_cansleep接口将返回非零值(需要一个有效的GPIO号码,并已经提前使用gpio_request进行分配)
int gpio_cansleep(unsigned gpio);
 
为了访问这些GPIO,一个不同的访问函数集被定义
/* GPIO INPUT:  return zero or nonzero, might sleep */
int gpio_get_value_cansleep(unsigned gpio);
 
/* GPIO OUTPUT, might sleep */
void gpio_set_value_cansleep(unsigned gpio, int value);
 
 
 
访问这样的gpio需要一个可能睡眠的上下文,例如一个线程级别中断处理程序,并且这些访问函数必须代替那些没有cansleep()后缀的spinlock-safe的函数。
 
除了这些访问函数可能休眠,且对那些不能从硬件中断处理函数中访问的GPIO起作用外,这些调用与那些spinlock-safe的调用效果一致
 
  ** IN ADDITION ** calls to setup and configure such GPIOs must be made
from contexts which may sleep, since they may need to access the GPIO
controller chip too:  (These setup calls are usually made from board
setup or driver probe/teardown code, so this is an easy constraint.)
附加的调用(用于建立和配置这样的GPIO)必须出自可以休眠的上下文,因为它们可能需要访问GPIO控制器芯片:(这些setup调用经常出自单板setup或是驱动probe/teardown(拆卸)代码,所以这是一个简单(无关紧要)的限制。)
 
gpio_direction_input()
gpio_direction_output()
gpio_request()
 
##  gpio_request_one()
## gpio_request_array()
##  gpio_free_array()
 
gpio_free()
gpio_set_debounce()
 
 
 
 
主张和释放GPIO
----------------------------
 
为了捕获系统配置错误,定义了两个调用
/* request GPIO, returning 0 or negative errno.
 * non-null labels may be useful for diagnostics.
 */
int gpio_request(unsigned gpio, const char *label);
 
/* release previously-claimed GPIO */
void gpio_free(unsigned gpio);
 
错误的GPIO号会导致gpio_request()失败,同样申请一个已经被主张的也会出错。gpio_request()的返回值必须被检查。通常应该在一个任务上下文中调用此函数,虽然如此,对于spinlock-safe GPIO来讲,在使能tasking之前申请GPIO是可以的(作为早期单板setup的一部分)。
 
这些调用服务于两个基本目的。一个是为了诊断目的标识实际使用GPIO的信号,系统可能有几百个GPIO,但在一个给定的单板上常常只有几组处于使用状态。另一个是为了捕获冲突、标示错误。当两个或多个驱动错误地认为它们独占此信号或是一些东西错误的认为移除驱动是安全的,这时需要管理一个信号用于管理活动状态。既是说,申请一个GPIO的过程可以为这种锁服务。
 
一些平台也使用GPIO用于功耗管理,如关掉不使用的芯片部分和更简单的门控不使用的时钟。
 
对于那些使用pinctrl子系统的管脚的GPIO,子系统应该被通知它们的用途。一个gpiolib驱动的.request()操作可能调用pinctrl_request_gpio(),且一个gpiolib驱动的.free()操作可能调用pinctrl_free_gpio()。pinctrl子系统允许一个pinctrl_request_gpio()在一个管脚或是管脚组被一个设备拥有时成功,为了复用目的。
 
任意支持管脚复用硬件的编程需要路由GPIO信号到适当的管脚。这些应该在一个GPIO驱动的.direction_input()或是.direction_output()操作中发生,且发生在任意一个输出GPIO值setup之后。这允许从一个管脚的特殊功能到GPIO的无障碍集成。当使用一个GPIO来实现一个关于一个非GPIO硬件模块驱动一个典型信号的工作区(变通方案)时,这时常会被需求。
 
一些平台允许一些或所有的GPIO信号被路由到不同的管脚。同样的,GPIO或管脚的别的方面可能需要配置,如上来/下拉。平台软件应该安排所有的这些细节优先于gpio_request()之前配置。例如,使用pinctrl子系统的映射表,这样GPIO使用者就不需要知道那些细节。
 
同样注意:在你释放GPIO之前需要停止使用它。
 
考虑到大多数场景中GPIO在它们被声明后实际已经被正确配置,定义了3各附加的调用:
/* request a single GPIO, with initial configuration specified by
 * 'flags', identical to gpio_request() wrt other arguments and
 * return value
 */
int gpio_request_one(unsigned gpio, unsigned long flags, const char *label);
 
/* request multiple GPIOs in a single call
 */
int gpio_request_array(struct gpio *array, size_t num);
 
/* release multiple GPIOs in a single call
 */
void gpio_free_array(struct gpio *array, size_t num);
 
 
其中,flags参数当前可以指定为下列属性:
* GPIOF_DIR_IN - 配置方向为输入
* GPIOF_DIR_OUT - 配置方向为输出
 
* GPIOF_INIT_LOW - 作为输出,设置初始值为低
* GPIOF_INIT_HIGH - 作为输出,设置初始值为高
* GPIOF_OPEN_DRAIN - GPIO管脚是开漏极形式
* GPIOF_OPEN_SOURCE - GPIO管脚是开源极形式
 
 
由于GPIOF_INIT_*仅仅当配置为输出时有效,所以有效组合为
* GPIOF_IN - 配置为输入
* GPIOF_OUT_INIT_LOW - 配置为输出,初始为低电平
* GPIOF_OUT_INIT_HIGH - 配置为输入,初始为高电平
 
 
当设置flag为GPIOF_OPEN_DRAIN时,它将假设管脚是开漏极方式。这类管脚在输出模式将不会被驱动为1。这样的管脚需要连接上拉。通过使能此flag,当它在输出模式被要求设置为1时,gpio lib将使得方向为输入以使得管脚变高。输出模式下,管脚输出值0以驱动电平为低。
 
当设置flag为GPIOF_OPEN_SOURCE,它假设管脚时开源极类型。这种管脚在输出模式不能驱动为0。此种管脚需要下拉。通过使能这个flag,当管脚要求输出1时,gpio lib将使得方向变为输入以使得管脚变低。管脚在输出模式驱动1为高
 
未来,这些flag可以被扩展以支持更多的特性。
 
此外,为了简化多个GPIO的声明/释放,引入了gpio结构来压缩这3个域
struct gpio {
unsigned gpio;
unsigned long flags;
const char *label;
};
 
 
一个典型用法的例子如下:
static struct gpio leds_gpios[] = {
{ 32, GPIOF_OUT_INIT_HIGH, "Power LED" }, /* default to ON */
{ 33, GPIOF_OUT_INIT_LOW,  "Green LED" }, /* default to OFF */
{ 34, GPIOF_OUT_INIT_LOW,  "Red LED"   }, /* default to OFF */
{ 35, GPIOF_OUT_INIT_LOW,  "Blue LED"  }, /* default to OFF */
{ ... },
};
 
err = gpio_request_one(31, GPIOF_IN, "Reset Button");
if (err)
...
 
err = gpio_request_array(leds_gpios, ARRAY_SIZE(leds_gpios));
if (err)
...
 
gpio_free_array(leds_gpios, ARRAY_SIZE(leds_gpios));
 
 
 
GPIO映射到中断
--------------------
 
GPIO号码是无符号整型数,同样中断号码也是这样。这些构成了两个逻辑区别的名字空间(GPIO0无须对应使用中断0)。你可以在它们之间使用以下调用进行映射:
/* map GPIO numbers to IRQ numbers */
int gpio_to_irq(unsigned gpio);
 
/* map IRQ numbers to GPIO numbers (avoid using this) */
int irq_to_gpio(unsigned irq);
 
 
它们返回一个对应名字空间的对应号码,或者如果映射不能完成的话返回错误。(例如:一些GPIO不能用于中断。)使用一个未setup的GPIO号码作为输出调用gpio_direction_input()或是使用一个不是来源于gpio_to_irq()的中断号是一个未核对的错误,
 
 
这两个映射调用会在单个增加或减少上有耗费。它们不能休眠。
 
gpio_to_irq()的返回值(非错误)可以传递给request_irq()或free_irq()。它们经常被保存到对应platform设备的IRQ resource中,这使用单板特定的初始化函数完成。注意,中断触发选项是中断接口的一部分,例如IRQF_TRIGGER_FALLING,作为系统唤醒能力。
 
irq_to_gpio()的返回值(非错误)通常用于gpio_get_value(),例如,为了在中断被边沿触发时 初始化和更新驱动状态。注意,一些platform不支持反转映射,所以你应该避免使用它。
 
 
仿真开漏信号
----------------------------
 
有时共享信号需要使用开漏信号,它只有低信号电平是实际驱动的。(此术语用于COMS晶体管,开集电极用于TTL。)一个上拉电阻引出高电平信号。这有时称为“线与”,或是事实上更多的,来自负逻辑观点(low=true)这是一个“线或”。
 
一个常见的开漏信号的例子是一个共享的低电平激活中断线。同样,双向数据总线信号有时也使用开漏信号
 
一些GPIO控制器直接支持开漏输出,更多的不支持。当你需要开漏信号但你的硬件不直接支持时,一个常见的方法你可以使用任意的即可用于输入也可以用于输出的GPIO来模拟它
 LOW: gpio_direction_output(gpio, 0) ... this drives the signal
and overrides the pullup.
 
 HIGH: gpio_direction_input(gpio) ... this turns off the output,
so the pullup (or some other device) controls the signal.
 
 
如果你正在驱动信号为高,但是 gpio_get_value(gpio)报告了一个低值(经过适当的上升时间准备),你应该知道可能是某些别的部件驱动了这个共享信号为低。这是不必要的错误。作为一个常见的例子,这是I2C时钟拉伸的方式:一个从部件需要一个低速时钟延迟了SCK的上升沿们,且I2C主设备因此调整了它的信号频率。
 
 
这些约定节省了什么?
===============================
 
这些约定节省的一个大方面是关于管脚复用,因为这是高度芯片相关且不可移植的。一个平台可能不需要显式的管脚复用,另一个可能只有两个选项用于任意给定的管脚,另一个可能每个管脚有八个选择,一个可能能够路由一个给定的GPIO到多个管脚中的一个。(是的,这些例子在今天的linux上都可以找到)
 
在一些平台上配置和上拉/下拉的使能与多路复用相关。并不是所有的平台都支持或是以同样的方式支持它们,任意一个给定的单板可能使用外部上拉(或下拉)以便片上ons不能被使用。(当一个电路需要5千欧姆,片上的100K欧姆电阻不能作用。)同样的,驱动能力(2mA 对 20mA)和电压(1.8V 对 3.3V)是平台特定的,与模型一样在配置的管脚和GPIO之间一一对应
 
还有别的系统特定的机制此处并没有提到,如上文提及的抗干扰和线或输出。
硬件可能按组读写GPIO,但是那通常是单独配置的:对于那些共享同一个bank的GPIO。
(GPIO通常16或32个为一组,一个给定的SOC系统一般拥有几个这样的BANK。)
一些系统可以从输出GPIO管脚触发中断,或是从一个没作为GPIO管理的管脚上读值。依赖于这种机制的代码将是不可移植的。
 
GPIO动态定义并不是当前的标准,例如,作为配置一个单板附加的GPIO扩展器的边界效应。
 
 
GPIO系统结构(可选)
=======================================
 
如前面提醒的一样,一个可选的实现结构使得平台支持不同种类的GPIO控制器使用同一个编程接口变得简单。这个结构称为gpiolib。
 
作为一个调试目的,如果debugfs有效,一个/sys/kernel/debug/gpio文件在那里将被找到。它列出了所有的通过这个结构注册的GPIO控制器,和GPIO当前的使用状态。
 
Controller Drivers: gpio_chip
控制器驱动:gpio_chip
-----------------------------
 
在这个架构中,每个GPIO控制器被封装为一个“gpio_chip”结构体,此结构体中包含了每个控制器的通用信息:
 
 --确定GPIO方向的方法
 --存取GPIO值的方法
 --声明方法是否休眠的flag
 --可选的debugfs dump方法(展现附加的状态如上拉配置等)
 --用于诊断目的的标签
 
每个实例也有自己的私有数据,可能来自device.platform_data:它的第一个GPIO和它暴露几个GPIO.
 
实现一个gpio_chip的代码应该支持多个控制器的实例,可能使用驱动模型。代码会配置每个gpio_chip并且执行gpiochip_add()。移除一个GPIO控制器是少见的,使用gpio_remove()移除一个不再有效的GPIO控制器。
 
大多数时候,一个gpio_chip是一个实例独有的结构,它的一些状态值不暴露给GPIO接口,如编址、电源管理等等。编码解码器之类的芯片会有复杂的非GPIO状态。
 
所有的debugfs dump 方式通常应该忽略那些未作为GPIO请求的信号。他们可以使用gpiochip_is_requested(),此函数返回与GPIO相关的label或是NULL。
 
 
平台支持
----------------
 
为了支持这个结构,一个平台的Kconfig需要选择ARCH_REQUIRE_GPIOLIB或是ARCH_WANT_OPTIONAL_GPIOLIB之一,且安排的它的<asm/gpio.h>包含<asm-generic/gpio.h>并且定义3个函数gpio_get_value(), gpio_set_value(), 和 gpio_cansleep()。
 
他也可以提供一个自定义的值:ARCH_NR_GPIOS,以便能更好的反映平台实际使用的GPIO数目,并不浪费静态区域空间。(它应该计数 内建/SOC GPIO和GPIO扩展器扩展的数目)
 
ARCH_REQUIRE_GPIOLIB意味着此平台上gpiolib代码将永久编译进内核
 
ARCH_WANT_OPTIONAL_GPIOLIB意味着gpiolib代码默认是关闭的,用于可以使能它并且将它可选的编译进内核。
 
如果这些选项都未被选上,平台不能通过GPIO-lib支持GPIO,这些代码也不能被用户使能。
 
那些函数琐细的实现可以直接使用架构代码,它们经常通过gpio_chip分配:
  #define gpio_get_value __gpio_get_value
  #define gpio_set_value __gpio_set_value
  #define gpio_cansleep __gpio_cansleep
 
 
爱好者实现可以代替定义他们使用内联函数,使用逻辑优化存取特定的基于SOC的GPIO。例如,如果 引用的GPIO是常数“12”,getting或setting它的值可能只需要2个或3个指令,且从不休眠。如果这样一个优化是不可能的话,这些调用实现必须委托给架构代码,它会耗费至少几十个指令。为了位拆型I/O,这些指令的节约是有相当大的意义的。
 
对于SOC来说,平台特定的代码为每个bank的片上GPIO定义和注册了gpio_chip实例。那些GPIO应该被编号和打上标签以匹配芯片厂商文档,且直接匹配单板设计图。他们可以从零开始一直到平台特定的限制。这些GPIO通常集成到单板初始化过程中以使得它们总是有效的,从arch_initcall()到更早,它们总是可以为中断服务。
 
 
板级支持
-------------
 
对于外部GPIO控制器(如I2C或SPI扩展)、ASIC、多功能器件、FPGA或是CPLD,通常单板私有代码例程注册控制器器件且确定它们的驱动使用什么GPIO号来调用gpiochip_add。它们的号码经常在平台特定GPIO之后开始。
 
例如,单板setup代码可以创建结构标示芯片想要暴露的GPIO的范围,且使用platform_data传递它们到每个GPIO扩展器芯片。这样芯片驱动的probe()历程可以传递这些数据到gpiochip_add()。
 
初始化顺序是很重要的。例如当一个依赖于基于I2C的GPIO的设备,它的probe()例程应该仅能在GPIO有效后调用。这意味着设备不能在GPIO可以工作之前注册。一个解决这样依赖的方法是在板级特定代码中,对于这种gpio_chip控制器来提供setup()和teardown()回调,这些板级特定的回调将注册设备一旦所有的需要资源有效时,并且在GPIO控制器无效时将它们移除。
 
 
用户空间的Sysfs接口(可选)
========================================
 
使用gpiolib实现结构的平台可以选择为GPIO配置一个sysfs用户接口。这与debugfs接口不同,因为它提供了覆盖GPIO方向和值的控制而不只是显示一个gpio状态信息摘要。另外,它可以在产品系统中提供而不需要调试支持。
 
为系统给出对应的硬件文档,用户空间可以知道例如GPIO#23控制着保护线,用于保护flash中的boot区域。系统升级程序可能需要临时移除这个保护,首先引入一个GPIO,然后改变它的输出状态,接下来在重新使能写保护之前升级代码。通常用法中,GPIO#23将不会被触碰,并且内核也不需知道它的信息。
 
同样依靠一个合适的硬件文档,在一些系统用户空间,GPIO可以被用于决定那些内核并不关心的系统配置数据。对于一些任务,简单用户空间GPIO驱动是系统真正需要的
 
注意,针对通用“LED和按钮”的标准内核驱动存在对应的GPIO任务“leds-gpio”和“gpio-keys”。使用它们代替直接与GPIO通话,它们集成在内核架构比你的用户态代码可能更好。
 
Paths in Sysfs
Sysfs路径
--------------
There are three kinds of entry in /sys/class/gpio:
/sys/class/gpio有3个入口条目:
   - 控制接口用于用户空间获取GPIO控制
   - GPIO自己
   - GPIO控制器(“gpio_chip”实例)
 
这是对于标准文件的补充,包括“device”符号
 
控制接口是只写的:
    /sys/class/gpio/
 
    
“export” ————通过写GPIO的号码到此文件,用户空间可以要求内核导出一个GPIO的控制到用户空间
 
例如:“echo 19 > export”将创建一个GPIO #19的“gpio19”节点(假设内核代码未申请此GPIO号)。
 
“unexport”————与“export”效果相反
 
例如:"echo 19 > unexport"将移除一个由“export”文件导出的“gpio19”节点。
 
GPIO信号拥有如/sys/class/gpio/gpio42/(对应于GPIO#42)的路径,并且具有下列读写属性:
    /sys/class/gpio/gpioN/
 
 
“direction”————读为“in”或是“out”。这个值通常可写。写“out”默认初始化此值为低。为了确定无障碍操作,值“low”和“high”可以被写入以配置GPIO的输出初始化值。
 
注意这个属性“将不存在”如果内核不支持改变一个GPIO的方向,或者它不能被内核代码导出(不能显式的允许用户空间来重新配置GPIO的选项。)
 
“value”—————读作“0”(低)或“1”(高)。如果GPIO被配置为一个输出,这个值可写;任何非零值均被视为高。
如果管脚可以被配置为中断产生中断管脚,且如果它已经被配置为产生中断(参考“edge”描述),你可以poll(2)此文件并且当中断触发时poll(2)将返回。如果你使用了poll(2),设置POLLPRI和POLLERR事件。如果你使用select(2),在exceptfds中设置文件描述符。在poll(2)返回之后,有两个选择一是lseek(2)到sysfs文件的开始且读新的值,另一个是关闭文件且重打开它来读取新的值。(为何这样设置?)
 
“edge”————读作“none”、“rising”、“falling”或是“both”。写这些字符串以选择边沿信号,他将使得“value”文件上的poll(2)操作返回。
 
这个文件只在管脚可以配置为中断产生输入管脚时存在。
 
“active_low”————读为0(false)或1(true)。写任何非零值都会反转读或写的值。目前和后来的poll(2)支持经由edge属性配置为“rising”或“falling”上升沿或下降沿将遵循这个设置。
 
GPIO控制器具有如/sys/class/gpio/gpiochip42/(针对控制器,实现GPIO开始于#42)的路径,且具有下列制度属性:
    /sys/class/gpio/gpiochipN/
 
    
“base”————与N相等,是第一个被此芯片管理的GPIO
    
“label”————提供用于诊断(并不总是独一无二的)
    
“ngpio”————管理的GPIO数(N到N+ngpio-1)
 
大多数情况下,单板文档应该提供GPIO的使用目的。虽然如此这些号码并非总是固定的,一个子板上的GPIO可能与基础板使用的不同。此种情况下,你可能需要使用gpiochip节点(可能与设计结合)来为每个信号决定正确的GPIO号码。
 
 
从内核代码中导出
--------------------------
 
内核代码可以显式管理那些使用gpio_request()申请的GPIO的导出
/* export the GPIO to userspace */
int gpio_export(unsigned gpio, bool direction_may_change);
 
/* reverse gpio_export() */
void gpio_unexport();
 
/* create a sysfs link to an exported GPIO node */
int gpio_export_link(struct device *dev, const char *name,
unsigned gpio)
 
/* change the polarity of a GPIO node in sysfs */
int gpio_sysfs_set_active_low(unsigned gpio, int value);
 
 
一个内核驱动申请一个GPIO后,它可以使用gpio_export()使得sysfs接口有效。驱动可以控制信号方向是否可以改变。这使得驱动可以防止用户空间代码不小心冲击重要的系统状态。
 
明确的exporting有助于调试(使得一些实验更简单),或是提供一个总是可以使用的接口,适合于bsp文档。
 
GPIO被导出后,gpio_export_link()允许在sysfs的任何地方创建GPIO sysfs节点的符号链接。驱动可以用此在它们自己设备sysfs目录下提供指定名字的接口(链接到GPIO节点)
 
驱动可以使用gpio_sysfs_set_active_low()隐藏GPIO在用户空间和单板之间的线极性不同。这仅影响sysfs接口。极性变换可以在gpio_export()之前和之后完成,并且前面使能的poll(2) (支持上升沿或下降沿事件)将被重新配置为遵循此设置。











本文转自张昺华-sky博客园博客,原文链接:http://www.cnblogs.com/sky-heaven/p/4565869.html,如需转载请自行联系原作者

版权声明:本文内容由阿里云实名注册用户自发贡献,版权归原作者所有,阿里云开发者社区不拥有其著作权,亦不承担相应法律责任。具体规则请查看《阿里云开发者社区用户服务协议》和《阿里云开发者社区知识产权保护指引》。如果您发现本社区中有涉嫌抄袭的内容,填写侵权投诉表单进行举报,一经查实,本社区将立刻删除涉嫌侵权内容。

分享: