作者 | 趙青窕
隨著Linux內核代碼的逐步完善,其GPIO口的操作接口也在不斷完善。內核中存在多種GPIO API接口,我們該如何使用這些API接口呢?我們又該如何在設備樹中配置GPIO呢?
目前的內核中提供了三種版本的API接口供我們使用,分別是Pinctrl子系統對應的API接口和GPIO子系統對應的API接口(GPIO子系統提供了兩種類型的API接口),在本文中我將會通過內核中GPIO架構的角度來說明這三類API接口;我們對GPIO的控制,除了合適的API接口外,還需要通過設備樹對GPIO進行配置,雖然不同架構中設備樹的配置方式不同,但本文中也會舉例說明設備樹如何配置,對應的代碼中如何使用API接口;最后說明我們可以用到的調試手段。
1.內核GPIO架構
下圖是內核中,GPIO架構中的核心部分框架圖(暫時不考慮GPIO架構對應的sysfs和debugfs):
圖 1:GPIO架構
從上圖中的上半部分可以看出以下關鍵信息:
- 內核提供了兩種控制引腳的方式,一種是采用Pinctrl子系統,一種是采用GPIO子系統(該系統又有兩種方式,后面小節中會進行說明),用戶編寫驅動時可以調用這兩個子系統提供的API接口來達到控制GPIO口的目的;
- GPIO子系統的功能是通過Pinctrl子系統來實現的。雖然從圖中看出,Pinctrl子系統和GPIO子系統并存,但內核需要對所有的GPIO口進行管理,因此就需要一個統一的管理接口或者模塊,Pinctrl就完成這種統一管理的目標,比如我們采用GPIO子系統的API接口gpio_request來申請GPIO口時,內核需要記錄哪些GPIO口已經申請過了,若Pinctrl子系統和GPIO子系統各維護一套GPIO管理策略,那就可能導致Pinctrl子系統和GPIO子系統同時操作同一個GPIO口的情況,這種顯然是不可行的,且從高通平臺內核代碼中可以看出gpio_request有如下的調用關系:
gpio_request--->gpiod_request----> gpiod_request_commit---->chip->request(系統啟動時設置為gpopchip_generic_request) ---->pinctrl_gpio_request
該調用關系從GPIO子系統的API函數gpio_request最后調用了Pinctrl子系統的函數pinctrl_gpio_request,這種調用關系,也證實了Pinctrl和GPIO子系統的關系。對于其他接口,如gpio_direction_input,gpio_set_value等函數的調用,同gpio_request相似,其最后均是調用到對應的chip->direction_input,chip->set,進而調用pinctrl_gpio_direction_input,等函數,由于這類API函數比較多,在此就不展示其調用關系了;
- 內核采用結構體struct pinctrl_dev來表示一個控制器,所有的pinctrl_dev會形成鏈表,鏈表頭就是pinctrldev_list,在函數pinctrl_gpio_request內部,會調用函數pinctrl_get_device_gpio_range來根據GPIO號,遍歷鏈表pinctrldev_list來查找該GPIO口對應的pinctrl_dev,當然這部分工作均是由系統來維護,我們只需知道整個框架,并如何使用GPIO的整個子系統即可。
注意:目前從我看到的代碼中發現,有些廠家在實現GPIO子系統時,并非所有的功能均通過Pinctrl子系統,但gpio_request是會通過Pinctrl子系統,因為Pinctrl子系統中會標記哪些GPIO已經request了,這樣后續模塊采用request繼續申請該資源時就會失敗;不過大家放心,即使其部分功能不通過Pinctrl子系統,但其對驅動模塊提供的API接口不受其影響。
2.用戶驅動中控制GPIO
在編寫驅動時,可以采用以下兩種方式來設置:
- Pinctrl方式,該方式最終是采用Pinctrl子系統來實現各項功能的;
- 采用GPIO子系統接口的方式,該方式其實有兩種,分別是legacy和gpio description的方式。但目前的內核中,legacy內部會調用gpio description的方式,后面內容中,我會以legacy的方式來說明使用方法。
Pinctrl控制引腳方式
下面,我以高通平臺Pinctrl的方式來說明其代碼和設備樹的配置。
我們先來看一下設備樹:
&tlmm{
client1_state1: client1_state1 {
mux {
pins = "gpio0";
function = "gpio";
};
config {
pins = "gpio0";
bias-disable;
drive-strength =<2> ;
input-enable;
};
};
client1_state2: client1_state2 {
mux {
pins = "gpio0";
function = "gpio";
};
config {
pins = "gpio0";
bias-disable;
drive-strength = <2>;
output-high;
};
};
};
&soc {
client1 {
pinctrl-names = "state1", "state2";
pinctrl-0 = <&client1_state1 >;
pinctrl-1 = <&client1_state2>;
};
};
上面是典型的設備樹的配置,其中包含兩個節點,分別是tlmm中的引腳配置和我們的設備client1。
tlmm中定義了兩種GPIO狀態,分別是作為輸入功能的GPIO0和作為輸出功能的GPIO0。不同平臺的設備樹中的配置方法不同,但均需要像上面設備樹那樣,配置兩部分:
引腳功能配置,是作為普通的GPIO口,還是復用為其他的功能;
引腳驅動配置,包括引腳內部的上拉或者下拉,驅動能力等。
接下來,一起看下代碼中如何進行操作,其操作需要按照下面的順序:
- 先調用devm_pinctrl_get或者pinctrl_get函數獲取對應的struct pinctrl*;
- 緊接著調用pinctrl_lookup_state(struct pinctrl *p, const char *name)來根據name獲取對應的配置;
- 最后我們采用函數pinctrl_select_state(struct pinctrl *p, struct pinctrl_state *state)來進行狀態的選擇。選擇某一狀態,就是設置了對應的引腳,且引腳的request操作是在該函數內部完成的,該函數內部會調用pin_request來進行申請,且調用該函數后,引腳的狀態就是設備樹中設備的狀態,如上面設備樹中,client1_state1對應的引腳使用了其GPIO功能,且配置為輸入(設備樹中是通過input-enable進行配置的),client1_state2對應的引腳也使用了其GPIO的功能,且配置為輸出高(設備樹中是通過out-high進行配置的)。
采用GPIO子系統
&soc {
client1 {
qcom,gpio-client1 = <&tlmm 100 0>; //100就是GPIO號
}
這種方式在設備樹的配置方式上比較簡單,其代碼操作如下:
- 使用函數of_get_named_gpio(node, " qcom,gpio-client1", 0)獲取GPIO號。
- 接下來是最重要的一步,調用函數gpio_request來進行GPIO的request操作。該函數最后會通過Pinctrl接口間接調用pin_request來進行引腳的申請操作。之前有一次工作中粗心,忘記request操作,但發現該GPIO可能也會正常操作,但會有工作不穩定的情況發生;
- 接下來可以調用函數gpio_direction_input或者gpio_direction_output來進行GPIO輸入或者輸出模式的配置,gpio_direction_output調用的同時,可以設置輸出高或者輸出低;
- 如果配置位輸入模式,則可以使用函數gpio_get_value來獲取GPIO口的狀態。
如果需要把引腳配置為中斷功能,則我們需要使用函數irq = gpio_to_irq(gpio)來獲得irq號,根據irq號來進行適當的中斷配置。
通過上面兩種方式,我們可以發現Pinctrl的方式,其設備樹復雜,但API接口簡單,只需要通過函數pinctrl_select_state選擇設備樹中某一配置即可。GPIO子系統的方式是設備樹簡單,但代碼復雜,設備樹中只配置了GPIO號,其余的如GPIO的方向及輸出信號均需要通過代碼來進行設置,這正是不同API接口及其設備樹的優缺點。
3.GPIO調試
不同平臺的調試方式可能會存在一些差異,比如MTK的不同平臺間都會有差異,在此我就介紹一種常見的需要debugfs支持的方式。
我們在編譯內核時,需要配置相應的debugfs宏來打開該功能,只有配置相應宏后,我們就可以進入機器的/sys/class/gpio下對GPIO口進行操作,下面是對應的操作順序:
- cd /sys/class/gpio/
- echo 99 > export(此處99代表引腳號,確切地說echo時,應采用對應引腳gpio_request獲得到的數值)
- cd gpio99
- echo in/out > direction //設置GPIO輸入或輸出
- cat direction //獲取GPIO輸入輸出狀態
- echo 0/1 > value //拉低或者拉高對應的GPIO口
- cat value //查看GPIO口的高低狀態
4.總結
希望大家通過本文,可以了解內核中GPIO的機制并掌握其操作方法,但需要說明的是在內核之前的階段也會進行引腳的配置,比如我們使用串口來打印bootloader階段的日志,這種情況下串口引腳一定是在內核之前的階段進行配置的。
作者介紹
趙青窕,51CTO社區編輯,從事多年驅動開發。研究興趣包含安全OS和網絡安全領域,發表過網絡相關專利。