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
內(nèi)核相關(guān)文檔
Documentation\devicetree\bindings\pinctrl\pinctrl-bindings.txt
Documentation\gpio\gpio.txt
Documentation\devicetree\bindings\gpio\gpio.txt
背景
隨著內(nèi)核的發(fā)展,linux驅(qū)動(dòng)框架在不斷的變化。在早期,GPIO子系統(tǒng)存在之前,我們驅(qū)動(dòng)需要在代碼中配置寄存器來(lái)使用GPIO引腳设捐。
此后,出現(xiàn)了gpio子系統(tǒng),后來(lái)又出現(xiàn)了pinctrl子系統(tǒng)猴凹。
有些平臺(tái)的實(shí)現(xiàn)沒(méi)有使用內(nèi)核提供的pinctrl子系統(tǒng),而是繼續(xù)采用在內(nèi)核提供pinctrl子系統(tǒng)前自己實(shí)現(xiàn)的那套機(jī)制來(lái)pinmux操作岭皂,如Ti的omap平臺(tái)精堕;
有些平臺(tái)則基于pinctrl子系統(tǒng)來(lái)實(shí)現(xiàn)pinmux、pinconf的控制蒲障。
介紹
GPIO子系統(tǒng)可以說(shuō)是Linux中最簡(jiǎn)單的子系統(tǒng)歹篓。
- GPIO(General Purpose Input Output):負(fù)責(zé)管理整個(gè)系統(tǒng)各gpio輸入輸出管腳的使用情況,同時(shí)通過(guò)sys文件系統(tǒng)導(dǎo)出了調(diào)試信息和應(yīng)用層控制接口揉阎。
- Pinctrl(Pin Control):負(fù)責(zé)管理SOC中各pin的狀態(tài)庄撮,比如輸出電流能力、是否有內(nèi)部上拉或者下拉毙籽,是否有功能復(fù)用等參數(shù)洞斯。
要想操作GPIO引腳,需要先把所用引腳配置成GPIO功能,這個(gè)通過(guò)pinctrl
子系統(tǒng)來(lái)實(shí)現(xiàn)烙如。然后可以根據(jù)設(shè)置的引腳的方向來(lái)讀取引腳的值和設(shè)置輸出值么抗。
在BSP工程師實(shí)現(xiàn)好GPIO子系統(tǒng)后,我們就可以在設(shè)備樹(shù)中指定GPIO引腳亚铁,在驅(qū)動(dòng)中使用GPIO子系統(tǒng)的標(biāo)準(zhǔn)函數(shù)來(lái)獲取GPIO蝇刀、設(shè)置GPIO方向、讀取/設(shè)置GPIO的值徘溢。這樣的驅(qū)動(dòng)代碼是于單板無(wú)關(guān)的吞琐。
gpio子系統(tǒng)
gpio子系統(tǒng)內(nèi)部實(shí)現(xiàn)主要提供了兩類接口:
一類給bsp工程師,用于注冊(cè)gpio chip(也就是所謂的gpio控制器驅(qū)動(dòng))
另一部分給驅(qū)動(dòng)工程師使用然爆,為驅(qū)動(dòng)工程師屏蔽了不同gpio chip之間的區(qū)別站粟,驅(qū)動(dòng)工程師調(diào)用的api的最終操作流程會(huì)導(dǎo)向gpio對(duì)應(yīng)的gpio chip的控制代碼,也就是bsp的代碼曾雕。
核心實(shí)現(xiàn)
gpio子系統(tǒng)的實(shí)現(xiàn)源碼在drivers/gpio
文件夾下奴烙,主要文件有:
在安卓系統(tǒng)中,實(shí)現(xiàn)源碼在
kernel/drivers/gpio
文件 | 作用 |
---|---|
devres.c | 針對(duì)gpio api增加的devres機(jī)制的支持 |
gpiolib.c | gpio子系統(tǒng)的核心實(shí)現(xiàn) |
gpiolib-of.c | 對(duì)設(shè)備樹(shù)的支持 |
gpiolib-acpi.c | 和acpi相關(guān)剖张,不分析 |
gpio-xxx.c | 根據(jù)平臺(tái)的不同切诀,所對(duì)應(yīng)的gpio控制 |
gpio子系統(tǒng)提供了兩層接口,一層給上層驅(qū)動(dòng)工程師調(diào)用修械,一層給下層bsp工程師調(diào)用趾牧。
上層使用前,當(dāng)然先得bsp工程師完成對(duì)應(yīng)的動(dòng)作肯污。
Linux內(nèi)核中GPIO子系統(tǒng)的軟件驅(qū)動(dòng)分層圖
GPIO子系統(tǒng)有兩套接口:
一是基于描述符(
descriptor-based
)的翘单,相關(guān)api函數(shù)都是以"gpiod_
"為前綴,它使用gpio_desc
結(jié)構(gòu)來(lái)表示一個(gè)引腳蹦渣。另一種是老(
legency
)的哄芜,相關(guān)api函數(shù)都是以"gpio_
"為前綴,它使用一個(gè)整數(shù)來(lái)表示一個(gè)引腳柬唯,強(qiáng)烈建議不要使用legacy的接口函數(shù)认臊。
其實(shí),
legacy gpio
大部分api就是基于描述符api來(lái)實(shí)現(xiàn)的锄奢,我們可以看到很多legacy api
內(nèi)部的實(shí)現(xiàn)調(diào)用了to_desc
失晴。
// 1.獲取GPIO
gpiod_get;
gpiod_get_index;
gpiod_get_array;
devm_gpiod_get;
devm_gpiod_get_index;
devm_gpiod_get_array;
// 2.設(shè)置方向
gpiod_direction_input;
gpiod_direction_output;
// 3.讀值、寫值
gpiod_get_value;
gpiod_set_value;
// 4\. 設(shè)為中斷(如果必要)
request_irq(gpiod_to_irq(gpio_desc)...); //將gpio轉(zhuǎn)為對(duì)應(yīng)的irq拘央,然后注冊(cè)該irq的中斷handler
// 5.釋放GPIO
gpiod_put;
gpiod_put_array;
devm_gpiod_put;
devm_gpiod_put_array;
前綴為"
devm_
"的含義是設(shè)備資源管理涂屁,這是一種自動(dòng)釋放資源的機(jī)制。思想:“資源是屬于設(shè)備的灰伟,設(shè)備不存在時(shí)資源就可以自動(dòng)釋放”拆又。
背景:在Linux驅(qū)動(dòng)開(kāi)發(fā)過(guò)程中,先申請(qǐng)了GPIO,再申請(qǐng)內(nèi)存帖族,如果內(nèi)存申請(qǐng)失敗栈源,那么在返回之前就需要先釋放GPIO資源。如果使用的是devm相關(guān)函數(shù)竖般,在內(nèi)存申請(qǐng)失敗時(shí)可以直接返回甚垦,設(shè)備的銷毀函數(shù)會(huì)自動(dòng)地釋放已經(jīng)申請(qǐng)了的GPIO資源。
因此捻激,建議使用devm相關(guān)函數(shù)操作GPIO制轰。
gpio控制api( descriptor)
使用基于描述符的接口時(shí)前计,GPIO被作為一個(gè)描述符來(lái)使用胞谭。
#include <linux/gpio/consumer.h>
// 更多相關(guān)的說(shuō)明可以參考 Documentation/gpio/consumer.txt
獲取一個(gè)或一組GPIO
struct gpio_desc * gpiod_get(struct device *dev,
const char *con_id,
enum gpiod_flags flags);
/*
在允許GPIO不存在時(shí),可以使用gpiod_get_optional()和gpiod_get_index_optional()函數(shù)男杈。
這兩個(gè)函數(shù)在沒(méi)有成功分配到GPIO的時(shí)候返回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; // 數(shù)量
struct gpio_desc *desc[]; // 每一個(gè) desc 的情況
}
// 返回gpio_descs 注意:不是 gpio_desc
struct gpio_descs * gpiod_get_array(struct device *dev,
const char *con_id,
enum gpiod_flags flags);
/*多個(gè)Pin時(shí)需要附帶index參數(shù)。*/
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);
描述:必須通過(guò)調(diào)用gpiod_get()
函數(shù)族來(lái)獲取對(duì)應(yīng)的描述符伶棒。
參數(shù)解析:
- con_id:字符串類型旺垒,即GPIO的名字;
一般需要查看設(shè)備樹(shù)中的定義肤无。除此之外先蒋,我們還可以在設(shè)備樹(shù)文件里添加參數(shù)(
GPIO_ACTIVE_LOW
、GPIO_OPEN_DRAIN
宛渐、GPIO_OPEN_SOURCE
)來(lái)觸發(fā)該接口內(nèi)部設(shè)置gpio竞漾,具體的參數(shù)格式和具體的gpio chip driver有關(guān),一般可以在Documentation/devicetree/bindings/gpio
里找到對(duì)應(yīng)平臺(tái)的方法窥翩。
有關(guān)DeviceTree情況中con_id參數(shù)的更詳細(xì)說(shuō)明請(qǐng)參閱Documentation/gpio/board.txt
例如:
在SD卡驅(qū)動(dòng)看到的去查找名字為cd-gpios
的gpio:
// simple.c:
ctx->cd_gpio = devm_gpiod_get_optional(dev, "cd", 0);
在使用SD卡驅(qū)動(dòng)的主dts就有cd pin的定義:
// xxx.dts:
cd-gpios = <&gpio2 12 GPIO_ACTIVE_LOW>;
- index:邏輯下標(biāo)业岁。將一個(gè)GPIO設(shè)備(DESC)下的多個(gè)Pin看成一個(gè)數(shù)組,此時(shí)index是數(shù)組成員下標(biāo)寇蚊。
內(nèi)核文檔有個(gè)例子笔时,比如gpio
如下定義:
led-gpios = <&gpio 15 GPIO_ACTIVE_HIGH>, /* red */
<&gpio 16 GPIO_ACTIVE_HIGH>, /* green */
<&gpio 17 GPIO_ACTIVE_HIGH>; /* blue */
如果index是0,那么對(duì)應(yīng)的就是gpio 15仗岸;如果index是1允耿,那么對(duì)應(yīng)就是gpio 16,以此類推扒怖。
- flags:用于可選地指定GPIO的方向和初始值较锡,它的值可以是:
-
GPIOD_ASIS
或0表示根本不初始化GPIO。需要隨后使用專門的函數(shù)設(shè)置方向 -
GPIOD_IN
初始化GPIO作為輸入姚垃。 -
GPIOD_OUT_LOW
將GPIO初始化為輸出念链,值為0。 -
GPIOD_OUT_HIGH
將GPIO初始化為輸出,值為1掂墓。 -
GPIOD_OUT_LOW_OPEN_DRAIN
:與GPIOD_OUT_LOW相同谦纱,但強(qiáng)制以開(kāi)漏的方式使用 -
GPIOD_OUT_HIGH_OPEN_DRAIN
:與GPIOD_OUT_HIGH相同,但強(qiáng)制以開(kāi)漏的方式使用
-
最后兩個(gè)標(biāo)志用于必須開(kāi)漏方式的情況君编,比如GPIO被用作I2C時(shí)跨嘉,如果該GPIO尚未在映射(參見(jiàn)board.txt)中被配置為開(kāi)漏方式,將被強(qiáng)制配置為開(kāi)漏方式并給出WARNING吃嘿。
這兩個(gè)函數(shù)都返回有效的GPIO描述符或可被IS_ERR()檢查的錯(cuò)誤代碼(它們永遠(yuǎn)不會(huì)返回NULL指針)祠乃。
返回值:成功返回一個(gè)GPIO描述符;失敗返回錯(cuò)誤編碼兑燥,可以使用IS_ERR()
進(jìn)行檢查錯(cuò)誤原因亮瓷。
- 返回-ENOENT只會(huì)發(fā)生在當(dāng)且僅當(dāng)沒(méi)有為設(shè)備/功能/索引三元組成功分配GPIO的時(shí)候。
- 其他錯(cuò)誤代碼用于已成功分配GPIO降瞳,但在試圖獲得它的時(shí)候發(fā)生了錯(cuò)誤的情況:這可以用于區(qū)分錯(cuò)誤原因是可選GPIO參數(shù)錯(cuò)誤還是GPIO缺失這兩種情況嘱支。
釋放
void gpiod_put(struct gpio_desc *desc);
void gpiod_put_array(struct gpio_descs *descs); // 對(duì)應(yīng) 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);
描述:釋放之前通過(guò)get獲取的GPIO描述符
注意:在釋放之后,嚴(yán)格禁止使用被釋放的描述符挣饥;也不允許在使用gpiod_get_array()
獲取的數(shù)組中單獨(dú)使用gpiod_put()
釋放描述符除师。
設(shè)置方向
注意:GPIO沒(méi)有默認(rèn)方向。因此扔枫,使用GPIO前必須首先設(shè)置其方向汛聚,否則將導(dǎo)致未定義的行為!
// 設(shè)置GPIO為輸入還是輸出
int gpiod_direction_input(struct gpio_desc *desc);
int gpiod_direction_output(struct gpio_desc *desc, int value);
// 查詢GPIO的當(dāng)前方向:返回0表示輸出短荐,1表示輸入倚舀,或錯(cuò)誤代碼(如果出錯(cuò))
int gpiod_get_direction(const struct gpio_desc *desc);
描述:使用設(shè)備驅(qū)動(dòng)必須首先確定GPIO的方向。如果在調(diào)用gpiod_get* ()
時(shí)搓侄,flag指定了nodirection
瞄桨,就可以調(diào)用上面的某個(gè)gpiod_direction_*()
函數(shù)來(lái)設(shè)置方向:
參數(shù)解析:
- value:對(duì)于輸出GPIO,提供的值將成為初始輸出值讶踪;用于避免系統(tǒng)啟動(dòng)期間的信號(hào)故障芯侥。
返回值:成功返回值為零,否則返回值為負(fù)的錯(cuò)誤代碼乳讥。
該返回值應(yīng)該被檢查柱查,因?yàn)橹螳@取/設(shè)置GPIO引腳值
get/set
調(diào)用不會(huì)返回錯(cuò)誤,所以錯(cuò)誤的配置是有可能的云石。您通常應(yīng)該在任務(wù)上下文進(jìn)行這些調(diào)用唉工。但是,對(duì)于自旋鎖安全(Spinlock-Safe)的GPIO汹忠,可以作為板級(jí)設(shè)置初期的一部分淋硝,在啟用任務(wù)之前使用它們雹熬。
使用單個(gè)GPIO
/*
Spinlock-Safe的GPIO訪問(wèn)
意義:如果操作GPIO可能導(dǎo)致sleep,那么同步機(jī)制不能采用spinlock谣膳,因?yàn)閟pinlock要求不能sleep
*/
// 讀取輸出引腳的值時(shí)竿报,返回的值應(yīng)該是引腳上的值。由于包括開(kāi)漏信號(hào)和輸出延遲在內(nèi)的問(wèn)題继谚,它并不總是匹配指定的輸出值烈菌。
int gpiod_get_value(const struct gpio_desc *desc);
void gpiod_set_value(struct gpio_desc *desc, int value);
描述:大多數(shù)GPIO控制器可通過(guò)存儲(chǔ)器讀/寫指令訪問(wèn)。在不能睡眠的環(huán)境下調(diào)用花履。
不能睡眠的環(huán)境:內(nèi)部hard(非線程的)IRQ handler芽世、類似的上下文中完成的操作(即原子操作中)。
參數(shù)解析:
- value:布爾值诡壁,零為低济瓢,非零為高。
返回值:get/set調(diào)用不會(huì)返回錯(cuò)誤欢峰,因?yàn)椤盁o(wú)效的GPIO”應(yīng)該在這之前就從gpiod_direction_*()
中得知葬荷。
但請(qǐng)注意涨共,并非所有平臺(tái)都可以讀取輸出引腳的值纽帖;對(duì)于那些不能讀取的平臺(tái),函數(shù)永遠(yuǎn)返回零举反。另外懊直,使用這些函數(shù)訪問(wèn)需要睡眠才能安全訪問(wèn)的GPIO(見(jiàn)下文)是錯(cuò)誤的操作。
/* 允許睡眠的GPIO訪問(wèn) */
// 判斷是否允許睡眠:返回非零 代表 可以睡眠:
int gpiod_cansleep(const struct gpio_desc *desc);
// 獲取火鼻、設(shè)置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)訪問(wèn)魁索。讀取或?qū)懭脒@些GPIO值的命令需要等待到達(dá)隊(duì)列的頭部以傳輸命令并獲得其響應(yīng)融撞。這樣就需要允許睡眠,導(dǎo)致這類GPIO的訪問(wèn)不能在內(nèi)部IRQ處理程序內(nèi)(原子上下文)完成粗蔚。
訪問(wèn)這樣的GPIO需要一個(gè)可以休眠的上下文尝偎,例如一個(gè)threaded IRQ處理程序,并且必須使用上述訪問(wèn)函數(shù)訪問(wèn)函數(shù)(而不是沒(méi)有帶cansleep()
后綴的)鹏控。
除了可以睡眠致扯,無(wú)法在hardIRQ處理程序訪問(wèn)的特點(diǎn)以外,這些調(diào)用與Spinlock-Safe的調(diào)用相同当辐。
使用gpio的時(shí)候需要了解一下低有效和開(kāi)漏語(yǔ)義抖僵,見(jiàn)附錄。
使用一組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);
## 設(shè)置值
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);
描述:如果相應(yīng)的芯片驅(qū)動(dòng)器支持缘揪,這些函數(shù)將嘗試同時(shí)訪問(wèn)屬于同一存儲(chǔ)體或芯片的GPIO耍群。在這種情況下义桂,可以預(yù)期顯著改善的性能。如果無(wú)法同時(shí)訪問(wèn)蹈垢,GPIO將按順序訪問(wèn)澡刹。用來(lái)獲取、設(shè)置GPIO的值耘婚。
參數(shù)解析:
- array_size - 數(shù)組元素的數(shù)量
- desc_array - GPIO描述符數(shù)組罢浇,可以是任意一組GPIO
如何理解“任意”:
我們可以先使用
gpiod_get()
和gpiod_get_array()
的任意組合來(lái)獲得描述符后,放入一個(gè)我們自己構(gòu)建數(shù)組中沐祷,再將其傳遞給上述函數(shù))嚷闭。同時(shí),如果為了獲得最佳性能赖临,屬于同一芯片的GPIO應(yīng)該在描述符數(shù)組中是連續(xù)的胞锰。
- value_array - 存儲(chǔ)GPIO值(get)的數(shù)組或要分配給GPIO的值數(shù)組(set)
返回值:
-
gpiod_get_array_value()
及其變體成功時(shí)返回0,錯(cuò)誤返回負(fù)數(shù)兢榨。 -
gpiod_get_value()
在成功傳遞GPIO值時(shí)返回0或1嗅榕。使用數(shù)組函數(shù)時(shí),GPIO值存儲(chǔ)在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相對(duì)應(yīng)的IRQ編號(hào)。
返回值:返回IRQ編號(hào)或負(fù)的errno代碼(很可能是因?yàn)樵撎囟℅PIO不能用作IRQ)吟逝。
注意:
- 使用未使用
gpiod_direction_input()
設(shè)置為輸入的GPIO帽蝶,或者使用最初不是來(lái)自gpiod_to_irq()
的IRQ編號(hào),是錯(cuò)誤的操作块攒。 - gpiod_to_irq()不允許休眠励稳。
從gpiod_to_irq()
返回的非錯(cuò)誤值可以傳遞給request_irq()
或free_irq()
。
它們通常通過(guò)特定于板的初始化代碼存儲(chǔ)到平臺(tái)設(shè)備的IRQ資源中囱井。
注意驹尼,IRQ觸發(fā)選項(xiàng)是IRQ接口的一部分,例如庞呕, IRQF_TRIGGER_FALLING新翎,
導(dǎo)出到應(yīng)用空間(可選)
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
提供了用戶層的訪問(wèn),主要用于驅(qū)動(dòng)工程師調(diào)試或者應(yīng)用程序控制千扶。
描述:將該gpio的信息通過(guò)sys文件系統(tǒng)導(dǎo)出料祠,這樣應(yīng)用層可以直接查看狀態(tài)、設(shè)置狀態(tài)等澎羞。
參數(shù)解析:
- direction_may_change: 用來(lái)標(biāo)記這個(gè)gpio的輸入輸出方向是否可以改變髓绽。
如果該gpio已經(jīng)設(shè)置了輸入或者輸出,那么它的
direction_may_change
為false妆绞。
兼容
舊的GPIO系統(tǒng)使用基于標(biāo)號(hào)的結(jié)構(gòu)而不是基于描述符顺呕》闩剩可以使用如下兩個(gè)函數(shù)進(jìn)行相互轉(zhuǎn)換:
int desc_to_gpio(const struct gpio_desc *desc);
struct gpio_desc *gpio_to_desc(unsigned gpio);
注意:不能使用一套API的方法釋放另一套API獲取的設(shè)備。
附錄:GPIO子系統(tǒng)其他內(nèi)容
低有效和開(kāi)漏語(yǔ)義
介紹
一般情況下株茶,使用GPIO子系統(tǒng)的開(kāi)發(fā)者并不需要關(guān)心GPIO對(duì)外的實(shí)際電平来涨,因此,gpiod_set_value_xxx()
或 gpiod_set_array_value_xxx()
這樣的函數(shù)都以邏輯值操作启盛。
這些函數(shù)會(huì)將低電平有效的性質(zhì)考慮在內(nèi)蹦掐。也就是說(shuō),低電平有效僵闯,物理值0對(duì)應(yīng)邏輯值的1卧抗。
如果我們事先告知內(nèi)核某一個(gè)GPIO是低電平有效(
active_low
)這些函數(shù)內(nèi)部會(huì)進(jìn)行處理,就不再需要我們關(guān)心“到底是不是電平1有效還是電平0有效”鳖粟,
例如社裆,如果設(shè)置了GPIO的低電平有效屬性,并且gpiod_set_(array)_value_xxx()
傳遞了邏輯值1(“asserted”
)向图,則物理線路電平將被驅(qū)動(dòng)為低電平泳秀。
這同樣適用于開(kāi)漏或開(kāi)源輸出:它們并不輸出高電平(開(kāi)漏)或低電平(開(kāi)源),它們只是將輸出切換到高阻抗值榄攀。使用者應(yīng)該不需要關(guān)注嗜傅。
有關(guān)的詳細(xì)信息,請(qǐng)參閱driver.txt中關(guān)于開(kāi)漏的細(xì)節(jié)航攒。
總結(jié):
函數(shù)(示例) | 線路屬性 | 物理線路 |
---|---|---|
gpiod_set_raw_value(desc, 0); | - | 低電平 |
gpiod_set_raw_value(desc, 0); | - | 高電平 |
gpiod_set_value(desc, 0); | 默認(rèn)(高電平有效) | 低電平 |
gpiod_set_value(desc, 1); | 默認(rèn)(高電平有效) | 高電平 |
gpiod_set_value(desc, 0); | 低電平有效 | 高電平 |
gpiod_set_value(desc, 1); | 低電平有效 | 低電平 |
gpiod_set_value(desc, 0); | 開(kāi)漏 | 低電平 |
gpiod_set_value(desc, 1); | 開(kāi)漏 | 高阻態(tài) |
gpiod_set_value(desc, 0); | 開(kāi)漏 | 高阻態(tài) |
gpiod_set_value(desc, 1); | 開(kāi)漏 | 高電平 |
接口
當(dāng)然磺陡,如果你硬是要知道GPIO此時(shí)的電平值(的確需要管理GPIO線路物理狀態(tài)),可以使用下面的一組函數(shù)來(lái)達(dá)到你要的目的漠畜。
但應(yīng)盡可能避免去讀原始值,尤其是系統(tǒng)無(wú)關(guān)的驅(qū)動(dòng)程序坞靶,它們只需要關(guān)心邏輯值憔狞。
下面的一組調(diào)用忽略GPIO的低有效或開(kāi)漏屬性,設(shè)置什么值彰阴,物理值就是什么什么值:
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);
請(qǐng)注意簇抵,這些函數(shù)只能在使用者明白自己在做什么的情況下使用射众;驅(qū)動(dòng)程序一般不應(yīng)該關(guān)心線路物理狀態(tài)或開(kāi)漏語(yǔ)義。
在設(shè)備樹(shù)中設(shè)置低有效
假設(shè)我們?cè)贒TS里面這樣設(shè)置
reset-gpios = <&gpio3 RK_PA3 GPIO_ACTIVE_LOW>;
然后我們這樣調(diào)用:
gpiod_set_value_cansleep(gc5025->reset_gpio, 1);
因?yàn)镈TS里面的active 狀態(tài)是 GPIO_ACTIVE_LOW典蜕,所以這個(gè)代碼輸出的是 低電平。
系統(tǒng)喚醒功能
這個(gè)功能與
ACPI
有關(guān)愉舔。有關(guān)詳細(xì)信息轩缤,請(qǐng)參閱
Documentation/acpi/gpio-properties.txt
在ACPI系統(tǒng)上,GPIO由設(shè)備的_CRS配置對(duì)象列出的GpioIo()/ GpioInt()
資源描述火的。這些資源不提供GPIO的連接ID(名稱)躺翻,因此有必要為此目的使用附加機(jī)制。
符合ACPI 5.1或更新版本的系統(tǒng)可能可以提供_DSD
配置對(duì)象卫玖,它可以用于提供_CRS
中的GpioIo()/ GpioInt()
資源描述的特定GPIO的連接ID公你。如果是這種情況,它將由GPIO子系統(tǒng)自動(dòng)處理假瞬。但是陕靠,如果不存在_DSD
,則GpioIo()/ GpioInt()
資源與GPIOconnection ID之間的映射需要由設(shè)備驅(qū)動(dòng)程序提供脱茉。
附錄:legacy-api
#include <linux/gpio.h>
還有一組用于允許睡眠場(chǎng)景的
api
沒(méi)有給出剪芥,更多相關(guān)的說(shuō)明可以參考Documentation/gpio/gpio-legacy.txt
使用流程:
- 申請(qǐng)、釋放:
gpio_request
琴许、gpio_free
- 設(shè)置GPIO方向:
gpio_direction_input
优妙、gpio_direction_output
- 獲取設(shè)置GPIO值:
gpio_get_value
、gpio_set_value
- (可選)設(shè)置為中斷:
gpio_to_irq
- (可選)導(dǎo)出到
sys
文件系統(tǒng):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;
}
描述:來(lái)判斷獲取到的gpio號(hào)是否是有效的胚宦,只有有效的gpio號(hào)脊凰,才能向內(nèi)核中進(jìn)行申請(qǐng)使用,因此箭券,當(dāng)我們從設(shè)備樹(shù)的設(shè)備節(jié)點(diǎn)獲取到gpio號(hào)净捅,可以使用該函數(shù)進(jìn)行判斷是否有效。
參數(shù)解析:
- numb:需要判斷的GPIO號(hào)辩块。
返回值:合法為1蛔六,否則為0。
申請(qǐng)废亭、釋放
/* 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
是它的擴(kuò)展,devm_
為前綴的是gpio devres機(jī)制的實(shí)現(xiàn)恋追。
描述:請(qǐng)求一個(gè)/一組gpio。
參數(shù)解析:
- gpio:gpio號(hào)撕彤,可以通過(guò)sdk開(kāi)發(fā)包說(shuō)明文檔查看羹铅,或者查看設(shè)備樹(shù)文件,也可以在
gpio chip
驅(qū)動(dòng)的實(shí)現(xiàn)中找到焊切。 - flags:可以指定
GPIOF_OPEN_DRAIN
、GPIOF_OPEN_SOURCE
牵祟、GPIOF_DIR_IN
咕晋、GPIOF_EXPORT
等標(biāo)志- 如果指定了
GPIOF_DIR_IN
滓玖,那么后面就不需要自己再額外調(diào)用gpio_direction_input
或者gpio_direction_output
了翩肌, - 如果指定了
GPIOF_EXPORT
念祭,后面就不需要自己調(diào)用gpio_export
了。
- 如果指定了
- label:向系統(tǒng)中申請(qǐng)GPIO使用的標(biāo)簽站玄,類似于GPIO的名稱
- array,num:是
gpio_request_array
對(duì)gpio_request_one
的封裝晾剖,用于處理同時(shí)申請(qǐng)多個(gè)gpio的情形。 - dev:帶有
devm_
前綴雕什,用于帶設(shè)備資源管理版本的函數(shù),因此在使用上面的函數(shù)時(shí)偿警,需要指定設(shè)備的struct device指針螟蒸,生命周期與設(shè)備相同。
返回值:成功返回0诵原。
意義:gpio_request
主要做了以下動(dòng)作:
- 檢查是否已經(jīng)被申請(qǐng)蔓纠,沒(méi)有的話腿倚,標(biāo)記為已申請(qǐng)
- 填充label到該pin數(shù)據(jù)結(jié)構(gòu),用于debug
- 如果chip driver提供了request回調(diào)懈叹,調(diào)用它
- 如果chip driver提供了get_direction回調(diào),調(diào)用它墨状,通過(guò)它更新pin數(shù)據(jù)結(jié)構(gòu),標(biāo)明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));
設(shè)置方向
//設(shè)置gpio方向?yàn)檩斎?輸出
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);
}
描述:當(dāng)我們使用gpio_request()
函數(shù)族向系統(tǒng)中申請(qǐng)了GPIO資源后镐确,可以使用上面的函數(shù)進(jìn)行GPIO的方向設(shè)置:
- 函數(shù)
gpio_direction_input()
用來(lái)設(shè)置GPIO的方向?yàn)檩斎?/li> - 函數(shù)
gpio_direction_output()
用來(lái)設(shè)置GPIO的方向?yàn)檩敵觯⑶彝ㄟ^(guò)value
值可以設(shè)置輸出的電平息堂。
意義: gpio_direction_input
或者gpio_direction_output
主要是回調(diào)gpio chip driver提供的direction_input
或者direction_output
來(lái)設(shè)置該gpio寄存器為輸入、輸出持隧。
導(dǎo)出
// 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
提供了用戶層的訪問(wèn),主要用于驅(qū)動(dòng)工程師調(diào)試或者應(yīng)用程序控制呀狼。
描述:將該gpio的信息通過(guò)sys文件系統(tǒng)導(dǎo)出,這樣應(yīng)用層可以直接查看狀態(tài)、設(shè)置狀態(tài)等窟勃。
參數(shù)解析:
- direction_may_change: 用來(lái)標(biāo)記這個(gè)gpio的輸入輸出方向是否可以改變眷昆。
如果該gpio已經(jīng)設(shè)置了輸入或者輸出,那么它的
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);
}
描述:當(dāng)我們將GPIO的方向設(shè)置為輸入時(shí)赖瞒,可以使用上面的函數(shù)gpio_get_value()
來(lái)獲取當(dāng)前的IO口電平值,當(dāng)GPIO的方向設(shè)置為輸出時(shí)抡爹,使用函數(shù)gpio_set_value()
可以設(shè)置IO口的電平值。
申請(qǐng)中斷
static inline int gpio_to_irq(unsigned int gpio)
{
return __gpio_to_irq(gpio);
}
描述:用于獲取該gpio對(duì)應(yīng)的中斷號(hào)泵殴,這個(gè)需要設(shè)備樹(shù)里的該gpio節(jié)點(diǎn)描述使用哪個(gè)中斷號(hào)
并不是所有的gpio都可以觸發(fā)中斷的。
意義:回調(diào)gpio chip driver
提供的to_irq
弦叶。
例子
設(shè)備樹(shù)(高通msm平臺(tái)):
dev_gpio {
status = "okay";
compatible = "dev-gpio";
label = "test_gpio";
gpios = <&msm_gpio 68 0>;
};
驅(qū)動(dòng)程序:
#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)) {
/* 申請(qǐng)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;
}
/* 設(shè)置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中導(dǎo)出gpio(方向不能改變) */
ret = gpio_export(pdata->gpio_num, false);
if (ret) {
dev_err(dev, "failed to export gpio %d\n", pdata->gpio_num);
goto fail3;
}
/* 設(shè)置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);
/* 釋放已經(jīng)申請(qǐng)的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");
當(dāng)驅(qū)動(dòng)模塊加載的時(shí)候蜓耻,由于嵌入了platform_driver
這個(gè)驅(qū)動(dòng)框架中,所以看起來(lái)復(fù)雜一點(diǎn)芦鳍,實(shí)際上根據(jù)上面的注釋進(jìn)行參考即可:
- 需要獲取要使用的GPIO號(hào),然后需要向系統(tǒng)申請(qǐng)使用GPIO資源
- 資源申請(qǐng)成功后菲宴,我們需要設(shè)置GPIO的方向(輸入或者輸出),
- 此外谣蠢,還能使用gpio_export()函數(shù)在sysfs中導(dǎo)出GPIO,導(dǎo)出的好處在于可以方便地debug代碼谈喳,
- 當(dāng)驅(qū)動(dòng)模塊卸載時(shí)婿禽,需要將已經(jīng)申請(qǐng)的GPIO資源進(jìn)行釋放掉
另外,在設(shè)備節(jié)點(diǎn)中導(dǎo)出了ctrl和gpio屬性文件谈宛,便可以很方便地在應(yīng)用層進(jìn)行設(shè)備的GPIO控制了次哈。
本文地址:https://www.cnblogs.com/schips/p/linux_subsystem_using_gpio_ss.html
若在頁(yè)首無(wú)特別聲明,本篇文章由 Schips 經(jīng)過(guò)整理后發(fā)布吆录。
博客地址:https://www.cnblogs.com/schips/