GPIO描述符消费者接口 【ChatGPT】

发布时间 2023-12-11 15:53:01作者: 摩斯电码

GPIO描述符消费者接口

本文档描述了GPIO框架的消费者接口。请注意,它描述了新的基于描述符的接口。有关已弃用的基于整数的GPIO接口的描述,请参阅“Legacy GPIO Interfaces”。
GPIO消费者的指南

不能在没有标准GPIO调用的情况下工作的驱动程序应该具有依赖于GPIOLIB的Kconfig条目或选择GPIOLIB。允许驱动程序获取和使用GPIO的函数可通过包含以下文件来使用:

#include <linux/gpio/consumer.h>

在禁用GPIOLIB时,头文件中的所有函数都有静态内联存根。当调用这些存根时,它们将发出警告。这些存根用于两种用例:

  • 通过例如COMPILE_TEST进行简单的编译覆盖——当前平台是否启用或选择GPIOLIB并不重要,因为我们不会执行系统。

  • 真正可选的GPIOLIB支持——在某些编译时配置的某些系统上,驱动程序实际上并不使用GPIO,但在其他编译时配置下会使用它。在这种情况下,消费者必须确保不调用这些函数,否则用户将收到可能被视为威胁的控制台警告。

所有使用基于描述符的GPIO接口的函数都以gpiod_为前缀。gpio_前缀用于旧接口。内核中不应该有其他函数使用这些前缀。强烈不建议使用旧函数,新代码应该专门使用<linux/gpio/consumer.h>和描述符。
获取和释放GPIO

使用基于描述符的接口,GPIO使用通过调用gpiod_get()函数获取一个不可伪造的句柄。与许多其他内核子系统一样,gpiod_get()接受将使用GPIO的设备和请求的GPIO应该执行的功能:

struct gpio_desc *gpiod_get(struct device *dev, const char *con_id,
                            enum gpiod_flags flags)

如果一个功能是通过一起使用多个GPIO实现的(例如,显示数字的简单LED设备),可以指定一个额外的索引参数:

struct gpio_desc *gpiod_get_index(struct device *dev,
                                  const char *con_id, unsigned int idx,
                                  enum gpiod_flags flags)

有关DeviceTree情况下con_id参数的更详细描述,请参阅GPIO Mappings。

flags参数用于可选地指定GPIO的方向和初始值。值可以是:

  • GPIOD_ASIS或0,根本不初始化GPIO。必须稍后使用专用函数设置方向。

  • GPIOD_IN,将GPIO初始化为输入。

  • GPIOD_OUT_LOW,将GPIO初始化为输出,值为0。

  • GPIOD_OUT_HIGH,将GPIO初始化为输出,值为1。

  • GPIOD_OUT_LOW_OPEN_DRAIN,与GPIOD_OUT_LOW相同,但还要求线路以开漏方式使用。

  • GPIOD_OUT_HIGH_OPEN_DRAIN,与GPIOD_OUT_HIGH相同,但还要求线路以开漏方式使用。

请注意,初始值是逻辑值,物理线路电平取决于线路是配置为主动高电平还是主动低电平(请参阅主动低电平和开漏语义)。

最后两个标志用于必须使用开漏的用例,例如I2C:如果线路在映射中尚未配置为开漏,则将强制执行开漏,并打印警告,指出需要更新板配置以匹配用例。

这两个函数都返回有效的GPIO描述符,或者可以使用IS_ERR()检查的错误代码(它们永远不会返回NULL指针)。如果设备/功能/索引三元组没有分配GPIO,则将返回-ENOENT,其他错误代码用于在尝试获取GPIO时发生错误的情况。这对于区分纯粹的错误和可选GPIO参数的缺失是有用的。对于GPIO是可选的常见模式,可以使用gpiod_get_optional()和gpiod_get_index_optional()函数。如果没有将任何GPIO分配给请求的功能,则这些函数返回NULL:

struct gpio_desc *gpiod_get_optional(struct device *dev,
                                     const char *con_id,
                                     enum gpiod_flags flags)

struct gpio_desc *gpiod_get_index_optional(struct device *dev,
                                           const char *con_id,
                                           unsigned int index,
                                           enum gpiod_flags flags)

请注意,gpio_get*_optional()函数(以及它们的托管变体)在禁用gpiolib支持时也返回NULL。这对于驱动程序作者很有帮助,因为他们不需要特别处理-ENOSYS返回代码。但是,系统集成商应该小心,在需要它的系统上启用gpiolib。

对于使用多个GPIO的函数,可以一次性获取所有这些GPIO:

struct gpio_descs *gpiod_get_array(struct device *dev,
                                   const char *con_id,
                                   enum gpiod_flags flags)

此函数返回一个包含描述符数组的struct gpio_descs。它还包含指向gpiolib私有结构的指针,如果传递回get/set数组函数,可能会加快I/O处理:

struct gpio_descs {
        struct gpio_array *info;
        unsigned int ndescs;
        struct gpio_desc *desc[];
}

以下函数在没有将任何GPIO分配给请求的功能时返回NULL:

struct gpio_descs *gpiod_get_array_optional(struct device *dev,
                                            const char *con_id,
                                            enum gpiod_flags flags)

这些函数的设备管理变体也已定义:

struct gpio_desc *devm_gpiod_get(struct device *dev, const char *con_id,
                                 enum gpiod_flags flags)

struct gpio_desc *devm_gpiod_get_index(struct device *dev,
                                       const char *con_id,
                                       unsigned int idx,
                                       enum gpiod_flags flags)

struct gpio_desc *devm_gpiod_get_optional(struct device *dev,
                                          const char *con_id,
                                          enum gpiod_flags flags)

struct gpio_desc *devm_gpiod_get_index_optional(struct device *dev,
                                                const char *con_id,
                                                unsigned int index,
                                                enum gpiod_flags flags)

struct gpio_descs *devm_gpiod_get_array(struct device *dev,
                                        const char *con_id,
                                        enum gpiod_flags flags)

struct gpio_descs *devm_gpiod_get_array_optional(struct device *dev,
                                                 const char *con_id,
                                                 enum gpiod_flags flags)

可以使用gpiod_put()函数释放GPIO描述符:

void gpiod_put(struct gpio_desc *desc)

对于一组GPIO,可以使用以下函数:

void gpiod_put_array(struct gpio_descs *descs)

在调用这些函数后严格禁止使用描述符。也不允许从使用gpiod_get_array()获取的数组中单独释放描述符(使用gpiod_put())。

设备管理变体可预见地是:

void devm_gpiod_put(struct device *dev, struct gpio_desc *desc)

void devm_gpiod_put_array(struct device *dev, struct gpio_descs *descs)

使用GPIO
设置方向

驱动程序必须首先使用GPIO设置其方向。如果没有向gpiod_get()提供方向设置标志,则可以通过调用gpiod_direction_()函数来执行此操作:

int gpiod_direction_input(struct gpio_desc *desc)
int gpiod_direction_output(struct gpio_desc *desc, int value)

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

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

驱动程序还可以查询GPIO的当前方向:

int gpiod_get_direction(const struct gpio_desc *desc)

此函数在错误情况下返回0表示输出,1表示输入,或错误代码。

请注意,GPIO没有默认方向。因此,在未设置其方向的情况下使用GPIO是非法的,并将导致未定义的行为!

GPIO访问和控制

在原子上下文中访问GPIO

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

使用以下调用来从原子上下文中访问GPIO:

int gpiod_get_value(const struct gpio_desc *desc);
void gpiod_set_value(struct gpio_desc *desc, int value);

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

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

可能休眠的GPIO访问

一些GPIO控制器必须使用诸如I2C或SPI之类的基于消息的总线进行访问。读取或写入这些GPIO值的命令需要等待以传输命令并获取其响应。这需要休眠,不能在中断处理程序内部执行。

支持这种类型GPIO的平台通过从以下调用返回非零值来将其与其他GPIO区分开来:

int gpiod_cansleep(const struct gpio_desc *desc)

要访问这种类型的GPIO,定义了一组不同的访问器:

int gpiod_get_value_cansleep(const struct gpio_desc *desc);
void gpiod_set_value_cansleep(struct gpio_desc *desc, int value);

访问这种GPIO需要一个可能休眠的上下文,例如一个线程化的中断处理程序,并且必须使用带有cansleep()后缀的访问器,而不是spinlock-safe访问器。

除了这些访问器可能会休眠,并且将在不能从hardIRQ处理程序中访问的GPIO上工作之外,这些调用与spinlock-safe调用的行为相同。

活动低电平和开漏语义

由于消费者不必关心物理线路电平,所有的gpiod_set_value_xxx()或gpiod_set_array_value_xxx()函数都使用逻辑值进行操作。因此,它们考虑了活动低属性。这意味着它们会检查GPIO是否配置为活动低,如果是,它们会在驱动物理线路电平之前操作传递的值。

对于开漏或开源输出线也适用相同的规则:它们不会主动将输出拉高(开漏)或拉低(开源),它们只是将它们的输出切换到高阻值。消费者不需要关心这些细节。(有关GPIO驱动程序接口中开漏的详细信息,请阅读相关文档。)

因此,所有的gpiod_set_(array)_value_xxx()函数将参数"value"解释为"断言"("1")或"取消断言"("0")。物理线路电平将相应地被驱动。

例如,如果专用GPIO的活动低属性被设置,并且gpiod_set_(array)_value_xxx()传递了"断言"("1"),那么物理线路电平将被拉低。

访问原始GPIO值

存在需要管理GPIO线的逻辑状态的消费者,即它们的设备实际上将接收到的值,无论它和GPIO线之间有什么。

以下一组调用忽略了GPIO的活动低或开漏属性,并且处理原始线路值:

int gpiod_get_raw_value(const struct gpio_desc *desc);
void gpiod_set_raw_value(struct gpio_desc *desc, int value);
int gpiod_get_raw_value_cansleep(const struct gpio_desc *desc);
void gpiod_set_raw_value_cansleep(struct gpio_desc *desc, int value);
int gpiod_direction_output_raw(struct gpio_desc *desc, int value);

还可以使用以下调用查询和切换GPIO的活动低状态:

int gpiod_is_active_low(const struct gpio_desc *desc);
void gpiod_toggle_active_low(struct gpio_desc *desc);

请注意,这些函数应该谨慎使用;驱动程序不应该关心物理线路电平或开漏语义。

用单个函数调用访问多个GPIO

以下函数获取或设置GPIO数组的值:

int gpiod_get_array_value(unsigned int array_size,
                          struct gpio_desc **desc_array,
                          struct gpio_array *array_info,
                          unsigned long *value_bitmap);
int gpiod_get_raw_array_value(unsigned int array_size,
                              struct gpio_desc **desc_array,
                              struct gpio_array *array_info,
                              unsigned long *value_bitmap);
int gpiod_get_array_value_cansleep(unsigned int array_size,
                                   struct gpio_desc **desc_array,
                                   struct gpio_array *array_info,
                                   unsigned long *value_bitmap);
int gpiod_get_raw_array_value_cansleep(unsigned int array_size,
                                   struct gpio_desc **desc_array,
                                   struct gpio_array *array_info,
                                   unsigned long *value_bitmap);

int gpiod_set_array_value(unsigned int array_size,
                          struct gpio_desc **desc_array,
                          struct gpio_array *array_info,
                          unsigned long *value_bitmap)
int gpiod_set_raw_array_value(unsigned int array_size,
                              struct gpio_desc **desc_array,
                              struct gpio_array *array_info,
                              unsigned long *value_bitmap)
int gpiod_set_array_value_cansleep(unsigned int array_size,
                                   struct gpio_desc **desc_array,
                                   struct gpio_array *array_info,
                                   unsigned long *value_bitmap)
int gpiod_set_raw_array_value_cansleep(unsigned int array_size,
                                       struct gpio_desc **desc_array,
                                       struct gpio_array *array_info,
                                       unsigned long *value_bitmap)

数组可以是任意一组GPIO。如果支持,这些函数将尝试同时访问属于同一组件或芯片的GPIO。在这种情况下,可以期望显着提高性能。如果无法同时访问这些GPIO,则将顺序访问这些GPIO。

这些函数接受四个参数:

  • array_size - 数组元素的数量
  • desc_array - GPIO描述符的数组
  • array_info - 从gpiod_get_array()获取的可选信息
  • value_bitmap - 用于存储GPIO值(获取)或要分配给GPIO的值的位图(设置)

描述符数组可以使用gpiod_get_array()函数或其变体来获取。如果该函数返回的描述符组与所需的GPIO组匹配,那么可以通过简单地使用gpiod_get_array()返回的struct gpio_descs来访问这些GPIO:

struct gpio_descs *my_gpio_descs = gpiod_get_array(...);
gpiod_set_array_value(my_gpio_descs->ndescs, my_gpio_descs->desc,
                      my_gpio_descs->info, my_gpio_value_bitmap);

也可以访问一组完全任意的描述符。然后,必须手动设置描述符数组,然后才能将其传递给上述函数。在这种情况下,array_info应设置为NULL。

请注意,为了获得最佳性能,属于同一芯片的GPIO应在描述符数组中是连续的。

如果描述符的数组索引与单个芯片的硬件引脚号匹配,可能会获得更好的性能。如果传递给get/set数组函数的数组与从gpiod_get_array()获取的数组匹配,并且与数组相关联的array_info也被传递,那么函数可能会采用快速位图处理路径,直接将value_bitmap参数传递给芯片的相应.get/set_multiple()回调。这允许利用GPIO bank作为数据I/O端口,而不会丢失太多性能。

gpiod_get_array_value()及其变体的返回值在成功时为0,出错时为负值。请注意,与gpiod_get_value()的返回值不同,后者在成功时返回0或1以传达GPIO值。对于数组函数,GPIO值存储在value_array中,而不是作为返回值传回。

将GPIO映射到IRQ

GPIO线通常可以用作IRQ。可以使用以下调用获取与给定GPIO对应的IRQ号:

int gpiod_to_irq(const struct gpio_desc *desc)

它将返回一个IRQ号,或者如果无法进行映射(很可能是因为该特定GPIO不能用作IRQ),则返回一个负的errno代码。使用gpiod_direction_input()设置为输入的GPIO或使用gpiod_to_irq()原始返回的IRQ号是一个未经检查的错误。gpiod_to_irq()不允许休眠。

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

GPIO和ACPI

在ACPI系统上,GPIO由设备的_CRS配置对象列出的GpioIo()/GpioInt()资源描述。这些资源不提供GPIO的连接ID(名称),因此需要使用其他机制来实现这一目的。

符合ACPI 5.1或更新版本的系统可能会提供一个_DSD配置对象,该对象除其他功能外,还可以用于为_CRS中描述的GpioIo()/GpioInt()资源提供特定GPIO的连接ID。如果是这种情况,GPIO子系统将自动处理。但是,如果_DSD不存在,则需要由设备驱动程序提供GpioIo()/GpioInt()资源与GPIO连接ID之间的映射。

有关详细信息,请参阅与GPIO相关的_DSD设备属性。

与传统GPIO子系统交互

许多内核子系统和驱动程序仍然使用传统的基于整数的接口处理GPIO。强烈建议将这些更新为新的gpiod接口。对于需要同时使用这两种接口的情况,以下两个函数允许将GPIO描述符转换为GPIO整数命名空间,反之亦然:

int desc_to_gpio(const struct gpio_desc *desc)
struct gpio_desc *gpio_to_desc(unsigned gpio)

desc_to_gpio()返回的GPIO号可以安全地用作gpio_*()函数的参数,只要GPIO描述符desc没有被释放。同样,传递给gpio_to_desc()的GPIO号必须首先使用gpio_request_one()等方法正确获取,并且返回的GPIO描述符只有在释放了该GPIO号后才被认为是有效的。

使用一个API获取的GPIO,然后使用另一个API释放,是被禁止的,是一个未经检查的错误。