前言
參考文件:
- 核心文件連結:https://www.kernel.org/doc/Documentation/
- 核心原始碼doc:Documentation/devicetree/bindings/pinctrl/pinctrl-bindings.txt
- 如在開發過程中遇到問題,建議可以查詢核心原始碼&核心文件。
建議:複製以下連結,到原文觀看,原文排版清晰,便於學習。
11. gpio子系統
引腳配置為 GPIO 模式後,便可使用 GPIO子系統 來控制引腳。
可以通過 pinctrl子系統 配置,也可以自己程式設計配置。
參考文件:Documentation/devicetree/bindings/gpio/ 下對應晶片廠商的檔案。
11.1 操作步驟
- 在裝置樹對應節點中指定引腳。(哪一組、組裡哪一個引腳)
- 在驅動程式中通過 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 驅動操作一個引腳的步驟
- get 引腳。
- 設定引腳方向。
- 讀、寫引腳。
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 值
有兩種訪問方式:
- 原子方式。
- 佇列方式。
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 值
有兩種訪問方式:
- 原子方式。
- 佇列方式。
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)