文章目錄
- 前言
- 一、Pinctrl子系統重要概念
-
- 1.1 引入
- 1.2 重要概念
- 1.3 示例
- 1.4 代碼中怎麼引用pinctrl
- 二、 GPIO子系統重要概念
- 2.1 引入
- 2.2 在裝置樹中指定引腳
- 2.3 在驅動代碼中調用GPIO子系統
- 2.4 sysfs中的通路方法
- 三、 在100ASK_IMX6ULL上機實驗
- 3.1 确定引腳并生成裝置樹節點
- 3.2 驅動注冊
- 總結
前言
本章的重點在于“使用”
提示:以下是本篇文章正文内容,下面案例可供參考
一、Pinctrl子系統重要概念
1.1 引入
無論是哪種晶片,都有類似下圖的結構:
要想讓pinA、B用于GPIO,需要設定IOMUX讓它們連接配接到GPIO子產品;
要想讓pinA、B用于I2C,需要設定IOMUX讓它們連接配接到I2C子產品。
是以GPIO、I2C應該是并列的關系,它們能夠使用之前,需要設定IOMUX。有時候并不僅僅是設定IOMUX,還要配置引腳,比如上拉、下拉、開漏等等。
現在的晶片動辄幾百個引腳,在使用到GPIO功能時,讓你一個引腳一個引腳去找對應的寄存器,這要瘋掉。術業有專攻,這些累活就讓晶片廠家做吧──他們是BSP工程師。我們在他們的基礎上開發,我們是驅動工程師。開玩笑的,BSP工程師是更懂他自家的晶片,但是如果驅動工程師看不懂他們的代碼,那你的進步也有限啊。
是以,要把引腳的複用、配置抽出來,做成Pinctrl子系統,給GPIO、I2C等子產品使用。
BSP工程師要做什麼?看下圖:
等BSP工程師在GPIO子系統、Pinctrl子系統中把自家晶片的支援加進去後,我們就可以非常友善地使用這些引腳了:點燈簡直太簡單了。
等等,GPIO子產品在圖中跟I2C不是并列的嗎?幹嘛在講Pinctrl時還把GPIO子系統拉進來?
大多數的晶片,沒有單獨的IOMUX子產品,引腳的複用、配置等等,就是在GPIO子產品内部實作的。
在硬體上GPIO和Pinctrl是如此密切相關,在軟體上它們的關系也非常密切。
是以這2個子系統我們一起講解。
1.2 重要概念
從裝置樹開始學習Pintrl會比較容易。
主要參考文檔是:核心Documentation\devicetree\bindings\pinctrl\pinctrl-bindings.txt
這會涉及2個對象:pin controller、client device。
前者提供服務:可以用它來複用引腳、配置引腳。
後者使用服務:聲明自己要使用哪些引腳的哪些功能,怎麼配置它們。
a. pin controller:
在晶片手冊裡你找不到pin controller,它是一個軟體上的概念,你可以認為它對應IOMUX──用來複用引腳,還可以配置引腳(比如上下拉電阻等)。
注意,pin controller和GPIO Controller不是一回事,前者控制的引腳可用于GPIO功能、I2C功能;後者隻是把引腳配置為輸入、輸出等簡單的功能。
b. client device
“客戶裝置”,誰的客戶?Pinctrl系統的客戶,那就是使用Pinctrl系統的裝置,使用引腳的裝置。它在裝置樹裡會被定義為一個節點,在節點裡聲明要用哪些引腳。
下面這個圖就可以把幾個重要概念理清楚:
上圖中,左邊是pincontroller節點,右邊是client device節點:
a. pin state:
對于一個“client device”來說,比如對于一個UART裝置,它有多個“狀态”:default、sleep等,那對應的引腳也有這些狀态。
怎麼了解?
比如預設狀态下,UART裝置是工作的,那麼所用的引腳就要複用為UART功能。
在休眠狀态下,為了省電,可以把這些引腳複用為GPIO功能;或者直接把它們配置輸出高電平。
上圖中,pinctrl-names裡定義了2種狀态:default、sleep。
第0種狀态用到的引腳在pinctrl-0中定義,它是state_0_node_a,位于pincontroller節點中。
第1種狀态用到的引腳在pinctrl-1中定義,它是state_1_node_a,位于pincontroller節點中。
當這個裝置處于default狀态時,pinctrl子系統會自動根據上述資訊把所用引腳複用為uart0功能。
當這這個裝置處于sleep狀态時,pinctrl子系統會自動根據上述資訊把所用引腳配置為高電平。
b. groups和function:
一個裝置會用到一個或多個引腳,這些引腳就可以歸為一組(group);
這些引腳可以複用為某個功能:function。
當然:一個裝置可以用到多能引腳,比如A1、A2兩組引腳,A1組複用為F1功能,A2組複用為F2功能。
c. Generic pin multiplexing node和Generic pin configuration node
在上圖左邊的pin controller節點中,有子節點或孫節點,它們是給client device使用的。
可以用來描述複用資訊:哪組(group)引腳複用為哪個功能(function);
可以用來描述配置資訊:哪組(group)引腳配置為哪個設定功能(setting),比如上拉、下拉等。
注意:pin controller節點的格式,沒有統一的标準!!!!每家晶片都不一樣。
甚至上面的group、function關鍵字也不一定有,但是概念是有的
1.3 示例
當系統休眠時,也會去設定該裝置sleep狀态對應的引腳,不需要我們自己去調用代碼。
1.4 代碼中怎麼引用pinctrl
這是透明的,我們的驅動基本不用管。當裝置切換狀态時,對應的pinctrl就會被調用。
比如在platform_device和platform_driver的枚舉過程中,流程如下:
二、 GPIO子系統重要概念
該處使用的url網絡請求的資料。
2.1 引入
要操作GPIO引腳,先把所用引腳配置為GPIO功能,這通過Pinctrl子系統來實作。
然後就可以根據設定引腳方向(輸入還是輸出)、讀值──獲得電平狀态,寫值──輸出高低電平。
以前我們通過寄存器來操作GPIO引腳,即使LED驅動程式,對于不同的闆子它的代碼也完全不同。
當BSP工程師實作了GPIO子系統後,我們就可以:
a. 在裝置樹裡指定GPIO引腳
b. 在驅動代碼中:
使用GPIO子系統的标準函數獲得GPIO、設定GPIO方向、讀取/設定GPIO值。
這樣的驅動代碼,将是單闆無關的。
2.2 在裝置樹中指定引腳
在幾乎所有ARM晶片中,GPIO都分為幾組,每組中有若幹個引腳。是以在使用GPIO子系統之前,就要先确定:它是哪組的?組裡的哪一個?
在裝置樹中,“GPIO組”就是一個GPIO Controller,這通常都由晶片廠家設定好。我們要做的是找到它名字,比如“gpio1”,然後指定要用它裡面的哪個引腳,比如<&gpio1 0>。
有代碼更直覺,下圖是一些晶片的GPIO控制器節點,它們一般都是廠家定義好,在xxx.dtsi檔案中
我們暫時隻需要關心裡面的這2個屬性:
gpio-controller;
#gpio-cells = <2>;
“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 Controller是晶片廠家的事,我們怎麼引用某個引腳呢?在自己的裝置節點中使用屬性"[-]gpios",示例如下:
2.3 在驅動代碼中調用GPIO子系統
在裝置樹中指定了GPIO引腳,在驅動代碼中如何使用?
也就是GPIO子系統的接口函數是什麼?
GPIO子系統有兩套接口:基于描述符的(descriptor-based)、老的(legacy)。前者的函數都有字首“gpiod_”,它使用gpio_desc結構體來表示一個引腳;後者的函數都有字首“gpio_”,它使用一個整數來表示一個引腳。
要操作一個引腳,首先要get引腳,然後設定方向,讀值、寫值。
驅動程式中要包含頭檔案,
#include <linux/gpio/consumer.h> // descriptor-based
或
#include <linux/gpio.h> // legacy
下表列出常用的函數:
//位于linux/of.h檔案中
static inline struct device_node *of_find_node_by_path(const char *path);
//通過節點路徑來查找裝置樹節點,若查找失敗,則傳回NULL
//位于linux/of_gpio.h檔案中
static inline int of_get_named_gpio(struct device_node *np, const char *propname, int index);
//通過裝置樹節點、屬性名、屬性索引号來擷取GPIO編号,若擷取失敗,則傳回一個負數
//位于linux/gpio.h檔案中
static inline int gpio_request(unsigned gpio, const char *label);
//申請GPIO,第一個參數是GPIO編号,第二個參數是給GPIO加的标簽(由程式員給定),如果申請成功,則傳回0,否則傳回一個非零數
static inline void gpio_free(unsigned gpio);
//釋放GPIO,參數是GPIO編号
static inline int gpio_direction_input(unsigned gpio);
//把GPIO設定為輸入模式,參數是GPIO編号,如果設定成功,則傳回0,否則傳回一個非零數
static inline int gpio_direction_output(unsigned gpio, int value);
//把GPIO設定為輸出模式,第一個參數是GPIO編号,第二個參數是預設輸出值,如果設定成功,則傳回0,否則傳回一個非零數
static inline void gpio_set_value(unsigned int gpio, int value);
//設定GPIO的輸出值,第一個參數是GPIO編号,第二個參數是輸出值
有字首“devm_”的含義是“裝置資源管理”(Managed Device Resource),這是一種自動釋放資源的機制。它的思想是“資源是屬于裝置的,裝置不存在時資源就可以自動釋放”。
比如在Linux開發過程中,先申請了GPIO,再申請記憶體;如果記憶體申請失敗,那麼在傳回之前就需要先釋放GPIO資源。如果使用devm的相關函數,在記憶體申請失敗時可以直接傳回:裝置的銷毀函數會自動地釋放已經申請了的GPIO資源。
建議使用“devm_”版本的相關函數。
舉例,假裝置在裝置樹中有如下節點:
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。
2.4 sysfs中的通路方法
在sysfs中通路GPIO,實際上用的就是引腳号,老的方法。
a. 先确定某個GPIO Controller的基準引腳号(base number),再計算出某個引腳的号碼。
方法如下:
① 先在開發闆的/sys/class/gpio目錄下,找到各個gpiochipXXX目錄:
② 然後進入某個gpiochip目錄,檢視檔案label的内容
③ 根據label的内容對比裝置樹
label内容來自裝置樹,比如它的寄存器基位址。用來跟裝置樹(dtsi檔案)比較,就可以知道這對應哪一個GPIO Controller。
下圖是在100asK_imx6ull上運作的結果,通過對比裝置樹可知gpiochip96對應gpio4:
是以gpio4這組引腳的基準引腳号就是96,這也可以“cat base”來再次确認。
b. 基于sysfs操作引腳:
以100ask_imx6ull為例,它有一個按鍵,原理圖如下:
那麼GPIO4_14的号碼是96+14=110,可以如下操作讀取按鍵值:
echo 110 > /sys/class/gpio/export
echo in > /sys/class/gpio/gpio110/direction
cat /sys/class/gpio/gpio110/value
echo 110 > /sys/class/gpio/unexport
注意:如果驅動程式已經使用了該引腳,那麼将會export失敗,會提示下面的錯誤:
對于輸出引腳,假設引腳号為N,可以用下面的方法設定它的值為1:
- echo N > /sys/class/gpio/export
- echo out > /sys/class/gpio/gpioN/direction
- echo 1 > /sys/class/gpio/gpioN/value
- echo N > /sys/class/gpio/unexport
三、 在100ASK_IMX6ULL上機實驗
3.1 确定引腳并生成裝置樹節點
NXP公司對于IMX6ULL晶片,有裝置樹生成工具。我們也把它上傳到GIT去了,使用GIT指令載後,在這個目錄下:
01_all_series_quickstart
04_快速入門_正式開始
02_嵌入式Linux驅動開發基礎知識\source
05_gpio_and_pinctrl
tools
imx
安裝“Pins_Tool_for_i.MX_Processors_v6_x64.exe”後運作,打開IMX6ULL的配置檔案“MCIMX6Y2xxx08.mex”,就可以在GUI界面中選擇引腳,配置它的功能,這就可以自動生成Pinctrl的子節點資訊。
100ASK_IMX6ULL使用的LED原理圖如下,可知引腳是GPIO5_3:
在裝置樹工具中,如下圖操作:
把自動生成的裝置樹資訊,放到核心源碼arch/arm/boot/dts/100ask_imx6ull-14x14.dts中,代碼如下:
a. Pinctrl資訊:
&iomuxc_snvs {
……
myled_for_gpio_subsys: myled_for_gpio_subsys{
fsl,pins = <
MX6ULL_PAD_SNVS_TAMPER3__GPIO5_IO03 0x000110A0
>;
};
b. 裝置節點資訊(放在根節點下):
myled {
compatible = "100ask,leddrv";
pinctrl-names = "default";
pinctrl-0 = <&myled_for_gpio_subsys>;
led-gpios = <&gpio5 3 GPIO_ACTIVE_LOW>;
};
修改後編譯裝置樹,更新裝置樹之後,我們再來看驅動部分;
3.2 驅動注冊
a. 注冊platform_driver
注意下面第122行的"100ask,leddrv",它會跟裝置樹中節點的compatible對應:
static const struct of_device_id ask100_leds[] = {
122 { .compatible = "100ask,leddrv" },
123 { },
124 };
125
126 /* 1. 定義platform_driver */
127 static struct platform_driver chip_demo_gpio_driver = {
128 .probe = chip_demo_gpio_probe,
129 .remove = chip_demo_gpio_remove,
130 .driver = {
131 .name = "100ask_led",
132 .of_match_table = ask100_leds,
133 },
134 };
static int __init led_init(void)
138 {
139 int err;
140
141 printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
142
143 err = platform_driver_register(&chip_demo_gpio_driver);
144
145 return err;
146 }
b. 在probe函數中獲得GPIO
核心代碼是第87行,它從該裝置(對應裝置樹中的裝置節點)擷取名為“led”的引腳。在裝置樹中,必定有一屬性名為“led-gpios”或“led-gpio”。
/* 4. 從platform_device獲得GPIO
78 * 把file_operations結構體告訴核心:注冊驅動程式
79 */
80 static int chip_demo_gpio_probe(struct platform_device *pdev)
81 {
82 //int err;
83
84 printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
85
86 /* 4.1 裝置樹中定義有: led-gpios=<...>; */
87 led_gpio = gpiod_get(&pdev->dev, "led", 0);
88 if (IS_ERR(led_gpio)) {
89 dev_err(&pdev->dev, "Failed to get GPIO for led\n");
90 return PTR_ERR(led_gpio);
91 }
c. 注冊file_operations結構體:略
d. 在open函數中調用GPIO函數設定引腳方向:
static int led_drv_open (struct inode *node, struct file *file)
52 {
53 //int minor = iminor(node);
54
55 printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
56 /* 根據次裝置号初始化LED */
57 gpiod_direction_output(led_gpio, 0);
58
59 return 0;
60 }
在write函數中調用GPIO函數設定引腳值:
/* write(fd, &val, 1); */
35 static ssize_t led_drv_write (struct file *file, const char __user *buf, size_t size, loff_t *offset)
36 {
37 int err;
38 char status;
39 //struct inode *inode = file_inode(file);
40 //int minor = iminor(inode);
41
42 printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
43 err = copy_from_user(&status, buf, 1);
44
45 /* 根據次裝置号和status控制LED */
46 gpiod_set_value(led_gpio, status);
47
48 return 1;
49 }
f. 釋放GPIO:
總結
提示:這裡對文章進行總結:
例如:以上就是今天要講的内容,本文僅僅簡單介紹了pandas的使用,而pandas提供了大量能使我們快速便捷地處理資料的函數和方法。