Pin Control 和 GPIO 子系統

大多數嵌入式Linux驅動程序和內核工程師使用gpio編寫或使用pin多路複用。所謂引腳,我指的是組件的引出線。SoC作多引腳複用,這意味着一個引腳可能有幾個功能; 例如,arch/arm/boot/dts/imx6dl- pinfunction .h中的MX6QDL_PAD_SD3_DAT1能夠是SD3數據線1,UART1的cts/rts, Flexcan2的Rx,或者普通的GPIO。node

你選擇一個引腳應該工做的模式的機制被稱爲引腳muxing。負責這個的系統被稱爲引腳控制器。在本章的第二部分,咱們將討論通用輸入輸出(GPIO),它是一種特殊的功能(模式),一個引腳能夠在其中工做。linux

在本章中,咱們將:ios

  • 遍歷引腳控制子系統,看看如何在DT中聲明它們的節點
  • 探索傳統的基於整數的GPIO接口和新的基於描述符的接口API
  • 處理映射到IRQ的GPIO
  • 處理專用於gpio的sysfs接口

Pin Control 子系統

pin control (pinctrl)子系統容許管理pin muxing。在DT中,須要以某種方式多路複用引腳的設備必須聲明它們須要的引腳控制配置。app

pinctrl子系統提供:ide

  • 引腳複用,它容許爲不一樣的目的重用相同的引腳,例如一個引腳做爲UART TX引腳,GPIO線,或HSI數據線。多路複用能夠影響一組引腳或單個引腳。
  • 引腳配置,應用引腳的電子特性,如上拉、下拉、驅動器強度、反彈週期等。

這篇文章的目的僅限於使用由引腳控制器驅動程序導出的函數,而不包括如何編寫引腳控制器驅動程序。函數

Pinctrl and the device tree

pinctrl子系統只是一種收集引腳(不只僅是GPIO)的方式,並將它們傳遞給驅動程序。引腳控制器驅動程序負責解析DT中的引腳描述,並將其配置應用到芯片中。驅動程序一般須要一組兩個嵌套節點來描述一組引腳配置。第一個節點描述組的功能(組將用於什麼目的),第二個節點保存引腳的配置。this

在DT中如何分配引腳組很大程度上取決於平臺,所以也取決於引腳控制器驅動程序。每一個引腳控制狀態都有一個從0開始的連續整數ID。
您可使用name屬性,它將映射到ID的頂部,這樣相同的名稱老是指向相同的ID。spa

每一個客戶機設備本身的綁定決定了必須在其DT節點中定義的一組狀態,以及是否認義必須提供的一組狀態id,仍是定義必須提供的一組狀態名稱。在任何狀況下,一個pin配置節點能夠經過兩個屬性分配到一個設備:線程

  • pinctrl-<ID>:這容許咱們給出設備特定狀態所需的pinctrl配置列表。它是一個句柄列表,每一個句柄都指向一個pin配置節點。這些被引用的引腳配置節點必須是它們配置的引腳控制器的子節點。在這個列表中可能存在多個條目,以即可以配置多個引腳控制器,或者從多個節點爲單個引腳控制器構建一個狀態,每一個節點都是整個配置的一部分。
  • pinctrl-name:這容許爲列表中的每一個狀態提供一個名稱。列表項0定義了整數狀態ID 0的名稱,列表項1定義了狀態ID 1的名稱,以此類推。狀態ID 0一般被命名爲default。標準化狀態列表能夠在include/linux/pinctrl/pinctrl-state.h中找到。

如下是DT的摘錄,顯示了一些設備節點及其引腳控制節點:debug

 1 usdhc@0219c000 { /* uSDHC4 */
 2   non-removable;
 3   vmmc-supply = <&reg_3p3v>;
 4   status = "okay";
 5   pinctrl-names = "default";
 6   pinctrl-0 = <&pinctrl_usdhc4_1>;
 7 };
 8 gpio-keys {
 9   compatible = "gpio-keys";
10   pinctrl-names = "default";
11   pinctrl-0 = <&pinctrl_io_foo &pinctrl_io_bar>;
12 };
13 iomuxc@020e0000 {
14   compatible = "fsl,imx6q-iomuxc";
15   reg = <0x020e0000 0x4000>;
16   /* shared pinctrl settings */
17   usdhc4 { /* first node describing the function */
18     pinctrl_usdhc4_1: usdhc4grp-1 { /* second node */
19       fsl,pins = <
20         MX6QDL_PAD_SD4_CMD__SD4_CMD 0x17059
21         MX6QDL_PAD_SD4_CLK__SD4_CLK 0x10059
22         MX6QDL_PAD_SD4_DAT0__SD4_DATA0 0x17059
23         MX6QDL_PAD_SD4_DAT1__SD4_DATA1 0x17059
24         MX6QDL_PAD_SD4_DAT2__SD4_DATA2 0x17059
25         MX6QDL_PAD_SD4_DAT3__SD4_DATA3 0x17059
26         MX6QDL_PAD_SD4_DAT4__SD4_DATA4 0x17059
27         MX6QDL_PAD_SD4_DAT5__SD4_DATA5 0x17059
28         MX6QDL_PAD_SD4_DAT6__SD4_DATA6 0x17059
29         MX6QDL_PAD_SD4_DAT7__SD4_DATA7 0x17059
30       >;
31    };
32   };
33   [...]
34   uart3 {
35     pinctrl_uart3_1: uart3grp-1 {
36     fsl,pins = <
37       MX6QDL_PAD_EIM_D24__UART3_TX_DATA 0x1b0b1
38       MX6QDL_PAD_EIM_D25__UART3_RX_DATA 0x1b0b1
39     >;
40    };
41   };
42    // GPIOs (Inputs)
43
44 45    gpios {
46      pinctrl_io_foo: pinctrl_io_foo {
47        fsl,pins = <
48          MX6QDL_PAD_DISP0_DAT15__GPIO5_IO09 0x1f059
49          MX6QDL_PAD_DISP0_DAT13__GPIO5_IO07 0x1f059
50        >;
51      };
52      pinctrl_io_bar: pinctrl_io_bar {
53        fsl,pins = <
54          MX6QDL_PAD_DISP0_DAT11__GPIO5_IO05 0x1f059
55          MX6QDL_PAD_DISP0_DAT9__GPIO4_IO30 0x1f059
56          MX6QDL_PAD_DISP0_DAT7__GPIO4_IO28 0x1f059
57          MX6QDL_PAD_DISP0_DAT5__GPIO4_IO26 0x1f059
58        >;
59      };
60   };
61 };

在上面的例子中,pin配置以<PIN_FUNCTION>  < PIN_SETTING > 的形式給出,例如:

MX6QDL_PAD_DISP0_DAT15__GPIO5_IO09 0x1f059

MX6QDL_PAD_DISP0_DAT15__GPIO5_IO09表明pin function,在本例中是GPIO, 0x1f059表示引腳設置。

如下行:

MX6QDL_PAD_EIM_D25__UART3_RX_DATA 0x1b0b1

MX6QDL_PAD_EIM_D25__UART3_RX_DATA表示pin function,即UART3的RX線,0x1b0b1表示設置。

pin function是一個宏,它的值只對pin控制器驅動有意義。它們一般定義在位於arch/<arch>/boot/dts/中的頭文件中。若是你使用UDOO四軸,例如,它有一個i.MX6四軸核心(ARM), pin函數頭將是arch/ ARM /boot/dts/imx6q- pinfunct .h。下面是GPIO5控制器第5行對應的宏:

/* 
* The pin function ID is a tuple of <mux_reg conf_reg input_reg mux_mode input_val>
*/
#define
MX6QDL_PAD_DISP0_DAT11__GPIO5_IO05 0x19c 0x4b0 0x000 0x5 0x0

<PIN_SETTING>能夠用來設置諸如上拉,下拉,保持器,驅動強度,等等。它應該如何指定取決於pin控制器綁定,它的值的含義取決於SoC數據表,一般在IOMUX部分。在i.MX6 IOMUXC上,少於17位專用於此目的。

前面的這些節點是從相應的特定於驅動程序的節點調用的。此外,這些引腳是在相應的驅動初始化過程當中配置的。在選擇一個pin組狀態以前,您必須首先使用pinctrl_get()函數得到pin控制,調用pinctrl_lookup_state()來檢查請求的狀態是否存在,最後使用pinctrl_select_state()來應用狀態。

下面的示例展現瞭如何獲取pin control並應用它的默認配置:

 1 struct pinctrl *p;
 2 struct pinctrl_state *s;
 3 int ret;
 4 p = pinctrl_get(dev);
 5 if (IS_ERR(p))
 6   return p;
 7 s = pinctrl_lookup_state(p, name);
 8 if (IS_ERR(s)) {
 9   devm_pinctrl_put(p);
10   return ERR_PTR(PTR_ERR(s));
11 }
12 ret = pinctrl_select_state(p, s);
13 if (ret < 0) {
14   devm_pinctrl_put(p);
15   return ERR_PTR(ret);
16 }

一般在驅動程序初始化期間執行這些步驟。這段代碼的合適位置多是probe()函數內部。

 pinctrl_select_state() 在內部調用 pinmux_enable_setting() ,而 pinmux_enable_setting() 依次調用pin控制節點中的每一個pin上的 pin_request() 。

pin控件能夠經過 pinctrl_put() 函數釋放。您可使用該API的resource-managed版本。也就是說,您可使用 pinctrl_get_select() ,給定要選擇的狀態名,來配置pinmux。該函數在 include/linux/pinctrl/consumer.h 中定義以下:

static struct pinctrl *pinctrl_get_select(struct device *dev, const char *name)

 這裏,*name是在pinctrl-name屬性中寫入的狀態名。若是狀態的名稱是default,你能夠調用pinctr_get_select_default()函數,它是對pinctl_get_select()的包裝:

1 static struct pinctrl * pinctrl_get_select_default(struct device *dev)
2 {
3     return pinctrl_get_select(dev, PINCTRL_STATE_DEFAULT);
4 }

讓咱們看一個板子特有的DTS文件中的實際示例(am335x-evm.dts):

1 dcan1: d_can@481d0000 {
2     status = "okay";
3     pinctrl-names = "default";
4     pinctrl-0 = <&d_can1_pins>;
5 };

對應的驅動程序示例以下:

1 pinctrl = devm_pinctrl_get_select_default(&pdev->dev);
2 if (IS_ERR(pinctrl))
3     dev_warn(&pdev->dev,"pins are not configured from the driver\n");

當設備被探測(probe)時,pin 控制核心將自動爲咱們聲明默認的 pinctrl 狀態。若是您定義了一個 init 狀態,那麼 pinctrl 核心會在 probe() 函數以前自動將 pinctrl 設置爲這個狀態,而後在 probe() 以後切換到默認狀態(除非驅動程序已經顯式地改變了狀態)。

The GPIO subsystem

從硬件的角度來看,GPIO 是一種功能,一種引腳能夠操做的模式。從軟件的角度來看,GPIO 只是一條數字線,它能夠做爲輸入或輸出,而且只能有兩個值(1表示高,0表示低)。內核 GPIO 子系統提供了您能夠想象的設置和處理的全部函數從你的驅動程序內部的 GPIO 行:

  • 在從驅動程序中使用 GPIO 以前,您應該向內核聲明它。這是一種得到 GPIO 全部權的方法,防止其餘驅動程序訪問同一 GPIO。在得到 GPIO 的全部權後,你能夠:
  1. 設置方向。
  2. 若是用做輸出,則切換輸出狀態(驅動線高或低)。
  3. 設置 debounce-interval,若是做爲輸入,則讀取狀態。對於映射到 IRQGPIO,你能夠定義中斷應該在什麼邊緣/級別被觸發,而且註冊一個處理程序,當中斷髮生時就會運行。

實際上,在內核中有兩種不一樣的方式來處理 GPIO,以下:

  • 舊的基於整數的接口,其中gpio用整數表示。
  • 新的和推薦的基於描述符的(descriptor-based)接口,其中 GPIO 由一個帶有專用 API 的不透明結構表示和描述。

基於整數的GPIO接口-遺留

基於整數的接口是最著名的。GPIO 由一個整數標識,這個整數用於須要在 GPIO 上執行的每個操做。下面是包含傳統 GPIO 訪問函數的頭文件:

#include <linux/gpio.h>

在內核中有一些處理 GPIO 的知名函數。

聲明和配置GPIO

你可使用 gpio_request() 函數來分配和得到 GPIO 的全部權:

static int gpio_request(unsigned gpio, const char *label)

 gpio  表示咱們感興趣的gpio號, label 是內核爲sysfs中的gpio所使用的標籤,咱們能夠在 /sys/kernel/debug/gpio 中看到。您必須檢查返回的值,其中0表示成功,一個錯誤產生一個負的錯誤代碼。一旦你用 GPIO 完成了這些,應該用 gpio_free() 函數釋放它:

void gpio_free(unsigned int gpio)

 若是有疑問,可使用 gpio_is_valid() 函數在分配這個 GPIO 號以前檢查它在系統上是否有效:

static bool gpio_is_valid(int number)

一旦咱們擁有了GPIO,咱們能夠根據須要改變它的方向,以及它應該是輸入仍是輸出,使用 gpio_direction_input() 或 gpio_direction_output() 函數:

static int gpio_direction_input(unsigned gpio)
static int gpio_direction_output(unsigned gpio, int value)

 gpio是咱們須要設定方向的 GPIO 號。第二個參數是配置 GPIO 做爲output, value,即輸出方向有效時GPIO的狀態。一樣,返回值是零或負的錯誤數。這些函數在內部映射到底層回調函數之上,這些回調函數由提供咱們使用的GPIO的GPIO控制器的驅動程序導出。

在處理GPIO控制器驅動時,咱們將看到GPIO控制器,經過它的結構gpio_chip結構,必須導出一組通用的回調函數來使用它的GPIO。

一些 GPIO 控制器提供了改變GPIO 防抖(debounc-interval) 的可能性(這隻在 GPIO 被配置爲輸入時有用)。該特性與平臺相關。你可使用 int gpio_set_debounce() 來實現:

static int gpio_set_debounce(unsigned gpio, unsigned debounce)

在這裏,debounce的值是ms。

前面全部的函數都應該在可能休眠的上下文中調用。從驅動程序的探測函數(probe)中聲明和配置gpio是一個很好的實踐。

訪問GPIO -獲取/設置值

在訪問 GPIO 時要注意。在原子上下文中,特別是在中斷處理程序中,您必須確保 GPIO 控制器回調函數不會休眠。一個設計良好的控制器驅動程序應該可以通知其餘驅動程序(其實是客戶端)對其方法的調用是否會休眠。這能夠經過 gpio_cansleep() 函數來檢查。

全部用於訪問 GPIO 的函數都沒有返回錯誤代碼。這就是爲何在 GPIO 分配和配置過程當中要注意並檢查返回值。

在原子上下文中

有一些 GPIO 控制器能夠經過簡單的內存讀寫操做來訪問和管理。它們一般嵌入在 SoC 中,不須要休眠。對於這些控制器, gpio_cansleep() 老是返回false。對於這樣的 GPIO,你可使用衆所周知的 gpio_get_value() 或 gpio_set_value() 從IRQ處理程序中獲取/設置它們的值,這取決於 GPIO 被配置爲輸入仍是輸出:

1 static int gpio_get_value(unsigned gpio)
2 void gpio_set_value(unsigned int gpio, int value);

當 GPIO 被配置爲 input (使用 gpio_direction_input() )時,應該使用 gpio_get_value() ,並返回 GPIO 的實際值(state)。另外一方面, gpio_set_value() 將影響 GPIO 的值,GPIO 應該使用 gpio_direction_output() 將其配置爲輸出。對於這兩個函數,value 均可以被認爲是布爾值,其中零表示低,非零值表示高。

在非原子上下文中(可能會休眠)

另外一方面,有 GPIO 控制器鏈接在總線上,如SPI和I2C。由於訪問這些總線的函數可能會致使睡眠, gpio_cansleep() 函數應該老是返回true(這取決於GPIO控制器返回true)。在這種狀況下,您不該該從IRQ中斷處理內部訪問這些gpio,至少不能在其上半部分訪問(硬IRQ)。此外,你必須使用的訪問器做爲你的通用訪問應該以_cansleep爲後綴:

static int gpio_get_value_cansleep(unsigned gpio);
void gpio_set_value_cansleep(unsigned gpio, int value);

它們的行爲與不帶 _cansleep() 後綴的訪問器徹底同樣,惟一的區別是它們防止內核在 gpio 被訪問時打印警告。

映射到IRQ的gpio

輸入 gpio 常常被用做 IRQ 信號。這種 irq 能夠是邊緣觸發或水平觸發。配置取決於您的須要。GPIO 控制器負責提供 GPIO 和它的 IRQ 之間的映射。你可使用 gpio_to_irq() 將給定的 GPIO 值映射到它的 IRQ 值:

int gpio_to_irq(unsigned gpio);

返回值是 IRQ 編號,你能夠調用 request_irq() (或者線程版本 request_threaded_irq() )來爲這個 IRQ 註冊一個處理程序:

 1 static irqreturn_t my_interrupt_handler(int irq, void *dev_id)
 2 {
 3     [...]
 4     return IRQ_HANDLED;
 5 }
 6 [...]
 7 int gpio_int = of_get_gpio(np, 0);
 8 int irq_num = gpio_to_irq(gpio_int);
 9 int error = devm_request_threaded_irq(&client->dev, irq_num,
10                NULL, my_interrupt_handler,
11                 IRQF_TRIGGER_RISING | IRQF_ONESHOT,
12                  input_dev->name, my_data_struct);
13 if (error) {
14     dev_err(&client->dev, "irq %d requested failed, %d\n",
15       client->irq, error);
16     return error;
17 }

彙總

下面的代碼是一個總結,將討論的全部關於基於整數的接口的概念付諸實踐。這個驅動程序管理四個gpio:兩個按鈕(btn1和btn2)和兩個led(綠色和紅色)。btn1被映射到IRQ,當它的狀態變爲低,btn2的狀態應用於led。例如,若是btn1的狀態是低的,而btn2的狀態是高的,綠色和紅色led將被驅動到高:

 1 #include <linux/init.h>
 2 #include <linux/module.h>
 3 #include <linux/kernel.h>
 4 #include <linux/gpio.h> /* For Legacy integer based GPIO */
 5 #include <linux/interrupt.h> /* For IRQ */
 6 static unsigned int GPIO_LED_RED = 49;
 7 static unsigned int GPIO_BTN1 = 115;
 8 static unsigned int GPIO_BTN2 = 116;
 9 static unsigned int GPIO_LED_GREEN = 120;
10 static int irq;
11 
12 static irqreturn_t btn1_pushed_irq_handler(int irq, void *dev_id)
13 {
14     int state;
15   
16     /* read BTN2 value and change the led state */
17     state = gpio_get_value(GPIO_BTN2);
18     gpio_set_value(GPIO_LED_RED, state);
19     gpio_set_value(GPIO_LED_GREEN, state);
20     pr_info("GPIO_BTN1 interrupt: Interrupt! GPIO_BTN2 state is %d)\n", state);
21 22     return IRQ_HANDLED;
23 }
24 
25 static int __init helloworld_init(void)
26 {
27     int retval;
28     /*
29      * One could have checked whether the GPIO is valid on the controller
30     or not,
31      * using gpio_is_valid() function.
32      * Ex:
33      *   if (!gpio_is_valid(GPIO_LED_RED)) {
34      *     pr_infor("Invalid Red LED\n");
35      *     return -ENODEV;
36      *   }
37      */
38     gpio_request(GPIO_LED_GREEN, "green-led");
39     gpio_request(GPIO_LED_RED, "red-led");
40     gpio_request(GPIO_BTN1, "button-1");
41     gpio_request(GPIO_BTN2, "button-2");
42     /*
43      * Configure Button GPIOs as input
44      *
45      * After this, one can call gpio_set_debounce()
46      * only if the controller has the feature
47      *
48      * For example, to debounce a button with a delay of 200ms
49      *   gpio_set_debounce(GPIO_BTN1, 200);
50      */
51     gpio_direction_input(GPIO_BTN1);
52     gpio_direction_input(GPIO_BTN2);
53     /*
54      * Set LED GPIOs as output, with their initial values set to 0
55      */
56     gpio_direction_output(GPIO_LED_RED, 0);
57     gpio_direction_output(GPIO_LED_GREEN, 0);
58     irq = gpio_to_irq(GPIO_BTN1);
59 
60     retval = request_threaded_irq(irq, NULL,\
61                         btn1_pushed_irq_handler, \
62                          IRQF_TRIGGER_LOW | IRQF_ONESHOT, \
63                          "device-name", NULL);
64     pr_info("Hello world!\n");
65     return 0;
66 }
67 
68 static void __exit hellowolrd_exit(void)
69 {
70     free_irq(irq, NULL);
71     gpio_free(GPIO_LED_RED);
72     gpio_free(GPIO_LED_GREEN);
73     gpio_free(GPIO_BTN1);
74     gpio_free(GPIO_BTN2);
75     pr_info("End of the world\n");
76 }
77 
78 module_init(hellowolrd_init);
79 module_exit(hellowolrd_exit);
80 MODULE_AUTHOR("aw aw@email.com");
81 MODULE_LICENSE("GPL");                            

 基於描述符的GPIO接口——新的和推薦的方式

使用新的基於描述符的GPIO接口,GPIO的特徵是一個一致的結構gpio_desc結構:

1 struct gpio_desc {
2     struct gpio_chip *chip;
3     unsigned long flags;
4     const char *label;
5 };

你應該使用如下頭文件來使用新的接口:

#include <linux/gpio/consumer.h>

對於基於描述符的接口,在分配和得到GPIOs全部權以前,這些GPIOs必須已經映射到某個地方。經過映射,個人意思是他們應該被分配給你的設備,然而,與傳統的基於整數的接口,你只須要在任何地方獲取一個數字,並請求它做爲GPIO。實際上,在內核中有三種映射:

  • Platform data mapping: 映射在board文件中完成。
  • Device tree: 映射是用DT風格完成的,這就是咱們將在本文中討論的映射。
  • Advanced Configuration and Power Interface mapping (ACPI): 映射是以ACPI風格完成的。一般用於基於x86的系統。

GPIO描述符映射-設備樹

GPIO描述符映射在消費設備的節點中定義。包含GPIO描述符映射的屬性必須命名爲<name>-gpios 或 <name>-gpio,其中<name>足夠有意義來描述這些GPIO將要使用的功能。

你應該老是在屬性名後面加上 -gpio 或 -gpios,由於每一個基於描述符的接口函數都依賴於 gpio_suffix[] 變量,該變量在 drivers/gpio/gpiolib.h 中定義,以下所示:

/* gpio suffixes used for ACPI and device tree lookup */
static const char * const gpio_suffixes[] = { "gpios", "gpio" };

讓咱們來看看如何在DT的設備中查找GPIO描述符映射:

static struct gpio_desc *of_find_gpio(struct device *dev,
           const char *con_id,
           unsigned int idx,
           enum gpio_lookup_flags *flags)
{
   char prop_name[32]; /* 32 is max size of property name */
   enum of_gpio_flags of_flags;
   struct gpio_desc *desc;
   unsigned int i;
   for (i = 0; i < ARRAY_SIZE(gpio_suffixes); i++) {
     if (con_id)
       snprintf(prop_name, sizeof(prop_name), "%s-%s", con_id, gpio_suffixes[i]);
     else
       snprintf(prop_name, sizeof(prop_name), "%s", gpio_suffixes[i]);
     desc = of_get_named_gpiod_flags(dev->of_node, prop_name, idx, &of_flags);
     if (!IS_ERR(desc) || (PTR_ERR(desc) == -EPROBE_DEFER))
       break;
   }
   if (IS_ERR(desc))
     return desc;
   if (of_flags & OF_GPIO_ACTIVE_LOW)
     *flags |= GPIO_ACTIVE_LOW;
   return desc;
}

如今,讓咱們考慮如下節點,它是從 Documentation/gpio/board.txt 節選的:

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>;
   reset-gpios = <&gpio 1 GPIO_ACTIVE_LOW>;
};

映射應該是這樣的,具備一個有意義的名稱。

分配和使用GPIO

你可使用gpiog_get()或者gpiod_get_index()來分配一個GPIO描述符:

struct gpio_desc *gpiod_get_index(struct device *dev, const char *con_id, unsigned int idx, enum gpiod_flags flags)
struct gpio_desc *gpiod_get(struct device *dev, const char *con_id, enum gpiod_flags flags)

在出現錯誤時,若是給定函數沒有指定GPIO,或者出現可使用IS_ERR()宏處理的錯誤,這些函數將返回-ENOENT。第一個函數返回與給定索引處的GPIO對應的GPIO描述符結構,而第二個函數返回索引爲0處的GPIO(對於單GPIO映射頗有用)。

dev是GPIO描述符所屬的設備。這是你的設備。con_id 對應於DT中屬性名的<name>前綴。idx是須要一個描述符的GPIO的索引(從0開始)。flags是一個可選參數,它決定了GPIO初始化標誌,用來配置方向和/或輸出值。它是enum gpiod_flags的一個實例,定義在include/linux/gpio/consumer.h中:

enum gpiod_flags {
     GPIOD_ASIS = 0,
     GPIOD_IN = GPIOD_FLAGS_BIT_DIR_SET,
     GPIOD_OUT_LOW = GPIOD_FLAGS_BIT_DIR_SET | GPIOD_FLAGS_BIT_DIR_OUT,
     GPIOD_OUT_HIGH = GPIOD_FLAGS_BIT_DIR_SET | GPIOD_FLAGS_BIT_DIR_OUT | GPIOD_FLAGS_BIT_DIR_VAL,
};

如今,讓咱們爲以前DT中定義的映射分配GPIO描述符:

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_LOW);

LED GPIO將爲active-high,而power GPIO將爲active-low(即gpiod_is_active_low(power)將爲true)。分配的反向操做是經過gpiod_put()函數完成的:

gpiod_put(struct gpio_desc *desc);

讓咱們看看如何釋放紅色和藍色GPIO led:

gpiod_put(blue);
gpiod_put(red);

在進一步討論以前,請記住,除了gpiod_get()/gpiod_get_index() 和 gpio_put() 函數(它們與 gpio_request() 和 gpio_free() 徹底不一樣)以外,您還能夠經過將gpio_前綴更改成gpiod_來執行從基於整數的接口到基於描述符的API轉換。

要改變方向,應該使用 gpiod_direction_input() 和 gpiod_direction_output() 函數:

int gpiod_direction_input(struct gpio_desc *desc);
int gpiod_direction_output(struct gpio_desc *desc, int value);

value是當方向設置爲輸出時應用到GPIO的狀態。若是GPIO控制器有這個特性,你可使用GPIO的描述符來設置它的超時:

int gpiod_set_debounce(struct gpio_desc *desc, unsigned debounce);

爲了訪問給定描述符的GPIO,必須像訪問基於整數的接口同樣注意它。換句話說,你應該注意你是在一個原子上下文(不能睡眠)仍是非原子上下文中,而後使用適當的函數:

int gpiod_cansleep(const struct gpio_desc *desc);

/* Value get/set from sleeping context */
int gpiod_get_value_cansleep(const struct gpio_desc *desc);
void gpiod_set_value_cansleep(struct gpio_desc *desc, int value);

/* Value get/set from non-sleeping context */
int gpiod_get_value(const struct gpio_desc *desc);
void gpiod_set_value(struct gpio_desc *desc, int value);

對於映射到IRQ的GPIO描述符,您可使用gpiod_to_irq()來獲取對應於給定GPIO描述符的IRQ編號,能夠與request_irq()函數一塊兒使用:

int gpiod_to_irq(const struct gpio_desc *desc);

在代碼中的任何給定點,均可以使用 desc_to_gpio() 或 gpio_to_desc() 函數,從基於描述符的接口切換到基於整數的傳統接口,反之亦然:

/* Convert between the old gpio_ and new gpiod_ interfaces */
struct gpio_desc *gpio_to_desc(unsigned gpio);
int desc_to_gpio(const struct gpio_desc *desc);

彙總

這裏的驅動程序總結了基於描述符的接口中引入的概念。和基於整數的gpio接口原理是同樣的:

#include <linux/init.h>
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/platform_device.h> /* For platform devices */
#include <linux/gpio/consumer.h> /* For GPIO Descriptor */
#include <linux/interrupt.h> /* For IRQ */
#include <linux/of.h> /* For DT*/
/*
 * Let us consider the below mapping in device tree:
 *
 * foo_device {
 * compatible = "packt,gpio-descriptor-sample";
 * led-gpios = <&gpio2 15 GPIO_ACTIVE_HIGH>, // red
 * <&gpio2 16 GPIO_ACTIVE_HIGH>, // green
 *
 * btn1-gpios = <&gpio2 1 GPIO_ACTIVE_LOW>;
 * btn2-gpios = <&gpio2 31 GPIO_ACTIVE_LOW>;
 * };
 */
static struct gpio_desc *red, *green, *btn1, *btn2;
static int irq;

static irqreturn_t btn1_pushed_irq_handler(int irq, void *dev_id) {   int state;   /* read the button value and change the led state */   state = gpiod_get_value(btn2);   gpiod_set_value(red, state);   gpiod_set_value(green, state);   pr_info("btn1 interrupt: Interrupt! btn2 state is %d)\n", state);
  
return IRQ_HANDLED; }
static const struct of_device_id gpiod_dt_ids[] = {   { .compatible = "packt,gpio-descriptor-sample", },   { /* sentinel */ } };
static int my_pdrv_probe (struct platform_device *pdev) {    int retval;    struct device *dev = &pdev->dev;
   /*    * We use gpiod_get/gpiod_get_index() along with the flags    * in order to configure the GPIO direction and an initial    * value in a single function call.    *    * One could have used:    *   red = gpiod_get_index(dev, "led", 0);    *   gpiod_direction_output(red, 0);    */
   red = gpiod_get_index(dev, "led", 0, GPIOD_OUT_LOW);   green = gpiod_get_index(dev, "led", 1, GPIOD_OUT_LOW);   /*   * Configure GPIO Buttons as input    *   * After this, one can call gpiod_set_debounce()   * only if the controller has the feature    * For example, to debounce a button with a delay of 200ms    *   gpiod_set_debounce(btn1, 200);    */   btn1 = gpiod_get(dev, "led", 0, GPIOD_IN);   btn2 = gpiod_get(dev, "led", 1, GPIOD_IN);   irq = gpiod_to_irq(btn1);    retval = request_threaded_irq(irq, NULL,\              btn1_pushed_irq_handler, \               IRQF_TRIGGER_LOW | IRQF_ONESHOT, \                 "gpio-descriptor-sample", NULL);   pr_info("Hello! device probed!\n");   
   return 0; }
static void my_pdrv_remove(struct platform_device *pdev) {   free_irq(irq, NULL);   gpiod_put(red);    gpiod_put(green);   gpiod_put(btn1);   gpiod_put(btn2);   pr_info("good bye reader!\n"); } static struct platform_driver mypdrv = {

  .probe = my_pdrv_probe,   .remove = my_pdrv_remove,   .driver = {     .name = "gpio_descriptor_sample",     .of_match_table = of_match_ptr(gpiod_dt_ids),     .owner = THIS_MODULE,   }, };
module_platform_driver(mypdrv); MODULE_AUTHOR("A W <aw@email.com>"); MODULE_LICENSE("GPL");

 GPIO接口和設備樹

不管您須要爲哪一個接口使用GPIO,如何指定GPIO取決於提供它們的控制器,特別是關於它的 #gpio-cells 屬性,它決定了GPIO說明符使用的單元數。一個GPIO說明符至少包含控制器句柄和一個或多個參數,其中包含提供GPIO的控制器的 #gpio-cells 屬性上的參數數量。第一個單元一般是控制器上的GPIO偏移數,第二個表明GPIO標誌。

GPIO屬性應該命名爲[<name>-]gpios], <name>是設備的GPIO的目的。請記住,這個規則對於基於描述符的接口是必須的,而且變成了<name>-gpios(注意沒有方括號,意味着<name>前綴是必須的):

gpio1: gpio1 {
   gpio-controller;
   #gpio-cells = <2>;
};
gpio2: gpio2 {
   gpio-controller;
   #gpio-cells = <1>;
};
[...]
cs
-gpios = <&gpio1 17 0>,        <&gpio2 2>;        <0>, /* holes are permitted, means no GPIO 2 */        <&gpio1 17 0>;
reset
-gpios = <&gpio1 30 0>; cd-gpios = <&gpio2 10>;

在上述示例中,CS GPIOs同時包含controller-1和controller-2 GPIOs。若是你不須要在列表中給定的索引處指定GPIO,你可使用<0>。reset GPIO有兩個cell(控制器phandle後的兩個參數),而CD GPIO只有一個cell。您能夠看到我給GPIO說明符的名稱是多麼有意義。

傳統的基於整數的接口和設備樹

該接口依賴於如下頭文件:

#include <linux/of_gpio.h>

當你須要在驅動中使用基於整數的接口來支持DT時,你應該記住兩個函數; 它們是 of_get_named_gpio() 和 of_get_named_gpio_count():

int of_get_named_gpio(struct device_node *np, const char *propname, int index)
int of_get_named_gpio_count(struct device_node *np, const char* propname)
給定一個設備節點,前者返回索引位置的*propname屬性的GPIO編號。第二個只是返回屬性中指定的gpio數量:
int n_gpios = of_get_named_gpio_count(dev.of_node, "cs-gpios"); /* return 4 */
int second_gpio = of_get_named_gpio(dev.of_node, "cs-gpio", 1);
int rst_gpio = of_get_named_gpio("reset-gpio", 0);
gpio_request(second_gpio, "my-gpio);

 

仍然有驅動程序支持舊的指定符,其中GPIO屬性被命名[<name>-gpio] 或 gpios。在這種狀況下,你應該使用未命名的API版本,經過 of_get_gpio() 和 of_gpio_count():

int of_gpio_count(struct device_node *np)
int of_get_gpio(struct device_node *np, int index)
  • DT節點以下所示:
my_node@addr {
   compatible = "[...]";
   gpios = <&gpio1 2 0>, /* INT */
        <&gpio1 5 0>; /* RST */
   [...]
};
  • 驅動程序中的代碼是這樣的:
struct device_node *np = dev->of_node;
if (!np)   return ERR_PTR(-ENOENT);
int n_gpios = of_gpio_count(); /* Will return 2 */ int gpio_int = of_get_gpio(np, 0); if (!gpio_is_valid(gpio_int)) {   dev_err(dev, "failed to get interrupt gpio\n");   return ERR_PTR(-EINVAL); }
gpio_rst
= of_get_gpio(np, 1); if (!gpio_is_valid(pdata->gpio_rst)) {   dev_err(dev, "failed to get reset gpio\n");   return ERR_PTR(-EINVAL); }

你能夠經過重寫第一個驅動(一個基於整數的接口)來總結這一點,爲了符合平臺驅動結構,並使用DT API:

#include <linux/init.h>
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/platform_device.h> /* For platform devices */
#include <linux/interrupt.h> /* For IRQ */
#include <linux/gpio.h> /* For Legacy integer based GPIO */
#include <linux/of_gpio.h> /* For of_gpio* functions */
#include <linux/of.h> /* For DT*/


/* * Let us consider the following node * * foo_device { * compatible = "packt,gpio-legacy-sample"; * led-gpios = <&gpio2 15 GPIO_ACTIVE_HIGH>, // red * <&gpio2 16 GPIO_ACTIVE_HIGH>, // green * * btn1-gpios = <&gpio2 1 GPIO_ACTIVE_LOW>; * btn2-gpios = <&gpio2 1 GPIO_ACTIVE_LOW>; * }; */ static unsigned int gpio_red, gpio_green, gpio_btn1, gpio_btn2; static int irq; static irqreturn_t btn1_pushed_irq_handler(int irq, void *dev_id) {   /* The content of this function remains unchanged */    [...] }
static const struct of_device_id gpio_dt_ids[] = {   { .compatible = "packt,gpio-legacy-sample", },   { /* sentinel */ } };
static int my_pdrv_probe (struct platform_device *pdev) {   int retval;    struct device_node *np = &pdev->dev.of_node;
  
if (!np)     return ERR_PTR(-ENOENT);
  gpio_red
= of_get_named_gpio(np, "led", 0);    gpio_green = of_get_named_gpio(np, "led", 1);    gpio_btn1 = of_get_named_gpio(np, "btn1", 0);   gpio_btn2 = of_get_named_gpio(np, "btn2", 0);
   gpio_request(gpio_green,
"green-led");   gpio_request(gpio_red, "red-led");   gpio_request(gpio_btn1, "button-1");   gpio_request(gpio_btn2, "button-2");
  
/* Code to configure GPIO and request IRQ remains unchanged */   [...]    return 0; }
static void my_pdrv_remove(struct platform_device *pdev) {   /* The content of this function remains unchanged */   [...] } static struct platform_driver mypdrv = {   .probe = my_pdrv_probe,   .remove = my_pdrv_remove,   .driver = {     .name = "gpio_legacy_sample",     .of_match_table = of_match_ptr(gpio_dt_ids),     .owner = THIS_MODULE,   }, };
module_platform_driver(mypdrv); MODULE_AUTHOR(
"A W <aw@email.com>"); MODULE_LICENSE("GPL");

 

GPIO映射到設備樹中的IRQ

您能夠很容易地將GPIO映射到設備樹中的IRQ。有兩個屬性用於指定中斷:

  • interrupt-parent: 這是GPIO的GPIO控制器
  • interrupts: 這是中斷說明符列表

這些應用於遺留的和基於描述符的接口。IRQ指示符取決於 #interrupt-cel l的GPIO控制器屬性。#interrupt-cell 肯定指定中斷時使用的單元數。一般,第一個單元表明映射到IRQ的GPIO號,第二個單元表明電平/邊緣觸發中斷機制。在任何狀況下,中斷指示符老是依賴於它的父(擁有中斷控制器集的父),因此請參考內核源代碼中的綁定文檔:

gpio4: gpio4 {
   gpio-controller;
   #gpio-cells = <2>;
   interrupt-controller;
   #interrupt-cells = <2>;
};
my_label: node@
0 {    reg = <0>;    spi-max-frequency = <1000000>;    interrupt-parent = <&gpio4>;   interrupts = <29 IRQ_TYPE_LEVEL_LOW>; };

有兩種方法能夠得到相應的IRQ:

  1. 設備位於一個已知的總線上(I2C或SPI): IRQ映射將爲你完成,而且能夠經過 struct i2c_client 或 struct spi_device 結構提供給 probe() 函數(經過i2c_client.irq 或 spi_device.irq)
  2. 設備位於虛擬平臺總線上: probe() 函數會給出一個 struct platform_device,在這個結構上你能夠調用 platform_get_irq():
int platform_get_irq(struct platform_device *dev, unsigned int num);

 

GPIO和sysfs

sysfs GPIO接口容許人們經過設置或文件來管理和控制GPIO。它位於 /sys/class/gpio 目錄下。設備模型在這裏被大量使用,有三種類型的入口:

  • /sys/class/gpio/: 這是一切開始的地方。這個目錄包含兩個特殊文件,export和unexport:
    • export: 這容許咱們要求內核導出給定的控制GPIO將其編號寫入用戶空間。一個例子是echo 21 > export,它將爲GPIO #21建立一個GPIO21節點,若是該GPIO沒有被內核代碼請求過的話。
    • unexport: 這與導出到用戶空間的效果相反。示例:echo 21 > unexport將刪除使用導出文件導出的任何GPIO21節點。
  • /sys/class/gpio/gpioN/:
    • direction 文件用於獲取/設置GPIO方向。容許的值是in或out字符串。這個值一般能夠寫入。寫爲out默認將值初始化爲low。爲了確保無端障的運行,低值和高值可能會被寫入,以配置GPIO做爲具備該初始值的輸出。若是內核代碼導出了這個GPIO,禁用方向(參見gpiod_export()或gpio_export()函數),這個屬性將不存在。
    • value 屬性讓咱們能夠根據方向、輸入或輸出來獲取/設置GPIO行的狀態。若是GPIO被配置爲輸出,任何寫入的非零值都將被視爲高狀態。若是配置爲輸出,寫0將輸出設置爲低,而寫1將輸出設置爲高。若是該引腳能夠配置爲一箇中斷生成行,而且若是它已經配置爲生成,您能夠調用poll(2)系統對該文件的調用,而且poll(2)將在中斷被觸發時返回。使用poll(2)將須要設置事件POLLPRI和POLLERR。
    • edge 決定讓poll()或select()函數返回的信號邊緣。容許的值是none, rising, falling, or both。這個文件是可讀/可寫的,而且只有當該引腳能夠配置爲一個產生中斷的輸入引腳時才存在。
    • active_low讀取爲0 (false)或1 (true)。寫入任何非零值都將反轉讀取和寫入的value屬性。現有的和後續的poll(2)支持配置經過邊緣屬性的上升和降低邊緣將遵循此設置。內核中設置這個值的相關函數是 gpio_sysf_set_active_low()。
       

從內核代碼導出GPIO

除了使用/sys/class/gpio/export文件導出gpio到用戶空間外,您能夠從內核代碼中使用諸如gpio_export(用於遺留接口)或gpioD_export(新接口)這樣的函數,以便顯式管理使用gpio_request()或gpiod_get()已經請求的gpio的導出:

int gpio_export(unsigned gpio, bool direction_may_change);
int gpiod_export(struct gpio_desc *desc, bool direction_may_change);

direction_may_change 參數決定是否能夠將信號方向從輸入改成輸出,反之亦然。內核的反向操做是gpio_unexport()或gpiod_unexport():

void gpio_unexport(unsigned gpio); /* Integer-based interface */
void gpiod_unexport(struct gpio_desc *desc) /* Descriptor-based */

導出後,您可使用gpio_export_link()(或gpiod_export_link()用於基於描述符的接口)從sysfs中的其餘地方建立符號連接,它將指向GPIO sysfs節點。在sysfs中,驅動程序能夠用一個描述性的名稱在本身的設備下提供接口:

int gpio_export_link(struct device *dev, const char *name, unsigned gpio)
int gpiod_export_link(struct device *dev, const char *name, struct gpio_desc *desc)

你能夠在probe()函數中使用它來實現基於描述符的接口,以下所示:

static struct gpio_desc *red, *green, *btn1, *btn2;
static int my_pdrv_probe (struct platform_device *pdev) {   [...]   red = gpiod_get_index(dev, "led", 0, GPIOD_OUT_LOW);   green = gpiod_get_index(dev, "led", 1, GPIOD_OUT_LOW);    gpiod_export(&pdev->dev, "Green_LED", green);   gpiod_export(&pdev->dev, "Red_LED", red);
   [...]   
return 0; }

對於基於整數的接口,代碼以下所示:

static int my_pdrv_probe (struct platform_device *pdev)
{
   [...]
  gpio_red
= of_get_named_gpio(np, "led", 0);   gpio_green = of_get_named_gpio(np, "led", 1);   [...]
  
int gpio_export_link(&pdev->dev, "Green_LED", gpio_green)    int gpio_export_link(&pdev->dev, "Red_LED", gpio_red)   return 0; }

總結

在內核中處理GPIO是一項簡單的任務,如本文所示。咱們討論了遺留的和新的接口,從而能夠選擇適合您須要的接口,以便編寫加強的GPIO驅動程序。您如今應該可以處理IRQs映射到gpio。下一篇文章將討論提供和公開的芯片GPIO lines,稱爲GPIO控制器。

相關文章
相關標籤/搜索