初探pinctrl子系統和GPIO子系統

tstars發表於2024-05-09

前言:
在前面的led驅動程式和按鍵驅動程式中,無論是最傳統的方法,還是匯流排裝置驅動模型,還是基於裝置樹的匯流排裝置驅動模型,都是直接操作暫存器的方法。驅動開發的本質確實是操作暫存器,但是一個晶片有幾百個引腳,只是操作少數的幾個引腳還好,如果是大量的引腳,比如LCD介面的引腳幾十個,一個一個地去找相關的暫存器,這是難以想象的工作量。好在晶片廠家的BSP工程師已經提供了對應的軟體支援(pinctrl子系統和GPIO子系統,除此之外還會有其它子系統),能夠方便的配置引腳相關的暫存器實現引腳功能的複用設定、配置(如上下拉,轉換速度),我們只需要在他們的基礎上開發驅動程式,這能節省我們的大量時間。即使是在他們的基礎上開發驅動程式,也並不意味著我們不需要掌握直接操作暫存器的驅動開發方法。直接操作暫存器是驅動開發的本質,但是晶片廠家的BSP工程師封裝了操作暫存器的方法,可以在驅動開發時直接使用,而使用的前提就是要了解他們是怎麼封裝的,以及他們所提供的pinctrl和gpio子系統是怎麼使用的,理解了這些,才能夠熟練地使用BSP工程師所提供的工具提高我們驅動開發的效率,甚至能夠在廠家不支援時自己使用直接操作暫存器的方法編寫驅動程式
需要記住的核心就是,無論怎麼樣,驅動開發的本質都是直接操作暫存器,只不過是在別人的基礎上使用封裝好的函式間接操作還是自己直接操作。一般來說是在BSP工程師所提供的軟體基礎上操作,萬不得已才使用直接操作的方法,而懂得如何直接操作暫存器來編寫驅動也有助於理解pinctrl子系統和GPIO子系統的工作原理,從而更好地在BSP工程師所提供的軟體基礎上開發驅動程式。

1、pinctrl子系統

如上圖所示,對於晶片中同一個引腳它可以被用於普通的GPIO功能,也可以被複用為I2C功能,具體用作哪個功能,透過晶片的IOMUX控制器來實現配置,配置的過程本質就是操作引腳相關的暫存器。如果只是少量的幾個引腳,完全可以查詢晶片手冊,一個個地找到暫存器地址然後操作,但是一個晶片幾百個引腳,每個都這麼操作,就太累了,而且需要對晶片非常屬性,而對晶片最熟悉的就是晶片廠家的工程師。因此晶片廠家工程師會把引腳的複用、配置、GPIO操作這些抽象出來成為pinctr子系統(對於IOMUX)和GPIO子系統(對應GPIO)模組,並且預先提供一些功能,我們只需要在它們的基礎上開發即可,除非它們沒有預先提供我們需要的功能,才需要使用直接操作暫存器的方式去開發驅動,否則就是呼叫它們提供的軟體系統工具。

需要注意的是,大多數的晶片,沒有IOMUX模組,因此引腳的複用、配置等就是在GPIO模組內實現。

在裝置樹中定義了引腳相關的暫存器資訊,然後核心中pinctrl子系統會根據裝置樹中描述的節點資訊來複用、配置引腳,然後我們就可以使用GPIO提供的介面函式來操作配置好的引腳。因此,可以把pinctrl看作是一個複雜的驅動。既然在裝置樹中為pinctrl子系統定義了引腳資訊,那麼可以從裝置樹開始學習pinctrl子系統。

在使用上,pinctrl子系統可以分為兩個部分:(1)pin_controller控制器;(2)client_device
前者用來複用引腳、配置引腳,後者就是宣告要使用哪些引腳得到哪些功能,怎麼配置引腳

(1)pin_controller
pin_controller是一個軟體概念,可以認為它把IOMUX模組要做的引腳複用、配置這些事情抽象出來了,它就是IOMUX的驅動,對應在裝置樹中有一個pin_controller節點,當然不會就命名為pin_controller,比如在imx6ull中就命名為IOMUX節點。

(2)client_device
client_device表示使用pinctrl子系統的裝置。在裝置樹裡要為這個裝置定義節點,然後在節點裡宣告使用哪些引腳。

首先需要在裝置樹中pinctroller節點下建立相應的子節點,然後在根節點下或者其它子節點下,client(客戶)根據自己的需要按照規範引用前面在pincontroller節點下建立的子節點,就可以使用pinctrl子系統了。

關於pinctrl子系統中如何透過client_device來使用pin_controller可以看下圖

在上圖中,在pinctroller節點下建立了節點state_0_node_a和節點state_1_node_a,然後右邊device節點的pinctrl_0和pinctrl_1屬性分別引用節點state_0_node_a和節點state_1_node_a。透過這樣向裝置樹檔案新增程式碼並編譯後,核心就可以使用pinctrl子系統初始化引腳的複用、配置了。

首先分析上圖中device節點
在這個節點裡,,pinctrl-names屬性表示這個節點所對應的裝置可以有哪些狀態。對於同一個裝置/同一個引腳/同一組引腳/同一個介面,本質都是對應一個/多個引腳,它們不止有一種功能,但是對於某一個具體的功能,它的複用、配置資訊是固定的。pinctrl-names屬性的值按照先後順序分別對應第0種、第1種狀態......

在上圖的device節點中,pinctrl-name有兩種狀態"default"和"sleep",分別對應第0種和第1種狀態。其中0種狀態用到的引腳資訊在pinctrl-0中定義,它引用了state_0_node_a節點第,1種狀態用到的引腳資訊在pinctrl-1中定義,它引用了state_1_node_a節點。

當裝置處於default狀態時,pinctrl子系統會自動根據pinctrl-0引用的節點資訊進行復用,當處於sleep狀態時pinctrl子系統會自動根據pinctrl-1引用的節點資訊複用、配置引腳。

pinctrl是在使用pinctrl子系統時規定的字首,加上0表示第0中狀態。可以理解為pinctrl子系統中就是根據pinctrl這個字串來找到對應的資訊。

分析上圖左邊的pinctroller節點
pinctroller節點中有子節點或者孫節點,它們就是給client_device使用的,這些子節點或孫節點有兩個功能:
(1)描述複用資訊:哪組(group)引腳複用為哪個功能(function)
(2)描述配置資訊:哪組(group)引腳配置為什麼設定功能,比如上下拉、轉換速度等。
client_device透過引用這些子節點或者孫節點後,pinctroller控制器就可以根據這些子節點或孫節點的屬性對引腳進行復用、配置。因此,在裝置樹中,client_device會轉換為platform_device,而pinctroller節點下的子節點一般不會轉換為platform_device。

注意:pin_controller節點的格式沒有統一的標準,會根據不同的裝置和平臺有變化,但是概念是相通的。
在使用pinctrl子系統時,內部是如何工作的我們一般不用管,BSP工程師已經做好了。

2、GPIO子系統
GPIO子系統用於操作被配置為GPIO功能的引腳,因此首先需要透過pinctrl子系統將引腳配置為GPIO子系統
在pinctrl子系統和GPIO子系統的基礎上,我們只需要:
(1)在裝置樹裡指定GPIO引腳,pinctrl子系統會自動進行復用、配置
(2)在驅動程式碼中使用GPIO子系統的標準函式獲取GPIO、設定GPIO方向、讀/寫GPIO

(1)在裝置樹中指定GPIO引腳
在幾乎所有 ARM 晶片中, GPIO 都分為幾組,每組中有若干個引腳。所以在使用 GPIO 子系統之前,就要先確定:它是哪組的?組裡的哪一個?
在裝置樹中,“ GPIO 組”就是一個 GPIO Controller,這通常都由晶片廠家設定好。我們要做的是找到它名字,比如“ gpio1”,然後指定要用它裡面的哪個引腳,比如<&gpio1 0>。

上面圖片中是不同晶片的板子的裝置樹中的GPIO控制器節點,對於不同的晶片,其格式還是不一樣的,一般由廠家定義好。
可以看到上面的圖片中有多個gpion節點,每一個節點就代表一組GPIO。

“ gpio-controller”表示這個節點是一個 GPIO Controller,它下面有很多引腳。
⚫ “ #gpio-cells = <2>”表示這個控制器下每一個引腳要用 2 個 32 位的數(cell)來描述。
為什麼要用 2 個數?其實使用多個 cell 來描述一個引腳,這是 GPIO Controller 自己決定的。比如可以用其中一個 cell 來表示那是哪一個引腳,用另一個 cell 來表示它是高電平有效還是低電平有效,甚至還可以用更多的cell 來示其他特性。
普遍的用法是,用第 1 個 cell 來表示哪一個引腳,用第 2 個 cell 來表示有效電平:

點選檢視程式碼
GPIO_ACTIVE_HIGH : 高電平有效
GPIO_ACTIVE_LOW : 低電平有效

如何引用某個GPIO引腳
在自己的裝置節點中使用屬性"[-]gpios",如下圖所示。

上圖中,可以使用 gpios 屬性,也可以使用 name-gpios 屬性。在上圖的節點中,除了引用GPIO引腳的裝置樹程式碼,還有使用pinctrl子系統時client_device部分的裝置樹程式碼,說明這兩部分是要一起寫在一個裝置節點裡的。

(2)在驅動程式碼中如何呼叫GPIO子系統
GPIO 子系統有兩套介面:基於描述符的(descriptor-based)、老的(legacy)。前者的函式都有字首“ gpiod_”,它使用 gpio_desc 結構體來表示一個引腳;後者的函式都有字首“ gpio_”,它使用一個整數來表示一個引腳。
要操作一個引腳,首先要 get 引腳,然後設定方向,讀值、寫值。

驅動程式要包含以下標頭檔案

點選檢視程式碼
#include <linux/gpio/consumer.h> // descriptor-based
點選檢視程式碼
#include <linux/gpio.h> // legacy

下面列出在使用GPIO子系統是常用到的函式

下面是一個示例:
在裝置樹中假如有這麼一個節點

點選檢視程式碼
foo_device {
compatible = "acme,foo";
...
led-gpios = <&gpio 15 GPIO_ACTIVE_HIGH>, /* red */
<&gpio 16 GPIO_ACTIVE_HIGH>, /* green */
<&gpio 17 GPIO_ACTIVE_HIGH>; /* blue */
power-gpios = <&gpio 1 GPIO_ACTIVE_LOW>;
};

那麼可以使用下面的函式獲得引腳:

點選檢視程式碼
struct gpio_desc *red, *green, *blue, *power;
red = gpiod_get_index(dev, "led", 0, GPIOD_OUT_HIGH);
green = gpiod_get_index(dev, "led", 1, GPIOD_OUT_HIGH);
blue = gpiod_get_index(dev, "led", 2, GPIOD_OUT_HIGH);
power = gpiod_get(dev, "power", GPIOD_OUT_HIGH);

注意: gpiod_set_value 設定的值是“邏輯值”,不一定等於物理值。

舊的“gpio_”函式沒辦法根據裝置樹資訊獲得引腳,它需要先知道引腳號。引腳號怎麼確定?
在 GPIO 子系統中,每註冊一個 GPIO Controller 時會確定它的“ base number”,那麼這個控制器裡的第 n 號引腳的號碼就是: base number + n。
但是如果硬體有變化、裝置樹有變化,這個 base number 並不能保證是固定的,應該檢視 sysfs 來確定 base number。

對於pinctrl子系統和GPIO子系統的簡單介紹到這裡結束了,在實際開發中用的最多的就是pinctrl子系統,並且對於引腳的操作,除了GPIO子系統外還會有其它子系統如SPI子系統,I2C子系統等等。

相關文章