# 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,举例说明如下图所示: ![](media/image-20221202082215060.png) ![](media/image-20221202082228411.png) ![](media/image-20221202082240724.png) 以imx6ull为例说明,gpio子系统中如何在设备树种定义: ![](media/image-20221202083711611.png) 在设备树中一般芯片原厂已经指定GPIO Controller,我们只需使用即可,比如上面的ds18b20设备 #### 设备驱动程序使用GPIO子系统 GPIO子系统有两套接口: - 基于描述符的(descriptor-based):函数都有前缀`gpiod_`,它使用`gpio_desc`结构体来表示一个引脚 需要包含头文件 ```c #include // descriptor-based ``` - 老的(legacy):函数都有前缀`gpio_`,它使用一个整数来表示一个引脚 ```c #include // 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呢 ```c 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)对应的实际物理电平是高电平。 > > 实际如下: > > ![](media/image-20221202085257702.png) ## sysfs中访问方法 在sysfs中访问GPIO,实际上用的就是引脚号,老的方法。 1. 先确定引脚的所在组的基准引脚号 - 开发板的/sys/class/gpio目录下,找到各个gpiochipXXX目录 ![](media/image-20221202085919642.png) - 然后进入某个gpiochip目录,查看文件label的内容 - 根据label的内容对比设备树 - 所以gpio4这组引脚的基准引脚号就是96,这也可以`cat base`来再次确认。 ![](media/image-20221202085939049.png) 2. 在命令行操作gpio 假如要读取那么GPIO4_14,号码是96+14=110,可以如下操作读取值 ```bash 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,可以如下操作设置值 ```bash 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编程使用方法 ```c #include #include #include #include #include #include #include #include // 等价于 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子系统的层次 ![](media/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接口 ![](media/image-20221202093858280.png) ## GPIO子系统核心数据结构 ### gpio_device ![](media/image-20221202094058135.png) ### gpio_chip * 控制引脚的函数 * 中断相关的函数 * 引脚信息:支持多少个引脚?各个引脚的名字? ![](media/image-20221202094133216.png) ### gpio_desc 我们去使用GPIO子系统时,首先是获得某个引脚对应的gpio_desc,gpio_device表示一个GPIO Controller,里面支持多个GPIO,在gpio_device中有一个gpio_desc数组,每一引脚有一项gpio_desc。 ![](media/image-20221202094255072.png) ### 如何编写GPIO Controller驱动程序 分配、设置、注册gpioc_chip结构体,示例:`drivers\gpio\gpio-74x164.c` ![](media/image-20221202094402624.png)