什么是GPIO?
GPIO----“通用目的輸入/輸出端口”----是一個(gè)靈活的軟件控制的數(shù)字信號(hào)。許多種類的芯片都會(huì)提供,嵌入式linux開發(fā)者和硬件定制者會(huì)對(duì)此比較熟悉。每個(gè)GPIO提供一位與特定的管腳(或是“球”,BGA(Ball Grid Array)封裝下)相連。單板電路圖會(huì)顯示外部硬件與GPIOs的連接關(guān)系。GPIO驅(qū)動(dòng)可寫成通用的,便于單板setup代碼可以將這些管腳配置數(shù)據(jù)傳遞給驅(qū)動(dòng)。
SOC處理器非常依賴于GPIO。某些情況下,普通管腳可以被配置為GPIO。大多數(shù)芯片至少擁有幾組類似的GPIO???a target="_blank">編程邏輯器件(如FPGAs)可以很容易提供GPIO。一些多功能芯片,如電源管理、聲音解碼等經(jīng)常具有一些這樣的管腳來彌補(bǔ)SOC芯片上面管腳的不足。同樣,也存在一些GPIO擴(kuò)展芯片,連接用于I2C或是SPI串行總線。多數(shù)PC南橋擁有幾組GPIO兼容的管腳(僅有BIOS固件知道如何使用它們)。
不同系統(tǒng)間的GPIO的確切作用不同。通用常有下面幾種:
----輸出值可寫(高=1,低=0)。一些芯片也可以選擇驅(qū)動(dòng)這些值的方式,以便支持“線-或”或類似方案(開漏信號(hào)線)。
----輸入值可讀(1,0)。一些芯片支持輸出管腳回讀,這在線或的情況下非常有用(以支持雙向信號(hào)線)。GPIO控制器可能具有一個(gè)輸入防故障/防反跳邏輯,有時(shí)還會(huì)有軟件控制。
----輸入經(jīng)常被用作中斷信號(hào),通常是邊沿觸發(fā),但也有可能是電平觸發(fā)。這些中斷可以配置為系統(tǒng)喚醒事件,從而將系統(tǒng)從低功耗模式喚醒。
----一個(gè)GPIO經(jīng)常被配置為輸入/輸出雙向,根據(jù)不同的產(chǎn)品單板需求,但也存在單向的情況。
----大多是GPIO可以在獲取到spinlock自旋鎖時(shí)訪問,但那些通過串行總線訪問的通常不能如此操作(休眠的原因)。一些系統(tǒng)中會(huì)同時(shí)存在這兩種形式的GPIO。
----在一個(gè)給定單板上,每個(gè)GPIO用于一個(gè)特定的目的,如監(jiān)控MMC/SD卡的插入/移除,檢查卡寫保護(hù)狀態(tài),驅(qū)動(dòng)LED,配置發(fā)送器,串行總線位拆,觸發(fā)一個(gè)硬件看門狗,觸發(fā)一個(gè)開關(guān)之類的。
GPIO約定
注意,它被稱為一個(gè)約定,因?yàn)槟悴皇潜仨氁袷厮?。如果你不使用此種方式操作,也不會(huì)遭到任何懲罰。存在一些可移植性不是關(guān)鍵的應(yīng)用場(chǎng)景:GPIO經(jīng)常用作板級(jí)膠合邏輯,這些邏輯甚至在單板的不同版本之間都會(huì)改變,且不能用在那些連接不同的單板上。只有極少通用的標(biāo)準(zhǔn)功能可以是可移植的。其余的特性是平臺(tái)特有的,且對(duì)于膠合邏輯是關(guān)鍵(且危險(xiǎn))的。
另外,這不需要任何實(shí)現(xiàn)框架,只是一個(gè)接口。一個(gè)平臺(tái)可以將它實(shí)現(xiàn)為一個(gè)簡(jiǎn)單的inline函數(shù)來存取芯片寄存器,另一個(gè)可能通過一個(gè)多個(gè)不同GPIO控制器的抽象委托實(shí)現(xiàn)。
?。ㄓ幸恍┛蛇x的代碼支持這種實(shí)施策略,這在本文后面會(huì)講到,但擔(dān)任客戶的GPIO接口驅(qū)動(dòng)不要關(guān)心他是如何實(shí)現(xiàn)的。)
也就是說,如果平臺(tái)支持約定,驅(qū)動(dòng)應(yīng)當(dāng)盡可能使用它。平臺(tái)必須在Kconfig中聲明GENERIC_GPIO來支持,并且提供一個(gè)《asm/gpio.h》文件。那些不能離開標(biāo)準(zhǔn)GPIO調(diào)用的驅(qū)動(dòng)應(yīng)該具有一個(gè)依賴于GENERIC_GPIO的條目。要使GPIO調(diào)用有效,無論是“真實(shí)代碼”或是作為“optimized-away stubs”,驅(qū)動(dòng)需要使用包含文件:
#include 《linux/gpio.h》
如果你堅(jiān)持此約定,這樣別的開發(fā)者理解你的代碼會(huì)比較容易且可以幫助維護(hù)它。
注意:在那些需要的平臺(tái)上,這些操作包括I/0操作間隔(barriers);驅(qū)動(dòng)不需要顯式添加它們。
標(biāo)識(shí)GPIO
GPIO使用一個(gè)無符號(hào)整型數(shù)進(jìn)行標(biāo)識(shí),范圍0到MAX_INT。那些保留的“negative”(負(fù))數(shù)用于其他的目的,如標(biāo)識(shí)信號(hào)為“在此單板上無效”,或是指出錯(cuò)誤。那些不涉及到基本硬件操作的代碼將這些整型數(shù)視為不透明的。
平臺(tái)定義了它們?nèi)绾问褂眠@些接口,并且通常為每個(gè)GPIO線使用#define宏定義符號(hào),以便單板的啟動(dòng)代碼與相關(guān)設(shè)計(jì)直接保持一致。與此相反,驅(qū)動(dòng)應(yīng)該只使用從setup代碼傳遞給他們的GPIO號(hào)碼,使用platform_data來保存單板特定的管腳配置數(shù)據(jù)(與其它所需的單板特定數(shù)據(jù)一起)。這避免了移植問題。
例如:一個(gè)平臺(tái)給GPIOs使用號(hào)碼32-159,同時(shí)另一個(gè)使用0-63支持一個(gè)GPIO控制器集合,64-79支持另一個(gè)類型的GPIO控制器,且在一個(gè)特定的單板上80-95支持一個(gè)FPGA。號(hào)碼不必是連續(xù)的,這些單板也可以使用2000-2063來標(biāo)識(shí)一組用于I2CGPIO擴(kuò)展
如果你想要使用一個(gè)無效的GPIO號(hào)碼初始化一個(gè)結(jié)構(gòu)體,使用一些負(fù)數(shù)(可以為“-EINVAL”),它將永遠(yuǎn)不會(huì)有效。為了測(cè)試來自這樣一個(gè)結(jié)構(gòu)的這樣一個(gè)號(hào)碼是否能夠引用一個(gè)GPIO,你需要使用:
int gpio_is_valid(int number);
一個(gè)無效的號(hào)碼將被調(diào)用(可以是申請(qǐng)或釋放GPIO)拒絕。別的號(hào)碼也可能被拒絕。例如,一個(gè)號(hào)碼可能是有效的,但在給定的單板上臨時(shí)未使用。
平臺(tái)是否支持多個(gè)GPIO控制器是平臺(tái)特定實(shí)現(xiàn)的關(guān)鍵,同樣是否支持GPIO號(hào)碼空間的“空洞”,是否支持在運(yùn)行時(shí)增加新控制器也是關(guān)鍵。這些關(guān)鍵會(huì)影響多個(gè)事情,包括相鄰的GPIO號(hào)碼是否都有效。
?
使用GPIOs
要使用GPIO,系統(tǒng)首先要分配一個(gè)GPIO,使用gpio_request() 為系統(tǒng)分配一個(gè)GPIO。
接下來要做的一件事是標(biāo)示GPIO的方向,通常在使用GPIO建立一個(gè)platform_device時(shí)(位于單板的setup代碼中):
/* set as input or output, returning 0 or negative errno */
int gpio_direction_input(unsigned gpio);
int gpio_direction_output(unsigned gpio, int value);
返回0標(biāo)示成功,或是一個(gè)負(fù)的errno錯(cuò)誤碼。它應(yīng)該被檢查,因?yàn)間et/set調(diào)用沒有錯(cuò)誤返回,且可能會(huì)有錯(cuò)誤配置。你通常應(yīng)該在線程上下文中使用這些調(diào)用。雖然如此,對(duì)于spinlock-safe的GPIO,在tasking使能之前使用也是可以的,作為一個(gè)早期的單板建立。
對(duì)于輸出GPIO,value參數(shù)提供了初始輸出值。這有助于避免系統(tǒng)啟動(dòng)過程中的信號(hào)干擾。
為了與GPIO早期的接口兼容,設(shè)置一個(gè)GPIO的方向,隱性要求申請(qǐng)GPIO。這個(gè)兼容性從可選的gpiolib架構(gòu)中移除了。
如果GPIO號(hào)碼無效或是指定的GPIO不能使用對(duì)應(yīng)模式操作的話,設(shè)置方向會(huì)失敗。依靠boot固件設(shè)置好GPIO的方向通常不是一個(gè)好主意,因?yàn)閎oot的功能可能沒有通過驗(yàn)證(除了boot linux)。(類似的,單板setup代碼可能需要將管腳復(fù)用為一個(gè)GPIO,和配置為合適的上拉/下拉。)
Spinlock-Safe GPIO訪問
-------------------------
大多數(shù)GPIO控制器可以使用內(nèi)存讀寫指令訪問。它們不需要休眠,且可以從內(nèi)部硬件中斷處理(非線程)和類似的上下文環(huán)境安全完成。
使用下列調(diào)用訪問這些GPIO,此時(shí)gpio_cansleep將總是返回錯(cuò)誤
/* GPIO INPUT: return zero or nonzero */
int gpio_get_value(unsigned gpio);
/* GPIO OUTPUT */
void gpio_set_value(unsigned gpio, int value);
其中,value是一個(gè)布爾型參數(shù),零表示低,非零表示高。當(dāng)讀一個(gè)輸出管腳的值時(shí),返回的值應(yīng)該是在管腳上看到的值。..這并不總是與指定輸出值相匹配的,因?yàn)榇嬖陂_漏信號(hào)和輸出延遲問題。
get/set調(diào)用沒有錯(cuò)誤返回,因?yàn)椤盁o效GPIO”應(yīng)該已經(jīng)由gpio_direction_*()提早報(bào)告了。雖然如此,并非所有的平臺(tái)都可以讀取輸出管腳的值,那些不能讀的應(yīng)該總是返回零。同時(shí),對(duì)那些可能導(dǎo)致睡眠的GPIO使用這些接口是一個(gè)錯(cuò)誤。
平臺(tái)的特定實(shí)現(xiàn)被鼓勵(lì)優(yōu)化這兩個(gè)調(diào)用以獲取GPIO值。在那些GPIO號(hào)碼是常量的情況下,它們通常只需一對(duì)指令(讀或?qū)懸粋€(gè)硬件寄存器)訪問,且不需要spinlock。這樣的優(yōu)化可以使位拆分應(yīng)用更有效率(在時(shí)間和空間上)(相比較于花費(fèi)一堆指令在子例程調(diào)用來說)。
可能睡眠的GPIO訪問
一些GPIO控制器必須使用基于消息的總線如I2C和SPI來進(jìn)行訪問。讀寫這些GPIO的命令需要等待到達(dá)發(fā)送隊(duì)列的開始和獲取它的響應(yīng)。這需要休眠,且不能從內(nèi)部中斷處理函數(shù)中完成。
對(duì)于這種GPIO調(diào)用gpio_cansleep接口將返回非零值(需要一個(gè)有效的GPIO號(hào)碼,并已經(jīng)提前使用gpio_request進(jìn)行分配)
int gpio_cansleep(unsigned gpio);
為了訪問這些GPIO,一個(gè)不同的訪問函數(shù)集被定義
/* GPIO INPUT: return zero or nonzero, might sleep */
int gpio_get_value_cansleep(unsigned gpio);
/* GPIO OUTPUT, might sleep */
void gpio_set_value_cansleep(unsigned gpio, int value);
訪問這樣的gpio需要一個(gè)可能睡眠的上下文,例如一個(gè)線程級(jí)別中斷處理程序,并且這些訪問函數(shù)必須代替那些沒有cansleep()后綴的spinlock-safe的函數(shù)。
除了這些訪問函數(shù)可能休眠,且對(duì)那些不能從硬件中斷處理函數(shù)中訪問的GPIO起作用外,這些調(diào)用與那些spinlock-safe的調(diào)用效果一致
** IN ADDITION ** calls to setup and configure such GPIOs must be made
from contexts which may sleep, since they may need to access the GPIO
controller chip too: (These setup calls are usually made from board
setup or driver probe/teardown code, so this is an easy constraint.)
附加的調(diào)用(用于建立和配置這樣的GPIO)必須出自可以休眠的上下文,因?yàn)樗鼈兛赡苄枰L問GPIO控制器芯片:(這些setup調(diào)用經(jīng)常出自單板setup或是驅(qū)動(dòng)probe/teardown(拆卸)代碼,所以這是一個(gè)簡(jiǎn)單(無關(guān)緊要)的限制。)
gpio_direction_input()
gpio_direction_output()
gpio_request()
## gpio_request_one()
##gpio_request_array()
## gpio_free_array()
gpio_free()
gpio_set_debounce()
主張和釋放GPIO
為了捕獲系統(tǒng)配置錯(cuò)誤,定義了兩個(gè)調(diào)用
/* request GPIO, returning 0 or negative errno.
* non-null labels may be useful for diagnostics.
*/
int gpio_request(unsigned gpio, const char *label);
/* release previously-claimed GPIO */
void gpio_free(unsigned gpio);
錯(cuò)誤的GPIO號(hào)會(huì)導(dǎo)致gpio_request()失敗,同樣申請(qǐng)一個(gè)已經(jīng)被主張的也會(huì)出錯(cuò)。gpio_request()的返回值必須被檢查。通常應(yīng)該在一個(gè)任務(wù)上下文中調(diào)用此函數(shù),雖然如此,對(duì)于spinlock-safe GPIO來講,在使能tasking之前申請(qǐng)GPIO是可以的(作為早期單板setup的一部分)。
這些調(diào)用服務(wù)于兩個(gè)基本目的。一個(gè)是為了診斷目的標(biāo)識(shí)實(shí)際使用GPIO的信號(hào),系統(tǒng)可能有幾百個(gè)GPIO,但在一個(gè)給定的單板上常常只有幾組處于使用狀態(tài)。另一個(gè)是為了捕獲沖突、標(biāo)示錯(cuò)誤。當(dāng)兩個(gè)或多個(gè)驅(qū)動(dòng)錯(cuò)誤地認(rèn)為它們獨(dú)占此信號(hào)或是一些東西錯(cuò)誤的認(rèn)為移除驅(qū)動(dòng)是安全的,這時(shí)需要管理一個(gè)信號(hào)用于管理活動(dòng)狀態(tài)。既是說,申請(qǐng)一個(gè)GPIO的過程可以為這種鎖服務(wù)。
一些平臺(tái)也使用GPIO用于功耗管理,如關(guān)掉不使用的芯片部分和更簡(jiǎn)單的門控不使用的時(shí)鐘。
對(duì)于那些使用pinctrl子系統(tǒng)的管腳的GPIO,子系統(tǒng)應(yīng)該被通知它們的用途。一個(gè)gpiolib驅(qū)動(dòng)的.request()操作可能調(diào)用pinctrl_request_gpio(),且一個(gè)gpiolib驅(qū)動(dòng)的.free()操作可能調(diào)用pinctrl_free_gpio()。pinctrl子系統(tǒng)允許一個(gè)pinctrl_request_gpio()在一個(gè)管腳或是管腳組被一個(gè)設(shè)備擁有時(shí)成功,為了復(fù)用目的。
任意支持管腳復(fù)用硬件的編程需要路由GPIO信號(hào)到適當(dāng)?shù)墓苣_。這些應(yīng)該在一個(gè)GPIO驅(qū)動(dòng)的.direction_input()或是.direction_output()操作中發(fā)生,且發(fā)生在任意一個(gè)輸出GPIO值setup之后。這允許從一個(gè)管腳的特殊功能到GPIO的無障礙集成。當(dāng)使用一個(gè)GPIO來實(shí)現(xiàn)一個(gè)關(guān)于一個(gè)非GPIO硬件模塊驅(qū)動(dòng)一個(gè)典型信號(hào)的工作區(qū)(變通方案)時(shí),這時(shí)常會(huì)被需求。
一些平臺(tái)允許一些或所有的GPIO信號(hào)被路由到不同的管腳。同樣的,GPIO或管腳的別的方面可能需要配置,如上來/下拉。平臺(tái)軟件應(yīng)該安排所有的這些細(xì)節(jié)優(yōu)先于gpio_request()之前配置。例如,使用pinctrl子系統(tǒng)的映射表,這樣GPIO使用者就不需要知道那些細(xì)節(jié)。
同樣注意:在你釋放GPIO之前需要停止使用它。
考慮到大多數(shù)場(chǎng)景中GPIO在它們被聲明后實(shí)際已經(jīng)被正確配置,定義了3各附加的調(diào)用:
/* request a single GPIO, with initial configuration specified by
* ‘flags’, identical to gpio_request() wrt other arguments and
* return value
*/
int gpio_request_one(unsigned gpio, unsigned long flags, const char *label);
/* request multiple GPIOs in a single call
*/
int gpio_request_array(struct gpio *array, size_t num);
/* release multiple GPIOs in a single call
*/
void gpio_free_array(struct gpio *array, size_t num);
其中,flags參數(shù)當(dāng)前可以指定為下列屬性:
* GPIOF_DIR_IN- 配置方向?yàn)檩斎?/p>
* GPIOF_DIR_OUT- 配置方向?yàn)檩敵?/p>
* GPIOF_INIT_LOW- 作為輸出,設(shè)置初始值為低
* GPIOF_INIT_HIGH- 作為輸出,設(shè)置初始值為高
* GPIOF_OPEN_DRAIN- GPIO管腳是開漏極形式
* GPIOF_OPEN_SOURCE- GPIO管腳是開源極形式
由于GPIOF_INIT_*僅僅當(dāng)配置為輸出時(shí)有效,所以有效組合為
* GPIOF_IN- 配置為輸入
* GPIOF_OUT_INIT_LOW- 配置為輸出,初始為低電平
* GPIOF_OUT_INIT_HIGH- 配置為輸入,初始為高電平
當(dāng)設(shè)置flag為GPIOF_OPEN_DRAIN時(shí),它將假設(shè)管腳是開漏極方式。這類管腳在輸出模式將不會(huì)被驅(qū)動(dòng)為1。這樣的管腳需要連接上拉。通過使能此flag,當(dāng)它在輸出模式被要求設(shè)置為1時(shí),gpio lib將使得方向?yàn)檩斎胍允沟霉苣_變高。輸出模式下,管腳輸出值0以驅(qū)動(dòng)電平為低。
當(dāng)設(shè)置flag為GPIOF_OPEN_SOURCE,它假設(shè)管腳時(shí)開源極類型。這種管腳在輸出模式不能驅(qū)動(dòng)為0。此種管腳需要下拉。通過使能這個(gè)flag,當(dāng)管腳要求輸出1時(shí),gpio lib將使得方向變?yōu)檩斎胍允沟霉苣_變低。管腳在輸出模式驅(qū)動(dòng)1為高
未來,這些flag可以被擴(kuò)展以支持更多的特性。
此外,為了簡(jiǎn)化多個(gè)GPIO的聲明/釋放,引入了gpio結(jié)構(gòu)來壓縮這3個(gè)域
struct gpio {
unsignedgpio;
unsigned longflags;
const char*label;
};
一個(gè)典型用法的例子如下:
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號(hào)碼是無符號(hào)整型數(shù),同樣中斷號(hào)碼也是這樣。這些構(gòu)成了兩個(gè)邏輯區(qū)別的名字空間(GPIO0無須對(duì)應(yīng)使用中斷0)。你可以在它們之間使用以下調(diào)用進(jìn)行映射:
/* map GPIO numbers to IRQ numbers */
int gpio_to_irq(unsigned gpio);
/* map IRQ numbers to GPIO numbers (avoid using this) */
int irq_to_gpio(unsigned irq);
它們返回一個(gè)對(duì)應(yīng)名字空間的對(duì)應(yīng)號(hào)碼,或者如果映射不能完成的話返回錯(cuò)誤。(例如:一些GPIO不能用于中斷。)使用一個(gè)未setup的GPIO號(hào)碼作為輸出調(diào)用gpio_direction_input()或是使用一個(gè)不是來源于gpio_to_irq()的中斷號(hào)是一個(gè)未核對(duì)的錯(cuò)誤,
這兩個(gè)映射調(diào)用會(huì)在單個(gè)增加或減少上有耗費(fèi)。它們不能休眠。
gpio_to_irq()的返回值(非錯(cuò)誤)可以傳遞給request_irq()或free_irq()。它們經(jīng)常被保存到對(duì)應(yīng)platform設(shè)備的IRQ resource中,這使用單板特定的初始化函數(shù)完成。注意,中斷觸發(fā)選項(xiàng)是中斷接口的一部分,例如IRQF_TRIGGER_FALLING,作為系統(tǒng)喚醒能力。
irq_to_gpio()的返回值(非錯(cuò)誤)通常用于gpio_get_value(),例如,為了在中斷被邊沿觸發(fā)時(shí) 初始化和更新驅(qū)動(dòng)狀態(tài)。注意,一些platform不支持反轉(zhuǎn)映射,所以你應(yīng)該避免使用它。
仿真開漏信號(hào)
有時(shí)共享信號(hào)需要使用開漏信號(hào),它只有低信號(hào)電平是實(shí)際驅(qū)動(dòng)的。(此術(shù)語用于COMS晶體管,開集電極用于TTL。)一個(gè)上拉電阻引出高電平信號(hào)。這有時(shí)稱為“線與”,或是事實(shí)上更多的,來自負(fù)邏輯觀點(diǎn)(low=true)這是一個(gè)“線或”。
一個(gè)常見的開漏信號(hào)的例子是一個(gè)共享的低電平激活中斷線。同樣,雙向數(shù)據(jù)總線信號(hào)有時(shí)也使用開漏信號(hào)
一些GPIO控制器直接支持開漏輸出,更多的不支持。當(dāng)你需要開漏信號(hào)但你的硬件不直接支持時(shí),一個(gè)常見的方法你可以使用任意的即可用于輸入也可以用于輸出的GPIO來模擬它
LOW:gpio_direction_output(gpio, 0) 。.. this drives the signal
and overrides the pullup.
HIGH:gpio_direction_input(gpio) 。.. this turns off the output,
so the pullup (or some other device) controls the signal.
如果你正在驅(qū)動(dòng)信號(hào)為高,但是 gpio_get_value(gpio)報(bào)告了一個(gè)低值(經(jīng)過適當(dāng)?shù)纳仙龝r(shí)間準(zhǔn)備),你應(yīng)該知道可能是某些別的部件驅(qū)動(dòng)了這個(gè)共享信號(hào)為低。這是不必要的錯(cuò)誤。作為一個(gè)常見的例子,這是I2C時(shí)鐘拉伸的方式:一個(gè)從部件需要一個(gè)低速時(shí)鐘延遲了SCK的上升沿們,且I2C主設(shè)備因此調(diào)整了它的信號(hào)頻率。
這些約定節(jié)省了什么?
這些約定節(jié)省的一個(gè)大方面是關(guān)于管腳復(fù)用,因?yàn)檫@是高度芯片相關(guān)且不可移植的。一個(gè)平臺(tái)可能不需要顯式的管腳復(fù)用,另一個(gè)可能只有兩個(gè)選項(xiàng)用于任意給定的管腳,另一個(gè)可能每個(gè)管腳有八個(gè)選擇,一個(gè)可能能夠路由一個(gè)給定的GPIO到多個(gè)管腳中的一個(gè)。(是的,這些例子在今天的linux上都可以找到)
在一些平臺(tái)上配置和上拉/下拉的使能與多路復(fù)用相關(guān)。并不是所有的平臺(tái)都支持或是以同樣的方式支持它們,任意一個(gè)給定的單板可能使用外部上拉(或下拉)以便片上ons不能被使用。(當(dāng)一個(gè)電路需要5千歐姆,片上的100K歐姆電阻不能作用。)同樣的,驅(qū)動(dòng)能力(2mA 對(duì) 20mA)和電壓(1.8V 對(duì) 3.3V)是平臺(tái)特定的,與模型一樣在配置的管腳和GPIO之間一一對(duì)應(yīng)
還有別的系統(tǒng)特定的機(jī)制此處并沒有提到,如上文提及的抗干擾和線或輸出。
硬件可能按組讀寫GPIO,但是那通常是單獨(dú)配置的:對(duì)于那些共享同一個(gè)bank的GPIO。
?。℅PIO通常16或32個(gè)為一組,一個(gè)給定的SOC系統(tǒng)一般擁有幾個(gè)這樣的BANK。)
一些系統(tǒng)可以從輸出GPIO管腳觸發(fā)中斷,或是從一個(gè)沒作為GPIO管理的管腳上讀值。依賴于這種機(jī)制的代碼將是不可移植的。
GPIO動(dòng)態(tài)定義并不是當(dāng)前的標(biāo)準(zhǔn),例如,作為配置一個(gè)單板附加的GPIO擴(kuò)展器的邊界效應(yīng)。
GPIO系統(tǒng)結(jié)構(gòu)(可選)
如前面提醒的一樣,一個(gè)可選的實(shí)現(xiàn)結(jié)構(gòu)使得平臺(tái)支持不同種類的GPIO控制器使用同一個(gè)編程接口變得簡(jiǎn)單。這個(gè)結(jié)構(gòu)稱為gpiolib。
作為一個(gè)調(diào)試目的,如果debugfs有效,一個(gè)/sys/kernel/debug/gpio文件在那里將被找到。它列出了所有的通過這個(gè)結(jié)構(gòu)注冊(cè)的GPIO控制器,和GPIO當(dāng)前的使用狀態(tài)。
Controller Drivers: gpio_chip
控制器驅(qū)動(dòng):gpio_chip
在這個(gè)架構(gòu)中,每個(gè)GPIO控制器被封裝為一個(gè)“gpio_chip”結(jié)構(gòu)體,此結(jié)構(gòu)體中包含了每個(gè)控制器的通用信息:
--確定GPIO方向的方法
--存取GPIO值的方法
--聲明方法是否休眠的flag
--可選的debugfs dump方法(展現(xiàn)附加的狀態(tài)如上拉配置等)
--用于診斷目的的標(biāo)簽
每個(gè)實(shí)例也有自己的私有數(shù)據(jù),可能來自device.platform_data:它的第一個(gè)GPIO和它暴露幾個(gè)GPIO.
實(shí)現(xiàn)一個(gè)gpio_chip的代碼應(yīng)該支持多個(gè)控制器的實(shí)例,可能使用驅(qū)動(dòng)模型。代碼會(huì)配置每個(gè)gpio_chip并且執(zhí)行g(shù)piochip_add()。移除一個(gè)GPIO控制器是少見的,使用gpio_remove()移除一個(gè)不再有效的GPIO控制器。
大多數(shù)時(shí)候,一個(gè)gpio_chip是一個(gè)實(shí)例獨(dú)有的結(jié)構(gòu),它的一些狀態(tài)值不暴露給GPIO接口,如編址、電源管理等等。編碼解碼器之類的芯片會(huì)有復(fù)雜的非GPIO狀態(tài)。
所有的debugfs dump 方式通常應(yīng)該忽略那些未作為GPIO請(qǐng)求的信號(hào)。他們可以使用gpiochip_is_requested(),此函數(shù)返回與GPIO相關(guān)的label或是NULL。
平臺(tái)支持
為了支持這個(gè)結(jié)構(gòu),一個(gè)平臺(tái)的Kconfig需要選擇ARCH_REQUIRE_GPIOLIB或是ARCH_WANT_OPTIONAL_GPIOLIB之一,且安排的它的《asm/gpio.h》包含《asm-generic/gpio.h》并且定義3個(gè)函數(shù)gpio_get_value(), gpio_set_value(), 和 gpio_cansleep()。
他也可以提供一個(gè)自定義的值:ARCH_NR_GPIOS,以便能更好的反映平臺(tái)實(shí)際使用的GPIO數(shù)目,并不浪費(fèi)靜態(tài)區(qū)域空間。(它應(yīng)該計(jì)數(shù) 內(nèi)建/SOC GPIO和GPIO擴(kuò)展器擴(kuò)展的數(shù)目)
ARCH_REQUIRE_GPIOLIB意味著此平臺(tái)上gpiolib代碼將永久編譯進(jìn)內(nèi)核
ARCH_WANT_OPTIONAL_GPIOLIB意味著gpiolib代碼默認(rèn)是關(guān)閉的,用于可以使能它并且將它可選的編譯進(jìn)內(nèi)核。
如果這些選項(xiàng)都未被選上,平臺(tái)不能通過GPIO-lib支持GPIO,這些代碼也不能被用戶使能。
那些函數(shù)瑣細(xì)的實(shí)現(xiàn)可以直接使用架構(gòu)代碼,它們經(jīng)常通過gpio_chip分配:
#define gpio_get_value__gpio_get_value
#define gpio_set_value__gpio_set_value
#define gpio_cansleep__gpio_cansleep
愛好者實(shí)現(xiàn)可以代替定義他們使用內(nèi)聯(lián)函數(shù),使用邏輯優(yōu)化存取特定的基于SOC的GPIO。例如,如果 引用的GPIO是常數(shù)“12”,getting或setting它的值可能只需要2個(gè)或3個(gè)指令,且從不休眠。如果這樣一個(gè)優(yōu)化是不可能的話,這些調(diào)用實(shí)現(xiàn)必須委托給架構(gòu)代碼,它會(huì)耗費(fèi)至少幾十個(gè)指令。為了位拆型I/O,這些指令的節(jié)約是有相當(dāng)大的意義的。
對(duì)于SOC來說,平臺(tái)特定的代碼為每個(gè)bank的片上GPIO定義和注冊(cè)了gpio_chip實(shí)例。那些GPIO應(yīng)該被編號(hào)和打上標(biāo)簽以匹配芯片廠商文檔,且直接匹配單板設(shè)計(jì)圖。他們可以從零開始一直到平臺(tái)特定的限制。這些GPIO通常集成到單板初始化過程中以使得它們總是有效的,從arch_initcall()到更早,它們總是可以為中斷服務(wù)。
板級(jí)支持
對(duì)于外部GPIO控制器(如I2C或SPI擴(kuò)展)、ASIC、多功能器件、FPGA或是CPLD,通常單板私有代碼例程注冊(cè)控制器器件且確定它們的驅(qū)動(dòng)使用什么GPIO號(hào)來調(diào)用gpiochip_add。它們的號(hào)碼經(jīng)常在平臺(tái)特定GPIO之后開始。
例如,單板setup代碼可以創(chuàng)建結(jié)構(gòu)標(biāo)示芯片想要暴露的GPIO的范圍,且使用platform_data傳遞它們到每個(gè)GPIO擴(kuò)展器芯片。這樣芯片驅(qū)動(dòng)的probe()歷程可以傳遞這些數(shù)據(jù)到gpiochip_add()。
初始化順序是很重要的。例如當(dāng)一個(gè)依賴于基于I2C的GPIO的設(shè)備,它的probe()例程應(yīng)該僅能在GPIO有效后調(diào)用。這意味著設(shè)備不能在GPIO可以工作之前注冊(cè)。一個(gè)解決這樣依賴的方法是在板級(jí)特定代碼中,對(duì)于這種gpio_chip控制器來提供setup()和teardown()回調(diào),這些板級(jí)特定的回調(diào)將注冊(cè)設(shè)備一旦所有的需要資源有效時(shí),并且在GPIO控制器無效時(shí)將它們移除。
用戶空間的Sysfs接口(可選)
使用gpiolib實(shí)現(xiàn)結(jié)構(gòu)的平臺(tái)可以選擇為GPIO配置一個(gè)sysfs用戶接口。這與debugfs接口不同,因?yàn)樗峁┝烁采wGPIO方向和值的控制而不只是顯示一個(gè)gpio狀態(tài)信息摘要。另外,它可以在產(chǎn)品系統(tǒng)中提供而不需要調(diào)試支持。
為系統(tǒng)給出對(duì)應(yīng)的硬件文檔,用戶空間可以知道例如GPIO#23控制著保護(hù)線,用于保護(hù)flash中的boot區(qū)域。系統(tǒng)升級(jí)程序可能需要臨時(shí)移除這個(gè)保護(hù),首先引入一個(gè)GPIO,然后改變它的輸出狀態(tài),接下來在重新使能寫保護(hù)之前升級(jí)代碼。通常用法中,GPIO#23將不會(huì)被觸碰,并且內(nèi)核也不需知道它的信息。
同樣依靠一個(gè)合適的硬件文檔,在一些系統(tǒng)用戶空間,GPIO可以被用于決定那些內(nèi)核并不關(guān)心的系統(tǒng)配置數(shù)據(jù)。對(duì)于一些任務(wù),簡(jiǎn)單用戶空間GPIO驅(qū)動(dòng)是系統(tǒng)真正需要的
注意,針對(duì)通用“LED和按鈕”的標(biāo)準(zhǔn)內(nèi)核驅(qū)動(dòng)存在對(duì)應(yīng)的GPIO任務(wù)“l(fā)eds-gpio”和“gpio-keys”。使用它們代替直接與GPIO通話,它們集成在內(nèi)核架構(gòu)比你的用戶態(tài)代碼可能更好。
Paths in Sysfs
Sysfs路徑
There are three kinds of entry in /sys/class/gpio:
/sys/class/gpio有3個(gè)入口條目:
-控制接口用于用戶空間獲取GPIO控制
-GPIO自己
-GPIO控制器(“gpio_chip”實(shí)例)
這是對(duì)于標(biāo)準(zhǔn)文件的補(bǔ)充,包括“device”符號(hào)
控制接口是只寫的:
/sys/class/gpio/
“export” ————通過寫GPIO的號(hào)碼到此文件,用戶空間可以要求內(nèi)核導(dǎo)出一個(gè)GPIO的控制到用戶空間
例如:“echo 19 》 export”將創(chuàng)建一個(gè)GPIO #19的“gpio19”節(jié)點(diǎn)(假設(shè)內(nèi)核代碼未申請(qǐng)此GPIO號(hào))。
“unexport”————與“export”效果相反
例如:“echo 19 》 unexport”將移除一個(gè)由“export”文件導(dǎo)出的“gpio19”節(jié)點(diǎn)。
GPIO信號(hào)擁有如/sys/class/gpio/gpio42/(對(duì)應(yīng)于GPIO#42)的路徑,并且具有下列讀寫屬性:
/sys/class/gpio/gpioN/
“direction”————讀為“in”或是“out”。這個(gè)值通??蓪憽憽皁ut”默認(rèn)初始化此值為低。為了確定無障礙操作,值“l(fā)ow”和“high”可以被寫入以配置GPIO的輸出初始化值。
注意這個(gè)屬性“將不存在”如果內(nèi)核不支持改變一個(gè)GPIO的方向,或者它不能被內(nèi)核代碼導(dǎo)出(不能顯式的允許用戶空間來重新配置GPIO的選項(xiàng)。)
“value”—————讀作“0”(低)或“1”(高)。如果GPIO被配置為一個(gè)輸出,這個(gè)值可寫;任何非零值均被視為高。
如果管腳可以被配置為中斷產(chǎn)生中斷管腳,且如果它已經(jīng)被配置為產(chǎn)生中斷(參考“edge”描述),你可以poll(2)此文件并且當(dāng)中斷觸發(fā)時(shí)poll(2)將返回。如果你使用了poll(2),設(shè)置POLLPRI和POLLERR事件。如果你使用select(2),在exceptfds中設(shè)置文件描述符。在poll(2)返回之后,有兩個(gè)選擇一是lseek(2)到sysfs文件的開始且讀新的值,另一個(gè)是關(guān)閉文件且重打開它來讀取新的值。(為何這樣設(shè)置?)
“edge”————讀作“none”、“rising”、“falling”或是“both”。寫這些字符串以選擇邊沿信號(hào),他將使得“value”文件上的poll(2)操作返回。
這個(gè)文件只在管腳可以配置為中斷產(chǎn)生輸入管腳時(shí)存在。
“active_low”————讀為0(false)或1(true)。寫任何非零值都會(huì)反轉(zhuǎn)讀或?qū)懙闹怠D壳昂秃髞淼膒oll(2)支持經(jīng)由edge屬性配置為“rising”或“falling”上升沿或下降沿將遵循這個(gè)設(shè)置。
GPIO控制器具有如/sys/class/gpio/gpiochip42/(針對(duì)控制器,實(shí)現(xiàn)GPIO開始于#42)的路徑,且具有下列制度屬性:
/sys/class/gpio/gpiochipN/
“base”————與N相等,是第一個(gè)被此芯片管理的GPIO
“l(fā)abel”————提供用于診斷(并不總是獨(dú)一無二的)
“ngpio”————管理的GPIO數(shù)(N到N+ngpio-1)
大多數(shù)情況下,單板文檔應(yīng)該提供GPIO的使用目的。雖然如此這些號(hào)碼并非總是固定的,一個(gè)子板上的GPIO可能與基礎(chǔ)板使用的不同。此種情況下,你可能需要使用gpiochip節(jié)點(diǎn)(可能與設(shè)計(jì)結(jié)合)來為每個(gè)信號(hào)決定正確的GPIO號(hào)碼。
從內(nèi)核代碼中導(dǎo)出
內(nèi)核代碼可以顯式管理那些使用gpio_request()申請(qǐng)的GPIO的導(dǎo)出
/* export the GPIO to userspace */
int gpio_export(unsigned gpio, bool direction_may_change);
/* reverse gpio_export() */
void gpio_unexport();
/* create a sysfs link to an exported GPIO node */
int gpio_export_link(struct device *dev, const char *name,
unsigned gpio)
/* change the polarity of a GPIO node in sysfs */
int gpio_sysfs_set_active_low(unsigned gpio, int value);
一個(gè)內(nèi)核驅(qū)動(dòng)申請(qǐng)一個(gè)GPIO后,它可以使用gpio_export()使得sysfs接口有效。驅(qū)動(dòng)可以控制信號(hào)方向是否可以改變。這使得驅(qū)動(dòng)可以防止用戶空間代碼不小心沖擊重要的系統(tǒng)狀態(tài)。
明確的exporting有助于調(diào)試(使得一些實(shí)驗(yàn)更簡(jiǎn)單),或是提供一個(gè)總是可以使用的接口,適合于bsp文檔。
GPIO被導(dǎo)出后,gpio_export_link()允許在sysfs的任何地方創(chuàng)建GPIO sysfs節(jié)點(diǎn)的符號(hào)鏈接。驅(qū)動(dòng)可以用此在它們自己設(shè)備sysfs目錄下提供指定名字的接口(鏈接到GPIO節(jié)點(diǎn))
驅(qū)動(dòng)可以使用gpio_sysfs_set_active_low()隱藏GPIO在用戶空間和單板之間的線極性不同。這僅影響sysfs接口。極性變換可以在gpio_export()之前和之后完成,并且前面使能的poll(2) (支持上升沿或下降沿事件)將被重新配置為遵循此設(shè)置。
評(píng)論
查看更多