字符设备驱动 — 3 GPIO和Pinctrl子系统
概述
目的:不再一味的去配置寄存器,简化 GPIO 驱动开发
要想让 PIN_A 引脚用于 GPIO 或者 I2C,就需要通过 IOMUX 将它们连接到不同的模块
大多数的芯片,没有单独的 IOMUX 模块,引脚的复用、配置等等,就是在GPIO 模块内部实现的。在硬件上 GPIO 和 Pinctrl 是如此密切相关,在软件上它们的关系也非常密切。
pinctrl:引脚控制,用来配置比如引脚mux复用信息,引脚电器属性(比如上/下拉、速度、驱动能力等)信息。
gpio:控制gpio的输入输出,以及高低电平。
Pinctrl
—— 用于配置引脚,一般都在设备树中使用
两个重要的概念:pin controller、client device
1. pin controller:
芯片手册里你找不到 pin controller,它是一个软件上的概念。对应 IOMUX──用来复用引脚,还可以配置引脚(比如上下拉电阻等)(格式没有统一的标准,下面的 groups、function 只是示例)
2. client device:
“客户设备”,客户是指Pinctrl系统的客户,即使用Pinctrl系统的设备,使用引脚的设备
下图,左边为 pin controller,右边为 client device
- pin state
对于一个"client device",如UART设备,它有多个“状态”:default、sleep等,那么对应的引脚也有这些状态。
比如,默认状态下,UART设备正常工作,那么所用的引脚就要复用为UART功能;
休眠状态下,为了省电,可以把这些引脚复用为GPIO功能;或者直接把它们配置输出高电平。
上图pinctrl-names定义2种状态:default,sleep。
第0种状态用到的引脚在pinctrl-0中定义,它是state_0_node_a,位于pincontroller节点中。
第1种状态用到的引脚在Pinctrl-1中定义,它是state_1_node_a,位于pincontroller节点中。
当UART设备处于default状态时,pinctrl子系统会自动根据上述信息将所用引脚复用为uart0功能。
当UART设备处于sleep状态时,pinctrl子系统会自动根据上述信息将所用引脚配置为高电平。
- groups和function
一个设备会用到一个或多个引脚,这些引脚可以归纳为一组(group);
这些引脚可以复用为某个功能:function,如I2C功能,SPI功能,GPIO功能等。当然:一个设备可以用到多组引脚,比如A1、A2两组引脚,A1组复用为F1功能,A2组复用为F2功能:
imx6ull 中的使用示例:
GPIO
要操作 GPIO 引脚,需要先把所用引脚通过 pinctrl 子系统配置为 GPIO 功能,这是前提条件
使用流程:
- 在设备树中指定 GPIO 引脚
- 在驱动代码中使用 GPIO子系统的标准函数来获取 GPIO、设置 GPIO 方向、读取 / 设置 GPIO 的值
1. 在设备树中指定引脚:
所有 ARM 芯片的引脚都是分为组的,这通常由芯片厂家设置好,每一组引脚都是一个节点,这个节点含有 gpio-controller 属性,这些节点都写在 dtsi 文件中
我们只需要关心两个属性 gpio-controller、gpio-cells
gpio-controller:表明这是一个引脚控制器,它下面有很多的引脚
gpio-cells:表示这个控制器下每一个引脚要用 2 个 32 位的数 ( cell ) 来描述(注意这是在去掉引脚引用的情况下)
普遍用法:第1个 cell 来表示是哪一个引脚,第二个 cell 来表示有效电平(通常)
eg: gpio1 中明确表示 gpio-cells = <2>; 所以 20 代表20号引脚,GPIO_ACTIVE_LOW 表示低电平有效
当然 第3个参数也可以设置为 IRQ_TYPE_EDGE_FALLING 表示这是一个中断引脚
2. 在驱动中使用:
有两套接口:
- 基于描述符(descriptor_based),函数有前缀 “ gpiod_ ”,使用 gpio_desc 结构体来表示引脚
- 老的(legacy) ,函数有前缀 “ gpio_ ”,使用一个整数来表示一个引脚
操作一个引脚:首先要 get 引脚,然后设置方向,最后读取 / 设置 引脚的值
#include <linux/gpio/consumer.h> // descriptor-based
#include <linux/gpio.h> // legacy
注意:” devm_ “ 表示设备资源管理,这是一种自动释放资源的机制,它的思想是 “ 资源是属于设备的,设备不存在时资源就可以自动释放 ”,比如在 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>;
};
驱动中使用 GPIO 子系统:
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(power, 1);
注意:gpiod_set_value 函数所设置的值不是物理值,而是逻辑值
比如这里设置的是1,但是在设备树中,这个引脚是低电平有效,所以实际电路会输出低电平