传统的GPIO接口 【ChatGPT】

发布时间 2023-12-11 16:11:39作者: 摩斯电码

GPIO(通用输入/输出)是什么?

"通用输入/输出"(GPIO)是一种灵活的软件控制数字信号。它们来自许多类型的芯片,对于在嵌入式和定制硬件上工作的 Linux 开发人员来说非常熟悉。每个 GPIO 代表连接到特定引脚或 BGA 封装球上的一个位。板级原理图显示了外部硬件连接到哪些 GPIO。驱动程序可以被编写成通用的,这样板级设置代码就可以将这样的引脚配置数据传递给驱动程序。

片上系统(SoC)处理器严重依赖于 GPIO。在某些情况下,每个非专用引脚都可以配置为 GPIO;大多数芯片至少有几十个。可编程逻辑设备(如 FPGA)可以轻松提供 GPIO;多功能芯片如电源管理器和音频编解码器通常有一些这样的引脚,以帮助解决 SoC 上引脚短缺的问题;还有通过 I2C 或 SPI 串行总线连接的“GPIO 扩展器”芯片。大多数 PC 南桥芯片有几十个 GPIO 可用引脚(只有 BIOS 固件知道它们的用途)。

GPIO 的确切功能因系统而异。常见选项包括:

  • 输出值可写(高=1,低=0)。一些芯片还有关于如何驱动该值的选项,例如只有一个值可能被驱动...支持其他值的“线或”和类似方案(特别是“开漏”信号)。
  • 输入值同样可读取(1,0)。一些芯片支持读回已配置为“输出”的引脚的功能,在这种“线或”情况下非常有用(以支持双向信号)。GPIO 控制器可能具有输入去抖动逻辑,有时带有软件控制。
  • 输入通常可用作 IRQ 信号,通常是边沿触发的,但有时是电平触发的。这样的 IRQ 可以配置为系统唤醒事件,以唤醒系统从低功耗状态中恢复。
  • 通常 GPIO 可以根据不同产品板的需要配置为输入或输出;也存在单向的 GPIO。
  • 大多数 GPIO 可以在持有自旋锁时访问,但通过串行总线访问的 GPIO 通常不能。一些系统支持这两种类型。

在给定的板上,每个 GPIO 用于一个特定的目的,比如监视 MMC/SD 卡的插入/拔出,检测卡的写保护状态,驱动 LED,配置收发器,对串行总线进行位操作,触发硬件看门狗,检测开关状态等。

GPIO 约定

请注意,这被称为“约定”,因为你不一定非得按照这种方式操作,如果你不这样做也不会有什么问题。有些情况下,可移植性并不是主要问题;GPIO 通常用于特定于板级的粘合逻辑,甚至可能在板级版本之间发生变化,并且不能在布线不同的板上使用。只有最低公共功能才能具有很高的可移植性。其他功能是特定于平台的,这对于粘合逻辑来说可能是至关重要的。

此外,这并不需要任何实现框架,只需要一个接口。一个平台可能将其实现为简单的内联函数访问芯片寄存器;另一个平台可能通过用于几种非常不同类型的 GPIO 控制器的抽象进行委托来实现。 (本文档后面描述了一些支持这种实现策略的可选代码,但作为 GPIO 接口的客户端的驱动程序不必关心它是如何实现的。)

尽管如此,如果该约定在其平台上得到支持,驱动程序应在可能的情况下使用它。如果 GPIO 功能是绝对必需的,平台必须选择 GPIOLIB。不能在没有标准 GPIO 调用的情况下工作的驱动程序应该有依赖于 GPIOLIB 的 Kconfig 条目。当驱动程序使用以下包含文件时,GPIO 调用是可用的,无论是作为“真实代码”还是作为优化掉的存根:

    #include <linux/gpio.h>

如果你遵循这个约定,那么其他开发人员就更容易看出你的代码在做什么,并帮助维护它。

请注意,这些操作包括在需要使用 I/O 屏障的平台上;驱动程序不需要显式添加它们。

识别 GPIO

GPIO 由范围在 0 到 MAX_INT 之间的无符号整数标识。这将“负”数保留给其他目的,比如标记信号为“在此板上不可用”,或指示故障。不涉及底层硬件的代码将这些整数视为不透明的标识符。

平台定义它们如何使用这些整数,并通常为 GPIO 线路定义 #define 符号,以便板级特定设置代码直接对应于相关的原理图。相反,驱动程序应该只使用从该设置代码传递给它们的 GPIO 数字,使用 platform_data 来保存板级特定的引脚配置数据(以及它们需要的其他板级特定数据)。这样可以避免可移植性问题。

例如,一个平台使用数字 32-159 用于 GPIO;而另一个平台使用数字 0 到 63 与一组 GPIO 控制器,使用数字 64-79 与另一种类型的 GPIO 控制器,以及在一个特定的板上使用数字 80-95 与一个 FPGA。这些数字不一定是连续的;这两个平台中的任何一个也可以使用数字 2000-2063 来标识 I2C GPIO 扩展器组中的 GPIO。

如果你想用一个无效的 GPIO 数字初始化一个结构,可以使用一些负数(比如“-EINVAL”);那将永远不会是有效的。要测试来自这样一个结构的数字是否可以引用一个 GPIO,你可以使用这个谓词:

    int gpio_is_valid(int number);

一个无效的数字将被拒绝由可能请求或释放 GPIO 的调用(见下文)。其他数字也可能被拒绝;例如,一个数字可能是有效的,但在给定板上暂时未使用。

一个平台是否支持多个 GPIO 控制器是一个特定于平台的实现问题,是否支持可以在 GPIO 数字空间中留下“空洞”,以及是否可以在运行时添加新的控制器也是如此。这些问题可能会影响一些事情,包括相邻的 GPIO 数字是否都有效。

使用 GPIO

系统应该对 GPIO 做的第一件事是分配它,使用 gpio_request() 调用;请参见后文。

接下来要做的一件事是将 GPIO 的方向标记为输入或输出,通常是在设置平台设备时的板级设置代码中使用 GPIO 时:

/* 设置为输入或输出,返回 0 或负的 errno */
int gpio_direction_input(unsigned gpio);
int gpio_direction_output(unsigned gpio, int value);

返回值为零表示成功,否则为负的 errno。应该检查返回值,因为获取/设置调用没有错误返回,而且可能存在配置错误。通常应该在任务上下文中发出这些调用。但是,对于自旋锁安全的 GPIO,可以在任务启用之前使用它们,作为早期板级设置的一部分。

对于输出 GPIO,提供的值将成为初始输出值。这有助于避免系统启动期间的信号抖动。

为了与 GPIO 的传统接口兼容,设置 GPIO 的方向会隐式请求该 GPIO(见下文),如果它尚未被请求。这种兼容性正在从可选的 gpiolib 框架中移除。

如果 GPIO 数字无效,或者该 GPIO 不能以该模式使用,设置方向可能会失败。依赖引导固件正确设置方向通常是一个坏主意,因为它可能没有被验证为除了引导 Linux 之外的其他用途。 (同样,该板级设置代码可能需要将该引脚多路复用为 GPIO,并适当配置上拉电阻/下拉电阻。)

自旋锁安全的 GPIO 访问

大多数 GPIO 控制器可以通过内存读/写指令访问。它们不需要休眠,并且可以安全地在硬件(非线程化)中断处理程序和类似上下文中执行。

使用以下调用来访问这样的 GPIO:

/* GPIO 输入:返回零或非零 */
int gpio_get_value(unsigned gpio);

/* GPIO 输出 */
void gpio_set_value(unsigned gpio, int value);

值是布尔值,低为零,非零为高。当读取输出引脚的值时,返回的值应该是引脚上看到的值...这不总是与指定的输出值匹配,因为存在开漏信号和输出延迟等问题。

获取/设置调用没有错误返回,因为“无效的 GPIO”应该在 gpio_direction_*() 中报告过。但是,请注意,并非所有平台都可以读取输出引脚的值;那些不能的平台应该始终返回零。此外,对于不能在没有休眠的情况下安全访问的 GPIO 使用这些调用是错误的。

鼓励特定于平台的实现在 GPIO 数字(以及对于输出,值)是常量的情况下优化这两个调用以访问 GPIO 值。在这种情况下,它们通常只需要几条指令(读取或写入硬件寄存器),并且不需要自旋锁。这样的优化调用可以使位操作应用程序比在子例程调用上花费几十条指令更有效(无论是空间上还是时间上)。

GPIO访问可能会休眠

某些GPIO控制器必须使用基于消息的总线(如I2C或SPI)进行访问。读取或写入这些GPIO值的命令需要等待排队以传输命令并获取其响应。这需要休眠,而无法在IRQ处理程序内部执行休眠操作。为了访问这些GPIO,定义了一组不同的访问器:

/* GPIO输入:返回零或非零,可能会休眠 */
int gpio_get_value_cansleep(unsigned gpio);

/* GPIO输出,可能会休眠 */
void gpio_set_value_cansleep(unsigned gpio, int value);

访问此类GPIO需要一个可能会休眠的上下文,例如线程化的IRQ处理程序,并且必须使用这些访问器,而不是带有cansleep()名称后缀的自旋锁安全访问器。

除了这些访问器可能会休眠,并且将在无法从hardIRQ处理程序访问的GPIO上工作之外,这些调用的行为与自旋锁安全调用相同。

此外,必须从可能会休眠的上下文中进行设置和配置此类GPIO的调用,因为它们可能需要访问GPIO控制器芯片(这些设置调用通常是从板级设置或驱动程序探测/拆卸代码中进行的,因此这是一个简单的约束):

        gpio_direction_input()
        gpio_direction_output()
        gpio_request()

##      gpio_request_one()
##      gpio_request_array()
##      gpio_free_array()

        gpio_free()

声明和释放GPIO

为了帮助捕获系统配置错误,定义了两个调用:

/* 请求GPIO,返回0或负的errno。
 * 非空标签可能对诊断有用。
 */
int gpio_request(unsigned gpio, const char *label);

/* 释放先前声明的GPIO */
void gpio_free(unsigned gpio);

向gpio_request()传递无效的GPIO编号将失败,向已使用该调用声明的GPIO请求GPIO也将失败。必须检查gpio_request()的返回值。通常应在任务上下文中发出这些调用。但是,对于自旋锁安全的GPIO,在启用任务之前请求GPIO是可以的,作为早期板级设置的一部分。

这些调用有两个基本目的。一个是标记实际使用的信号作为GPIO,以获得更好的诊断结果;系统可能具有几百个潜在的GPIO,但通常在任何给定的板上只使用十几个。另一个目的是捕获冲突,识别错误,当(a)两个或多个驱动程序错误地认为它们独占使用该信号,或者(b)某些东西错误地认为可以安全地删除需要管理的信号的驱动程序时。也就是说,请求GPIO可以作为一种锁。

某些平台还可以使用有关哪些GPIO处于活动状态的信息进行电源管理,例如通过关闭未使用的芯片区块和更容易地关闭未使用的时钟。

对于使用已知的pinctrl子系统的引脚的GPIO,应通知该子系统其使用情况;gpiolib驱动程序的.request()操作可以调用pinctrl_gpio_request(),gpiolib驱动程序的.free()操作可以调用pinctrl_gpio_free()。pinctrl子系统允许pinctrl_gpio_request()与设备拥有的引脚或引脚组"拥有"并发成功,以进行引脚多路复用。

应在GPIO驱动程序的.direction_input()或.direction_output()操作中对需要将GPIO信号路由到适当引脚的引脚多路复用硬件进行任何编程,并在设置输出GPIO的值之后进行,以实现从引脚的特殊功能到GPIO的无故障迁移。当使用GPIO来实现非GPIO HW块通常驱动的信号的解决方法时,有时需要这样做。

某些平台允许将某些或所有GPIO信号路由到不同的引脚。类似地,可能需要配置GPIO或引脚的其他方面,例如上拉/下拉。平台软件应确保在为这些GPIO调用gpio_request()之前配置任何此类详细信息,例如使用pinctrl子系统的映射表,以便GPIO用户不需要了解这些详细信息。

还要注意,在释放GPIO之前,您有责任停止使用GPIO。

考虑到在大多数情况下,GPIO在声明后立即配置,定义了三个额外的调用以简化此过程:

/* 请求单个GPIO,使用'flags'指定初始配置,
 * 其他参数和返回值与gpio_request()相同
 */
int gpio_request_one(unsigned gpio, unsigned long flags, const char *label);

/* 一次请求多个GPIO */
int gpio_request_array(struct gpio *array, size_t num);

/* 一次释放多个GPIO */
void gpio_free_array(struct gpio *array, size_t num);

其中'flags'当前定义为指定以下属性:

  • GPIOF_DIR_IN - 将方向配置为输入

  • GPIOF_DIR_OUT - 将方向配置为输出

  • GPIOF_INIT_LOW - 作为输出,将初始电平设置为低

  • GPIOF_INIT_HIGH - 作为输出,将初始电平设置为高

由于GPIOF_INIT_*仅在配置为输出时有效,因此将有效组合分组为:

  • GPIOF_IN - 配置为输入

  • GPIOF_OUT_INIT_LOW - 配置为输出,初始电平为低

  • GPIOF_OUT_INIT_HIGH - 配置为输出,初始电平为高

此外,为了方便声明/释放多个GPIO,引入了'struct gpio'来封装所有三个字段,如下所示:

struct gpio {
        unsigned        gpio;
        unsigned long   flags;
        const char      *label;
};

使用的典型示例:

static struct gpio leds_gpios[] = {
        { 32, GPIOF_OUT_INIT_HIGH, "Power LED" }, /* 默认为ON */
        { 33, GPIOF_OUT_INIT_LOW,  "Green LED" }, /* 默认为OFF */
        { 34, GPIOF_OUT_INIT_LOW,  "Red LED"   }, /* 默认为OFF */
        { 35, GPIOF_OUT_INIT_LOW,  "Blue LED"  }, /* 默认为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映射到IRQ

GPIO编号是无符号整数;IRQ编号也是如此。它们组成了两个逻辑上不同的命名空间(GPIO 0不需要使用IRQ 0)。您可以使用以下调用在它们之间进行映射:

/* 将GPIO编号映射到IRQ编号 */
int gpio_to_irq(unsigned gpio);

这些调用返回另一个命名空间中的相应编号,如果无法进行映射,则返回负的errno代码(例如,某些GPIO无法用作IRQ)。对于未使用gpio_direction_input()设置为输入的GPIO编号使用gpio_to_irq()或使用未经gpio_to_irq()原始获取的IRQ编号是未经检查的错误。

从gpio_to_irq()返回的非错误值可以传递给request_irq()或free_irq()。它们通常会被存储到平台设备的IRQ资源中,由特定于板级的初始化代码完成。请注意,IRQ触发选项是IRQ接口的一部分,例如IRQF_TRIGGER_FALLING,系统唤醒功能也是如此。

模拟开漏信号

有时共享信号需要使用“开漏”信号传输,其中只有低信号电平实际上被驱动。这个术语适用于CMOS晶体管;TTL则使用“开集电极”。上拉电阻导致高信号电平。这有时被称为“线路与”;或者更实际地,从负逻辑(低=真)的角度来看,这是“线路或”。

开漏信号的一个常见示例是共享的主动低电平IRQ线。此外,双向数据总线信号有时也使用开漏信号。

一些GPIO控制器直接支持开漏输出;而许多则不支持。当您需要开漏信号传输但您的硬件不直接支持时,您可以使用一个常见的习语来模拟任何可以用作输入或输出的GPIO引脚:

  • 低电平:gpio_direction_output(gpio, 0) ... 这会驱动信号并覆盖上拉电阻。
  • 高电平:gpio_direction_input(gpio) ... 这会关闭输出,因此上拉电阻(或其他某个设备)控制信号。

如果您正在“驱动”信号使其为高电平,但gpio_get_value(gpio)在适当的上升时间后报告低值,那么您就知道某些其他组件正在将共享信号拉低。这不一定是错误。一个常见的例子是I2C时钟的拉伸:需要较慢时钟的从设备会延迟SCK的上升沿,而I2C主设备会相应地调整其信号速率。

GPIO控制器和pinctrl子系统

SOC上的GPIO控制器可能与pinctrl子系统紧密耦合,因为这些引脚可以与其他功能一起使用,还可以选择使用GPIO功能。我们已经讨论了例如一个GPIO控制器需要通过调用以下任一函数来保留引脚或设置引脚方向的情况:

  • pinctrl_gpio_request()
  • pinctrl_gpio_free()
  • pinctrl_gpio_direction_input()
  • pinctrl_gpio_direction_output()

但是引脚控制子系统如何将全局的GPIO编号与特定引脚控制器上的某个引脚进行交叉参考呢?

这是通过注册引脚的“范围”来完成的,这些实质上是交叉参考表。这些在PINCTRL(引脚控制)子系统中进行描述。

虽然引脚分配完全由pinctrl子系统管理,但是gpio(在gpiolib下)仍然由gpio驱动程序维护。在SOC中,不同的引脚范围可能由不同的gpio驱动程序管理。

这使得让gpio驱动程序在调用'pinctrl_gpio_request'之前向引脚控制子系统宣布它们的引脚范围成为合乎逻辑的做法。目前有两种方法可以做到这一点:使用设备树(DT)支持或者不使用设备树支持。

对于设备树支持,请参阅Documentation/devicetree/bindings/gpio/gpio.txt。

对于非设备树支持,用户可以使用适当的参数调用gpiochip_add_pin_range()来向pinctrl驱动程序注册一系列GPIO引脚的范围。对于这个具体的名称字符串,必须作为这个例程的一个参数传递给pinctrl设备。

这些约定遗漏了什么?

这些约定最大的遗漏之一是引脚复用,因为这是高度特定于芯片并且不可移植的。一个平台可能不需要显式的复用;另一个可能对于任何给定的引脚只有两个选项的使用;另一个可能每个引脚有八个选项;另一个可能可以将给定的GPIO路由到几个引脚中的任何一个。 (是的,这些例子都来自今天运行Linux的系统。)

与复用相关的是集成在某些平台上的上拉电阻或下拉电阻的配置和使能。并非所有平台都支持它们,或者以相同的方式支持它们;任何给定的板可能使用外部上拉电阻(或下拉电阻),因此不应使用芯片上的上拉电阻(或下拉电阻)。 (当电路需要5千欧姆时,芯片上的100千欧姆电阻就不行了。)同样,驱动强度(2毫安 vs 20毫安)和电压(1.8V vs 3.3V)是一个特定于平台的问题,就像(不)具有可配置引脚和GPIO之间的一对一对应关系一样。

还有其他一些未在此处指定的特定于系统的机制,比如上述用于输入去抖动和开漏输出的选项。硬件可能支持以组的方式读取或写入GPIO,但这通常取决于配置:对于共享同一组的GPIO。 (GPIO通常以16或32个一组分组,一个给定的SOC可能有几个这样的组。)一些系统可以从输出GPIO触发IRQ,或者从未作为GPIO管理的引脚读取值。依赖这种机制的代码必然是不可移植的。

动态定义GPIO目前不是标准的;例如,作为配置附加板的副作用来配置GPIO扩展器。

GPIO实现者的框架(可选)

正如前面所述,有一个可选的实现框架,使得平台可以使用相同的编程接口支持不同类型的GPIO控制器。这个框架被称为“gpiolib”。

作为调试辅助,如果debugfs可用,将在/sys/kernel/debug/gpio中找到一个文件。它将列出通过这个框架注册的所有控制器,以及当前使用的GPIO的状态。

控制器驱动程序:gpio_chip

在这个框架中,每个GPIO控制器都被打包为一个“struct gpio_chip”,其中包含每个该类型控制器的公共信息:

  • 用于建立GPIO方向的方法
  • 用于访问GPIO值的方法
  • 指示其方法是否可以休眠的标志
  • 可选的debugfs转储方法(显示额外状态,如上拉配置)
  • 诊断标签

还有每个实例的数据,这些数据可能来自device.platform_data:它的第一个GPIO的编号,以及它公开的GPIO数量。

实现gpio_chip的代码应该支持控制器的多个实例,可能使用驱动模型。当不可避免地需要移除GPIO控制器时,该代码将配置每个gpio_chip并发出gpiochip_add()。大多数情况下,gpio_chip是实例特定结构的一部分,该结构具有GPIO接口未公开的状态,例如寻址、电源管理等。像编解码器这样的芯片将具有复杂的非GPIO状态。

任何debugfs转储方法通常应忽略未被请求为GPIO的信号。它们可以使用gpiochip_is_requested(),该函数返回请求该GPIO时与该GPIO关联的标签或NULL。

平台支持

为了强制启用这个框架,平台的Kconfig将“select” GPIOLIB,否则用户需要配置支持GPIO。

如果这两个选项都没有被选择,那么该平台不支持通过GPIO-lib支持GPIO,并且用户无法启用该代码。

这些函数的简单实现可以直接使用框架代码,该框架始终通过gpio_chip进行分派:

#define gpio_get_value        __gpio_get_value
#define gpio_set_value        __gpio_set_value

更复杂的实现可以将这些定义为内联函数,逻辑上优化对特定SOC GPIO的访问。例如,如果引用的GPIO是常量“12”,那么获取或设置其值的成本可能只有两三条指令,永远不会休眠。当这样的优化不可能时,这些调用必须委托给框架代码,成本至少为几十条指令。对于位操作IO,这样的指令节省可能是显著的。

对于SOC,平台特定的代码为芯片上每个GPIO组定义并注册gpio_chip实例。这些GPIO应该按照芯片供应商的文档进行编号/标记,并直接匹配板的原理图。它们可能从零开始,一直到特定于平台的限制。这些GPIO通常被集成到平台初始化中,以使它们始终可用,从arch_initcall()或更早的时候;它们通常可以作为IRQ使用。

外部GPIO控制器的板支持

对于外部GPIO控制器(如I2C或SPI扩展器、ASIC、多功能设备、FPGA或CPLD),通常是特定于板的代码处理注册控制器设备,并确保它们的驱动程序知道要使用哪些GPIO编号来进行gpiochip_add()。它们的编号通常从特定于平台的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状态摘要。此外,它可以存在于没有调试支持的生产系统中。

通过系统的适当硬件文档,用户空间可以知道例如GPIO#23控制用于保护闪存存储器中引导加载程序段的写保护线。系统升级过程可能需要暂时移除该保护,首先导入一个GPIO,然后更改其输出状态,然后更新代码,然后重新启用写保护。在正常使用中,GPIO#23将永远不会被触摸,内核也不需要知道它。

再次取决于适当的硬件文档,在某些系统上,用户空间GPIO可以用于确定标准内核不知道的系统配置数据。对于某些任务,简单的用户空间GPIO驱动程序可能是系统真正需要的一切。

请注意,常见的"LED和按钮"GPIO任务存在标准内核驱动程序:"leds-gpio"和"gpio_keys"。请使用它们,而不是直接与GPIO通信;它们与内核框架集成得比您的用户空间代码更好。

Sysfs中的路径

/sys/class/gpio/中有三种条目:

  • 用于获取用户空间对GPIO的控制的控制接口;
  • GPIO本身;以及
  • GPIO控制器("gpio_chip"实例)。

这些是标准文件,包括"device"符号链接。

控制接口只能写入:

  • /sys/class/gpio/
    • "export" ... 用户空间可以通过向该文件写入其编号来请求内核将GPIO的控制权导出给用户空间。
      • 例如:"echo 19 > export"将为GPIO#19创建一个"gpio19"节点,如果内核代码没有请求。
    • "unexport" ... 反转将GPIO导出给用户空间的效果。
      • 例如:"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事件。如果使用select(2),请将文件描述符设置为exceptfds。在poll(2)返回后,要么lseek(2)到sysfs文件的开头并读取新值,要么关闭文件并重新打开它以读取值。
    • "edge" ... 读取为"none"、"rising"、"falling"或"both"。写入这些字符串以选择将使"value"文件上的poll(2)返回的信号边缘。
      • 如果引脚可以配置为生成中断的输入引脚,则存在此文件。
    • "active_low" ... 读取为0(假)或1(真)。写入任何非零值以反转读取和写入的值属性。现有和随后的poll(2)支持通过"rising"和"falling"边缘的edge属性配置将遵循此设置。

GPIO控制器的路径类似于/sys/class/gpio/gpiochip42/(实现从#42开始的GPIO的控制器),并具有以下只读属性:

  • /sys/class/gpio/gpiochipN/
    • "base" ... 与N相同,由该芯片管理的第一个GPIO
    • "label" ... 用于诊断(不一定唯一)
    • "ngpio" ... 这个芯片管理多少个GPIO(从N到N + ngpio - 1)

在大多数情况下,板文档应该涵盖GPIO用于什么目的。但是,这些编号并不总是稳定的;在使用的基板不同的情况下,子卡上的GPIO可能会有所不同,或者堆栈中的其他卡片。在这种情况下,您可能需要使用gpiochip节点(可能与原理图结合使用)来确定要为给定信号使用的正确GPIO编号。

API参考

本节列出的函数已被弃用。应该在新代码中使用基于GPIO描述符的API。
https://www.kernel.org/doc/html/v6.6/driver-api/gpio/legacy.html#api-reference