Linux內核驅動之GPIO子系統API接口概述


1、前言

在嵌入式Linux開發中,對嵌入式SoC中的GPIO進行控制非常重要,Linux內核中提供了GPIO子系統,驅動開發者在驅動代碼中使用GPIO子系統提供的API函數,便可以達到對GPIO控制的效果,例如將IO口的方向設置為輸入或輸出,當IO口的方向為輸入時,可以通過調用API函數獲取相應的IO口電平,當IO口設置為輸出方向時,可以調用相關的API函數去設置IO口電平,本文將簡單描述如何去使用Linux內核中GPIO子系統的API接口。

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

 

2、常用API接口

當我們在驅動代碼中要使用內核中提供的GPIO子系統,需要在驅動代碼中包含<linux/gpio.h>頭文件,另外,關於API接口函數的實現在內核源碼drivers/gpio/gpiolib.c文件中,關於GPIO子系統的使用說明文檔為Documentation/gpio.txt,該文檔具有更詳細的使用說明,接下來,將簡單介紹一下常用的API接口。

/*
 * "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_is_valid()用來判斷獲取到的gpio號是否是有效的,只有有效的gpio號,才能向內核中進行申請使用,因此,當我們從設備樹的設備節點獲取到gpio號,可以使用該函數進行判斷是否有效。

/* 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);

上面這兩個函數用來向系統中申請GPIO和釋放已經申請的GPIO,在函數gpio_request()中傳入的形參中,gpio為IO號,label為向系統中申請GPIO使用的標簽,類似於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;
};

結構體struct gpio用來描述一個需要配置的GPIO。

extern int gpio_request_one(unsigned gpio, unsigned long flags, const char *label);
extern int gpio_request_array(const struct gpio *array, size_t num);
extern void gpio_free_array(const struct gpio *array, size_t num);

上面的3個函數也是用來向系統申請或者釋放GPIO資源,函數gpio_request_one()用來申請單個GPIO,但是在申請的時候可以設置flag標志,例如,該函數在申請GPIO資源的同時,直接將GPIO的方向設置為輸入或者輸出,函數gpio_request_array()和gpio_free_array()用來向系統中申請或者釋放多個GPIO資源。

/* CONFIG_GPIOLIB: bindings for managed devices that want to request gpios */

struct device;

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);

上面的3個函數也是用來向系統申請或者釋放GPIO資源,但是函數帶有devm_前綴,也就是說,這是帶設備資源管理版本的函數,因此在使用上面的函數時,需要指定設備的struct device指針。

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值可以設置輸出的電平。

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_cansleep(unsigned int gpio)
{
    return __gpio_cansleep(gpio);
}

使用函數gpio_cansleep()判斷是否能處於休眠狀態,當該函數返回非零值時,說明讀或寫GPIO的電平值時能夠處於休眠狀態。

static inline int gpio_get_value_cansleep(unsigned gpio)
{
    return gpiod_get_raw_value_cansleep(gpio_to_desc(gpio));
}
static inline void gpio_set_value_cansleep(unsigned gpio, int value)
{
    return gpiod_set_raw_value_cansleep(gpio_to_desc(gpio), value);
}

上面的函數同樣是獲取或者設置GPIO的電平值,只不過是帶休眠版本的函數。

static inline int gpio_to_irq(unsigned int gpio)
{
    return __gpio_to_irq(gpio);
}

函數gpio_to_irq()用於將當前已經申請GPIO號轉換為IRQ號,也就是獲取當前GPIO的中斷線,函數調用成功后,將返回對應的IRQ號。

以上就是Linux內核中GPIO子系統的常用的API接口,關於其代碼的實現,可以進一步分析Linux內核源碼。

 

3、實例說明

 在上面,已經分析過了Linux驅動中GPIO子系統的常用API接口函數,接下來,將通過一個具體的實例來講解GPIO子系統中API接口如何使用。

首先,先了解一下GPIO的使用思路,如下所示:

#include <linux/module.h>
#include <linux/init.h>
#include <linux/gpio.h>
...

struct gpio_drvdata {
    /* gpio號 */
    int gpio_num;
    ...
};

static int __init gpio_init(void)
{
   struct gpio_drvdata *ddata;
   int ret;
   
   ddata = kzalloc(sizeof(*ddata), GFP_KERNEL);
   if (!ddata)
       return -ENOMEM;
    ...
    
    /* gpio初始化 */
    if (gpio_is_valid(ddata->gpio_num)) {
        /* 申請gpio資源 */
        ret = gpio_request(ddata->gpio_num, "test-gpio");
        if (ret) {
            printk("failed to request gpio\n");
            return ret;
        }
        
        /* 設置gpio的方向(輸出) */
        ret = gpio_direction_output(ddata->gpio_num, 0);
        if (ret) {
            printk("failed to set output direction\n");
            return ret;
        }
        
        /* 在sysfs中導出gpio(方向能改變) */
        ret = gpio_export(ddata->gpio_num, true);
        if (ret) {
            printk("failed to export gpio in sysfs\n");
            return ret;
        }
        
        /* 設置gpio電平值(高電平) */
        gpio_set_value(ddata->gpio_num, 1);
    }
    ...
    
    return 0;
}

static void __exit gpio_exit(void)
{
    ...
    /* 釋放已經申請的gpio資源 */
    if (gpio_is_valid(ddata->gpio_num))
        gpio_free(ddata->gpio_num);
    ...
}

module_init(gpio_init);
module_exit(gpio_exit);

上面的代碼已經很清楚地體現了GPIO的使用思路,當驅動模塊加載的時候,需要獲取要使用的GPIO號,然后需要向系統申請使用GPIO資源,資源申請成功后,我們需要設置GPIO的方向(輸入或者輸出),此外,還能使用gpio_export()函數在sysfs中導出GPIO,導出的好處在於可以方便地debug代碼,當驅動模塊卸載時,需要將已經申請的GPIO資源進行釋放掉,基本的使用思路就這樣,比較簡單。

接下來,給出具體的實例,功能為簡單的GPIO控制,驅動程序中嵌入platform_driver框架,另外,在設備節點中導出ctrl和gpio兩個屬性文件,應用層對ctrl屬性文件進行讀寫操作,能夠獲取和設置GPIO的電平狀態,對gpio讀操作,能夠獲取使用的GPIO號,下面是具體實例的實現過程:

先定義相關的設備節點,如下:

dev_gpio {
    status = "okay";
    compatible = "dev-gpio";
    label = "test_gpio";
    gpios = <&msm_gpio 68 0>;
};

使用了GPIO_68這個引腳,compatible屬性的值用於和驅動程序進行匹配,接下來是驅動代碼的實現:

#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;
    }

    if (gpio_is_valid(pdata->gpio_num)) {
        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;
        }
        
        ret = gpio_direction_output(pdata->gpio_num, 0);
        if (ret) {
            dev_err(dev, "failed to set gpio direction for output\n");
            goto fail3;
        }
        
        ret = gpio_export(pdata->gpio_num, false);
        if (ret) {
            dev_err(dev, "failed to export gpio %d\n", pdata->gpio_num);
            goto fail3;
        }
    }

    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);
    
    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這個驅動框架,另外,在設備節點中導出了ctrl和gpio屬性文件,便可以很方便地在應用層進行設備的GPIO控制了。

接下來,看看實現的效果,首先是生成的設備節點信息,可以使用下面的命令:

# ls -al
# cat uevent

輸出如下:

通過uevent可以看到設備節點的路徑以及驅動和設備匹配的屬性值,此外,在上面圖片中,也可以看到ctrl和gpio屬性文件已經被成功導出到了該設備節點下面,使用下面的命令可以進行GPIO的控制:

##將GPIO置高電平
# echo "enable" > ctrl

##將GPIO置低電平
# echo "disable" > ctrl

控制的效果如下所示:

另外,在驅動程序中,我們使用了函數gpio_export()在sysfs中導出相關的GPIO信息,我們可以到/sys/class/gpio/gpioN目錄下查看相關的GPIO信息,如下:

屬性文件value保存了當前GPIO的電平值,當我們調用gpio_export()函數時,將第二個形參傳入為true時,表示GPIO的方向還能改變,將在上面的目錄中生成direction屬性文件,里面保存了當前GPIO的方向,我們還能使用echo命令對文件進行寫操作,從而改變GPIO的方向。

 

4、小結

 本文簡單介紹了Linux中GPIO子系統中的常用的API接口函數,並且給出了驅動程序中使用GPIO子系統的思路,另外還通過一個簡單的實例進行說明。


免責聲明!

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



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