GPIO子系统

GPIO子系统的作用

管理GPIO,既能支持芯片本身的GPIO,也能支持扩展的GPIO。提供统一的、简便的访问接口,实现:输入、输出、中断。

GPIO通用功能

  • 可以设为输出:让它输出高低电平;

  • 可以设为输入,读取引脚当前电平;

  • 可以用来触发中断

对于芯片自带的GPIO,它的访问时很快的,可以在获得spinlocks的情况下操作它。但是,对于通过I2C、SPI等接口扩展的GPIO,访问它们时可能导致休眠,所以这些GPIO Expander就不能在获得spinlocks的情况下使用。

GPIO子系统的重要概念

GPIO Controller

芯片GPIO一般分为几组,例如GPIO1、GPIO2又或者GPIOA、GPIOB等等,而每一组GPIO下面又有多个引脚,比如GPIO1这组引脚,下面有32个脚可以用,定义为GPIO1_0~GPIO1_31

我们这里所说的GPIO Controller 就是GPIO组的概念,每个组就是一个GPIO Controller,举例说明如下图所示:

../../_images/image-20221202082215060.png

../../_images/image-20221202082228411.png

../../_images/image-20221202082240724.png

以imx6ull为例说明,gpio子系统中如何在设备树种定义:

../../_images/image-20221202083711611.png

在设备树中一般芯片原厂已经指定GPIO Controller,我们只需使用即可,比如上面的ds18b20设备

设备驱动程序使用GPIO子系统

GPIO子系统有两套接口:

  • 基于描述符的(descriptor-based):函数都有前缀gpiod_,它使用gpio_desc结构体来表示一个引脚

    需要包含头文件

    #include <linux/gpio/consumer.h>   // descriptor-based
    
  • 老的(legacy):函数都有前缀gpio_,它使用一个整数来表示一个引脚

    #include <linux/gpio.h>            // legacy
    

两套驱动API接口如下对比如下

descriptor-based legacy 说明
获得GPIO
gpiod_get gpio_request
gpiod_get_index
gpiod_get_array gpio_request_array
devm_gpiod_get
devm_gpiod_get_index
devm_gpiod_get_array
设置方向
gpiod_direction_input gpio_direction_input
gpiod_direction_output gpio_direction_output
读值、写值
gpiod_get_value gpio_get_value
gpiod_set_value gpio_set_value
释放GPIO
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>;
	};

在驱动中如何获取red green blue和power三个引脚的gpio呢

struct gpio_desc *red, *green, *blue, *power;
// 取led-gpios 里面的0 1 2索引的gpio 分别对应red green blue
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的gpio 对应为power
power = gpiod_get(dev, "power", GPIOD_OUT_HIGH);

注意:如果设置了GPIO_ACTIVE_LOW属性,说明激活的时候是低电平,也就是使用gpiod_set_value(gpio,1),对应的实际物理电平是低电平,gpiod_set_value(gpio,0)对应的实际物理电平是高电平。

实际如下:

../../_images/image-20221202085257702.png

sysfs中访问方法

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

  1. 先确定引脚的所在组的基准引脚号

    • 开发板的/sys/class/gpio目录下,找到各个gpiochipXXX目录

      ../../_images/image-20221202085919642.png

    • 然后进入某个gpiochip目录,查看文件label的内容

    • 根据label的内容对比设备树

    • 所以gpio4这组引脚的基准引脚号就是96,这也可以cat base来再次确认。

    ../../_images/image-20221202085939049.png

  2. 在命令行操作gpio

    假如要读取那么GPIO4_14,号码是96+14=110,可以如下操作读取值

    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
    

    假如设置GPIO4_14,号码是96+14=110,可以如下操作设置值

    echo  110 > /sys/class/gpio/export
    echo out > /sys/class/gpio/gpio110/direction
    echo 1 > /sys/class/gpio/gpio110/value #设置high
    echo 0 > /sys/class/gpio/gpio110/value #设置低
    echo  110 > /sys/class/gpio/unexport
    

    C编程使用方法

    #include <stdio.h>
    #include <unistd.h>
    #include <sys/stat.h>
    #include <linux/fs.h>
    #include <fcntl.h>
    #include <string.h>
    #include <termios.h>
    #include <sys/ioctl.h>
    // 等价于 echo  110 > /sys/class/gpio/export
    int gpio_fd;
    gpio_fd = open("/sys/class/gpio/export",O_WRONLY);
    write(gpio_fd,"110",strlen("110"));
    close(gpio_fd);
    
    //等价于echo out > /sys/class/gpio/gpio110/direction
    gpio_fd = open("/sys/class/gpio/gpio110/direction",O_RDWR);
    write(gpio_fd,"out",strlen("out"));
    close(gpio_fd);
    
    gpio_fd = open("/sys/class/gpio/gpio24/value",O_RDWR);
    write(gpio_fd,"1",strlen("1")); //等价于 echo 1 > /sys/class/gpio/gpio110/value
    write(gpio_fd,"0",strlen("0")); //等价于 echo 0 > /sys/class/gpio/gpio110/value
    close(gpio_fd);
    

GPIO子系统的层次

../../_images/image-20221202093016852.png

GPIO lib向上提供API接口

descriptor-based legacy 说明
获得GPIO
gpiod_get gpio_request
gpiod_get_index
gpiod_get_array gpio_request_array
devm_gpiod_get
devm_gpiod_get_index
devm_gpiod_get_array
设置方向
gpiod_direction_input gpio_direction_input
gpiod_direction_output gpio_direction_output
读值、写值
gpiod_get_value gpio_get_value
gpiod_set_value gpio_set_value
释放GPIO
gpio_free gpio_free
gpiod_put gpio_free_array
gpiod_put_array
devm_gpiod_put
devm_gpiod_put_array

GPIO lib向下提供API接口

../../_images/image-20221202093858280.png

GPIO子系统核心数据结构

gpio_device

../../_images/image-20221202094058135.png

gpio_chip

  • 控制引脚的函数

  • 中断相关的函数

  • 引脚信息:支持多少个引脚?各个引脚的名字?

../../_images/image-20221202094133216.png

gpio_desc

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

../../_images/image-20221202094255072.png

如何编写GPIO Controller驱动程序

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

../../_images/image-20221202094402624.png