GPIO子系统视频介绍

参考资料:

  • Linux 5.x内核文档
    • Linux-5.4\Documentation\driver-api
    • Linux-5.4\Documentation\devicetree\bindings\gpio\gpio.txt
  • Linux 4.x内核文档
    • Linux-4.9.88\Documentation\gpio
    • Linux-4.9.88\Documentation\devicetree\bindings\gpio\gpio.txt

通用属性

Active-High and Active-Low

gpiod_set_value(gpio, 1);  // 输出逻辑1
                           // 在Active-High的情况下它会输出高电平
                           // 在Active-Low的情况下它会输出低电平

Open Drain and Open Source

多个GPIO驱动同时驱动一个电路时,就需要设置Open Drain或Open Source。

  • Open Drain:引脚被设置为低电平时才会驱动电路,典型场景是I2C接口。
  • Open Source:引脚被设置为高电平时才会驱动电路

使用GPIO子系统要掌握的重要概念

设备树

在设备树 dtsi 中,“GPIO组”就是一个GPIO Controller,这通常都由芯片厂家设置好。我们要做的是找到它名字,比如“gpio1”,然后在 dts 中指定要用它里面的哪个引脚,比如<&gpio1 0>。

下图是一些芯片的GPIO控制器节点,它们一般都是厂家定义好,在xxx.dtsi文件中:

我们暂时只需要关心里面的这2个属性:

  • “gpio-controller”表示这个节点是一个GPIO Controller,它下面有很多引脚。
  • “#gpio-cells = <2>”表示这个控制器下每一个引脚要用2个32位的数(cell)来描述。这个 2 指的是除了gpioN 以外还要 2个值来描述。 普遍的用法是,用第1个cell来表示哪一个引脚,用第2个cell来表示有效电平。例如在使用引脚的时候,如 gpios = <&gpio5 3 GPIO_ACTIVE_LOW>

在自己的设备节点中使用属性[<name>-]gpios,name 用来做标识作用,示例如下:

代码

GPIO子系统有两套接口:基于描述符的(descriptor-based)、老的(legacy)。前者的函数都有前缀“gpiod_”,它使用gpiodesc结构体来表示一个引脚;后者的函数都有前缀“gpio”,它使用一个整数来表示一个引脚。

驱动程序中要包含头文件,

#include <linux/gpio/consumer.h>   // descriptor-based
或
#include <linux/gpio.h>            // legacy

常用的函数:

  • 获得GPIO

    descriptor-based legacy 说明
    gpiod_get gpio_request
    gpiod_get_index
    gpiod_get_array gpio_request_array
    devm_gpiod_get
    devm_gpiod_get_index
    devm_gpiod_get_array
  • 设置方向

    descriptor-based legacy 说明
    gpiod_direction_input gpio_direction_input
    gpiod_direction_output gpio_direction_output
  • 读值、写值

    descriptor-based legacy 说明
    gpiod_get_value gpio_get_value
    gpiod_set_value gpio_set_value
  • 释放GPIO

    descriptor-based legacy 说明
    gpio_free gpio_free
    gpiod_put gpio_free_array
    gpiod_put_array
    devm_gpiod_put
    devm_gpiod_put_array

有前缀devm_的含义是“设备资源管理”(Managed Device Resource),这是一种自动释放资源的机制。比如在Linux开发过程中,先申请了GPIO,再申请内存;如果内存申请失败,那么在返回之前就需要先释放GPIO资源。如果使用devm的相关函数,在内存申请失败时可以直接返回:设备的销毁函数会自动地释放已经申请了的GPIO资源。建议使用devm_版本的相关函数。

代码从设备树中获取数值

    foo_device {
        compatible = "acme,foo";
        ...
        led-gpios = <&gpio 15 GPIO_ACTIVE_HIGH>, /* red */
                <&gpio 16 GPIO_ACTIVE_HIGH>, /* green */
                <&gpio 17 GPIO_ACTIVE_HIGH>; /* blue */

        power-gpios = <&gpio 1 GPIO_ACTIVE_LOW>;
    };
struct gpio_desc *red, *green, *blue, *power;
red = gpiod_get_index(dev, "led", 0, GPIOD_OUT_HIGH);
green = gpiod_get_index(dev, "led", 1, GPIOD_OUT_HIGH);
blue = gpiod_get_index(dev, "led", 2, GPIOD_OUT_HIGH);
power = gpiod_get(dev, "power", GPIOD_OUT_HIGH);

要注意的是,gpiod_set_value设置的值是“逻辑值”,不一定等于物理值。

旧的“gpio_”函数没办法根据设备树信息获得引脚,它需要先知道引脚号。在GPIO子系统中,每注册一个GPIO Controller时会确定它的“base number”,那么这个控制器里的第n号引脚的号码就是:base number + n。但是如果硬件有变化、设备树有变化,这个base number并不能保证是固定的,应该查看sysfs来确定base number。

sysfs中的访问方法_IMX6ULL

在sysfs中访问GPIO,实际上用的就是引脚号,老的方法。

计算引脚号

先确定某个GPIO Controller的基准引脚号(base number),再计算出某个引脚的号码。

  • 先在开发板的/sys/class/gpio目录下,找到各个gpiochipXXX目录
  • 然后进入某个gpiochip目录,查看文件label的内容
  • 根据label的内容对比设备树. label内容来自设备树,比如它的寄存器基地址。用来跟设备树(dtsi文件)比较,就可以知道这对应哪一个GPIO Controller。

基于sysfs操作引脚

echo  110 > /sys/class/gpio/export
echo in > /sys/class/gpio/gpio110/direction
cat /sys/class/gpio/gpio110/value
echo  110 > /sys/class/gpio/unexport

注意:如果驱动程序已经使用了该引脚,那么将会export失败,会提示 busy.

对于输出引脚,假设引脚号为N,可以用下面的方法设置它的值为1:

echo  N > /sys/class/gpio/export
echo out > /sys/class/gpio/gpioN/direction
echo 1 > /sys/class/gpio/gpioN/value
echo  N > /sys/class/gpio/unexport

基于GPIO子系统的LED驱动程序

GPIO的地位跟其他模块,比如I2C、UART的地方是一样的,要使用某个引脚,需要先把引脚配置为GPIO功能,这要使用Pinctrl子系统,只需要在设备树里指定就可以。在驱动代码上不需要我们做任何事情。

编程示例

先按照正常的驱动框架去写,再进行细化。

probe

gpiod_get 的第二个参数对应到设备树中的名称需要去掉后面的 -gpios. 比如如果参数是 "led" 那么对应的设备树中是 led-gpios = <&gpio0 18 GPIO_ACTIVE_HIGH>.

86      /* 4.1 设备树中定义有: led-gpios=<...>; */
87     led_gpio = gpiod_get(&pdev->dev, "led", 0);
88      if (IS_ERR(led_gpio)) {
89              dev_err(&pdev->dev, "Failed to get GPIO for led\n");
90              return PTR_ERR(led_gpio);
91      }
93      /* 4.2 注册file_operations      */
94      major = register_chrdev(0, "100ask_led", &led_drv);  /* /dev/led */
95
96      led_class = class_create(THIS_MODULE, "100ask_led_class");
97      if (IS_ERR(led_class)) {
98              printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
99              unregister_chrdev(major, "led");
100             gpiod_put(led_gpio);
101             return PTR_ERR(led_class);
102     }
103
104     device_create(led_class, NULL, MKDEV(major, 0), NULL, "100ask_led%d", 0); /*dev/100ask_led0 */
105

open函数中调用GPIO函数设置引脚方向

51 static int led_drv_open (struct inode *node, struct file *file)
52 {
53      //int minor = iminor(node);
54
55      printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
56      /* 根据次设备号初始化LED */
57      gpiod_direction_output(led_gpio, 0);
58
59      return 0;
60 }

write函数中调用GPIO函数设置引脚值

34 /* write(fd, &val, 1); */
35 static ssize_t led_drv_write (struct file *file, const char __user *buf, size_t size, loff_t *offset)
36 {
37      int err;
38      char status;
39      //struct inode *inode = file_inode(file);
40      //int minor = iminor(inode);
41
42      printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
43      err = copy_from_user(&status, buf, 1);
44
45      /* 根据次设备号和status控制LED */
46      gpiod_set_value(led_gpio, status);
47
48      return 1;
49 }

释放GPIO

gpiod_put(led_gpio);

在100ASK_IMX6ULL上机实验

设备树中修改:

&iomuxc_snvs {
……
    imx6ul-evk {    
    myled_for_gpio_subsys: myled_for_gpio_subsys{ 
            fsl,pins = <
                MX6ULL_PAD_SNVS_TAMPER3__GPIO5_IO03        0x000110A0
            >;
        };
……
}
        myled {
            compatible = "100ask,leddrv";
            pinctrl-names = "default";
            pinctrl-0 = <&myled_for_gpio_subsys>;
            led-gpios = <&gpio5 3 GPIO_ACTIVE_LOW>;
        };

GPIO子系统层次与数据结构

参考资料:

  • Linux 5.x内核文档
    • Linux-5.4\Documentation\driver-api
    • Linux-5.4\Documentation\devicetree\bindings\gpio\gpio.txt
    • Linux-5.4\drivers\gpio\gpio-74x164.c
  • Linux 4.x内核文档
    • Linux-4.9.88\Documentation\gpio
    • Linux-4.9.88\Documentation\devicetree\bindings\gpio\gpio.txt
    • Linux-4.9.88\drivers\gpio\gpio-74x164.c

层次

GPIOLIB向下提供的接口 在 drivers\gpio\gpiolib.c 中的 int gpiochip_add_data(struct gpio_chip *chip, void *data)

重要的3个核心数据结构

以Linux面向对象编程的思想,一个GPIO Controller必定会使用一个结构体来表示,这个结构体必定含有这些信息:

  • GPIO引脚信息. 有多少个引脚?有哪些引脚?
  • 控制引脚的函数. 设置引脚方向、读取/设置数值
  • 中断相关的函数. 把引脚转换为中断

三个核心数据结构的关系

在 Linux 内核的 GPIO 子系统中,gpio_device、gpio_chip 和 gpio_desc 是三个核心数据结构,它们共同管理 GPIO 设备的硬件和软件资源。以下是它们的关系和作用:

gpio_device

作用
  • 表示一个 GPIO 设备,是 GPIO 子系统的最高层次结构。
  • 每个 GPIO 设备对应一个 gpio_device,通常与硬件中的 GPIO 控制器(如 SoC 中的 GPIO 模块)相对应。
关键字段
字段 说明
chip 指向 gpio_chip 结构体的指针,表示 GPIO 控制器。
base GPIO 的起始编号(全局编号)。
ngpio GPIO 的数量。
label GPIO 设备的名称(如 "gpiochip0")。
使用场景
  • 内核内部使用,用于管理 GPIO 设备和全局 GPIO 编号。
  • 用户空间通过 /dev/gpiochipX 访问 GPIO 设备。

gpio_chip

作用
  • 表示一个 GPIO 控制器,是 GPIO 设备的硬件抽象。
  • 每个 GPIO 控制器对应一个 gpio_chip,提供对 GPIO 引脚的操作函数(如设置方向、读取值、写入值等)。
关键字段
字段 说明
label GPIO 控制器的名称(如 "gpiochip0")。
ngpio GPIO 的数量。
base GPIO 的起始编号(全局编号)。
direction_input 函数指针,用于将 GPIO 设置为输入模式。
direction_output 函数指针,用于将 GPIO 设置为输出模式。
get 函数指针,用于读取 GPIO 的值。
set 函数指针,用于设置 GPIO 的值。
使用场景
  • 驱动开发者实现 gpio_chip,提供 GPIO 控制器的硬件操作函数。
  • 内核通过 gpio_chip 操作 GPIO 引脚。

gpio_desc

作用
  • 表示一个 GPIO 引脚,是 GPIO 引脚的软件抽象。
  • 每个 GPIO 引脚对应一个 gpio_desc,用于管理引脚的状态和操作。
关键字段
字段 说明
chip 指向 gpio_chip 结构体的指针,表示 GPIO 控制器。
flags GPIO 的标志(如方向、状态等)。
label GPIO 引脚的名称(如 "led")。
使用场景
  • 驱动开发者通过 gpio_desc 操作 GPIO 引脚(如设置方向、读取值、写入值等)。
  • 用户空间通过 libgpiod 库访问 GPIO 引脚。

三者的关系

以下是 gpio_device、gpio_chip 和 gpio_desc 的关系:

gpio_device
    |
    |-- gpio_chip
    |       |
    |       |-- direction_input
    |       |-- direction_output
    |       |-- get
    |       |-- set
    |
    |-- gpio_desc
            |
            |-- chip
            |-- flags
            |-- label
  • gpio_device:
    • 表示一个 GPIO 设备,管理多个 gpio_chip 和 gpio_desc。
    • 提供全局 GPIO 编号和设备管理功能。
  • gpio_chip:
    • 表示一个 GPIO 控制器,提供对 GPIO 引脚的操作函数。
    • 每个 gpio_chip 对应一个硬件 GPIO 模块。
  • gpio_desc:
    • 表示一个 GPIO 引脚,管理引脚的状态和操作。
    • 每个 gpio_desc 对应一个具体的 GPIO 引脚。

总结

  • gpio_device:表示一个 GPIO 设备,管理全局 GPIO 编号和设备资源。
  • gpio_chip:表示一个 GPIO 控制器,提供对 GPIO 引脚的操作函数。
  • gpio_desc:表示一个 GPIO 引脚,管理引脚的状态和操作。

三者共同构成了 Linux GPIO 子系统的核心,用于管理 GPIO 设备和引脚。

gpio_device

每个GPIO Controller用一个gpio_device来表示:

  • 里面每一个gpio引脚用一个gpio_desc来表示
  • gpio引脚的函数(引脚控制、中断相关),都放在gpio_chip里

gpio_chip

我们并不需要自己创建gpio_device,编写驱动时要创建的是gpio_chip,里面提供了:

  • 控制引脚的函数
  • 中断相关的函数
  • 引脚信息:支持多少个引脚?各个引脚的名字?

gpio_desc

我们去使用GPIO子系统时,首先是获得某个引脚对应的gpio_desc。 gpio_device表示一个GPIO Controller,里面支持多个GPIO。 在gpio_device中有一个gpio_desc数组,每一引脚有一项gpio_desc。

编写GPIO Controller驱动程序

分配、设置、注册gpioc_chip结构体,示例:drivers\gpio\gpio-74x164.c

IMX6ULL的GPIO驱动源码分析

参考资料:

  • Linux 4.x内核文档
    • Linux-4.9.88\Documentation\gpio
    • Linux-4.9.88\Documentation\devicetree\bindings\gpio\gpio.txt
    • Linux-4.9.88\drivers\gpio\gpio-mxc.c
    • Linux-4.9.88\arch\arm\boot\dts\imx6ull.dtsi

设备树

已有的设备树文件分为 dts, dtsi 等,直接看比较麻烦。 简单点,可以把 dtb 反汇编为单个的 dts 文件,然后去里面查找相对容易点。 通过 gpio-controller 找到 gpio 的节点,然后通过 grep 搜索这个节点的名称,找到对应的 dts 源文件。 通过 compatible 在源码中找到对应的驱动。

GPIO控制器的设备树中,有两项是必须的:

  • gpio-controller : 表明这是一个GPIO控制器
  • gpio-cells : 指定使用多少个cell(就是整数)来描述一个引脚

源码

Linux-4.9.88\drivers\gpio\gpio-mxc.c static int mxc_gpio_probe(struct platform_device *pdev)

分配

    struct mxc_gpio_port *port;
    port = devm_kzalloc(&pdev->dev, sizeof(*port), GFP_KERNEL);

设置gpio_chip

    err = bgpio_init(&port->gc, &pdev->dev, 4,
             port->base + GPIO_PSR,
             port->base + GPIO_DR, NULL,
             port->base + GPIO_GDIR, NULL,
             BGPIOF_READ_OUTPUT_REG_SET);
    port->gc.request = mxc_gpio_request;
    port->gc.free = mxc_gpio_free;
    port->gc.parent = &pdev->dev;
    port->gc.to_irq = mxc_gpio_to_irq;
    port->gc.base = (pdev->id < 0) ? of_alias_get_id(np, "gpio") * 32 :
                         pdev->id * 32;

of_alias_get_id 这个是从设备树的 aliases 里面获取 gpio 后面的值。比如 gpio0 = &gpio1; of_alias_get_id 返回的就是 0.

注册gpio_chip

err = devm_gpiochip_add_data(&pdev->dev, &port->gc, port);

编写一个虚拟GPIO控制器的驱动程序

参考资料:

  • Linux 5.x内核文档
    • Linux-5.4\Documentation\driver-api
    • Linux-5.4\Documentation\devicetree\bindings\gpio\gpio.txt
    • Linux-5.4\drivers\gpio\gpio-74x164.c
  • Linux 4.x内核文档
    • Linux-4.9.88\Documentation\gpio
    • Linux-4.9.88\Documentation\devicetree\bindings\gpio\gpio.txt
    • Linux-4.9.88\drivers\gpio\gpio-74x164.c

设备树

gpio_virt: virtual_gpiocontroller {
    compatible = "100ask,virtual_gpio";
    gpio-controller;
    #gpio-cells = <2>;
    ngpios = <4>;
};

ngpios 表明支持 4个引脚。

源码

还是分配,设置,注册这一套。

  • 在设置的时候,需要专门设置一下 ngpio, 数值从设备树中获取。
  • 设置的时候,如果 gc.base = -1 是让系统自动设置 base 值。

调试与使用虚拟的GPIO控制器

设备树

/ {
    gpio_virt: virtual_gpiocontroller {
        compatible = "100ask,virtual_gpio";
        gpio-controller;
        #gpio-cells = <2>;
        ngpios = <4>;
    };

    myled {
        compatible = "100ask,leddrv";
        led-gpios = <&gpio_virt 2 GPIO_ACTIVE_LOW>;
    };
};

GPIO子系统与Pinctrl子系统的交互

使用GPIO前应该设置Pinctrl

要使用pinA来控制LED,首先要通过Pinctrl子系统把它设置为GPIO功能,然后才能设置它为输出引脚、设置它的输出值。所以在设备树文件里,应该添加Pinctrl的内容:

virtual_pincontroller {
    compatible = "100ask,virtual_pinctrl";
    myled_pin: myled_pin {
            functions = "gpio";
            groups = "pin0";
            configs = <0x11223344>;
    };
};

gpio_virt: virtual_gpiocontroller {
    compatible = "100ask,virtual_gpio";
    gpio-controller;
    #gpio-cells = <2>;
    ngpios = <4>;
};

myled {
    compatible = "100ask,leddrv";
    led-gpios = <&gpio_virt 0 GPIO_ACTIVE_LOW>;
    pinctrl-names = "default";
    pinctrl-0 = <&myled_pin>;    
};

但是很多芯片,并不要求在设备树中把把引脚复用为GPIO功能。一个引脚可能被用作GPIO,也可能被用作I2C,GPIO和I2C这些功能是相同地位的。要用作GPIO,需要先通过Pinctrl把引脚复用为GPIO功能。 但是Pinctrl和GPIO关系密切,当你使用gpiod_get获得GPIO引脚时,它就偷偷地通过Pinctrl把引脚复用为GPIO功能了。

我觉得原因是,一般情况下,引脚默认的配置就是 gpio,所以,在作为 gpio 的时候,有些芯片,会自动的用 pinctrl 来配置。

GPIO和Pinctrl的映射关系

示例

  • 左边的Pinctrl支持8个引脚,在Pinctrl的内部编号为0~7
  • 图中有2个GPIO控制器
    • GPIO0内部引脚编号为0~3,假设在GPIO子系统中全局编号为100~103
    • GPIO1内部引脚编号为0~3,假设在GPIO子系统中全局编号为104~107
  • 假设我们要使用pin1_1,应该这样做:
    • 根据GPIO1的内部编号1,可以换算为Pinctrl子系统中的编号5
    • 使用Pinctrl的函数,把第5个引脚配置为GPIO功能

这种对应关系可以通过设备树中的 gpio-ranges 来表明 gpio 和 pinctrl 直接的关联。

// 当前GPIO控制器的0号引脚, 对应pinctrlA中的128号引脚, 数量为12
gpio-ranges = <&pinctrlA 0 128 12>; 

数据结构

GPIO调用Pinctrl的过程

可以从 drivers\gpio\gpiolib.c 中的 gpiod_get 开始分析. GPIO子系统中的request函数,用来申请某个GPIO引脚,它会导致Pinctrl子系统中的这2个函数之一被调用:pmxops->gpio_request_enablepmxops->request

调用关系如下:

gpiod_get
    gpiod_get_index
        desc = of_find_gpio(dev, con_id, idx, &lookupflags);
        ret = gpiod_request(desc, con_id ? con_id : devname);
                    ret = gpiod_request_commit(desc, label);
                                if (chip->request) {
                                    ret = chip->request(chip, offset);
                                }

我们编写GPIO驱动程序时,所设置chip->request函数,一般直接调用gpiochip_generic_request,它导致Pinctrl把引脚复用为GPIO功能。

gpiochip_generic_request(struct gpio_chip *chip, unsigned offset)
    pinctrl_request_gpio(chip->gpiodev->base + offset)
        ret = pinctrl_get_device_gpio_range(gpio, &pctldev, &range); // gpio是引脚的全局编号

        /* Convert to the pin controllers number space */
        pin = gpio_to_pin(range, gpio);

        ret = pinmux_request_gpio(pctldev, range, pin, gpio);
                    ret = pin_request(pctldev, pin, owner, range);

Pinctrl子系统中的pin_request函数就会把引脚配置为GPIO功能:

static int pin_request(struct pinctrl_dev *pctldev,
               int pin, const char *owner,
               struct pinctrl_gpio_range *gpio_range)
{
    const struct pinmux_ops *ops = pctldev->desc->pmxops;

    /*
     * If there is no kind of request function for the pin we just assume
     * we got it by default and proceed.
     */
    if (gpio_range && ops->gpio_request_enable)
        /* This requests and enables a single GPIO pin */
        status = ops->gpio_request_enable(pctldev, gpio_range, pin);
    else if (ops->request)
        status = ops->request(pctldev, pin);
    else
        status = 0;
}

我们要做什么

设备树表明联系

// 当前GPIO控制器的0号引脚, 对应pinctrlA中的128号引脚, 数量为12
gpio-ranges = <&pinctrlA 0 128 12>; 

源码解析联系

不需要我们自己写代码, 注册gpio_chip时会自动调用.

int gpiochip_add_data(struct gpio_chip *chip, void *data)
    status = of_gpiochip_add(chip);
                status = of_gpiochip_add_pin_range(chip);

of_gpiochip_add_pin_range
    for (;; index++) {
        ret = of_parse_phandle_with_fixed_args(np, "gpio-ranges", 3,
                index, &pinspec);

        pctldev = of_pinctrl_get(pinspec.np); // 根据gpio-ranges的第1个参数找到pctldev

        // 增加映射关系    
        /* npins != 0: linear range */
        ret = gpiochip_add_pin_range(chip,
                                     pinctrl_dev_get_devname(pctldev),
                                     pinspec.args[0],
                                     pinspec.args[1],
                                     pinspec.args[2]);

实现

在自己的驱动程序中,需要实现以下函数:

  • 在GPIO驱动程序中,提供gpio_chip->request
  • 在Pinctrl驱动程序中,提供pmxops->gpio_request_enablepmxops->request

编程_GPIO使用Pinctrl

设备树

表明GPIO和Pinctrl间的联系, 需要增加类似下面的写法:

// 当前GPIO控制器的0号引脚, 对应pinctrlA中的128号引脚, 数量为12
gpio-ranges = <&pinctrlA 0 128 12>; 

未增加联系

virtual_pincontroller {
    compatible = "100ask,virtual_pinctrl";
    myled_pin: myled_pin {
            functions = "gpio";
            groups = "pin0";
            configs = <0x11223344>;
    };
};

gpio_virt: virtual_gpiocontroller {
    compatible = "100ask,virtual_gpio";
    gpio-controller;
    #gpio-cells = <2>;
    ngpios = <4>;
};

myled {
    compatible = "100ask,leddrv";
    led-gpios = <&gpio_virt 0 GPIO_ACTIVE_LOW>;
    pinctrl-names = "default";
    pinctrl-0 = <&myled_pin>;    
};

增加联系

pinctrl_virt: virtual_pincontroller {
    compatible = "100ask,virtual_pinctrl";
};

gpio_virt: virtual_gpiocontroller {
    compatible = "100ask,virtual_gpio";
    gpio-controller;
    #gpio-cells = <2>;
    ngpios = <4>;
    gpio-ranges = <&pinctrl_virt 0 0 4>; 
};

myled {
    compatible = "100ask,leddrv";
    led-gpios = <&gpio_virt 0 GPIO_ACTIVE_LOW>;
};

源码

gpio

gpio_chip 驱动中提供request函数并配置:

chip->request = gpiochip_generic_request;

pinctrl

pinctrl 驱动中提供 gpio_request_enable 函数并配置:

static const struct pinmux_ops virtual_pmx_ops = {
    .get_functions_count = virtual_pmx_get_funcs_count,
    .get_function_name = virtual_pmx_get_func_name,
    .get_function_groups = virtual_pmx_get_groups,
    .set_mux = virtual_pmx_set,
    .gpio_request_enable = virtual_pmx_gpio_request_enable,
};

IMX6ULL 和 STM32MP157

IMX6ULL

IMX6ULL使用GPIO时必须设置Pinctrl,如果不设置,只有那些默认就是GPIO功能的引脚可以正常使用。原因是:

  • GPIO控制器的设备树中,没有gpio-ranges
  • Pinctrl驱动中并没有提供pmxops->gpio_request_enablepmxops->request
  • gpio_chip结构体中direction_inputdirection_output,并没有配置引脚为GPIO功能

STM32MP157

在STM32MP157的内核中,Pinctrl驱动中并没有提供pmxops->gpio_request_enablepmxops->request,为什么也可一直接使用GPIO功能?

它的gpio_chip结构体中,有direction_inputdirection_output,这2个函数的调用关系如下:

direction_output/direction_input
    pinctrl_gpio_direction
        ret = pinmux_gpio_direction(pctldev, range, pin, input);
                    ret = ops->gpio_set_direction(pctldev, range, pin, input);
                                stm32_pmx_gpio_set_direction
                                    stm32_pmx_set_mode  // 它会设置引脚为GPIO功能                  

GPIO子系统的sysfs接口

参考资料:

  • Linux 5.x内核文档
    • Linux-5.4\Documentation\driver-api
    • Linux-5.4\Documentation\devicetree\bindings\gpio\gpio.txt
    • Linux-5.4\drivers\gpio\gpiolib-sysfs.c
  • Linux 4.x内核文档
    • Linux-4.9.88\Documentation\gpio
    • Linux-4.9.88\Documentation\devicetree\bindings\gpio\gpio.txt
    • Linux-4.9.88\drivers\gpio\gpiolib-sysfs.c

驱动

驱动程序为drivers\gpio\gpiolib-sysfs.c.

常用的SYSFS文件

查看GPIO控制器

/sys/bus/gpio/devices目录下,列出了所有的GPIO控制器

GPIO控制器的详细信息

/sys/class/gpio/gpiochipXXX下,有这些信息:

/sys/class/gpio/gpiochip508]# ls -1
base     // 这个GPIO控制器的GPIO编号
device
label    // 名字
ngpio    // 引脚个数
power
subsystem
uevent

查看GPIO使用情况

cat /sys/kernel/debug/gpio

通过SYSFS使用GPIO

确定GPIO编号

  • 查看每个/sys/class/gpio/gpiochipXXX目录下的label,确定是你要用的GPIO控制器,也称为GPIO Bank。
  • 根据它名字gpiochipXXX,就可以知道基值是XXX。
  • 基值加上引脚offset,就是这个引脚的编号。

导出/设置方向/读写值

echo 509 > /sys/class/gpio/export
echo out > /sys/class/gpio/gpio509/direction
echo 1 > /sys/class/gpio/gpio509/value
echo 509 > /sys/class/gpio/unexport

echo 509 > /sys/class/gpio/export
echo in > /sys/class/gpio/gpio509/direction
cat /sys/class/gpio/gpio509/value
echo 509 > /sys/class/gpio/unexport

总结

  • 正常情况下: 需要先用 pinctrl 配置为 gpio, 然后再用 gpio 详细设置。设备树中,也需要都写出来。
  • 特殊情况: 有些芯片厂商开了后门,可以只配置 gpio,就可以使用。 设备树中,可以简略配置。

我们正常只需要在设备树中配置即可。pinctrl 和 gpio 这个是 bsp 工程师来实现。

发表评论