0%

Linux驱动之GPIO子系统

GPIO概述

以前学习stm32的时候看到手册里有关于gpio的电路图,如下:

  • 保护二极体:
    IO引脚上下两边两个二极体用于防止引脚外部过高、过低的电压输入。当引脚电压高于VDD时,上方的二极体导通;当引脚电压低于VSS时,下方的二极体导通,防止不正常电压引入晶片导致晶片烧毁

  • P-MOS管和N-MOS管:
    由P-MOS管和N-MOS管组成的单元电路使得GPIO具有“推挽输出”和“开漏输出”的模式

  • TTL肖特基触发器:
    信号经过触发器后,模拟信号转化为0和1的数字信号。但是,当GPIO引脚作为ADC采集电压的输入通道时,用其“模拟输入”功能,此时信号不再经过触发器进行TTL电平转换。ADC外设要采集到的原始的模拟信号

STM32的GPIO支持4种输入模式(浮空输入、上拉输入、下拉输入、模拟输入)和4种输出模式(开漏输出、开漏复用输出、推挽输出、推挽复用输出)

  • 浮空输入模式

  • 上拉输入模式

  • 下拉输入模式

  • 模拟输入模式

  • 开漏输出模式

开漏输出模式下,通过设定位设定/清除寄存器或者输出寄存器的值,途经N-MOS管,最终输出到I/O口。这里要注意N-MOS管,当设定输出的值为高电平的时候,N-MOS管处于关闭状态,此时I/O口的电平就不会由输出的高低电平决定,而是由I/O口外部的上拉或者下拉决定;当设定输出的值为低电平的时候,N-MOS管处于开启状态,此时I/O口的电平就是低电平。同时,I/O口的电平也可以通过输入电路进行读取;注意,I/O口的电平不一定是输出的电平。

  • 开漏复用输出模式

GPIO驱动中几个概念

  • Active-High and Active-Low
    以LED为例,需要设置GPIO电平。但是有些电路可能是高电平点亮LED,有些是低电平点亮LED。

    可以使用如下代码:

    1
    2
    gpiod_set_value(gpio, 1);  // 输出高电平点亮LED
    gpiod_set_value(gpio, 0); // 输出低电平点亮LED

    对应同一个目标:点亮LED,对于不同的LED,就需要不同的代码,原因在于上面的代码中1、0表示的是"物理值"。

    如果能使用"逻辑值",同样的逻辑值在不同的配置下输出对应的物理值,就可以保持代码一致,比如:

    1
    2
    3
    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数据结构

要理解一个驱动框架的设计就一定要理清楚该框架下的数据结构组织关系,首先列出gpio子系统的数据结构关系图如下:原图

  • gpio_chip
    gpio_chip这个数据结构用于描述一组gpio,soc里的gpio一般都是分为几组,同一组内的gpio具有相同的操作方式

  • gpio_device
    gpio_device是具体的device它管理一个gpio_chip和该组下的所有pin。gpio_device通过链表连接起来就是soc内的多组gpio

  • gpio_desc
    gpio_desc这个结构用于描述一个pin,一组gpio里面有32个pin或者多个pin,每一个pin由gpio_desc描述,同一组的pin挂载在gpio_device下

GPIO子系统的层次和接口

硬件层之上是soc原厂提供的gpio controller驱动这部分提供了具体soc的pin脚的描述和控制接口,在这之上是gpiolib层为其他驱动程序提供统一的接口来配置和控制gpio,再往上就是其他驱动程序使用gpio的接口完成他们想要的工作。当然在gpiolib之下还会使用pinctrl子系统来管理pin的功能复用与配置,这个在pinctrl一篇文章里再详细说明。

实现

gpio_chip是描述一个gpio控制器的数据结构,将一个gpio控制器添加到系统中的流程如下:原图

对于gpio驱动一般由原厂写好,其他驱动工程师不需要编写具体驱动,需要的时候直接在dts里配置就可以直接使用。其中gpio_chip结构体的注册如上图所示,每个gpio_chip都需要一个gpio_device结构,要先分配一个gpio_device结构体,然后就是处理该组下的pin,以及各种中断信息。

sysfs接口

/sys/bus/gpio/devices目录下,列出了所有的GPIO控制器 /sys/class/gpio/gpiochipXXX下有每个GPIO控制器的详细信息

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

查看gpio使用情况:

1
cat /sys/kernel/debug/gpio

导出/设置方向/读写值:

1
2
3
4
5
6
7
8
9
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

参考文献

https://iter01.com/539041.html
《韦东山老师的书籍和课程》