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






























評論