reference:
- https://blog.csdn.net/shiyongyue/article/details/75103446
- http://blog.rongpmcu.com/gpiozi-xi-tong-he-pinctrlzi-xi-tong/
- https://www.cnblogs.com/Cqlismy/p/11891789.html
- https://www.cnblogs.com/hellokitty2/p/12500546.html
- https://blog.csdn.net/ccwzhu/article/details/103079297
- pinctrl:https://www.cnblogs.com/hellokitty2/p/12501493.html
內核相關文檔
Documentation\devicetree\bindings\pinctrl\pinctrl-bindings.txt
Documentation\gpio\gpio.txt
Documentation\devicetree\bindings\gpio\gpio.txt
背景
隨着內核的發展,linux驅動框架在不斷的變化。在早期,GPIO子系統存在之前,我們驅動需要在代碼中配置寄存器來使用GPIO引腳。
此后,出現了gpio子系統,后來又出現了pinctrl子系統。
有些平台的實現沒有使用內核提供的pinctrl子系統,而是繼續采用在內核提供pinctrl子系統前自己實現的那套機制來pinmux操作,如Ti的omap平台;
有些平台則基於pinctrl子系統來實現pinmux、pinconf的控制。
介紹
GPIO子系統可以說是Linux中最簡單的子系統。
- GPIO(General Purpose Input Output):負責管理整個系統各gpio輸入輸出管腳的使用情況,同時通過sys文件系統導出了調試信息和應用層控制接口。
- Pinctrl(Pin Control):負責管理SOC中各pin的狀態,比如輸出電流能力、是否有內部上拉或者下拉,是否有功能復用等參數。
要想操作GPIO引腳,需要先把所用引腳配置成GPIO功能,這個通過pinctrl
子系統來實現。然后可以根據設置的引腳的方向來讀取引腳的值和設置輸出值。
在BSP工程師實現好GPIO子系統后,我們就可以在設備樹中指定GPIO引腳,在驅動中使用GPIO子系統的標准函數來獲取GPIO、設置GPIO方向、讀取/設置GPIO的值。這樣的驅動代碼是於單板無關的。
gpio子系統
gpio子系統內部實現主要提供了兩類接口:
-
一類給bsp工程師,用於注冊gpio chip(也就是所謂的gpio控制器驅動)
-
另一部分給驅動工程師使用,為驅動工程師屏蔽了不同gpio chip之間的區別,驅動工程師調用的api的最終操作流程會導向gpio對應的gpio chip的控制代碼,也就是bsp的代碼。
核心實現
gpio子系統的實現源碼在drivers/gpio
文件夾下,主要文件有:
在安卓系統中,實現源碼在
kernel/drivers/gpio
文件 | 作用 |
---|---|
devres.c | 針對gpio api增加的devres機制的支持 |
gpiolib.c | gpio子系統的核心實現 |
gpiolib-of.c | 對設備樹的支持 |
gpiolib-acpi.c | 和acpi相關,不分析 |
gpio-xxx.c | 根據平台的不同,所對應的gpio控制 |
gpio子系統提供了兩層接口,一層給上層驅動工程師調用,一層給下層bsp工程師調用。
上層使用前,當然先得bsp工程師完成對應的動作。
GPIO子系統有兩套接口:
-
一是基於描述符(
descriptor-based
)的,相關api函數都是以"gpiod_
"為前綴,它使用gpio_desc
結構來表示一個引腳。 -
另一種是老(
legency
)的,相關api函數都是以"gpio_
"為前綴,它使用一個整數來表示一個引腳,強烈建議不要使用legacy的接口函數。
其實,
legacy gpio
大部分api就是基於描述符api來實現的,我們可以看到很多legacy api
內部的實現調用了to_desc
。
// 1.獲取GPIO
gpiod_get;
gpiod_get_index;
gpiod_get_array;
devm_gpiod_get;
devm_gpiod_get_index;
devm_gpiod_get_array;
// 2.設置方向
gpiod_direction_input;
gpiod_direction_output;
// 3.讀值、寫值
gpiod_get_value;
gpiod_set_value;
// 4. 設為中斷(如果必要)
request_irq(gpiod_to_irq(gpio_desc)...); //將gpio轉為對應的irq,然后注冊該irq的中斷handler
// 5.釋放GPIO
gpiod_put;
gpiod_put_array;
devm_gpiod_put;
devm_gpiod_put_array;
前綴為"
devm_
"的含義是設備資源管理,這是一種自動釋放資源的機制。思想:“資源是屬於設備的,設備不存在時資源就可以自動釋放”。
背景:在Linux驅動開發過程中,先申請了GPIO,再申請內存,如果內存申請失敗,那么在返回之前就需要先釋放GPIO資源。如果使用的是devm相關函數,在內存申請失敗時可以直接返回,設備的銷毀函數會自動地釋放已經申請了的GPIO資源。
因此,建議使用devm相關函數操作GPIO。
gpio控制api( descriptor)
使用基於描述符的接口時,GPIO被作為一個描述符來使用。
#include <linux/gpio/consumer.h>
// 更多相關的說明可以參考 Documentation/gpio/consumer.txt
獲取一個或一組GPIO
struct gpio_desc * gpiod_get(struct device *dev,
const char *con_id,
enum gpiod_flags flags);
/*
在允許GPIO不存在時,可以使用gpiod_get_optional()和gpiod_get_index_optional()函數。
這兩個函數在沒有成功分配到GPIO的時候返回NULL而不是-ENOENT。
*/
struct gpio_desc * gpiod_get_optional(struct device *dev,
const char *con_id,
enum gpiod_flags flags);
struct gpio_descs {
unsigned int ndescs; // 數量
struct gpio_desc *desc[]; // 每一個 desc 的情況
}
// 返回gpio_descs 注意:不是 gpio_desc
struct gpio_descs * gpiod_get_array(struct device *dev,
const char *con_id,
enum gpiod_flags flags);
/*多個Pin時需要附帶index參數。*/
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_index_optional(struct device *dev,
const char *con_id,
unsigned int index,
enum gpiod_flags flags);
struct gpio_desc * devm_gpiod_get(struct device *dev, const char *con_id,
enum gpiod_flags flags);
struct gpio_desc * devm_gpiod_get_index(struct device *dev,
const char *con_id,
unsigned int idx,
enum gpiod_flags flags);
描述:必須通過調用gpiod_get()
函數族來獲取對應的描述符。
參數解析:
- con_id:字符串類型,即GPIO的名字;
一般需要查看設備樹中的定義。除此之外,我們還可以在設備樹文件里添加參數(
GPIO_ACTIVE_LOW
、GPIO_OPEN_DRAIN
、GPIO_OPEN_SOURCE
)來觸發該接口內部設置gpio,具體的參數格式和具體的gpio chip driver有關,一般可以在Documentation/devicetree/bindings/gpio
里找到對應平台的方法。
有關DeviceTree情況中con_id參數的更詳細說明請參閱Documentation/gpio/board.txt
例如:
在SD卡驅動看到的去查找名字為cd-gpios
的gpio:
// simple.c:
ctx->cd_gpio = devm_gpiod_get_optional(dev, "cd", 0);
在使用SD卡驅動的主dts就有cd pin的定義:
// xxx.dts:
cd-gpios = <&gpio2 12 GPIO_ACTIVE_LOW>;
- index:邏輯下標。將一個GPIO設備(DESC)下的多個Pin看成一個數組,此時index是數組成員下標。
內核文檔有個例子,比如gpio
如下定義:
led-gpios = <&gpio 15 GPIO_ACTIVE_HIGH>, /* red */
<&gpio 16 GPIO_ACTIVE_HIGH>, /* green */
<&gpio 17 GPIO_ACTIVE_HIGH>; /* blue */
如果index是0,那么對應的就是gpio 15;如果index是1,那么對應就是gpio 16,以此類推。
- flags:用於可選地指定GPIO的方向和初始值,它的值可以是:
GPIOD_ASIS
或0表示根本不初始化GPIO。需要隨后使用專門的函數設置方向GPIOD_IN
初始化GPIO作為輸入。GPIOD_OUT_LOW
將GPIO初始化為輸出,值為0。GPIOD_OUT_HIGH
將GPIO初始化為輸出,值為1。GPIOD_OUT_LOW_OPEN_DRAIN
:與GPIOD_OUT_LOW相同,但強制以開漏的方式使用GPIOD_OUT_HIGH_OPEN_DRAIN
:與GPIOD_OUT_HIGH相同,但強制以開漏的方式使用
最后兩個標志用於必須開漏方式的情況,比如GPIO被用作I2C時,如果該GPIO尚未在映射(參見board.txt)中被配置為開漏方式,將被強制配置為開漏方式並給出WARNING。
這兩個函數都返回有效的GPIO描述符或可被IS_ERR()檢查的錯誤代碼(它們永遠不會返回NULL指針)。
返回值:成功返回一個GPIO描述符;失敗返回錯誤編碼,可以使用IS_ERR()
進行檢查錯誤原因。
- 返回-ENOENT只會發生在當且僅當沒有為設備/功能/索引三元組成功分配GPIO的時候。
- 其他錯誤代碼用於已成功分配GPIO,但在試圖獲得它的時候發生了錯誤的情況:這可以用於區分錯誤原因是可選GPIO參數錯誤還是GPIO缺失這兩種情況。
釋放
void gpiod_put(struct gpio_desc *desc);
void gpiod_put_array(struct gpio_descs *descs); // 對應 get*array
void devm_gpiod_put(struct device *dev, struct gpio_desc *desc);
void devm_gpiod_put_array(struct device *dev, struct gpio_descs *descs);
描述:釋放之前通過get獲取的GPIO描述符
注意:在釋放之后,嚴格禁止使用被釋放的描述符;也不允許在使用gpiod_get_array()
獲取的數組中單獨使用gpiod_put()
釋放描述符。
設置方向
注意:GPIO沒有默認方向。因此,使用GPIO前必須首先設置其方向,否則將導致未定義的行為!
// 設置GPIO為輸入還是輸出
int gpiod_direction_input(struct gpio_desc *desc);
int gpiod_direction_output(struct gpio_desc *desc, int value);
// 查詢GPIO的當前方向:返回0表示輸出,1表示輸入,或錯誤代碼(如果出錯)
int gpiod_get_direction(const struct gpio_desc *desc);
描述:使用設備驅動必須首先確定GPIO的方向。如果在調用gpiod_get* ()
時,flag指定了nodirection
,就可以調用上面的某個gpiod_direction_*()
函數來設置方向:
參數解析:
- value:對於輸出GPIO,提供的值將成為初始輸出值;用於避免系統啟動期間的信號故障。
返回值:成功返回值為零,否則返回值為負的錯誤代碼。
該返回值應該被檢查,因為之后獲取/設置GPIO引腳值
get/set
調用不會返回錯誤,所以錯誤的配置是有可能的。您通常應該在任務上下文進行這些調用。但是,對於自旋鎖安全(Spinlock-Safe)的GPIO,可以作為板級設置初期的一部分,在啟用任務之前使用它們。
使用單個GPIO
/*
Spinlock-Safe的GPIO訪問
意義:如果操作GPIO可能導致sleep,那么同步機制不能采用spinlock,因為spinlock要求不能sleep
*/
// 讀取輸出引腳的值時,返回的值應該是引腳上的值。由於包括開漏信號和輸出延遲在內的問題,它並不總是匹配指定的輸出值。
int gpiod_get_value(const struct gpio_desc *desc);
void gpiod_set_value(struct gpio_desc *desc, int value);
描述:大多數GPIO控制器可通過存儲器讀/寫指令訪問。在不能睡眠的環境下調用。
不能睡眠的環境:內部hard(非線程的)IRQ handler、類似的上下文中完成的操作(即原子操作中)。
參數解析:
- value:布爾值,零為低,非零為高。
返回值:get/set調用不會返回錯誤,因為“無效的GPIO”應該在這之前就從gpiod_direction_*()
中得知。
但請注意,並非所有平台都可以讀取輸出引腳的值;對於那些不能讀取的平台,函數永遠返回零。另外,使用這些函數訪問需要睡眠才能安全訪問的GPIO(見下文)是錯誤的操作。
/* 允許睡眠的GPIO訪問 */
// 判斷是否允許睡眠:返回非零 代表 可以睡眠:
int gpiod_cansleep(const struct gpio_desc *desc);
// 獲取、設置GPIO的值。
int gpiod_get_value_cansleep(const struct gpio_desc * desc);
void gpiod_set_value_cansleep(struct gpio_desc * desc,int value);
描述:有些GPIO控制器必須使用基於消息的總線(如I2C或SPI)訪問。讀取或寫入這些GPIO值的命令需要等待到達隊列的頭部以傳輸命令並獲得其響應。這樣就需要允許睡眠,導致這類GPIO的訪問不能在內部IRQ處理程序內(原子上下文)完成。
訪問這樣的GPIO需要一個可以休眠的上下文,例如一個threaded IRQ處理程序,並且必須使用上述訪問函數訪問函數(而不是沒有帶cansleep()
后綴的)。
除了可以睡眠,無法在hardIRQ處理程序訪問的特點以外,這些調用與Spinlock-Safe的調用相同。
使用gpio的時候需要了解一下低有效和開漏語義,見附錄。
使用一組GPIO
## 獲取值
int gpiod_get_array_value(unsigned int array_size,
struct gpio_desc **desc_array,
int *value_array);
int gpiod_get_raw_array_value(unsigned int array_size,
struct gpio_desc **desc_array,
int *value_array);
int gpiod_get_array_value_cansleep(unsigned int array_size,
struct gpio_desc **desc_array,
int *value_array);
int gpiod_get_raw_array_value_cansleep(unsigned int array_size,
struct gpio_desc **desc_array,
int *value_array);
## 設置值
void gpiod_set_array_value(unsigned int array_size,
struct gpio_desc **desc_array,
int *value_array);
void gpiod_set_raw_array_value(unsigned int array_size,
struct gpio_desc **desc_array,
int *value_array);
void gpiod_set_array_value_cansleep(unsigned int array_size,
struct gpio_desc **desc_array,
int *value_array);
void gpiod_set_raw_array_value_cansleep(unsigned int array_size,
struct gpio_desc **desc_array,
int *value_array);
描述:如果相應的芯片驅動器支持,這些函數將嘗試同時訪問屬於同一存儲體或芯片的GPIO。在這種情況下,可以預期顯著改善的性能。如果無法同時訪問,GPIO將按順序訪問。用來獲取、設置GPIO的值。
參數解析:
- array_size - 數組元素的數量
- desc_array - GPIO描述符數組,可以是任意一組GPIO
如何理解“任意”:
我們可以先使用
gpiod_get()
和gpiod_get_array()
的任意組合來獲得描述符后,放入一個我們自己構建數組中,再將其傳遞給上述函數)。同時,如果為了獲得最佳性能,屬於同一芯片的GPIO應該在描述符數組中是連續的。
- value_array - 存儲GPIO值(get)的數組或要分配給GPIO的值數組(set)
返回值:
gpiod_get_array_value()
及其變體成功時返回0,錯誤返回負數。gpiod_get_value()
在成功傳遞GPIO值時返回0或1。使用數組函數時,GPIO值存儲在value_array中,而不是作為返回值傳回。
小例子:
struct gpio_descs *my_gpio_descs = gpiod_get_array(...);
if(!my_gpio_descs)
return ERROR...
gpiod_set_array_value(my_gpio_descs->ndescs, my_gpio_descs->desc,
my_gpio_values);
配置為中斷(可選)
int gpiod_to_irq(const struct gpio_desc *desc);
描述:獲取與給定GPIO相對應的IRQ編號。
返回值:返回IRQ編號或負的errno代碼(很可能是因為該特定GPIO不能用作IRQ)。
注意:
- 使用未使用
gpiod_direction_input()
設置為輸入的GPIO,或者使用最初不是來自gpiod_to_irq()
的IRQ編號,是錯誤的操作。 - gpiod_to_irq()不允許休眠。
從gpiod_to_irq()
返回的非錯誤值可以傳遞給request_irq()
或free_irq()
。
它們通常通過特定於板的初始化代碼存儲到平台設備的IRQ資源中。
注意,IRQ觸發選項是IRQ接口的一部分,例如, IRQF_TRIGGER_FALLING,
導出到應用空間(可選)
drivers/gpio/gpiolib.c
/**
* gpiod_export - export a GPIO through sysfs
* @gpio: gpio to make available, already requested
* @direction_may_change: true if userspace may change gpio direction
* Context: arch_initcall or later
*
* When drivers want to make a GPIO accessible to userspace after they
* have requested it -- perhaps while debugging, or as part of their
* public interface -- they may use this routine. If the GPIO can
* change direction (some can't) and the caller allows it, userspace
* will see "direction" sysfs attribute which may be used to change
* the gpio's direction. A "value" attribute will always be provided.
*
* Returns zero on success, else an error.
*/
int gpiod_export(struct gpio_desc *desc, bool direction_may_change)
gpiod_export
提供了用戶層的訪問,主要用於驅動工程師調試或者應用程序控制。
描述:將該gpio的信息通過sys文件系統導出,這樣應用層可以直接查看狀態、設置狀態等。
參數解析:
- direction_may_change: 用來標記這個gpio的輸入輸出方向是否可以改變。
如果該gpio已經設置了輸入或者輸出,那么它的
direction_may_change
為false。
兼容
舊的GPIO系統使用基於標號的結構而不是基於描述符。可以使用如下兩個函數進行相互轉換:
int desc_to_gpio(const struct gpio_desc *desc);
struct gpio_desc *gpio_to_desc(unsigned gpio);
注意:不能使用一套API的方法釋放另一套API獲取的設備。
附錄:GPIO子系統其他內容
低有效和開漏語義
介紹
一般情況下,使用GPIO子系統的開發者並不需要關心GPIO對外的實際電平,因此,gpiod_set_value_xxx()
或 gpiod_set_array_value_xxx()
這樣的函數都以邏輯值操作。
這些函數會將低電平有效的性質考慮在內。也就是說,低電平有效,物理值0對應邏輯值的1。
如果我們事先告知內核某一個GPIO是低電平有效(
active_low
)這些函數內部會進行處理,就不再需要我們關心“到底是不是電平1有效還是電平0有效”,
例如,如果設置了GPIO的低電平有效屬性,並且gpiod_set_(array)_value_xxx()
傳遞了邏輯值1(“asserted”
),則物理線路電平將被驅動為低電平。
這同樣適用於開漏或開源輸出:它們並不輸出高電平(開漏)或低電平(開源),它們只是將輸出切換到高阻抗值。使用者應該不需要關注。
有關的詳細信息,請參閱driver.txt中關於開漏的細節。
總結:
函數(示例) | 線路屬性 | 物理線路 |
---|---|---|
gpiod_set_raw_value(desc, 0); | - | 低電平 |
gpiod_set_raw_value(desc, 0); | - | 高電平 |
gpiod_set_value(desc, 0); | 默認(高電平有效) | 低電平 |
gpiod_set_value(desc, 1); | 默認(高電平有效) | 高電平 |
gpiod_set_value(desc, 0); | 低電平有效 | 高電平 |
gpiod_set_value(desc, 1); | 低電平有效 | 低電平 |
gpiod_set_value(desc, 0); | 開漏 | 低電平 |
gpiod_set_value(desc, 1); | 開漏 | 高阻態 |
gpiod_set_value(desc, 0); | 開漏 | 高阻態 |
gpiod_set_value(desc, 1); | 開漏 | 高電平 |
接口
當然,如果你硬是要知道GPIO此時的電平值(的確需要管理GPIO線路物理狀態),可以使用下面的一組函數來達到你要的目的。
但應盡可能避免去讀原始值,尤其是系統無關的驅動程序,它們只需要關心邏輯值。
下面的一組調用忽略GPIO的低有效或開漏屬性,設置什么值,物理值就是什么什么值:
raw-value
的意思就是不在乎DTS里面的ACTIVE,我set 高電平,就是高電平。
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);
還可以使用以下方法查詢GPIO的低有效屬性:
int gpiod_is_active_low(const struct gpio_desc *desc);
請注意,這些函數只能在使用者明白自己在做什么的情況下使用;驅動程序一般不應該關心線路物理狀態或開漏語義。
在設備樹中設置低有效
假設我們在DTS里面這樣設置
reset-gpios = <&gpio3 RK_PA3 GPIO_ACTIVE_LOW>;
然后我們這樣調用:
gpiod_set_value_cansleep(gc5025->reset_gpio, 1);
因為DTS里面的active 狀態是 GPIO_ACTIVE_LOW,所以這個代碼輸出的是 低電平。
系統喚醒功能
這個功能與
ACPI
有關。有關詳細信息,請參閱
Documentation/acpi/gpio-properties.txt
在ACPI系統上,GPIO由設備的_CRS配置對象列出的GpioIo()/ GpioInt()
資源描述。這些資源不提供GPIO的連接ID(名稱),因此有必要為此目的使用附加機制。
符合ACPI 5.1或更新版本的系統可能可以提供_DSD
配置對象,它可以用於提供_CRS
中的GpioIo()/ GpioInt()
資源描述的特定GPIO的連接ID。如果是這種情況,它將由GPIO子系統自動處理。但是,如果不存在_DSD
,則GpioIo()/ GpioInt()
資源與GPIOconnection ID之間的映射需要由設備驅動程序提供。
附錄:legacy-api
#include <linux/gpio.h>
還有一組用於允許睡眠場景的
api
沒有給出,更多相關的說明可以參考Documentation/gpio/gpio-legacy.txt
使用流程:
- 申請、釋放:
gpio_request
、gpio_free
- 設置GPIO方向:
gpio_direction_input
、gpio_direction_output
- 獲取設置GPIO值:
gpio_get_value
、gpio_set_value
- (可選)設置為中斷:
gpio_to_irq
- (可選)導出到
sys
文件系統:gpio_export
判斷
/*
* "valid" GPIO numbers are nonnegative and may be passed to
* setup routines like gpio_request(). only some valid numbers
* can successfully be requested and used.
*
* Invalid GPIO numbers are useful for indicating no-such-GPIO in
* platform data and other tables.
*/
static inline bool gpio_is_valid(int number)
{
return number >= 0 && number < ARCH_NR_GPIOS;
}
描述:來判斷獲取到的gpio號是否是有效的,只有有效的gpio號,才能向內核中進行申請使用,因此,當我們從設備樹的設備節點獲取到gpio號,可以使用該函數進行判斷是否有效。
參數解析:
- numb:需要判斷的GPIO號。
返回值:合法為1,否則為0。
申請、釋放
/* Always use the library code for GPIO management calls,
* or when sleeping may be involved.
*/
extern int gpio_request(unsigned gpio, const char *label);
extern void gpio_free(unsigned gpio);
/**
* struct gpio - a structure describing a GPIO with configuration
* @gpio: the GPIO number
* @flags: GPIO configuration as specified by GPIOF_*
* @label: a literal description string of this GPIO
*/
struct gpio {
unsigned gpio;
unsigned long flags;
const char *label;
};
int gpio_request_one(unsigned gpio, unsigned long flags, const char *label);
int gpio_request_array(struct gpio *array, size_t num);
void gpio_free_array(const struct gpio *array, size_t num);
/* CONFIG_GPIOLIB: bindings for managed devices that want to request gpios */
int devm_gpio_request(struct device *dev, unsigned gpio, const char *label);
int devm_gpio_request_one(struct device *dev, unsigned gpio,
unsigned long flags, const char *label);
void devm_gpio_free(struct device *dev, unsigned int gpio);
以
gpio_request
為例,gpio_request_one
、gpio_request_array
是它的擴展,devm_
為前綴的是gpio devres機制的實現。
描述:請求一個/一組gpio。
參數解析:
- gpio:gpio號,可以通過sdk開發包說明文檔查看,或者查看設備樹文件,也可以在
gpio chip
驅動的實現中找到。 - flags:可以指定
GPIOF_OPEN_DRAIN
、GPIOF_OPEN_SOURCE
、GPIOF_DIR_IN
、GPIOF_EXPORT
等標志- 如果指定了
GPIOF_DIR_IN
,那么后面就不需要自己再額外調用gpio_direction_input
或者gpio_direction_output
了, - 如果指定了
GPIOF_EXPORT
,后面就不需要自己調用gpio_export
了。
- 如果指定了
- label:向系統中申請GPIO使用的標簽,類似於GPIO的名稱
- array,num:是
gpio_request_array
對gpio_request_one
的封裝,用於處理同時申請多個gpio的情形。 - dev:帶有
devm_
前綴,用於帶設備資源管理版本的函數,因此在使用上面的函數時,需要指定設備的struct device指針,生命周期與設備相同。
返回值:成功返回0。
意義:gpio_request
主要做了以下動作:
- 檢查是否已經被申請,沒有的話,標記為已申請
- 填充label到該pin數據結構,用於debug
- 如果chip driver提供了request回調,調用它
- 如果chip driver提供了get_direction回調,調用它,通過它更新pin數據結構,標明gpio方向
用法舉例:
static struct gpio leds_gpios[] = {
{ 32, GPIOF_OUT_INIT_HIGH, "Power LED" }, /* default to ON */
{ 33, GPIOF_OUT_INIT_LOW, "Green LED" }, /* default to OFF */
{ 34, GPIOF_OUT_INIT_LOW, "Red LED" }, /* default to OFF */
{ 35, GPIOF_OUT_INIT_LOW, "Blue LED" }, /* default to OFF */
{ ... },
};
err = gpio_request_one(31, GPIOF_IN, "Reset Button");
if (err)
...;
err = gpio_request_array(leds_gpios, ARRAY_SIZE(leds_gpios));
if (err)
...;
gpio_free_array(leds_gpios, ARRAY_SIZE(leds_gpios));
設置方向
//設置gpio方向為輸入/輸出
gpio_direction_input 或者gpio_direction_output ---------<2> ;
static inline int gpio_direction_input(unsigned gpio)
{
return gpiod_direction_input(gpio_to_desc(gpio));
}
static inline int gpio_direction_output(unsigned gpio, int value)
{
return gpiod_direction_output_raw(gpio_to_desc(gpio), value);
}
描述:當我們使用gpio_request()
函數族向系統中申請了GPIO資源后,可以使用上面的函數進行GPIO的方向設置:
- 函數
gpio_direction_input()
用來設置GPIO的方向為輸入 - 函數
gpio_direction_output()
用來設置GPIO的方向為輸出,並且通過value
值可以設置輸出的電平。
意義: gpio_direction_input
或者gpio_direction_output
主要是回調gpio chip driver提供的direction_input
或者direction_output
來設置該gpio寄存器為輸入、輸出。
導出
// include/asm-generic/gpio.h
/*
* A sysfs interface can be exported by individual drivers if they want,
* but more typically is configured entirely from userspace.
*/
static inline int gpio_export(unsigned gpio, bool direction_may_change)
{
return gpiod_export(gpio_to_desc(gpio), direction_may_change);
}
gpio_export
提供了用戶層的訪問,主要用於驅動工程師調試或者應用程序控制。
描述:將該gpio的信息通過sys文件系統導出,這樣應用層可以直接查看狀態、設置狀態等。
參數解析:
- direction_may_change: 用來標記這個gpio的輸入輸出方向是否可以改變。
如果該gpio已經設置了輸入或者輸出,那么它的
direction_may_change
為false。
使用
static inline int gpio_get_value(unsigned int gpio)
{
return __gpio_get_value(gpio);
}
static inline void gpio_set_value(unsigned int gpio, int value)
{
__gpio_set_value(gpio, value);
}
描述:當我們將GPIO的方向設置為輸入時,可以使用上面的函數gpio_get_value()
來獲取當前的IO口電平值,當GPIO的方向設置為輸出時,使用函數gpio_set_value()
可以設置IO口的電平值。
申請中斷
static inline int gpio_to_irq(unsigned int gpio)
{
return __gpio_to_irq(gpio);
}
描述:用於獲取該gpio對應的中斷號,這個需要設備樹里的該gpio節點描述使用哪個中斷號
並不是所有的gpio都可以觸發中斷的。
意義:回調gpio chip driver
提供的to_irq
。
例子
設備樹(高通msm平台):
dev_gpio {
status = "okay";
compatible = "dev-gpio";
label = "test_gpio";
gpios = <&msm_gpio 68 0>;
};
驅動程序:
#include <linux/module.h>
#include <linux/init.h>
#include <linux/of.h>
#include <linux/of_gpio.h>
#include <linux/platform_device.h>
#include <linux/kernel.h>
#include <linux/slab.h>
#include <linux/errno.h>
#include <linux/gpio.h>
#include <linux/sysfs.h>
struct gpio_platform_data {
const char *label;
unsigned int gpio_num;
enum of_gpio_flags gpio_flag;
};
struct gpio_drvdata {
struct gpio_platform_data *pdata;
bool gpio_state;
};
static ssize_t ctrl_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct gpio_drvdata *ddata = dev_get_drvdata(dev);
int ret;
if (ddata->gpio_state)
ret = snprintf(buf, PAGE_SIZE - 2, "%s", "enable");
else
ret = snprintf(buf, PAGE_SIZE - 2, "%s", "disable");
buf[ret++] = '\n';
buf[ret] = '\0';
return ret;
}
static ssize_t ctrl_store(struct device *dev,
struct device_attribute *attr, const char *buf, size_t count)
{
struct gpio_drvdata *ddata = dev_get_drvdata(dev);
bool state = ddata->gpio_state;
if (!strncmp(buf, "enable", strlen("enable"))) {
if (!state) {
gpio_set_value(ddata->pdata->gpio_num, !state);
ddata->gpio_state = !state;
goto ret;
}
} else if (!strncmp(buf, "disable", strlen("disable"))) {
if (state) {
gpio_set_value(ddata->pdata->gpio_num, !state);
ddata->gpio_state = !state;
goto ret;
}
}
return 0;
ret:
return strlen(buf);
}
static DEVICE_ATTR(ctrl, 0644, ctrl_show, ctrl_store);
static ssize_t gpio_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct gpio_drvdata *ddata = dev_get_drvdata(dev);
int ret;
ret = snprintf(buf, PAGE_SIZE - 2, "gpio-number: GPIO_%d",
ddata->pdata->gpio_num - 911);
buf[ret++] = '\n';
buf[ret] = '\0';
return ret;
}
static DEVICE_ATTR(gpio, 0444, gpio_show, NULL);
static struct attribute *gpio_attrs[] = {
&dev_attr_ctrl.attr,
&dev_attr_gpio.attr,
NULL
};
static struct attribute_group attr_grp = {
.attrs = gpio_attrs,
};
static struct gpio_platform_data *
gpio_parse_dt(struct device *dev)
{
int ret;
struct device_node *np = dev->of_node;
struct gpio_platform_data *pdata;
pdata = kzalloc(sizeof(*pdata), GFP_KERNEL);
if (!pdata) {
dev_err(dev, "failed to alloc memory of platform data\n");
return NULL;
}
ret = of_property_read_string(np, "label", &pdata->label);
if (ret) {
dev_err(dev, "failed to read property of lable\n");
goto fail;
}
pdata->gpio_num = of_get_named_gpio_flags(np, "gpios",
0, &pdata->gpio_flag);
if (pdata->gpio_num < 0) {
dev_err(dev, "invalid gpio number %d\n", pdata->gpio_num);
ret = pdata->gpio_num;
goto fail;
}
return pdata;
fail:
kfree(pdata);
return ERR_PTR(ret);
}
static int gpio_probe(struct platform_device *pdev)
{
struct gpio_drvdata *ddata;
struct gpio_platform_data *pdata;
struct device *dev = &pdev->dev;
struct device_node *np = dev->of_node;
int ret;
printk("[%s]==========gpio_probe start==========\n", __func__);
if (!np) {
dev_err(dev, "failed to find device node of gpio device\n");
return -ENODEV;
}
ddata = kzalloc(sizeof(*ddata), GFP_KERNEL);
if (!ddata) {
dev_err(dev, "failed to alloc memory for driver data\n");
return -ENOMEM;
}
pdata = gpio_parse_dt(dev);
if (IS_ERR(pdata)) {
dev_err(dev, "failed to parse device node\n");
ret = PTR_ERR(pdata);
goto fail1;
}
/* gpio初始化 */
if (gpio_is_valid(pdata->gpio_num)) {
/* 申請gpio資源 */
ret = gpio_request(pdata->gpio_num, pdata->label);
if (ret) {
dev_err(dev, "failed to request gpio number %d\n",
pdata->gpio_num);
goto fail2;
}
/* 設置gpio的方向(輸出) */
ret = gpio_direction_output(pdata->gpio_num, 0);
if (ret) {
dev_err(dev, "failed to set gpio direction for output\n");
goto fail3;
}
/* 在sysfs中導出gpio(方向不能改變) */
ret = gpio_export(pdata->gpio_num, false);
if (ret) {
dev_err(dev, "failed to export gpio %d\n", pdata->gpio_num);
goto fail3;
}
/* 設置gpio電平值(高電平) */
gpio_set_value(pdata->gpio_num, 1);
}
ddata->gpio_state = false;
ddata->pdata = pdata;
platform_set_drvdata(pdev, ddata);
ret = sysfs_create_group(&dev->kobj, &attr_grp);
if (ret) {
dev_err(dev, "failed to create sysfs files\n");
goto fail3;
}
printk("[%s]==========gpio_probe over==========\n", __func__);
return 0;
fail3:
gpio_free(pdata->gpio_num);
fail2:
kfree(pdata);
fail1:
kfree(ddata);
return ret;
}
static int gpio_remove(struct platform_device *pdev)
{
struct gpio_drvdata *ddata = platform_get_drvdata(pdev);
struct gpio_platform_data *pdata = ddata->pdata;
sysfs_remove_group(&pdev->dev.kobj, &attr_grp);
/* 釋放已經申請的gpio資源 */
if (gpio_is_valid(pdata->gpio_num))
gpio_free(pdata->gpio_num);
kfree(pdata);
pdata = NULL;
kfree(ddata);
ddata = NULL;
return 0;
}
static struct of_device_id device_match_table[] = {
{ .compatible = "dev-gpio",},
{ },
};
MODULE_DEVICE_TABLE(of, device_match_table);
static struct platform_driver dev_gpio_driver = {
.probe = gpio_probe,
.remove = gpio_remove,
.driver = {
.name = "dev-gpio",
.owner = THIS_MODULE,
.of_match_table = device_match_table,
},
};
module_platform_driver(dev_gpio_driver);
MODULE_AUTHOR("HLY");
MODULE_LICENSE("GPL v2");
當驅動模塊加載的時候,由於嵌入了platform_driver
這個驅動框架中,所以看起來復雜一點,實際上根據上面的注釋進行參考即可:
- 需要獲取要使用的GPIO號,然后需要向系統申請使用GPIO資源
- 資源申請成功后,我們需要設置GPIO的方向(輸入或者輸出),
- 此外,還能使用gpio_export()函數在sysfs中導出GPIO,導出的好處在於可以方便地debug代碼,
- 當驅動模塊卸載時,需要將已經申請的GPIO資源進行釋放掉
另外,在設備節點中導出了ctrl和gpio屬性文件,便可以很方便地在應用層進行設備的GPIO控制了。
本文地址:https://www.cnblogs.com/schips/p/linux_subsystem_using_gpio_ss.html