【linux】驅動-11-gpio子系統

李柱明發表於2021-04-13


前言

參考文件:

建議:複製以下連結,到原文觀看,原文排版清晰,便於學習。

11. gpio子系統

引腳配置為 GPIO 模式後,便可使用 GPIO子系統 來控制引腳。
可以通過 pinctrl子系統 配置,也可以自己程式設計配置。

參考文件:Documentation/devicetree/bindings/gpio/ 下對應晶片廠商的檔案。

11.1 操作步驟

  1. 在裝置樹對應節點中指定引腳。(哪一組、組裡哪一個引腳)
  2. 在驅動程式中通過 GPIO 子系統提供的 API 控制引腳。

11.1.1 新版 API 操作流程

個人喜歡用程式碼格式表述:

/** @file driver.c
 * @brief 驅動教程檔案
 * @details 
 * @author lzm
 * @date 2021-04-09 20:22:22
 * @version v1.0
 * @copyright Copyright By lizhuming, All Rights Reserved
 * @cnblogs   https://www.cnblogs.com/lizhuming/
 **********************************************************
 * @LOG 修改日誌:
 **********************************************************
 */

/* gpio 子系統開發步驟 */

/* [gpio][1] 請求引腳 */
/* 使用 gpiod_get() 或 devm_gpiod_get() 或 gpiod_get_index() 或 devm_gpiod_get_index() 等等函式 */

/* [gpio][2] 設定方向 */
/* 使用 gpiod_direction_input() 或 gpiod_direction_output() 等等函式 */

/* [gpio][3] 匯出到應用層 */
/* 使用 gpiod_export() 函式 */

/* [gpio][4] 設定/獲取 值 */
/* 使用 gpiod_set_value() 或 gpiod_get_value() 等等函式 */

/* [gpio][5] 轉中斷,註冊中斷 */
/* 使用 gpiod_set_value() 和 request_irq() 函式 */

/* [gpio][6] 釋放引腳 */
/* 使用 gpiod_put() 或 gpiod_put() 函式 */

11.1.2 舊版 API 操作流程

流程和下班 API 操作流程差不多。找到對應的函式即可。

11.2 裝置樹中使用gpio子系統

在裝置樹中,GPIO組 就是一個 GPIO Controller

GPIO組 的節點內容是由晶片廠商設定好的,一般在晶片廠商提供的裝置樹標頭檔案 xxx.dtsi 中。如 IMX6UL 的就在 imx6ull.dtsi 檔案中定義。
使用者只需要做的是根據晶片廠商文件格式要求,在相應裝置樹節點中填寫引腳資訊。
如 IMX6ULL:fsl-imx-gpio.txt

一般,我們參考 GPIO組 節點裡面的兩個屬性即可:

  • gpio-controller
    • 如果 GPIO組 節點內含有該屬性,則表示該節點為 GPIO 控制器節點。
  • gpio-cells
    • 表示這個控制器下的每一個引腳需要用多少個 32 位數來描述。

如 IMX6ULL:

  • gpio-controller: Marks the device node as a gpio controller.
  • #gpio-cells: Should be two.
    • The first cell is the pin number.
    • The second cell is used to specify the gpio polarity:
      • 0 = active high.
      • 1 = active low.

例子:

/*新增rgb_led節點*/
rgb_led{
    #address-cells = <1>;
    #size-cells = <1>;
    compatible = "fire,rgb-led"; // 節點相容性
    /* pinctrl 子系統 */
    pinctrl-names = "default"; // 引腳狀態名稱表
    pinctrl-0 = <&pinctrl_rgb_led>; // 第 0 個狀態使用的引腳
    /* GPIO子系統 */
    rgb_led_red = <&gpio1 4 GPIO_ACTIVE_LOW>; // 使用的引腳 。舊版
    rgb_led_green-gpios = <&gpio4 20 GPIO_ACTIVE_LOW>; // 新版
    rgb_led_blue-gpios = <&gpio4 19 GPIO_ACTIVE_LOW>; // 新版
    status = "okay";
};

分析 rgb_led_red = <&gpio1 4 GPIO_ACTIVE_LOW>;

  • rgb_led_red:自定義的引腳名字。(舊版
    • 新版必須使用 gpios 或 字尾為 -gpios 的屬性名。這樣才能使用新的 GPIO 子系統 API。
    • 如:rgb_led_red 改為 rgb_led_red-gpios
  • &gpio1:GPIO組1。
  • 4:第 4 號引腳。
  • GPIO_ACTIVE_LOW:低電平有效。

11.3 GPIO 子系統 API 說明

GPIO 子系統有兩套API

  • 基於描述符(descriptor-based)
    • 字首為 gpiod_
    • 使用 gpio_desc 幾個題來表示一個引腳。
    • 參考文件:Documentation/gpio/consumer.txt
  • 老介面(legacy)
    • 字首為 gpio_
    • 使用一個整數來表示一個引腳。
    • 參考文件:Documentation/gpio/gpio-legacy.txt

11.3.1 驅動操作一個引腳的步驟

  1. get 引腳。
  2. 設定引腳方向。
  3. 讀、寫引腳。

11.3.2 API 所需標頭檔案

#include <linux/gpio/consumer.h>   // 基於描述符

#include <linux/gpio.h>            // 老介面 

11.3.3 主要結構體

gpio_desc:

struct gpio_desc 
{
    struct gpio_chip  *chip;  /* 這個 gpio pin 所在的 chip */
    unsigned long  flags;    /*  設定 is_out flag */
    const char  *label;        /* label 就是名字 */
};
  • 原始碼路徑:核心原始碼\drivers\gpio\gpiolib.h
struct gpio_descs 
{
    unsigned int ndescs; // gpio_desc 個數
    struct gpio_desc *desc[]; // gpio_desc
}
  • 原始碼路徑:核心原始碼\drivers\gpio\gpiolib.h

11.4 新舊版互相相容轉換 API

舊版API是使用整數標記引腳的;
新版API是使用字元標記引腳的。
但是引腳都是唯一的,所以兩者可以相互轉化。

轉化函式:

  • desc_to_gpio()
  • gpio_to_desc()

desc_to_gpio

  • 函式原型:int desc_to_gpio(const struct gpio_desc *desc)
    • 原始碼路徑:drivers\gpio\gpiolib.c
    • 通過引腳 gpio_desc 結構體指標 獲取引腳 GPIO 號。
    • gpio:GPIO number。

gpio_to_desc

  • 函式原型:struct gpio_desc *gpio_to_desc(unsigned gpio)
    • 原始碼路徑:drivers\gpio\gpiolib.c
    • 通過引腳 GPIO 號獲取引腳 gpio_desc 結構體指標。
    • gpio:GPIO number。

11.5 descriptor-based 版常用 API

11.5.1 獲取 GPIO

gpiod_get

  • 函式原型:struct gpio_desc *gpiod_get(struct device *dev, const char *con_id,enum gpiod_flags flags)
    • 原始碼路徑:drivers\gpio\gpiolib.c
    • 獲取 dev 裝置,con_id 的第 0 個引腳資訊,並做 flags 初始化。
    • dev:裝置指標。從該裝置獲取引腳資訊。
    • con_id:引腳組名稱(不包含字首)。
      • 如引腳組名為 rgb_led_green-gpios。則 con_id = "rgb_led_green"
    • flags:初始化標誌。
      • GPIOD_ASIS or 0 to not initialize the GPIO at all. The direction must be set later with one of the dedicated functions.
      • GPIOD_IN to initialize the GPIO as input.
      • GPIOD_OUT_LOW to initialize the GPIO as output with a value of 0.
      • GPIOD_OUT_HIGH to initialize the GPIO as output with a value of 1.
      • GPIOD_OUT_LOW_OPEN_DRAIN same as GPIOD_OUT_LOW but also enforce the line to be electrically used with open drain.
      • GPIOD_OUT_HIGH_OPEN_DRAIN same as GPIOD_OUT_HIGH but also enforce the line to be electrically used with open drain.
    • 返回:
      • 成功:gpio_desc 結構體指標。
      • 失敗:-ENOENT。具體可通過 IS_ERR() 獲取返回碼。

gpiod_get_index

  • 函式原型:struct gpio_desc *gpiod_get_index(struct device *dev, const char *con_id, unsigned int idx, enum gpiod_flags flags)
    • 原始碼路徑:drivers\gpio\gpiolib.c
    • 獲取 dev 裝置,con_id 的第 idx 個引腳資訊,並做 flags 初始化。

gpiod_get_array

  • 函式原型:struct gpio_descs *__must_check gpiod_get_array(struct device *dev, const char *con_id, enum gpiod_flags flags)
    • 原始碼路徑:drivers\gpio\gpiolib.c
    • 獲取 dev 裝置 con_id 的所有引腳資訊,並做 flags 初始化。

其它獲取GPIO的函式

  • gpiod_get_optional:和 gpiod_get 差不多。不同的是該函式返回 gpio_desc 結構體指標NULL
  • gpiod_get_index_optional
  • gpiod_get_index_optional
  • devm_xxx:以上函式均可新增 devm_ 字首。比以上函式多了繫結裝置,裝置被刪除時,自動釋放引腳。

11.5.2 釋放 GPIO

gpiod_put

  • 函式原型:void gpiod_put(struct gpio_desc *desc)
    • 原始碼路徑:drivers\gpio\gpiolib.c
    • 釋放 desc 引腳。
    • desc:gpio_desc 結構體指標。

其它釋放GPIO的函式

  • gpiod_put_array
  • devm_gpiod_put
  • devm_gpiod_put_array

11.5.3 設定/獲取 GPIO 方向

gpiod_direction_input

  • 函式原型:int gpiod_direction_input(struct gpio_desc *desc)
    • 原始碼路徑:drivers\gpio\gpiolib.c
    • 設定該引腳為輸入方向。

gpiod_direction_output

  • 函式原型:int gpiod_direction_output(struct gpio_desc *desc, int value)
    • 原始碼路徑:drivers\gpio\gpiolib.c
    • 設定該引腳為輸入方向。
    • value:設定方向後的初始值。

gpiod_get_direction

  • 函式原型:int gpiod_get_direction(struct gpio_desc *desc)
    • 原始碼路徑:drivers\gpio\gpiolib.c
    • 獲取引腳方向。
    • 返回:
      • 0:輸出。
      • 1:輸入。

11.5.4 匯出 GPIO 到 sysfs

gpio 通過 sys 檔案系統匯出,應用層可以通過檔案操作gpio。如檢視狀態、設定狀態等等。

主要用於除錯

匯出後訪問路徑:/sys/class/gpio 下。

gpiod_export

  • 函式原型:int gpiod_direction_input(struct gpio_desc *desc)
    • 原始碼路徑:drivers\gpio\gpiolib-sysfs.c
    • 把該 gpio 匯出到 sys。

gpiod_unexport

  • 函式原型:void gpiod_unexport(struct gpio_desc *desc)
    • 原始碼路徑:drivers\gpio\gpiolib-sysfs.c
    • 取消匯出。

11.5.5 設定/獲取 GPIO 值

有兩種訪問方式:

  1. 原子方式。
  2. 佇列方式。

gpiod_get_value

  • 函式原型:int gpiod_get_value(const struct gpio_desc *desc)
    • 原始碼路徑:drivers\gpio\gpiolib.c
    • 獲取引腳值。
    • 返回:
      • 成功:非負數:zero for low, nonzero for high。
      • 失敗:負數。

gpiod_set_value

  • 函式原型:int gpiod_set_value(struct gpio_desc *desc, int value)
    • 原始碼路徑:drivers\gpio\gpiolib.c

以上兩種函式均屬原子操作,能作用於中斷程式
以下兩種函式,在佇列中等待訪問引腳,可能會進入睡眠,不能作用於中斷

訪問必須通過訊息匯流排比如I2C或者SPI,這些需要在佇列中訪問。

gpiod_get_value_cansleep

  • 函式原型:int gpiod_get_value_cansleep(const struct gpio_desc *desc)

gpiod_set_value_cansleep

  • 函式原型:void gpiod_set_value_cansleep(struct gpio_desc *desc, int value)

可以使用 gpiod_cansleep() 函式分辨該引腳是否需要通過訊息匯流排訪問

gpiod_cansleep

  • 函式原型:int gpiod_cansleep(const struct gpio_desc *desc)

11.5.6 GPIO IRQ

gpiod_to_irq

  • 函式原型:int gpiod_to_irq(const struct gpio_desc *desc)
    • 原始碼路徑:drivers\gpio\gpiolib.c
    • 獲取該引腳對應的 IRQ number
    • 返回:
      • 成功:中斷號。
      • 失敗:負數。

request_irq

  • 函式原型:static inline int __must_check request_irq(unsigned int irq, irq_handler_t handler, unsigned long flags, const char *name, void *dev)
    • 原始碼路徑:drivers\gpio\gpiolib.c
    • 請求中斷。
    • irq:中斷號。
    • handler:中斷回撥函式。
    • flags:中斷型別。
    • name:請求中斷的裝置名稱。
    • dev:可取任意值。
      • 但必須唯一能夠代表發出中斷請求的裝置。
      • 通常取描述該裝置的結構體,或NULL。
      • 用於共享中斷時。(若中斷被共享,則不能為 NULL

11.5.7 GPIO 邏輯電平與物理電平

當裝置採用低電平有效時,即是低電平為邏輯 1,高電平為邏輯 0

raw-value:忽略 DTS 中的 ACTIVE。即是實際的物理電平。
有以下函式:

int gpiod_get_raw_value(const struct gpio_desc *desc);
void gpiod_set_raw_value(struct gpio_desc *desc, int value);
int gpiod_get_raw_value_cansleep(const struct gpio_desc *desc);
void gpiod_set_raw_value_cansleep(struct gpio_desc *desc, int value);
int gpiod_direction_output_raw(struct gpio_desc *desc, int value);

邏輯電平相關函式 與 物理電平相關函式對比

Function (example) line property physical line
gpiod_set_raw_value(desc, 0); don't care low
gpiod_set_raw_value(desc, 1); don't care high
gpiod_set_value(desc, 0); default (active high) low
gpiod_set_value(desc, 1); default (active high) high
gpiod_set_value(desc, 0); active low high
gpiod_set_value(desc, 1); active low low
gpiod_set_value(desc, 0); default (active high) low
gpiod_set_value(desc, 1); default (active high) high
gpiod_set_value(desc, 0); open drain low
gpiod_set_value(desc, 1); open drain high impedance
gpiod_set_value(desc, 0); open source high impedance
gpiod_set_value(desc, 1); open source high

11.6 legacy 版常用 API

儘管建議升級使用新版API,但是現在很多子系統依然使用舊版API。
所以,本筆記也記錄舊版API。

11.6.1 判斷 GPIO number

gpio_is_valid

  • 函式原型:static inline bool gpio_is_valid(int number)
    • 原始碼路徑:include\asm-generic\gpio.h
    • 判斷 GPIO number 是否有效。
    • 返回:
      • 有效:1。
      • 無效:0。

11.6.2 申請 GPIO

gpio_request

  • 函式原型:int gpio_request(unsigned gpio, const char *label)

    • 原始碼路徑:drivers\gpio\gpiolib-legacy.c
    • 申請 GPIO。
    • gpio:需要申請的 GPIO 編號。
    • label:引腳名字,相當於為申請到的引腳取個別名。
    • 返回:
      • 成功:0。
      • 失敗:負數。

    其它請求函式

    • gpio_request_one()
    • gpio_request_array()

11.6.3 釋放 GPIO

gpio_free

  • 函式原型:static inline void gpio_free(unsigned gpio);

    • 原始碼路徑:drivers\gpio\gpiolib-legacy.c
    • 申請 GPIO。
    • gpio:需要釋放的 GPIO 編號。

    其它釋放函式

    • gpio_free_array()

11.6.3 設定 GPIO 方向

gpio_direction_input

  • 函式原型:static inline int gpio_direction_input(unsigned gpio)
    • 原始碼路徑:include\linux\gpio.h
    • 把 gpio 引腳設定為為輸入方向。
    • gpio:GPIO 編號。
    • 返回:
      • 成功:0。
      • 失敗:負數。

gpio_direction_output

  • 函式原型:static inline int gpio_direction_output(unsigned gpio, int value)
    • 原始碼路徑:include\linux\gpio.h
    • 把 gpio 引腳設定為為輸出方向。
    • gpio:GPIO 編號。
    • value:初始值。
    • 返回:
      • 成功:0。
      • 失敗:負數。

11.6.4 匯出 GPIO 到 sysfs

gpio 通過 sys 檔案系統匯出,應用層可以通過檔案操作gpio。如檢視狀態、設定狀態等等。
主要用於除錯

匯出後訪問路徑:/sys/class/gpio 下。

gpio_export

  • 函式原型:static inline int gpio_export(unsigned gpio, bool direction_may_change)
    • 原始碼路徑:include\linux\gpio.h
    • 把該 gpio 匯出到 sys。
    • gpio:GPIO number。
    • direction_may_change:表示使用者是否可以改變方向。

gpio_unexport

  • 函式原型:static inline void gpio_unexport(unsigned gpio)
    • 原始碼路徑:include\linux\gpio.h
    • 取消匯出。
    • gpio:GPIO number。

11.6.5 設定/獲取 GPIO 值

有兩種訪問方式:

  1. 原子方式。
  2. 佇列方式。

gpio_get_value

  • 函式原型:static inline int gpio_get_value(unsigned gpio)
    • 原始碼路徑:include\asm-generic\gpio.h
    • 獲取引腳值。
    • 返回:
      • 成功:非負數:zero for low, nonzero for high。
      • 失敗:負數。

gpio_set_value

  • 函式原型:static inline void gpio_set_value(unsigned int gpio, int value)

    • 原始碼路徑:include\asm-generic\gpio.h

    其它設定函式

    • gpio_set_debounce():支援消抖。

以上兩種函式均屬原子操作,能作用於中斷程式
以下兩種函式,在佇列中等待訪問引腳,可能會進入睡眠,不能作用於中斷

訪問必須通過訊息匯流排比如I2C或者SPI,這些需要在佇列中訪問。

gpio_get_value_cansleep

  • 函式原型:int gpio_get_value_cansleep(unsigned gpio)

gpio_set_value_cansleep

  • 函式原型:void gpio_set_value_cansleep(unsigned gpio, int value)

可以使用 gpiod_cansleep() 函式分辨該引腳是否需要通過訊息匯流排訪問

gpio_cansleep

  • 函式原型:int gpiod_cansleep(unsigned gpio)

11.6.6 GPIO IRQ

gpio_to_irq

  • 函式原型:int gpio_to_irq(unsigned gpio)
    • 原始碼路徑:drivers\gpio\gpiolib.c
    • 獲取該引腳對應的 IRQ number
    • 返回:
      • 成功:中斷號。
      • 失敗:負數。

irq_to_gpio:(儘量避免使用

  • 函式原型:int irq_to_gpio(unsigned irq)
    • 原始碼路徑:drivers\gpio\gpiolib.c
    • 獲取該引腳對應的 GPIO number
    • 返回:
      • 成功:gpio號。
      • 失敗:負數。

request_irq

  • 函式原型:static inline int __must_check request_irq(unsigned int irq, irq_handler_t handler, unsigned long flags, const char *name, void *dev)
    • 原始碼路徑:drivers\gpio\gpiolib.c
    • 請求中斷。
    • irq:中斷號。
    • handler:中斷回撥函式。
    • flags:中斷型別。
    • name:請求中斷的裝置名稱。
    • dev:可取任意值。
      • 但必須唯一能夠代表發出中斷請求的裝置。
      • 通常取描述該裝置的結構體,或NULL。
      • 用於共享中斷時。(若中斷被共享,則不能為 NULL

相關文章