前言
參考文檔:
- 內核文檔鏈接: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)
