STM32MP157驱动开发——GPIO 和 和 Pinctrl 子系统的概念

文章目录

  • Pinctrl 子系统重要概念
    • 概述
    • 重要概念
      • pin controller:
      • client device:
    • 代码中怎么引用 pinctrl
  • GPIO 子系统重要概念
    • 概述
    • 在设备树中指定引脚
    • 在驱动代码中调用 GPIO 子系统
      • 头文件
      • 常用函数
      • 实例:

BSP工程师针对芯片的寄存器写Pinctrl子系统,驱动工程师使用Pinctrl 子系统

Pinctrl 子系统重要概念

概述

无论是哪种芯片,都有类似图的结构, IOMUX 认为是引脚功能选择器,有时候还需要配置引脚,比如上拉、下拉、开漏等等。前面提到的这些操作都可以交给Pinctrl 子系统完成。即把引脚的复用、配置抽出来,做成 Pinctrl 子系统,给 GPIO、I2C 等模块使用

STM32MP157驱动开发——GPIO 和 和 Pinctrl 子系统的概念_第1张图片
ps:大多数的芯片,没有单独的 IOMUX 模块,即没有pinctl子系统去管理引脚的复用、配置等等,这些功能都在GPIO 模块内部实现的,所以在硬件上 GPIO 和 Pinctrl 是密切相关,在软件上它们的关系也非常密切

重要概念

涉及 2 个对象:pin controller、client device。

  • 前者提供服务:可以用它来复用引脚、配置引脚。
  • 后者使用服务:声明自己要使用哪些引脚的哪些功能,怎么配置它们。

pin controller:

在芯片手册里你找不到 pin controller,它是一个软件上的概念,你可以认为它对应 IOMUX──用来复用引脚,还可以配置引脚(比如上下拉电阻等)

注意,pin controller 和 GPIO Controller 不是一回事,前者控制的引脚可用于 GPIO 功能、I2C 功能;后者只是把引脚配置为输入、输出等简单的功能。即先用 pin controller 把引脚配置为 GPIO,再用 GPIO Controler 把引脚配置为输入或输出

client device:

Pinctrl 系统的客户,那就是使用 Pinctrl 系统的设备,使用引脚的设备。它在设备树里会被定义为一个节点,在节点里声明要用哪些引脚

关系图,右边device是设备树的一个节点,它是Pinctrl 系统的客户

STM32MP157驱动开发——GPIO 和 和 Pinctrl 子系统的概念_第2张图片

首先是介绍一个概念引脚的状态:pin state

  • 对于一个“client device”来说,比如对于一个 UART 设备,它有多个“状态”:default、sleep 等,如默认状态下,UART 设备是工作的,那么所用的引脚就要复用为 UART功能。在休眠状态下,为了省电,可以把这些引脚复用为 GPIO 功能;或者直接把它们配置输出高电平。上图中,pinctrl-names 里定义了 2 种状态:default、sleep。

某种状态下对应的引脚配置有如下关联:

  • 第 0 种状态用到的引脚在 pinctrl-0 中定义,它是 state_0_node_a,位于 pincontroller 节点中。当这个设备处于 default 状态时,pinctrl 子系统会自动根据上述信息把所用引脚复用为 uart0 功能
  • 第 1 种状态用到的引脚在 pinctrl-1 中定义,它是 state_1_node_a,位于 pincontroller 节点中。当这这个设备处于 sleep 状态时,pinctrl 子系统会自动根据上述信息把所用引脚配置为高电平

左图 pin controller 节点中,是给client device 使用的。

  • 可以用来描述复用信息:哪组(group)引脚复用为哪个功能(function);
  • 可以用来描述配置信息:哪组(group)引脚配置为哪个设置功能(setting),比如上拉、下拉等。

注意:pin controller 节点的格式,没有统一的标准,每家芯片都不一样。甚至上面的 group、function关键字也不一定有,但是概念是有的。

代码中怎么引用 pinctrl

当设备切换状态时,对应的pinctrl 就会被调用。比如在 platform_device 和 platform_driver 的枚举过程中,流程如下:
STM32MP157驱动开发——GPIO 和 和 Pinctrl 子系统的概念_第3张图片

当系统休眠时,也会去设置该设备 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); // 不再使用, 退出时调用

GPIO 子系统重要概念

概述

ps:大多数的芯片,没有单独的 IOMUX 模块,即没有pinctl子系统去管理引脚的复用、配置等等,这些功能都在GPIO 模块内部实现的,所以在硬件上 GPIO 和 Pinctrl 是密切相关,在软件上它们的关系也非常密切

STM32MP157驱动开发——GPIO 和 和 Pinctrl 子系统的概念_第4张图片
通过 Pinctrl 子系统,先把所用引脚配置为 GPIO 功能,然后就可以根据设置引脚方向(输入还是输出)、读值──获得电平状态,
写值──输出高低电平。

当 BSP 工程师实现了 GPIO 子系统后,我们就可以:

  • 在设备树里指定 GPIO 引脚
  • 在驱动代码中:使用 GPIO 子系统的标准函数获得 GPIO、设置 GPIO 方向、读取/设置 GPIO 值。

这样的驱动代码,将是单板无关的,更换单板只需要修改设备树文件,指定GPIO引脚

在设备树中指定引脚

指定引脚就要先确定:它是哪组的?组里的哪一个?

在设备树中,“GPIO 组”就是一个 GPIO Controller,这通常都由芯片厂家设置好。我们要做的是找到它名字,一般都是厂家定
义好,在 xxx.dtsi 文件中:该节点中有关键属性gpio-controller#gpio-cells

  • “gpio-controller”表示这个节点是一个 GPIO Controller,它下面有很多引脚。
  • “#gpio-cells = <2>”表示这个控制器下每一个引脚要用 2 个 32 位的数(cell)来描述。普遍的用法是,用第 1 个 cell 来表示哪一个引脚,用第 2 个 cell 来表示有效电平:
GPIO_ACTIVE_HIGH : 高电平有效
GPIO_ACTIVE_LOW : 低电平有效

在自己的设备节点中使用属性"[-]gpios",引用某个引脚,可以使用 gpios 属性,也可以使用 name-gpios 属性,示例如下:
STM32MP157驱动开发——GPIO 和 和 Pinctrl 子系统的概念_第5张图片
name:描述符,在GPIO标准函数中作为参数
在这里插入图片描述

在驱动代码中调用 GPIO 子系统

GPIO 子系统有两套接口:

  • 基于描述符的(descriptor-based)。函数都有前缀“gpiod_”,它使用 gpio_desc 结构体来表示一个引脚;
  • 老的(legacy)。函数都有前缀“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_”版本的相关函数

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

实例:

假设备在设备树中有如下节点

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设置的值是“逻辑值”,不一定等于物理值,需要对比设备树文件中设置的属性,例如:

STM32MP157驱动开发——GPIO 和 和 Pinctrl 子系统的概念_第6张图片

旧的“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得到的是这组引脚里面有多少个引脚
STM32MP157驱动开发——GPIO 和 和 Pinctrl 子系统的概念_第7张图片
打开ubuntu,进入内核的设备树文件目录,cd /home/book/100ask_stm32mp157_pro-sdk/Linux-5.4/arch/arm64/boot/dts,打开设备树文件:vi stm32mp15xxx-100ask.dtsi,根据label值找到对应的那组寄存器

如图,如果想要操作PG2引脚,可知该引脚属于GPIOG那一组,通过设备树和控制器的对比,找到该组的引脚的基准引脚号,然后在该基准引脚号的基础上加2得到该引脚的引脚号
STM32MP157驱动开发——GPIO 和 和 Pinctrl 子系统的概念_第8张图片

那么,我们可以在终端输入如下指令操作该引脚:

将引脚暴露在/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

你可能感兴趣的:(stm32,驱动开发,嵌入式硬件)