GPIO 驱动接口

本文档用作 GPIO 芯片驱动程序编写者的指南。

每个 GPIO 控制器驱动程序都需要包含以下头文件,该头文件定义了用于定义 GPIO 驱动程序的结构

#include <linux/gpio/driver.h>

GPIO 的内部表示

一个 GPIO 芯片处理一个或多个 GPIO 线。要被视为 GPIO 芯片,这些线必须符合定义:通用输入/输出。如果该线不是通用的,则它不是 GPIO,不应由 GPIO 芯片处理。用例是指示性的:系统中的某些线可能被称为 GPIO,但具有非常特殊的用途,因此不符合通用 I/O 的标准。另一方面,LED 驱动程序线可以用作 GPIO,因此仍然应该由 GPIO 芯片驱动程序处理。

在 GPIO 驱动程序内部,各个 GPIO 线由它们的硬件编号标识,有时也称为 offset,这是一个介于 0 和 n-1 之间的唯一编号,其中 n 是芯片管理的 GPIO 的数量。

硬件 GPIO 编号应该是硬件直观的,例如,如果一个系统使用内存映射的 I/O 寄存器集,其中 32 个 GPIO 线由 32 位寄存器中每条线的一个位处理,则为这些使用硬件偏移量 0..31 是有意义的,对应于寄存器中的位 0..31。

这个数字纯粹是内部的:特定 GPIO 线的硬件编号永远不会在驱动程序之外可见。

在这个内部编号之上,每条 GPIO 线还需要在整数 GPIO 命名空间中具有一个全局编号,以便可以将其与旧的 GPIO 接口一起使用。因此,每个芯片必须有一个“基本”编号(可以自动分配),对于每条 GPIO 线,全局编号将是(基本 + 硬件编号)。尽管整数表示被认为是已弃用的,但它仍然有许多用户,因此需要维护。

因此,例如,一个平台可以使用全局编号 32-159 作为 GPIO,控制器在 32 的“基本”值上定义 128 个 GPIO;而另一个平台使用全局编号 0..63 作为一组 GPIO 控制器,64-79 作为另一种类型的 GPIO 控制器,并且在某个特定板上,80-95 与 FPGA 一起使用。旧的编号不必是连续的;这些平台中的任何一个平台也可以使用编号 2000-2063 来标识 I2C GPIO 扩展器组中的 GPIO 线。

控制器驱动程序:gpio_chip

在 gpiolib 框架中,每个 GPIO 控制器都打包为一个“struct gpio_chip”(有关其完整定义,请参见 <linux/gpio/driver.h>),其成员对于该类型的每个控制器都是通用的,这些成员应由驱动程序代码分配

  • 建立 GPIO 线方向的方法

  • 用于访问 GPIO 线值的方法

  • 用于为给定 GPIO 线设置电气配置的方法

  • 用于返回与给定 GPIO 线关联的 IRQ 编号的方法

  • 一个标志,表示对其方法的调用是否可以休眠

  • 用于标识行的可选行名称数组

  • 可选的 debugfs 转储方法(显示额外的状态信息)

  • 可选的基本编号(如果省略,将自动分配)

  • 用于诊断和使用平台数据进行 GPIO 芯片映射的可选标签

实现 gpio_chip 的代码应支持控制器的多个实例,最好使用驱动程序模型。该代码将配置每个 gpio_chip 并发出 gpiochip_add_data() 或 devm_gpiochip_add_data()。删除 GPIO 控制器的情况应该很少见;当不可避免时,请使用 gpiochip_remove()

通常,gpio_chip 是实例特定的结构的一部分,该结构具有 GPIO 接口未公开的状态,例如寻址、电源管理等。诸如音频编解码器之类的芯片将具有复杂的非 GPIO 状态。

任何 debugfs 转储方法都应正常忽略尚未请求的行。它们可以使用 gpiochip_is_requested(),当请求该 GPIO 线时,该函数返回 NULL 或与该 GPIO 线关联的标签。

实时注意事项:如果希望从实时内核上的原子上下文(硬 IRQ 处理程序和类似上下文内部)调用 GPIO API,则 GPIO 驱动程序不应在其 gpio_chip 实现(.get/.set 和方向控制回调)中使用 spinlock_t 或任何可睡眠的 API(例如 PM 运行时)。通常,这不应该需要。

GPIO 电气配置

可以使用 .set_config() 回调为多种电气操作模式配置 GPIO 线。目前,此 API 支持设置

  • 去抖动

  • 单端模式(开漏/开源)

  • 上拉和下拉电阻使能

这些设置如下所述。

.set_config() 回调使用与通用引脚控制驱动程序相同的枚举器和配置语义。这并非巧合:可以将 .set_config() 分配给函数 gpiochip_generic_config(),这将导致调用 pinctrl_gpio_set_config(),并最终在 GPIO 控制器“背后”的引脚控制后端结束,通常更靠近实际引脚。这样,引脚控制器可以管理下面列出的 GPIO 配置。

如果使用了引脚控制后端,则 GPIO 控制器或硬件描述需要提供“GPIO 范围”,将 GPIO 线偏移量映射到引脚控制器上的引脚编号,以便它们可以正确地交叉引用彼此。

具有去抖动支持的 GPIO 线

去抖动是设置为引脚的配置,表示该引脚连接到机械开关或按钮,或类似的可能反弹的开关或按钮。反弹意味着该线出于机械原因在很短的时间间隔内快速被拉高/低。除非该线去抖动,否则这可能导致值不稳定或 irq 反复触发。

在实践中,去抖动涉及在线路发生变化时设置一个定时器,等待一小段时间,然后再次采样线路,以查看它是否仍然具有相同的值(低电平或高电平)。 也可以通过巧妙的状态机重复此操作,等待线路变得稳定。 无论哪种情况,它都会为去抖动设置一定毫秒数,或者如果该时间不可配置,则只设置“开/关”。

具有开漏/源极支持的GPIO线路

开漏(CMOS)或开集电极(TTL)意味着线路不会被主动驱动为高电平:而是将漏极/集电极作为输出提供,因此当晶体管未导通时,它将向外部电源轨呈现高阻抗(三态)。

CMOS CONFIGURATION      TTL CONFIGURATION

         ||--- out              +--- out
  in ----||                   |/
         ||--+         in ----|
             |                |\
            GND                 GND

此配置通常用作实现以下两种目的之一的方法

  • 电平转换:达到比输出所在的硅片更高的逻辑电平。

  • I/O 线路上的反向线或,例如 GPIO 线路,使得线路上的任何驱动级都可以在同一线路上的任何其他输出同时驱动高电平时将其驱动为低电平。一个特例是驱动 I2C 总线的 SCL 和 SDA 线路,这在定义上是线或总线。

这两种用例都需要线路配备上拉电阻。该电阻将使线路趋于高电平,除非电源轨上的晶体管之一主动将其下拉。

线路上的电平将与上拉电阻上的 VDD 一样高,这可能高于晶体管支持的电平,从而实现向更高 VDD 的电平转换。

集成电子设备通常具有 CMOS “图腾柱” 形式的输出驱动级,其中一个 N-MOS 和一个 P-MOS 晶体管,其中一个驱动线路为高电平,另一个驱动线路为低电平。这称为推挽输出。“图腾柱” 看起来像这样

                 VDD
                  |
        OD    ||--+
     +--/ ---o||     P-MOS-FET
     |        ||--+
IN --+            +----- out
     |        ||--+
     +--/ ----||     N-MOS-FET
        OS    ||--+
                  |
                 GND

所需的输出信号(例如,直接来自某些 GPIO 输出寄存器)到达 IN。名为“OD”和“OS”的开关通常是闭合的,从而创建了一个推挽电路。

考虑一下名为“OD”和“OS”的小“开关”,它们可以在输入分裂后立即启用/禁用 P-MOS 或 N-MOS 晶体管。如您所见,如果此开关打开,则任何一个晶体管都将完全失效。然后将图腾柱减半,并提供高阻抗,而不是主动驱动线路为高电平或低电平。这通常是软件控制的开漏/源极的工作原理。

一些 GPIO 硬件采用开漏/开源配置。有些是硬连线的线路,无论如何都只支持开漏或开源:那里只有一个晶体管。有些是软件可配置的:通过在寄存器中翻转一位,可以将输出配置为开漏或开源,实际上是通过打开上图中标记为“OD”和“OS”的开关来实现的。

通过禁用 P-MOS 晶体管,可以在 GND 和高阻抗(开漏)之间驱动输出,通过禁用 N-MOS 晶体管,可以在 VDD 和高阻抗(开源)之间驱动输出。在第一种情况下,需要在输出导轨上使用上拉电阻来完成电路,在第二种情况下,需要在导轨上使用下拉电阻。

支持开漏或开源或两者兼有的硬件可以在 gpio_chip 中实现一个特殊的 回调:.set_config(),该回调采用一个通用 pinconf 打包值,指示是否将线路配置为开漏、开源或推挽。 这将响应机器文件中设置的 GPIO_OPEN_DRAIN 或 GPIO_OPEN_SOURCE 标志,或来自其他硬件描述。

如果无法在硬件中配置此状态,即如果 GPIO 硬件在硬件中不支持开漏/开源,则 GPIO 库将改为使用一个技巧:当线路设置为输出时,如果线路被标记为开漏,并且 IN 输出值为低电平,它将像往常一样驱动为低电平。 但是,如果 IN 输出值设置为高电平,则它将 *不* 被驱动为高电平,而是切换为输入,因为输入模式等效于高阻抗,从而实现某种“开漏仿真”:在电气上,其行为将相同,但在线路模式切换时可能出现硬件故障。

对于开源配置,使用相同的原理,只是将其设置为输入,而不是主动驱动线路为低电平。

具有上拉/下拉电阻支持的GPIO线路

GPIO 线路可以使用 .set_config() 回调支持上拉/下拉。这意味着 GPIO 线路的输出端提供了一个上拉或下拉电阻,并且该电阻是软件控制的。

在离散设计中,上拉或下拉电阻只是焊接在电路板上。 这不是我们在软件中处理或建模的内容。 您最有可能考虑的是这些线路很可能会被配置为开漏或开源(请参阅上面的部分)。

.set_config() 回调只能打开和关闭上拉或下拉,并且不会对使用的电阻有任何语义知识。 它只会说在寄存器中切换一位,从而启用或禁用上拉或下拉。

如果 GPIO 线路支持对上拉或下拉电阻使用不同的电阻值进行分流,则 GPIO 芯片回调 .set_config() 将不足以满足要求。 对于这些复杂的用例,需要实现组合的 GPIO 芯片和引脚控制器,因为引脚控制器的引脚配置接口支持对电气属性进行更通用的控制,并且可以处理不同的上拉或下拉电阻值。

提供 IRQ 的 GPIO 驱动程序

GPIO 驱动程序(GPIO 芯片)也提供中断是很常见的,通常是从父中断控制器级联出来的,在某些特殊情况下,GPIO 逻辑与 SoC 的主中断控制器融合在一起。

GPIO 块的 IRQ 部分是使用 irq_chip 实现的,使用标头 <linux/irq.h>。 因此,此组合驱动程序同时利用两个子系统:gpio 和 irq。

任何 IRQ 使用者都可以从任何 irqchip 请求 IRQ,即使它是组合的 GPIO+IRQ 驱动程序也是如此。 基本前提是 gpio_chip 和 irq_chip 是正交的,并且它们的服务彼此独立提供。

gpiod_to_irq() 只是一个方便的函数,用于找出特定 GPIO 线路的 IRQ,不应依赖于在 IRQ 使用之前被调用。

始终准备好硬件,并使其在 GPIO 和 irq_chip API 的相应回调中准备好进行操作。 不要依赖于先调用 gpiod_to_irq()

我们可以将 GPIO irqchip 分为两大类

  • 级联中断芯片:这意味着 GPIO 芯片具有一个公共中断输出线路,该线路由该芯片上任何已启用的 GPIO 线路触发。 然后,中断输出线路将被路由到上一级的父中断控制器,在最简单的情况下,路由到系统主中断控制器。 这是通过 irqchip 建模的,该 irqchip 将检查 GPIO 控制器内部的位以找出哪个线路触发了它。 驱动程序的 irqchip 部分需要检查寄存器以找出这一点,并且很可能还需要通过清除一些位(有时是隐式地,只需读取一个状态寄存器)来确认它正在处理中断,并且通常还需要设置配置,例如边沿灵敏度(例如,上升沿或下降沿,或高/低电平中断)。

  • 分层中断芯片:这意味着每个 GPIO 线路都有一条通向上一级的父中断控制器的专用 irq 线路。 无需查询 GPIO 硬件即可找出哪个线路已触发,但可能仍需要确认中断并设置配置,例如边沿灵敏度。

实时注意事项:符合实时要求的 GPIO 驱动程序不应在其 irqchip 实现中使用 spinlock_t 或任何可睡眠的 API(如 PM 运行时)。

  • spinlock_t 应替换为 raw_spinlock_t。[1]

  • 如果必须使用可睡眠的 API,则可以从 .irq_bus_lock() 和 .irq_bus_unlock() 回调中完成这些操作,因为这些是 irqchip 上唯一的慢速路径回调。 如果需要,请创建回调。[2]

级联 GPIO irqchip

级联 GPIO irqchip 通常分为以下三类之一

  • 链式级联 GPIO IRQCHIP:这些通常是嵌入在 SoC 上的类型。 这意味着 GPIO 有一个快速 IRQ 流处理程序,该处理程序在父 IRQ 处理程序(最典型的是系统中断控制器)的链中被调用。 这意味着 GPIO irqchip 处理程序将从父 irqchip 立即调用,同时保持 IRQ 禁用状态。 然后,GPIO irqchip 将最终在其中断处理程序中调用如下序列

    static irqreturn_t foo_gpio_irq(int irq, void *data)
        chained_irq_enter(...);
        generic_handle_irq(...);
        chained_irq_exit(...);
    

    链式 GPIO irqchip 通常不能在 struct gpio_chip 上设置 .can_sleep 标志,因为所有操作都直接在回调中发生:不能使用 I2C 等慢速总线流量。

    实时注意事项:请注意,链式 IRQ 处理程序不会在 -RT 上强制线程化。 因此,spinlock_t 或任何可睡眠的 API(如 PM 运行时)不能在链式 IRQ 处理程序中使用。

    如果需要(并且如果它无法转换为嵌套的线程 GPIO irqchip,请参见下文),则可以将链式 IRQ 处理程序转换为通用 IRQ 处理程序,这样它将在 -RT 上成为线程 IRQ 处理程序,而在非 RT 上成为硬 IRQ 处理程序(例如,请参见 [3])。

    预计 generic_handle_irq() 将在禁用 IRQ 的情况下调用,因此如果从强制为线程的 IRQ 处理程序调用它,则 IRQ 核心将报错。“伪”原始锁可用于解决此问题

    raw_spinlock_t wa_lock;
    static irqreturn_t omap_gpio_irq_handler(int irq, void *gpiobank)
        unsigned long wa_lock_flags;
        raw_spin_lock_irqsave(&bank->wa_lock, wa_lock_flags);
        generic_handle_irq(irq_find_mapping(bank->chip.irq.domain, bit));
        raw_spin_unlock_irqrestore(&bank->wa_lock, wa_lock_flags);
    
  • 通用链式 GPIO IRQCHIP:这些与“链式 GPIO irqchip”相同,但不使用链式 IRQ 处理程序。 相反,GPIO IRQ 分派由使用 request_irq() 配置的通用 IRQ 处理程序执行。 然后,GPIO irqchip 将最终在其中断处理程序中调用如下序列

    static irqreturn_t gpio_rcar_irq_handler(int irq, void *dev_id)
        for each detected GPIO IRQ
            generic_handle_irq(...);
    

    实时性考量:这类处理程序将被强制在 -RT 上以线程方式运行,因此 IRQ 核心会抱怨 generic_handle_irq() 在启用 IRQ 的情况下被调用,并且可以应用与“链式 GPIO irqchip”相同的解决方法。

  • 嵌套线程 GPIO IRQCHIPS:这些是片外 GPIO 扩展器以及任何其他位于睡眠总线(如 I2C 或 SPI)另一侧的 GPIO irqchip。

    当然,需要慢速总线传输来读取 IRQ 状态和类似信息的驱动程序,这种传输反过来可能会导致其他 IRQ 发生,无法在禁用 IRQ 的快速 IRQ 处理程序中进行处理。相反,它们需要生成一个线程,然后屏蔽父 IRQ 线,直到中断由驱动程序处理。此驱动程序的标志是在其中断处理程序中调用类似这样的代码

    static irqreturn_t foo_gpio_irq(int irq, void *data)
        ...
        handle_nested_irq(irq);
    

    线程化 GPIO irqchip 的标志是在 struct gpio_chip 上设置 .can_sleep 标志为 true,表示此芯片在访问 GPIO 时可能会休眠。

    这类 irqchip 本质上是实时容忍的,因为它们已经设置为处理睡眠上下文。

GPIO irqchip 的基础设施助手

为了帮助处理 GPIO irqchip 以及相关的 irqdomain 和资源分配回调的设置和管理。这些通过选择 Kconfig 符号 GPIOLIB_IRQCHIP 来激活。如果也选择了符号 IRQ_DOMAIN_HIERARCHY,则还会提供分层助手。大部分开销代码将由 gpiolib 管理,前提是您的中断与 GPIO 线索引是一对一映射的

GPIO 线偏移

硬件 IRQ

0

0

1

1

2

2

...

...

ngpio-1

ngpio-1

如果某些 GPIO 线没有对应的 IRQ,则可以使用 gpio_irq_chip 中的位掩码 valid_mask 和标志 need_valid_mask 来屏蔽某些线,使其不能与 IRQ 关联。

设置助手程序的首选方法是在添加 gpio_chip 之前,在 struct gpio_chip 中填写 struct gpio_irq_chip。如果这样做,gpiolib 将在设置其余 GPIO 功能的同时设置额外的 irq_chip。以下是使用 gpio_irq_chip 的链式级联中断处理程序的典型示例。请注意 mask/unmask(或 disable/enable)函数如何调用核心 gpiolib 代码

/* Typical state container */
struct my_gpio {
    struct gpio_chip gc;
};

static void my_gpio_mask_irq(struct irq_data *d)
{
    struct gpio_chip *gc = irq_data_get_irq_chip_data(d);
    irq_hw_number_t hwirq = irqd_to_hwirq(d);

    /*
     * Perform any necessary action to mask the interrupt,
     * and then call into the core code to synchronise the
     * state.
     */

    gpiochip_disable_irq(gc, hwirq);
}

static void my_gpio_unmask_irq(struct irq_data *d)
{
    struct gpio_chip *gc = irq_data_get_irq_chip_data(d);
    irq_hw_number_t hwirq = irqd_to_hwirq(d);

    gpiochip_enable_irq(gc, hwirq);

    /*
     * Perform any necessary action to unmask the interrupt,
     * after having called into the core code to synchronise
     * the state.
     */
}

/*
 * Statically populate the irqchip. Note that it is made const
 * (further indicated by the IRQCHIP_IMMUTABLE flag), and that
 * the GPIOCHIP_IRQ_RESOURCE_HELPER macro adds some extra
 * callbacks to the structure.
 */
static const struct irq_chip my_gpio_irq_chip = {
    .name             = "my_gpio_irq",
    .irq_ack          = my_gpio_ack_irq,
    .irq_mask         = my_gpio_mask_irq,
    .irq_unmask       = my_gpio_unmask_irq,
    .irq_set_type     = my_gpio_set_irq_type,
    .flags            = IRQCHIP_IMMUTABLE,
    /* Provide the gpio resource callbacks */
    GPIOCHIP_IRQ_RESOURCE_HELPERS,
};

int irq; /* from platform etc */
struct my_gpio *g;
struct gpio_irq_chip *girq;

/* Get a pointer to the gpio_irq_chip */
girq = &g->gc.irq;
gpio_irq_chip_set_chip(girq, &my_gpio_irq_chip);
girq->parent_handler = ftgpio_gpio_irq_handler;
girq->num_parents = 1;
girq->parents = devm_kcalloc(dev, 1, sizeof(*girq->parents),
                             GFP_KERNEL);
if (!girq->parents)
    return -ENOMEM;
girq->default_type = IRQ_TYPE_NONE;
girq->handler = handle_bad_irq;
girq->parents[0] = irq;

return devm_gpiochip_add_data(dev, &g->gc, g);

该助手还支持使用线程中断。然后,您只需单独请求中断并继续操作即可

/* Typical state container */
struct my_gpio {
    struct gpio_chip gc;
};

static void my_gpio_mask_irq(struct irq_data *d)
{
    struct gpio_chip *gc = irq_data_get_irq_chip_data(d);
    irq_hw_number_t hwirq = irqd_to_hwirq(d);

    /*
     * Perform any necessary action to mask the interrupt,
     * and then call into the core code to synchronise the
     * state.
     */

    gpiochip_disable_irq(gc, hwirq);
}

static void my_gpio_unmask_irq(struct irq_data *d)
{
    struct gpio_chip *gc = irq_data_get_irq_chip_data(d);
    irq_hw_number_t hwirq = irqd_to_hwirq(d);

    gpiochip_enable_irq(gc, hwirq);

    /*
     * Perform any necessary action to unmask the interrupt,
     * after having called into the core code to synchronise
     * the state.
     */
}

/*
 * Statically populate the irqchip. Note that it is made const
 * (further indicated by the IRQCHIP_IMMUTABLE flag), and that
 * the GPIOCHIP_IRQ_RESOURCE_HELPER macro adds some extra
 * callbacks to the structure.
 */
static const struct irq_chip my_gpio_irq_chip = {
    .name             = "my_gpio_irq",
    .irq_ack          = my_gpio_ack_irq,
    .irq_mask         = my_gpio_mask_irq,
    .irq_unmask       = my_gpio_unmask_irq,
    .irq_set_type     = my_gpio_set_irq_type,
    .flags            = IRQCHIP_IMMUTABLE,
    /* Provide the gpio resource callbacks */
    GPIOCHIP_IRQ_RESOURCE_HELPERS,
};

int irq; /* from platform etc */
struct my_gpio *g;
struct gpio_irq_chip *girq;

ret = devm_request_threaded_irq(dev, irq, NULL, irq_thread_fn,
                                IRQF_ONESHOT, "my-chip", g);
if (ret < 0)
    return ret;

/* Get a pointer to the gpio_irq_chip */
girq = &g->gc.irq;
gpio_irq_chip_set_chip(girq, &my_gpio_irq_chip);
/* This will let us handle the parent IRQ in the driver */
girq->parent_handler = NULL;
girq->num_parents = 0;
girq->parents = NULL;
girq->default_type = IRQ_TYPE_NONE;
girq->handler = handle_bad_irq;

return devm_gpiochip_add_data(dev, &g->gc, g);

该助手还支持使用分层中断控制器。在这种情况下,典型的设置如下所示

/* Typical state container with dynamic irqchip */
struct my_gpio {
    struct gpio_chip gc;
    struct fwnode_handle *fwnode;
};

static void my_gpio_mask_irq(struct irq_data *d)
{
    struct gpio_chip *gc = irq_data_get_irq_chip_data(d);
    irq_hw_number_t hwirq = irqd_to_hwirq(d);

    /*
     * Perform any necessary action to mask the interrupt,
     * and then call into the core code to synchronise the
     * state.
     */

    gpiochip_disable_irq(gc, hwirq);
    irq_mask_mask_parent(d);
}

static void my_gpio_unmask_irq(struct irq_data *d)
{
    struct gpio_chip *gc = irq_data_get_irq_chip_data(d);
    irq_hw_number_t hwirq = irqd_to_hwirq(d);

    gpiochip_enable_irq(gc, hwirq);

    /*
     * Perform any necessary action to unmask the interrupt,
     * after having called into the core code to synchronise
     * the state.
     */

    irq_mask_unmask_parent(d);
}

/*
 * Statically populate the irqchip. Note that it is made const
 * (further indicated by the IRQCHIP_IMMUTABLE flag), and that
 * the GPIOCHIP_IRQ_RESOURCE_HELPER macro adds some extra
 * callbacks to the structure.
 */
static const struct irq_chip my_gpio_irq_chip = {
    .name             = "my_gpio_irq",
    .irq_ack          = my_gpio_ack_irq,
    .irq_mask         = my_gpio_mask_irq,
    .irq_unmask       = my_gpio_unmask_irq,
    .irq_set_type     = my_gpio_set_irq_type,
    .flags            = IRQCHIP_IMMUTABLE,
    /* Provide the gpio resource callbacks */
    GPIOCHIP_IRQ_RESOURCE_HELPERS,
};

struct my_gpio *g;
struct gpio_irq_chip *girq;

/* Get a pointer to the gpio_irq_chip */
girq = &g->gc.irq;
gpio_irq_chip_set_chip(girq, &my_gpio_irq_chip);
girq->default_type = IRQ_TYPE_NONE;
girq->handler = handle_bad_irq;
girq->fwnode = g->fwnode;
girq->parent_domain = parent;
girq->child_to_parent_hwirq = my_gpio_child_to_parent_hwirq;

return devm_gpiochip_add_data(dev, &g->gc, g);

正如您所看到的,非常相似,但您不为 IRQ 提供父处理程序,而是提供父 irqdomain、硬件的 fwnode 以及一个函数 .child_to_parent_hwirq(),该函数用于从子硬件 irq(即此 gpio 芯片)中查找父硬件 irq。与往常一样,最好查看内核树中的示例,以获取有关如何查找所需部分的建议。

如果需要从这些助手处理的 IRQ 域中排除某些 GPIO 线,我们可以在调用 devm_gpiochip_add_data() 或 gpiochip_add_data() 之前设置 gpiochip 的 .irq.need_valid_mask。这将分配一个 .irq.valid_mask,其中设置的位数与芯片中的 GPIO 线数相同,每个位表示线 0..n-1。驱动程序可以通过清除此掩码中的位来排除 GPIO 线。该掩码可以在 init_valid_mask() 回调中填充,它是 struct gpio_irq_chip 的一部分。

要使用助手,请记住以下几点

  • 确保分配 struct gpio_chip 的所有相关成员,以便 irqchip 可以初始化。例如,.dev 和 .can_sleep 应正确设置。

  • 通常将 gpio_irq_chip.handler 设置为 handle_bad_irq。然后,如果您的 irqchip 是级联的,请根据您的控制器支持的内容和使用者请求的内容,在 irqchip .set_type() 回调中将处理程序设置为 handle_level_irq() 和/或 handle_edge_irq()

锁定 IRQ 用法

由于 GPIO 和 irq_chip 是正交的,我们可能会在不同的用例之间发生冲突。例如,用于 IRQ 的 GPIO 线应该是输入线,在输出 GPIO 上触发中断是没有意义的。

如果子系统中存在竞争,即哪一方正在使用资源(例如,某个 GPIO 线和寄存器),则需要拒绝某些操作并跟踪 gpiolib 子系统内部的使用情况。

输入 GPIO 可以用作 IRQ 信号。发生这种情况时,会请求驱动程序将 GPIO 标记为用作 IRQ

int gpiochip_lock_as_irq(struct gpio_chip *chip, unsigned int offset)

这将阻止使用非 IRQ 相关的 GPIO API,直到 GPIO IRQ 锁被释放

void gpiochip_unlock_as_irq(struct gpio_chip *chip, unsigned int offset)

在 GPIO 驱动程序中实现 irqchip 时,这两个函数通常应从 irqchip 的 .startup() 和 .shutdown() 回调中调用。

当使用 gpiolib irqchip 助手时,会自动分配这些回调。

禁用和启用 IRQ

在某些(边缘)用例中,驱动程序可能会使用 GPIO 线作为 IRQ 的输入,但偶尔会将该线切换为驱动输出,然后再次切换回带有中断的输入。这种情况发生在诸如 CEC(消费电子控制)之类的设备上。

当 GPIO 用作 IRQ 信号时,gpiolib 也需要知道 IRQ 是启用还是禁用。为了将此信息通知 gpiolib,irqchip 驱动程序应调用

void gpiochip_disable_irq(struct gpio_chip *chip, unsigned int offset)

这允许驱动程序在 IRQ 禁用时将 GPIO 用作输出。当再次启用 IRQ 时,驱动程序应调用

void gpiochip_enable_irq(struct gpio_chip *chip, unsigned int offset)

在 GPIO 驱动程序中实现 irqchip 时,这两个函数通常应从 irqchip 的 .irq_disable() 和 .irq_enable() 回调中调用。

当 irqchip 未声明 IRQCHIP_IMMUTABLE 时,会自动分配这些回调。此行为已被弃用,并且即将从内核中删除。

GPIO IRQ 芯片的实时合规性

任何 irqchip 提供商都需要仔细调整以支持实时抢占。GPIO 子系统中的所有 irqchip 都应牢记这一点,并进行适当的测试以确保它们已启用实时功能。

因此,请注意文档中上述实时性考量。

以下是为实时合规性准备驱动程序时要遵循的清单

  • 确保 spinlock_t 不用作 irq_chip 实现的一部分

  • 确保可睡眠 API 不用作 irq_chip 实现的一部分。如果必须使用可睡眠 API,则可以从 .irq_bus_lock() 和 .irq_bus_unlock() 回调中完成这些操作

  • 链式 GPIO irqchip:确保 spinlock_t 或任何可睡眠 API 不在链式 IRQ 处理程序中使用

  • 通用链式 GPIO irqchip:注意 generic_handle_irq() 调用并应用相应的解决方法

  • 链式 GPIO irqchip:如果可能,请摆脱链式 IRQ 处理程序并使用通用 irq 处理程序

  • regmap_mmio:可以通过设置 .disable_locking 并在 GPIO 驱动程序中处理锁定来禁用 regmap 中的内部锁定

  • 使用适当的内核实时测试用例测试您的驱动程序,以测试电平和边沿 IRQ

请求自有的 GPIO 引脚

有时允许 GPIO 芯片驱动程序通过 gpiolib API 请求自己的 GPIO 描述符很有用。GPIO 驱动程序可以使用以下函数来请求和释放描述符

struct gpio_desc *gpiochip_request_own_desc(struct gpio_desc *desc,
                                            u16 hwnum,
                                            const char *label,
                                            enum gpiod_flags flags)

void gpiochip_free_own_desc(struct gpio_desc *desc)

使用 gpiochip_request_own_desc() 请求的描述符必须使用 gpiochip_free_own_desc() 释放。

这些函数必须谨慎使用,因为它们不会影响模块使用计数。请勿使用这些函数来请求调用驱动程序不拥有的 gpio 描述符。