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 线,全局编号将是(基准 + 硬件编号)。尽管整数表示被认为是已弃用,但它仍然有许多用户,因此需要维护。
例如,一个平台可能为 GPIO 使用全局编号 32-159,其中一个控制器定义了 128 个 GPIO,基准为 32;而另一个平台使用全局编号 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 线关联的标签。
实时考虑:如果 GPIO 驱动程序预期在实时内核上的原子上下文(硬 IRQ 处理器和类似上下文内)调用 GPIO API,则不应在其 gpio_chip 实现(.get/.set 和方向控制回调)中使用 spinlock_t 或任何可休眠 API(如 PM runtime)。通常,这不应是必需的。
GPIO 电气配置¶
GPIO 线可以通过使用 .set_config() 回调配置为多种电气操作模式。目前此 API 支持设置
去抖
单端模式(开漏/开源)
上拉和下拉电阻使能
这些设置如下所述。
该 .set_config() 回调使用与通用引脚控制驱动相同的枚举器和配置语义。这并非巧合:可以将 .set_config() 分配给函数 gpiochip_generic_config()
,这将导致调用 pinctrl_gpio_set_config(),并最终在 GPIO 控制器“后面”的引脚控制后端结束,通常更接近实际引脚。这样,引脚控制器可以管理下面列出的 GPIO 配置。
如果使用引脚控制器后端,GPIO 控制器或硬件描述需要提供“GPIO 范围”,将 GPIO 线偏移映射到引脚控制器上的引脚编号,以便它们可以正确地相互交叉引用。
带去抖支持的 GPIO 线¶
去抖是一种对引脚的配置,指示其连接到机械开关或按钮等可能发生抖动(bouncing)的设备。抖动是指由于机械原因,线在非常短的时间间隔内快速拉高/拉低。这可能导致值不稳定或 IRQ 反复触发,除非对线进行去抖。
实际中的去抖包括在线路上发生某事时设置一个定时器,等待一小段时间,然后再次采样线路,看看它是否仍然具有相同的值(低或高)。这也可以通过一个巧妙的状态机重复,等待一条线变得稳定。无论哪种情况,它都会为去抖设置一定的毫秒数,如果时间不可配置,则只设置“开/关”。
带开漏/源支持的 GPIO 线¶
开漏 (CMOS) 或开集电极 (TTL) 意味着线未主动驱动高电平:相反,您提供漏极/集电极作为输出,因此当晶体管未导通时,它将向外部电轨呈现高阻抗(三态)
CMOS CONFIGURATION TTL CONFIGURATION
||--- out +--- out
in ----|| |/
||--+ in ----|
| |\
GND GND
此配置通常用于实现以下两种情况之一
电平转换:达到高于输出所在硅芯片逻辑电平的逻辑电平。
I/O 线上的反向线或 (wire-OR),例如 GPIO 线,使得线上任何驱动级都可以将其驱动为低电平,即使同时有其他输出将其驱动为高电平。一个特殊情况是驱动 I2C 总线的 SCL 和 SDA 线,I2C 总线按定义是一种线或总线。
这两种用例都要求该线路配备一个上拉电阻。该电阻将使该线路倾向于高电平,除非电轨上的某个晶体管主动将其拉低。
线上的电平将升高到上拉电阻上的 VDD,这可能高于晶体管支持的电平,从而实现向更高 VDD 的电平转换。
集成电子产品通常具有一个输出驱动级,其形式为 CMOS“图腾柱”,带有一个 N-MOS 和一个 P-MOS 晶体管,其中一个驱动线高电平,另一个驱动线低电平。这称为推挽输出。“图腾柱”如下图所示:
VDD
|
OD ||--+
+--/ ---o|| P-MOS-FET
| ||--+
IN --+ +----- out
| ||--+
+--/ ----|| N-MOS-FET
OS ||--+
|
GND
所需的输出信号(例如直接来自某个 GPIO 输出寄存器)到达 IN 端。名为“OD”和“OS”的开关通常是闭合的,形成推挽电路。
考虑在输入分支后使 P-MOS 或 N-MOS 晶体管启用/禁用的名为“OD”和“OS”的小“开关”。如你所见,如果这个开关打开,任何一个晶体管都会完全失效。图腾柱因此被一分为二,并提供高阻抗而不是主动驱动高或低电平。这通常是软件控制的开漏/开源工作方式。
某些 GPIO 硬件采用开漏/开源配置。有些是硬连接的线路,无论如何都只支持开漏或开源:那里只有一个晶体管。有些是软件可配置的:通过翻转寄存器中的一位,输出可以配置为开漏或开源,实际上是通过拨动上图中标记为“OD”和“OS”的开关。
通过禁用 P-MOS 晶体管,输出可以在 GND 和高阻抗之间驱动(开漏),通过禁用 N-MOS 晶体管,输出可以在 VDD 和高阻抗之间驱动(开源)。在第一种情况下,需要在线路输出端串联一个上拉电阻来完成电路,在第二种情况下,需要在电路上串联一个下拉电阻。
支持开漏或开源或两者兼有的硬件,可以在 gpio_chip 中实现一个特殊的 callback:.set_config(),它接受一个通用的 pinconf 封装值,指示是将线路配置为开漏、开源还是推挽。这将响应机器文件中设置的 GPIO_OPEN_DRAIN 或 GPIO_OPEN_SOURCE 标志,或来自其他硬件描述的标志。
如果此状态无法在硬件中配置,即如果 GPIO 硬件不支持硬件中的开漏/开源,则 GPIO 库将转而使用一个技巧:当一条线被设置为输出时,如果该线被标记为开漏,并且 IN 输出值为低,它将像往常一样被驱动为低。但如果 IN 输出值设置为高,它将不会被驱动为高,而是被切换为输入,因为输入模式等同于高阻抗,从而实现一种“开漏仿真”:电气上行为将相同,除了在切换线模式时可能出现的硬件故障。
对于开源配置,使用相同的原理,只是不是主动将线路驱动为低电平,而是设置为输入。
带上拉/下拉电阻支持的 GPIO 线¶
GPIO 线可以通过 .set_config() 回调支持上拉/下拉。这意味着 GPIO 线输出端有上拉或下拉电阻可用,并且该电阻是软件控制的。
在分立设计中,上拉或下拉电阻直接焊接到电路板上。这不是我们在软件中处理或建模的东西。您最常考虑这些线路的是它们很可能被配置为开漏或开源(请参阅上一节)。
该 .set_config() 回调只能打开和关闭上拉或下拉,并且不会对所使用的电阻有任何语义知识。它只会说在寄存器中切换一位来启用或禁用上拉或下拉。
如果 GPIO 线支持上拉或下拉电阻的不同阻值,gpio_chip 回调 .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 之前已被调用。
始终准备好硬件,并使其在 GPIO 和 irq_chip API 的相应回调中做好准备。不要依赖于 gpiod_to_irq()
先被调用。
我们可以将 GPIO irqchips 分为两大类
级联中断芯片:这意味着 GPIO 芯片有一个公共中断输出线,该中断输出线由该芯片上任何已启用的 GPIO 线触发。然后,该中断输出线将被路由到上一级父中断控制器,在最简单的情况下是系统主中断控制器。这由一个 irqchip 建模,该 irqchip 将检查 GPIO 控制器内部的位以找出是哪条线触发了它。驱动的 irqchip 部分需要检查寄存器以找出这一点,并且它可能还需要通过清除某些位(有时是隐式的,只需读取状态寄存器)来确认它正在处理中断,并且它通常需要设置配置,例如边沿敏感度(例如上升沿或下降沿,或高/低电平中断)。
分层中断芯片:这意味着每条 GPIO 线都有一个专用的 irq 线连接到上一级的父中断控制器。无需查询 GPIO 硬件来找出是哪条线触发了,但可能仍然需要确认中断并设置配置,例如边沿敏感度。
实时考虑:一个实时兼容的 GPIO 驱动不应在其 irqchip 实现中使用 spinlock_t 或任何可休眠的 API(如 PM runtime)。
spinlock_t 应替换为 raw_spinlock_t。[1]
如果必须使用可休眠 API,可以从 .irq_bus_lock() 和 .irq_bus_unlock() 回调中完成,因为这些是 irqchip 上唯一的慢路径回调。如果需要,创建回调。[2]
级联 GPIO irqchips¶
级联 GPIO irqchips 通常属于以下三类之一
链式级联 GPIO IRQCHIPS:这些通常是嵌入在 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 irqchips 通常不能设置
struct gpio_chip
上的 .can_sleep 标志,因为所有操作都直接在回调中发生:不能使用慢速总线流量(如 I2C)。实时考虑:请注意,链式 IRQ 处理程序在 -RT 上不会被强制线程化。因此,在链式 IRQ 处理程序中不能使用 spinlock_t 或任何可休眠的 API(如 PM runtime)。
如果需要(并且如果无法将其转换为嵌套线程化 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 IRQCHIPS:这些与“链式 GPIO irqchips”相同,但未使用链式 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 irqchips”相同的解决方法。嵌套线程化 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 irqchips 的显著特点是它们将
struct gpio_chip
上的 .can_sleep 标志设置为 true,表明此芯片在访问 GPIO 时可能休眠。这类 irqchips 本质上对实时是容忍的,因为它们已经设置为处理休眠上下文。
GPIO irqchips 的基础设施辅助函数¶
为了帮助处理 GPIO irqchips 的设置和管理以及相关的 irqdomain 和资源分配回调。这些通过选择 Kconfig 符号 GPIOLIB_IRQCHIP 激活。如果也选择了符号 IRQ_DOMAIN_HIERARCHY,则还将提供分层辅助函数。在假定您的中断与 GPIO 线索引一对一映射的情况下,gpiolib 将管理很大一部分开销代码。
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
。如果您这样做,额外的 irq_chip 将在设置其余 GPIO 功能的同时由 gpiolib 设置。以下是使用 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() 函数,其目的是从子(即此 gpio 芯片)硬件 irq 中查找父硬件 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 irqchips:确保在链式 IRQ 处理程序中不使用 spinlock_t 或任何可休眠 API
通用链式 GPIO irqchips:注意
generic_handle_irq()
调用并应用相应的解决方法链式 GPIO irqchips:如果可能,摆脱链式 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 描述符。