1. 程式人生 > >GPIO驅動實踐-基於4.18.7核心

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定義如下:

  1. 獲取/釋放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);
    
  2. 基於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);
    
  3. 配置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);
    
  4. 獲取、設定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時,基本規範如下:

  1. 使用devm_gpiod_get或者gpiod_get過去gpio_desc描述符;
  2. 使用gpiod_direction_input或者gpiod_direction_output配置gpio的輸入/輸出模式;
  3. 使用gpiod_get_value或者gpiod_get_value系列函式獲取、設定gpio I/O值;
  4. 使用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進行驅動程式的開發。