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 描述符。