天天看點

GPIO和Pinctrl子系統的使用前言一、Pinctrl子系統重要概念總結

文章目錄

  • 前言
  • 一、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 引入

無論是哪種晶片,都有類似下圖的結構:

GPIO和Pinctrl子系統的使用前言一、Pinctrl子系統重要概念總結

要想讓pinA、B用于GPIO,需要設定IOMUX讓它們連接配接到GPIO子產品;

要想讓pinA、B用于I2C,需要設定IOMUX讓它們連接配接到I2C子產品。

是以GPIO、I2C應該是并列的關系,它們能夠使用之前,需要設定IOMUX。有時候并不僅僅是設定IOMUX,還要配置引腳,比如上拉、下拉、開漏等等。

現在的晶片動辄幾百個引腳,在使用到GPIO功能時,讓你一個引腳一個引腳去找對應的寄存器,這要瘋掉。術業有專攻,這些累活就讓晶片廠家做吧──他們是BSP工程師。我們在他們的基礎上開發,我們是驅動工程師。開玩笑的,BSP工程師是更懂他自家的晶片,但是如果驅動工程師看不懂他們的代碼,那你的進步也有限啊。

是以,要把引腳的複用、配置抽出來,做成Pinctrl子系統,給GPIO、I2C等子產品使用。

BSP工程師要做什麼?看下圖:

GPIO和Pinctrl子系統的使用前言一、Pinctrl子系統重要概念總結

等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系統的裝置,使用引腳的裝置。它在裝置樹裡會被定義為一個節點,在節點裡聲明要用哪些引腳。

下面這個圖就可以把幾個重要概念理清楚:

GPIO和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 示例

GPIO和Pinctrl子系統的使用前言一、Pinctrl子系統重要概念總結

當系統休眠時,也會去設定該裝置sleep狀态對應的引腳,不需要我們自己去調用代碼。

1.4 代碼中怎麼引用pinctrl

這是透明的,我們的驅動基本不用管。當裝置切換狀态時,對應的pinctrl就會被調用。

比如在platform_device和platform_driver的枚舉過程中,流程如下:

GPIO和Pinctrl子系統的使用前言一、Pinctrl子系統重要概念總結

二、 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檔案中

GPIO和Pinctrl子系統的使用前言一、Pinctrl子系統重要概念總結

我們暫時隻需要關心裡面的這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",示例如下:

GPIO和Pinctrl子系統的使用前言一、Pinctrl子系統重要概念總結

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

下表列出常用的函數:

GPIO和Pinctrl子系統的使用前言一、Pinctrl子系統重要概念總結
//位于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和Pinctrl子系統的使用前言一、Pinctrl子系統重要概念總結

舊的“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目錄:

GPIO和Pinctrl子系統的使用前言一、Pinctrl子系統重要概念總結

② 然後進入某個gpiochip目錄,檢視檔案label的内容

③ 根據label的内容對比裝置樹

label内容來自裝置樹,比如它的寄存器基位址。用來跟裝置樹(dtsi檔案)比較,就可以知道這對應哪一個GPIO Controller。

下圖是在100asK_imx6ull上運作的結果,通過對比裝置樹可知gpiochip96對應gpio4:

GPIO和Pinctrl子系統的使用前言一、Pinctrl子系統重要概念總結

是以gpio4這組引腳的基準引腳号就是96,這也可以“cat base”來再次确認。

b. 基于sysfs操作引腳:

以100ask_imx6ull為例,它有一個按鍵,原理圖如下:

GPIO和Pinctrl子系統的使用前言一、Pinctrl子系統重要概念總結

那麼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失敗,會提示下面的錯誤:

GPIO和Pinctrl子系統的使用前言一、Pinctrl子系統重要概念總結

對于輸出引腳,假設引腳号為N,可以用下面的方法設定它的值為1:

  1. echo N > /sys/class/gpio/export
  2. echo out > /sys/class/gpio/gpioN/direction
  3. echo 1 > /sys/class/gpio/gpioN/value
  4. 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:

GPIO和Pinctrl子系統的使用前言一、Pinctrl子系統重要概念總結

在裝置樹工具中,如下圖操作:

GPIO和Pinctrl子系統的使用前言一、Pinctrl子系統重要概念總結

把自動生成的裝置樹資訊,放到核心源碼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提供了大量能使我們快速便捷地處理資料的函數和方法。