BSP工程师针对芯片的寄存器写Pinctrl子系统,驱动工程师使用Pinctrl 子系统
无论是哪种芯片,都有类似图的结构, IOMUX 认为是引脚功能选择器,有时候还需要配置引脚,比如上拉、下拉、开漏等等。前面提到的这些操作都可以交给Pinctrl 子系统完成。即把引脚的复用、配置抽出来,做成 Pinctrl 子系统,给 GPIO、I2C 等模块使用
ps:大多数的芯片,没有单独的 IOMUX 模块,即没有pinctl子系统去管理引脚的复用、配置等等,这些功能都在GPIO 模块内部实现的,所以在硬件上 GPIO 和 Pinctrl 是密切相关,在软件上它们的关系也非常密切
涉及 2 个对象:pin controller、client device。
在芯片手册里你找不到 pin controller,它是一个软件上的概念,你可以认为它对应 IOMUX──用来复用引脚,还可以配置引脚(比如上下拉电阻等)
注意,pin controller 和 GPIO Controller 不是一回事,前者控制的引脚可用于 GPIO 功能、I2C 功能;后者只是把引脚配置为输入、输出等简单的功能。即
先用 pin controller 把引脚配置为 GPIO,再用 GPIO Controler 把引脚配置为输入或输出
。
Pinctrl 系统的客户,那就是使用 Pinctrl 系统的设备,使用引脚的设备。它在设备树里会被定义为一个节点,在节点里声明要用哪些引脚
关系图,右边device是设备树的一个节点,它是Pinctrl 系统的客户
首先是介绍一个概念引脚的状态:pin state
:
某种状态下对应的引脚配置有如下关联:
把所用引脚复用为 uart0 功能
。把所用引脚配置为高电平
。左图 pin controller 节点中,是给client device 使用的。
注意:pin controller 节点的格式,没有统一的标准,每家芯片都不一样。甚至上面的 group、function关键字也不一定有,但是概念是有的。
当设备切换状态时,对应的pinctrl 就会被调用。比如在 platform_device 和 platform_driver 的枚举过程中,流程如下:
当系统休眠时,也会去设置该设备 sleep 状态对应的引脚,不需要我们自己去调用代码。非要自己调用,也有函数:
devm_pinctrl_get_select_default(struct device *dev); // 使用"default" 状态的引脚
pinctrl_get_select(struct device *dev, const char *name); // 根据 name 选择某种状态的引脚
pinctrl_put(struct pinctrl *p); // 不再使用, 退出时调用
ps:大多数的芯片,没有单独的 IOMUX 模块,即没有pinctl子系统去管理引脚的复用、配置等等,这些功能都在GPIO 模块内部实现的,所以在硬件上 GPIO 和 Pinctrl 是密切相关,在软件上它们的关系也非常密切
通过 Pinctrl 子系统,先把所用引脚配置为 GPIO 功能,然后就可以根据设置引脚方向(输入还是输出)、读值──获得电平状态,
写值──输出高低电平。
当 BSP 工程师实现了 GPIO 子系统后,我们就可以:
在设备树里指定 GPIO 引脚
使用 GPIO 子系统的标准函数
获得 GPIO、设置 GPIO 方向、读取/设置 GPIO 值。这样的驱动代码,将是单板无关的,更换单板只需要修改设备树文件,指定GPIO引脚
指定引脚就要先确定:它是哪组的?组里的哪一个?
在设备树中,“GPIO 组”就是一个 GPIO Controller,这通常都由芯片厂家设置好
。我们要做的是找到它名字,一般都是厂家定
义好,在 xxx.dtsi 文件中:该节点中有关键属性gpio-controller
和#gpio-cells
GPIO_ACTIVE_HIGH : 高电平有效
GPIO_ACTIVE_LOW : 低电平有效
在自己的设备节点中使用属性"[
",引用某个引脚,可以使用 gpios
属性,也可以使用 name-gpios
属性,示例如下:
name:描述符,在GPIO标准函数中作为参数
GPIO 子系统有两套接口:
#include // descriptor-based
或
#include // legacy
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_”版本的相关函数
假设备在设备树中有如下节点
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 时会确定它的“basenumber”,那么这个控制器里的第 n 号引脚的号码就是:base number + n。但是如果硬件有变化、设备树有变化,这个 base number 并不能保证是固定的,应该查看 sysfs 虚拟文件系统中来确定 base number。
先在开发板的/sys/class/gpio 目录下,找到各个 gpiochipXXX 目录,每个目录对应一个GPIO控制器,然后进入某个 gpiochip 目录,查看文件 label 的内容,根据 label 的内容对比设备树,label 内容来自设备树,比如它的寄存器基地址。用来跟设备树(dtsi 文件)比较,就可以知道这对应哪一个 GPIO Controller。
例如,下图是在 stm32mp157 上运行的结果,通过对比设备树可知 gpiochip112对应 gpioH,所以 gpioH 这组引脚的基准引脚号就是 112,cat base 指令得到的也是基准引脚,cat ngpio得到的是这组引脚里面有多少个引脚
打开ubuntu,进入内核的设备树文件目录,cd /home/book/100ask_stm32mp157_pro-sdk/Linux-5.4/arch/arm64/boot/dts,打开设备树文件:vi stm32mp15xxx-100ask.dtsi,根据label值找到对应的那组寄存器
如图,如果想要操作PG2引脚,可知该引脚属于GPIOG那一组,通过设备树和控制器的对比,找到该组的引脚的基准引脚号,然后在该基准引脚号的基础上加2得到该引脚的引脚号
那么,我们可以在终端输入如下指令操作该引脚:
将引脚暴露在/sys/class/gpio 目录下
echo 98 > /sys/class/gpio/export
修改方向位输入
echo in > /sys/class/gpio/gpio98/direction
获取引脚值
cat /sys/class/gpio/gpio98/value
销毁该引脚控制器
echo 98 > /sys/class/gpio/unexport
对于输出引脚,我们可以调用如下指令使其输入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