GPIO驅動實踐-基於4.18.7核心
1. GPIO子系統的變化
最近在研究最新的Linux kernel 4.18.7時,發現其關於GPIO子系統的發生了比較大的變化。而且在linux/gpio.h中做了關於宣告:
* This is the LEGACY GPIO bulk include file, including legacy APIs. It is * used for GPIO drivers still referencing the global GPIO numberspace, * and should not be included in new code. * * If you're implementing a GPIO driver, only include <linux/gpio/driver.h> * If you're implementing a GPIO consumer, only include <linux/gpio/consumer.h> *
上述說明的基本含義是:linux/gpio.h已經作為歷史API出現,並且建議最新的使用GPIO的驅動程式不要再使用該linux/gpio.h中的API。同時,對於GPIO控制器驅動器程式,應該引用linux/gpio/driver.h檔案;對於使用GPIO的一般驅動程式,應該引用linux/gpio/consumer.h檔案。
新版GPIO相關的API中,每個API第一個引數都改成了gpio_desc,下面是該資料結構的定義:
struct gpio_desc { struct gpio_device *gdev; unsigned long flags; /* flag symbols are bit numbers */ #define FLAG_REQUESTED 0 #define FLAG_IS_OUT 1 #define FLAG_EXPORT 2 /* protected by sysfs_lock */ #define FLAG_SYSFS 3 /* exported via /sys/class/gpio/control */ #define FLAG_ACTIVE_LOW 6 /* value has active low */ #define FLAG_OPEN_DRAIN 7 /* Gpio is open drain type */ #define FLAG_OPEN_SOURCE 8 /* Gpio is open source type */ #define FLAG_USED_AS_IRQ 9 /* GPIO is connected to an IRQ */ #define FLAG_IS_HOGGED 11 /* GPIO is hogged */ #define FLAG_TRANSITORY 12 /* GPIO may lose value in sleep or reset */ /* Connection label */ const char *label; /* Name of the GPIO */ const char *name; };
gpio_desc是GPIO的不透明描述符。 gpio_desc是使用gpiod_get()獲得的,並且優於舊的基於整數的控制代碼。與基於gpio num的整數相反,指向gpio_desc的指標保證在GPIO釋放之前始終有效。
2. 程式設計模式
新版的GPIO APIs同樣工作於GPIOLIB框架之下,Linux核心需要啟用CONFIG_GPIOLIB配置選項;
2.1. GPIO APIs
如果使用gpio編寫一般的驅動程式,需要包含linux/gpio/consumer.h,其中比較重要的API定義如下:
獲取/釋放gpio_desc的介面:
struct gpio_desc *__must_check gpiod_get(struct device *dev, const char *con_id, enum gpiod_flags flags); struct gpio_desc *__must_check gpiod_get_optional(struct device *dev, const char *con_id, enum gpiod_flags flags); struct gpio_descs *__must_check gpiod_get_array(struct device *dev, const char *con_id, enum gpiod_flags flags); struct gpio_descs *__must_check gpiod_get_array_optional(struct device *dev, const char *con_id, enum gpiod_flags flags); void gpiod_put(struct gpio_desc *desc); void gpiod_put_array(struct gpio_descs *descs);
基於devm機制的獲取/釋放gpio_desc的介面:
struct gpio_desc *__must_check devm_gpiod_get(struct device *dev, const char *con_id, enum gpiod_flags flags); struct gpio_desc *__must_check devm_gpiod_get_index(struct device *dev, const char *con_id, unsigned int idx, enum gpiod_flags flags); struct gpio_desc *__must_check devm_gpiod_get_optional(struct device *dev, const char *con_id, enum gpiod_flags flags); struct gpio_desc *__must_check devm_gpiod_get_index_optional(struct device *dev, const char *con_id, unsigned int index, enum gpiod_flags flags); struct gpio_descs *__must_check devm_gpiod_get_array(struct device *dev, const char *con_id, enum gpiod_flags flags); struct gpio_descs *__must_check devm_gpiod_get_array_optional(struct device *dev, const char *con_id, enum gpiod_flags flags); void devm_gpiod_put(struct device *dev, struct gpio_desc *desc); void devm_gpiod_put_array(struct device *dev, struct gpio_descs *descs);
配置gpio輸入/輸出模式的介面:
int gpiod_get_direction(struct gpio_desc *desc); int gpiod_direction_input(struct gpio_desc *desc); int gpiod_direction_output(struct gpio_desc *desc, int value); int gpiod_direction_output_raw(struct gpio_desc *desc, int value);
獲取、設定GPIO I/O值的介面( non-sleeping context):
int gpiod_get_value(const struct gpio_desc *desc); int gpiod_get_array_value(unsigned int array_size, struct gpio_desc **desc_array, int *value_array); void gpiod_set_value(struct gpio_desc *desc, int value); void gpiod_set_array_value(unsigned int array_size, struct gpio_desc **desc_array, int *value_array); int gpiod_get_raw_value(const struct gpio_desc *desc); int gpiod_get_raw_array_value(unsigned int array_size, struct gpio_desc **desc_array, int *value_array); void gpiod_set_raw_value(struct gpio_desc *desc, int value); int gpiod_set_raw_array_value(unsigned int array_size, struct gpio_desc **desc_array, int *value_array);
5.獲取、設定GPIO I/O值的介面( sleeping context):
int gpiod_get_value_cansleep(const struct gpio_desc *desc);
int gpiod_get_array_value_cansleep(unsigned int array_size,
struct gpio_desc **desc_array,
int *value_array);
void gpiod_set_value_cansleep(struct gpio_desc *desc, int value);
void gpiod_set_array_value_cansleep(unsigned int array_size,
struct gpio_desc **desc_array,
int *value_array);
int gpiod_get_raw_value_cansleep(const struct gpio_desc *desc);
int gpiod_get_raw_array_value_cansleep(unsigned int array_size,
struct gpio_desc **desc_array,
int *value_array);
void gpiod_set_raw_value_cansleep(struct gpio_desc *desc, int value);
int gpiod_set_raw_array_value_cansleep(unsigned int array_size,
struct gpio_desc **desc_array,
int *value_array);
注:
gpiod_xxx_cansleep系列介面與gpiod_xxx之間的區別:
6.新舊gpio描述符之間的轉換介面:
struct gpio_desc *gpio_to_desc(unsigned gpio);
int desc_to_gpio(const struct gpio_desc *desc);
2.2 程式設計規範
使用新GPIO APIs驅動一個普通的GPIO時,基本規範如下:
- 使用devm_gpiod_get或者gpiod_get過去gpio_desc描述符;
- 使用gpiod_direction_input或者gpiod_direction_output配置gpio的輸入/輸出模式;
- 使用gpiod_get_value或者gpiod_get_value系列函式獲取、設定gpio I/O值;
- 使用devm_gpiod_put或者gpiod_put釋放gpio資源;
3. 驅動示例
下面列舉一個基於最新的gpio驅動程式設計介面的例子,我們選取driver/input/gpio_mouse.c最為示例。下面為典型的gpio_mouse裝置的DTS配置資訊:
gpio-mouse { compatible = "gpio-mouse";
scan-interval-ms = <50>;
up-gpios = <&gpio0 0 GPIO_ACTIVE_LOW>;
down-gpios = <&gpio0 1 GPIO_ACTIVE_LOW>;
left-gpios = <&gpio0 2 GPIO_ACTIVE_LOW>;
right-gpios = <&gpio0 3 GPIO_ACTIVE_LOW>;
button-left-gpios = <&gpio0 4 GPIO_ACTIVE_LOW>;
button-middle-gpios = <&gpio0 5 GPIO_ACTIVE_LOW>;
button-right-gpios = <&gpio0 6 GPIO_ACTIVE_LOW>;
};
gpio_mouse.c的gpio_mouse_probe對於各個gpio進行了資源申請:
......
gmouse->up = devm_gpiod_get(dev, "up", GPIOD_IN);------------------------------>(1)
if (IS_ERR(gmouse->up))
return PTR_ERR(gmouse->up);
gmouse->down = devm_gpiod_get(dev, "down", GPIOD_IN);
if (IS_ERR(gmouse->down))
return PTR_ERR(gmouse->down);
gmouse->left = devm_gpiod_get(dev, "left", GPIOD_IN);
if (IS_ERR(gmouse->left))
return PTR_ERR(gmouse->left);
gmouse->right = devm_gpiod_get(dev, "right", GPIOD_IN);
if (IS_ERR(gmouse->right))
return PTR_ERR(gmouse->right);
......
其中,(1)中”up”引數對應於DTS中的”up-gpios”,注意,如果devm_gpiod_get的con_id引數在DTS中找不到對應的配置,那麼devm_gpiod_get將返回NULL。
x = gpiod_get_value(gpio->right) - gpiod_get_value(gpio->left);
y = gpiod_get_value(gpio->down) - gpiod_get_value(gpio->up);
上述程式碼通過gpiod_get_value獲取各個gpio的I/O值。
4. 總結
雖然,核心對於GPIO相關的APIs做了較大的改動,但是,基本的程式設計方式並未發生改變,熟悉舊的GPIO APIs的使用者可以很容易的使用新版的GPIO APIs進行驅動程式的開發。