gpio接口的用法分析

网友投稿 393 2022-11-10

gpio接口的用法分析

什么是GPIO?

不同系统间的GPIO的确切作用不同。通用常有下面几种:

----输入值可读(1,0)。一些芯片支持输出管脚回读,这在线或的情况下非常有用(以支持双向信号线)。GPIO控制器可能具有一个输入防故障/防反跳逻辑,有时还会有软件控制。

----输入经常被用作中断信号,通常是边沿触发,但也有可能是电平触发。这些中断可以配置为系统唤醒事件,从而将系统从低功耗模式唤醒。

----一个GPIO经常被配置为输入/输出双向,根据不同的产品单板需求,但也存在单向的情况。

----大多是GPIO可以在获取到spinlock自旋锁时访问,但那些通过串行总线访问的通常不能如此操作(休眠的原因)。一些系统中会同时存在这两种形式的GPIO。

GPIO约定

注意,它被称为一个约定,因为你不是必须要遵守它。如果你不使用此种方式操作,也不会遭到任何惩罚。存在一些可移植性不是关键的应用场景:GPIO经常用作板级胶合逻辑,这些逻辑甚至在单板的不同版本之间都会改变,且不能用在那些连接不同的单板上。只有极少通用的标准功能可以是可移植的。其余的特性是平台特有的,且对于胶合逻辑是关键(且危险)的。

(有一些可选的代码支持这种实施策略,这在本文后面会讲到,但担任客户的GPIO接口驱动不要关心他是如何实现的。)

#include 《linux/gpio.h》

如果你坚持此约定,这样别的开发者理解你的代码会比较容易且可以帮助维护它。

标识GPIO

平台定义了它们如何使用这些接口,并且通常为每个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)拒绝。别的号码也可能被拒绝。例如,一个号码可能是有效的,但在给定的单板上临时未使用。

使用GPIOs

要使用GPIO,系统首先要分配一个GPIO,使用gpio_request() 为系统分配一个GPIO。

接下来要做的一件事是标示GPIO的方向,通常在使用GPIO建立一个platform_device时(位于单板的setup代码中):

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 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控制器必须使用基于消息的总线如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的调用效果一致

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的过程可以为这种锁服务。

对于那些使用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之前需要停止使用它。

/* 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可以被扩展以支持更多的特性。

struct gpio {

unsignedgpio;

unsigned longflags;

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”);

。..

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不支持反转映射,所以你应该避免使用它。

仿真开漏信号

一个常见的开漏信号的例子是一个共享的低电平激活中断线。同样,双向数据总线信号有时也使用开漏信号

一些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上都可以找到)

还有别的系统特定的机制此处并没有提到,如上文提及的抗干扰和线或输出。

硬件可能按组读写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值的方法

--可选的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来说,平台特定的代码为每个bank的片上GPIO定义和注册了gpio_chip实例。那些GPIO应该被编号和打上标签以匹配芯片厂商文档,且直接匹配单板设计图。他们可以从零开始一直到平台特定的限制。这些GPIO通常集成到单板初始化过程中以使得它们总是有效的,从arch_initcall()到更早,它们总是可以为中断服务。

板级支持

例如,单板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) (支持上升沿或下降沿事件)将被重新配置为遵循此设置。

版权声明:本文内容由网络用户投稿,版权归原作者所有,本站不拥有其著作权,亦不承担相应法律责任。如果您发现本站中有涉嫌抄袭或描述失实的内容,请联系我们jiasou666@gmail.com 处理,核实后本网站将在24小时内删除侵权内容。

上一篇:SD卡的接口是怎样设计的
下一篇:Jumpserver 部署安装
相关文章

 发表评论

暂时没有评论,来抢沙发吧~