在Linux驅動中使用gpio子系統


reference:

內核相關文檔

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工程師完成對應的動作。

Linux內核中GPIO子系統的軟件驅動分層圖

GPIO子系統有兩套接口

  1. 一是基於描述符(descriptor-based)的,相關api函數都是以"gpiod_"為前綴,它使用gpio_desc結構來表示一個引腳。

  2. 另一種是老(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_LOWGPIO_OPEN_DRAINGPIO_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_requestgpio_free
  • 設置GPIO方向:gpio_direction_inputgpio_direction_output
  • 獲取設置GPIO值:gpio_get_valuegpio_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_onegpio_request_array是它的擴展,devm_為前綴的是gpio devres機制的實現。

描述:請求一個/一組gpio。

參數解析:

  • gpio:gpio號,可以通過sdk開發包說明文檔查看,或者查看設備樹文件,也可以在gpio chip驅動的實現中找到。
  • flags:可以指定GPIOF_OPEN_DRAINGPIOF_OPEN_SOURCEGPIOF_DIR_INGPIOF_EXPORT等標志
    • 如果指定了GPIOF_DIR_IN,那么后面就不需要自己再額外調用gpio_direction_input或者gpio_direction_output了,
    • 如果指定了GPIOF_EXPORT,后面就不需要自己調用gpio_export了。
  • label:向系統中申請GPIO使用的標簽,類似於GPIO的名稱
  • array,num:是gpio_request_arraygpio_request_one的封裝,用於處理同時申請多個gpio的情形。
  • dev:帶有devm_前綴,用於帶設備資源管理版本的函數,因此在使用上面的函數時,需要指定設備的struct device指針,生命周期與設備相同。

返回值:成功返回0。

意義gpio_request主要做了以下動作:

  1. 檢查是否已經被申請,沒有的話,標記為已申請
  2. 填充label到該pin數據結構,用於debug
  3. 如果chip driver提供了request回調,調用它
  4. 如果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

例子

reference:https://www.cnblogs.com/Cqlismy/p/11891789.html

設備樹(高通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


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM