GPIO 驱动接口 【ChatGPT】

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

GPIO 驱动接口

这份文档是 GPIO 芯片驱动程序编写者的指南。

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

#include <linux/gpio/driver.h>

GPIO 的内部表示

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

在 GPIO 驱动程序内部,单独的 GPIO 线路通过它们的硬件编号来标识,有时也称为偏移量,这是一个介于 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()、gpiochip_add_data() 或 devm_gpiochip_add_data()。当不可避免地需要移除 GPIO 控制器时,应该很少发生;这时应使用 gpiochip_remove()。

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

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

实时考虑:如果预期在实时内核上从原子上下文中调用 GPIO API(在硬中断处理程序和类似上下文中),GPIO 驱动程序不应在其 gpio_chip 实现(.get/.set 和方向控制回调)中使用 spinlock_t 或任何可休眠的 API(如 PM runtime)。通常情况下,这是不需要的。

GPIO 电气配置

可以使用 .set_config() 回调将 GPIO 线路配置为几种电气操作模式。当前此 API 支持设置:

  • 消抖
  • 单端模式(开漏/开源)
  • 上拉和下拉电阻使能

下面描述了这些设置。

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

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

具有消抖支持的 GPIO 线路

消抖是一种设置为指示它连接到机械开关或按钮等可能会反弹的东西的引脚的配置,或类似的东西。反弹意味着由于机械原因,引脚以非常短的间隔迅速地被拉高/拉低。这可能导致值不稳定或中断重复触发,除非引脚被消抖。

在实践中,消抖涉及在引脚上发生某些事情时设置一个定时器,等待一小段时间,然后再次对引脚进行采样,以查看它是否仍然具有相同的值(低或高)。这也可以由一个聪明的状态机重复,等待引脚变得稳定。在任一情况下,它设置了一定数量的毫秒进行消抖,或者如果该时间不可配置,则只是“开/关”。

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

开漏(CMOS)或开集电极(TTL)意味着该线路不是主动驱动高电平的:相反,您提供漏极/集电极作为输出,因此当晶体管未打开时,它将对外部电路呈现高阻态(三态):

      CMOS 配置              TTL 配置

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

通常使用此配置有两种情况:

  • 电平转换:以达到比输出所在硅片的逻辑电平更高的逻辑电平。
  • 在 I/O 线路上进行反向有源-无源 OR,例如 GPIO 线路,使得该线路上的任何驱动级都可以将其拉低,即使同时有其他输出将其拉高。这种情况的特例是驱动 I2C 总线的 SCL 和 SDA 线路,这根据定义是一条 OR 总线。

这两种用例都要求该线路配备上拉电阻。该电阻将使该线路倾向于高电平,除非电路上的一个晶体管主动将其拉低。

线路上的电平将上升到上拉电阻的 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(),该回调接受一个通用引脚配置打包值,告诉是否将线路配置为开漏、开源或推挽。这将是来自机器文件中设置的 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_chipirq_chip是正交的,它们独立地提供它们的服务。

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

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

我们可以将GPIO irqchip分为两个广泛的类别:

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

  • 分层中断芯片:这意味着每个GPIO线都有一个专用的irq线连接到上一级的父中断控制器。无需查询GPIO硬件来弄清楚哪条线已触发,但可能仍需要确认中断并设置配置,比如边沿灵敏度。

实时考虑:实时兼容的GPIO驱动程序不应该在其irqchip实现中使用spinlock_t或任何可睡眠的API(如PM runtime)。

  • spinlock_t应该替换为raw_spinlock_t
  • 如果必须使用可睡眠的API,可以在.irq_bus_lock().irq_bus_unlock()回调中使用,因为这些是irqchip上的唯一的慢路径回调。如有需要,可以创建这些回调。

级联GPIO irqchip

级联GPIO irqchip通常分为三类:

  • 链式级联GPIO IRQ芯片:这通常是嵌入在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上不会被强制为线程化。因此,在链式IRQ处理程序中不能使用spinlock_t或任何可睡眠的API。

如果需要(并且无法转换为嵌套线程化的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 IRQ芯片:这与“链式GPIO irqchip”相同,但不使用链式IRQ处理程序。而是使用request_irq()配置的通用IRQ处理程序来执行GPIO IRQ分发。GPIO irqchip将在其中断处理程序中调用类似于以下序列的内容:
static irqreturn_t gpio_rcar_irq_handler(int irq, void *dev_id)
    对于每个检测到的GPIO IRQ
        generic_handle_irq(...);

实时考虑:这种类型的处理程序在-RT上将被强制为线程化,因此IRQ核心将会报错,指出generic_handle_irq()是在启用IRQ的情况下被调用,可以应用与“链式GPIO irqchip”相同的解决方法。

  • 嵌套线程化GPIO IRQ芯片:这些是位于睡眠总线的GPIO扩展器和其他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将管理大部分开销代码,假设你的中断是1对1映射到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的典型级联级联中断处理程序的示例。注意屏蔽/解除屏蔽(或禁用/启用)函数调用核心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。通常情况下,查看内核树中的示例以获取所需部分的建议是很有帮助的。

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

使用这些辅助程序时,请牢记以下几点:

  • 确保分配 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:确保从链式 IRQ 处理程序中不使用 spinlock_t 或任何可休眠的 API。
  • 通用链式 GPIO irqchip:注意 generic_handle_irq() 的调用,并应用相应的解决方法。
  • 链式 GPIO irqchip:尽可能摆脱链式 IRQ 处理程序,并在可能的情况下使用通用 IRQ 处理程序。
  • regmap_mmio:可以通过设置 .disable_locking 来禁用 regmap 中的内部锁定,并在 GPIO 驱动程序中处理锁定。
  • 使用适当的内核实时测试用例测试你的驱动程序,包括电平和边缘 IRQ。

[1] http://www.spinics.net/lists/linux-omap/msg120425.html

[2] https://lore.kernel.org/r/1443209283-20781-2-git-send-email-grygorii.strashko@ti.com

[3] https://lore.kernel.org/r/1443209283-20781-3-git-send-email-grygorii.strashko@ti.com

请求自拥有的 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 描述符。