手把手教你使用 Gpio 子系統(tǒng) API
本文講解 pinctrl 子系統(tǒng)和 gpio 子系統(tǒng)的 API,以及使用示例。
傳統(tǒng)的配置 pin 的方式就是直接操作相應(yīng)的寄存器,但是這種配置方式比較繁瑣、而且容易出問(wèn)題(比如 pin 功能沖突)。pinctrl 子系統(tǒng)就是為了解決這個(gè)問(wèn)題而引入的,pinctrl 子系統(tǒng)主要工作內(nèi)容如下:
①獲取設(shè)備樹(shù)中 pin 信息。
②根據(jù)獲取到的 pin 信息來(lái)設(shè)置 pin 的復(fù)用功能
③根據(jù)獲取到的 pin 信息來(lái)設(shè)置 pin 的電氣特性,比如上/下拉、速度、驅(qū)動(dòng)能力等。
對(duì)于我們使用者來(lái)講,只需要在設(shè)備樹(shù)里面設(shè)置好某個(gè) pin 的相關(guān)屬性即可,其他的初始化工作均由 pinctrl 子系統(tǒng)來(lái)完成。
如果 pinctrl 將一個(gè) pin 腳初始化為 GPIO 而不是 IIC 或者 SPI,那么接下來(lái)就可以使用 gpio 子系統(tǒng)的API。
gpio 子系統(tǒng)是基于 pinctrl 子系統(tǒng)的!pin controller 和 GPIO Controller 不是一回事,前者控制引腳可用于 GPIO 功能、I2C 功能等功能性切換;后者只是把引腳配置為輸入、輸出、設(shè)置GPIO方向、獲取值等簡(jiǎn)單的功能。(pinctrl 的 api 其實(shí)可以實(shí)現(xiàn)所有需求,但 gpio 的函數(shù)更常用一些)
1、gpio 子系統(tǒng) API
gpio 子系統(tǒng)中操作一個(gè) GPIO 需要如下幾步:
- 1、of_find_compatible_node
- 2、of_get_named_gpio
- 3、gpio_request
- 4、控制gpio(gpio_direction_input、gpio_direction_output……)
- 5、gpio_free
1)of_find_compatible_node 函數(shù)在設(shè)備樹(shù)中根據(jù) device_type 和 compatible 這兩個(gè)屬性查找指定的節(jié)點(diǎn),此處是為了獲取在設(shè)備樹(shù)中設(shè)置的 GPIO 的節(jié)點(diǎn)句柄。如果其他地方有獲得句柄,那么可以直接使用這個(gè)句柄。
2) of_get_named_gpio ,獲取所設(shè)置的 gpio number。
3) gpio_request ,請(qǐng)求這個(gè) gpio 。如果其他地方請(qǐng)求了這個(gè) gpio,還沒(méi)有釋放,那么我們會(huì)請(qǐng)求不到。
4)請(qǐng)求到這個(gè) gpio 以后,我們就可以對(duì)它進(jìn)行操作,比如獲取到它的值,設(shè)置它的值。
5)使用完以后,釋放這個(gè) gpio。
原理圖:
博主手里有一個(gè) 正點(diǎn)原子 imx6ull 開(kāi)發(fā)板,查原理圖,發(fā)現(xiàn)蜂鳴器直連的 GPIO 是 GPIO5_1。我把此 IO 口拉低,蜂鳴器就會(huì)響。
在設(shè)備樹(shù)中增加如下代碼(imx6ull-alientek-emmc.dts)
- test:test {
- compatible = "Jason_hello";
- hello = <&gpio5 1 GPIO_ACTIVE_HIGH>;
- };
設(shè)置 GPIO 為 GPIO5_1,高電平有效,但實(shí)際上第三個(gè)參數(shù)我沒(méi)有使用。
gpio.c
- #include <linux/init.h>
- #include <linux/kernel.h>
- #include <linux/module.h>
- #include <linux/gpio.h>
- #include <linux/of.h>
- #include <linux/of_gpio.h>
- static int __init mypinctrl_init(void)
- {
- int gpionum = 0;
- int ret = 0;
- struct device_node *node = NULL;
- node = of_find_compatible_node(NULL,NULL,"Jason_hello");
- if(!node){
- printk("get node error\n");
- return ret;
- }
- gpionum = of_get_named_gpio(node,"hello",0);
- if(gpionum < 0){
- printk("get gpionum error\n");
- return ret;
- }
- ret = gpio_request(gpionum,"hello");
- if(ret){
- printk("gpio_request error\n");
- return ret;
- }
- printk("gpio(%d) value = %d\n",gpionum,ret);
- ret = gpio_get_value(gpionum);
- printk("gpio(%d) value = %d\n",gpionum,ret);
- gpio_direction_output(gpionum,0); // 設(shè)置 gpio 輸出低電平
- ret = gpio_get_value(gpionum);
- printk("gpio(%d) value = %d\n",gpionum,ret);
- return 0;
- }
- static void __exit mypinctrl_exit(void)
- {
- printk("%s\n",__func__);
- }
- module_init(mypinctrl_init);
- module_exit(mypinctrl_exit);
- MODULE_LICENSE("GPL");
Makefile
- KERNELDIR := /home/book/linux/tool/kernel/linux-imx-rel_imx_4.1.15_2.1.0_ga_alientek
- CURRENT_PATH := $(shell pwd)
- obj-m := gpio.o
- build: kernel_modules
- kernel_modules:
- $(MAKE) -C $(KERNELDIR) M=$(CURRENT_PATH) modules
- clean:
- $(MAKE) -C $(KERNELDIR) M=$(CURRENT_PATH) clean
在 Linux 內(nèi)核源碼根目錄中輸入 make dtbs,編譯一份設(shè)備樹(shù),下載進(jìn)開(kāi)發(fā)板。
在 kernel/drivers/misc/ 中新建文件夾,命名為 mygpio,里面放置 gpio.c 和 Makefile。然后輸入 make 編譯出 gpio.ko。然后拷貝進(jìn)板子,insmod 上去,可以發(fā)現(xiàn)蜂鳴器有響。
2、pinctrl 子系統(tǒng) API
pinctrl 子系統(tǒng)的 API 有很多,對(duì)于驅(qū)動(dòng)工程師來(lái)說(shuō),pinctrl 操作一個(gè) GPIO 只需要三步:
- 1、devm_pinctrl_get
- 2、pinctrl_lookup_state
- 3、pinctrl_select_state
在 Linux 中,加 devm_ 開(kāi)頭的函數(shù),代表這個(gè)函數(shù)支持資源管理。一般情況下,我們寫(xiě)一個(gè)驅(qū)動(dòng)程序,在程序開(kāi)頭都會(huì)申請(qǐng)資源,比如內(nèi)存、中斷號(hào)等,萬(wàn)一后面哪一步申請(qǐng)出錯(cuò),我們要回滾到第一步,去釋放已經(jīng)申請(qǐng)的資源,這樣很麻煩。后來(lái) Linux 開(kāi)發(fā)出了很多 devm_ 開(kāi)頭的函數(shù),代表這個(gè)函數(shù)有支持資源管理的版本,不管哪一步出錯(cuò),只要錯(cuò)誤退出,就會(huì)自動(dòng)釋放所申請(qǐng)的資源。
1)devm_pinctrl_get:用于獲取設(shè)備樹(shù)中自己用 pinctrl 建立的節(jié)點(diǎn)的句柄;
2) pinctrl_lookup_state:用于選擇其中一個(gè) pinctrl 的狀態(tài),同一個(gè) pinctrl 可以有很多狀態(tài)。比如 GPIO50 ,一開(kāi)始初始化的時(shí)候是 I2C ,設(shè)備待機(jī)時(shí)候,我希望切換到普通 GPIO 模式,并且配置為下拉輸入,省電。這時(shí)候如果 pinctrl 節(jié)點(diǎn)有描述,我們就可以在代碼中切換 pin 的功能,從 I2C 功能切換成普通 GPIO 功能;
3) pinctrl_select_stat:用于真正設(shè)置,在上一步獲取到某個(gè)狀態(tài)以后,這一步真正設(shè)置為這個(gè)狀態(tài)。
對(duì)于 pinctrl 子系統(tǒng)的設(shè)備樹(shù)配置,是遵守 service 和 client 結(jié)構(gòu)。
client 端各個(gè)平臺(tái)基本都是一樣的,server 端每個(gè)平臺(tái)都不一樣,使用的字符串的配置也不一樣。
設(shè)備樹(shù)配置:
- //client端,設(shè)置不同狀態(tài)
- &test {
- pinctrl-names = "default","test_low","test_high";
- pinctrl-0 = <&test_default>;
- pinctrl-1 = <&test_low>;
- pinctrl-2 = <&test_high>;
- gpio = <&gpio5 1 GPIO_ACTIVE_LOW>;
- status = "okay";
- };
- //server 即 pin controller 端,設(shè)置 GPIO 幾種功能狀態(tài)
- &gpio5 {
- test_default:test_default{};
- test_low:test_low{
- fsl,pins = <
- MX6UL_PAD_GPIO5_IO01__GPIO5_IO01 0x17059
- >
- };
- test_high:test_low{
- fsl,pins = <
- MX6UL_PAD_GPIO5_IO01__GPIO5_IO01 0x1b0b1
- >
- };
- };
pinctrl.c
- #include <linux/init.h>
- #include <linux/kernel.h>
- #include <linux/module.h>
- #include <linux/platform_device.h>
- #include <linux/delay.h>
- #include <linux/pinctrl/pinctrl.h>
- #include <linux/pinctrl/consumer.h>
- static int __init mypinctrl_init(void)
- {
- int ret = 0;
- struct pinctrl *pctrl;
- struct platform_device *pdev;
- struct pinctrl_state *test_high;
- struct pinctrl_state *test_low;
- pctrl = devm_pinctrl_get(&pdev->dev);
- if(IS_ERR(pctrl)){
- ret = PTR_ERR(pctrl);
- printk("devm_pinctrl_get error\n");
- return ret;
- }
- test_high = pinctrl_lookup_state(pctrl,"test_high");
- if(IS_ERR(pctrl)){
- ret = PTR_ERR(test_high);
- printk("pinctrl_lookup_state test_high error\n");
- return ret;
- }
- test_low = pinctrl_lookup_state(pctrl,"test_low");
- if(IS_ERR(pctrl)){
- ret = PTR_ERR(test_low);
- printk("pinctrl_lookup_state test_low error\n");
- return ret;
- }
- pinctrl_select_state(pctrl,test_low);
- udelay(200);
- pinctrl_select_state(pctrl,test_high);
- return 0;
- }
- static void __exit mypinctrl_exit(void)
- {
- printk("%s\n",__func__);
- }
- module_init(mypinctrl_init);
- module_exit(mypinctrl_exit);
- MUDULE_LICENSE("GPL");
Makefile 與上面相同,只是更改一下編譯輸出的名字。
這個(gè)驅(qū)動(dòng)加載上去,可以切換GPIO口的功能狀態(tài),我這里只是控制GPIO輸出高低,具體看你設(shè)備樹(shù)怎么配,比如你可以配置某個(gè)GPIO一開(kāi)始是I2C功能,待機(jī)時(shí)候是普通GPIO功能,達(dá)到省電的目的。
補(bǔ)充:
設(shè)備樹(shù)是用來(lái)描述板子上的設(shè)備信息的,不同的設(shè)備其信息不同,反映到設(shè)備樹(shù)中就是屬性不同。那么我們?cè)谠O(shè)備樹(shù)中添加一個(gè)硬件對(duì)應(yīng)的節(jié)點(diǎn)的時(shí)候從哪里查閱相關(guān)的說(shuō)明呢?在Linux 內(nèi)核源碼中有詳細(xì)的.txt 文檔描述了如何添加節(jié)點(diǎn),這些.txt 文檔叫做綁定文檔,路徑為:Linux 源碼目錄/Documentation/devicetree/bindings。
比如我們現(xiàn)在要想在 I.MX6ULL 這顆 SOC 的 I2C 下添加一個(gè)節(jié)點(diǎn),那么就可以查看Documentation/devicetree/bindings/i2c/i2c-imx.txt,此文檔詳細(xì)的描述了 I.MX 系列的 SOC 如何在設(shè)備樹(shù)中添加 I2C 設(shè)備節(jié)點(diǎn)。
有時(shí)候使用的一些芯片在 Documentation/devicetree/bindings 目錄下找不到對(duì)應(yīng)的文檔,這個(gè)時(shí)候就要咨詢(xún)芯片的提供商,讓他們給你提供參考的設(shè)備樹(shù)文件。
小技巧:很多時(shí)候我們看設(shè)備樹(shù)文件,里面的內(nèi)容看不懂,這時(shí)候你看 .dts 最開(kāi)始引用的頭文件,點(diǎn)進(jìn)去,你就會(huì)發(fā)現(xiàn)這些字符串是定義在這里的。
參考文檔:
Documentation\devicetree\bindings\Pinctrl\Pinctrl-bindings.txt
Documentation\gpio\Pinctrl-bindings.txt
Documentation\devicetree\bindings\gpio\gpio.txt
Documentation\devicetree\bindings\pinctrl\pinctrl-bindings.txt
【編輯推薦】