所学来自百问网
目录
1. Pinctrl 子系统
1.1 引入
要想让pinA、B用于GPIO或I2C,需要设置IOMUX(输入/输出多路复用器)让它们连接到GPIO模块或I2C模块;
所以GPIO、I2C应该是并列的关系,它们能够使用之前,需要设置IOMUX。 有时候并不仅仅是设置IOMUX,还要配置引脚,比如上拉、下拉、开漏等等。
现在的芯片动辄几百个引脚,在使用到GPIO功能时,让你一个引脚一个引脚去找对应的寄存器,这要疯掉。所以BSP工程师已经提前做好的了,BSP工程师把引脚的复用、配置抽出来,做成Pinctrl子系统,给GPIO、I2C 等模块使用,我们只需要引用就行了
1.2 概念
pin controller:可以用它来复用引脚、配置引脚
client device:声明自己要使用哪些引脚的哪些功能,怎么配置它们
1.2.1 pin controller
是一个软件上的概念,你可以认为它对应IOMUX──用来复用引脚,还可以配置引脚(比如上下拉电阻等)
注意:pin controller和GPIO Controller不是一回事,前者控制的引脚可用于GPIO功能、I2C功能;后者只是把引脚配置为输入、输出等简单的功能。即先用pin controller把引脚配置为GPIO,再用GPIO Controler把引脚配置为输入或输出。
1.2.2 client device
Pinctrl系统的客户,那就是使用Pinctrl系统的设备,使用引脚的设备。它在设备树里会被定义为一个节点,在节点里声明要用哪些引脚。
1.pin state
pin state 对应图中的 pinctrl-names
对于一个“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 节点中。
当这个设备处于 default 状态时,pinctrl 子系统会自动根据上述信息把所用引脚复用为uart0功能。
当这这个设备处于 sleep 状态时,pinctrl 子系统会自动根据上述信息把所用引脚配置为高电平。
2.groups 和 function
group:一个设备会用到一个或多个引脚,这些引脚就可以归为一组;
function:这些引脚可以复用为某个功能。
当然,一个设备可以用到多组引脚,比如A1、A2两组引脚,A1组复用为F1功能,A2组复用为F2功能。
3.Generic pin multiplexing node 和 Generic pin configuration node
在上图左边的 pin controller 节点中,有子节点或孙节点,它们是给 client device 使用的。
Generic pin multiplexing node:可以用来描述复用信息,哪组(group)引脚复用为哪个功能 (function);
Generic pin configuration node:可以用来描述配置信息,哪组(group)引脚配置为哪个设置功能 (setting),比如上拉、下拉等。
注意:pin controller节点的格式,没有统一的标准!!!!每家芯片都不一样。 甚至上面的group、function关键字也不一定有,但是概念是有的。
如下图所示:
4.引用pinctrl
这是透明的,我们的驱动基本不用管。当设备切换状态时,对应的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); // 不再使用, 退出时调用
2. GPIO 子系统
2.1 引入
要操作GPIO引脚,先把所用引脚配置为GPIO功能,这通过Pinctrl子系统来实现。
然后就可以根据设置引脚方向(输入还是输出)、读值──获得电平状态,写值──输出高低电平。
以前我们通过寄存器来操作GPIO引脚,即使LED驱动程序,对于不同的板子它的代码也完全不同。
BSP工程师实现了GPIO子系统,我们就可以:
在设备树里指定GPIO引脚
在驱动代码中:使用GPIO子系统的标准函数获得GPIO、设置GPIO方向、读取/设置GPIO值。
2.2 GPIO的使用
2.2.1 设备树指定引脚
在几乎所有ARM芯片中,GPIO都分为几组,每组中有若干个引脚。所以在使用GPIO子系统之前,就要先确定:它是哪组的?组里的哪一个?
在设备树中,“GPIO组”就是一个GPIO Controller,这通常都由芯片厂家设置好。我们要做的是找到它名字,比如“gpio1”,然后指定要用它里面的哪个引脚,比如<&gpio1 0>。
下图是一些芯片的GPIO控制器节点,它们一般都是厂家定义好,在xxx.dtsi文件中:
我们暂时只需要关心里面的这2个属性:
gpio-controller; // 表示这个节点是一个GPIO Controller,它下面有很多引脚
#gpio-cells = <2>; /* 表示这个控制器下每一个引脚要用2个32位的数(cell)来描述 使用多个
* cell 来描述一个引脚,这是GPIO Controller 自己决定的。比如可以用
* 其中一个 cell 来表示那是哪一个引脚,用另一个cell来表示它是高电平
* 有效还是低电平有效,甚至还可以用更多的cell来示其他特性。
* 比如下图中 gpios = <&gpio5 3 GPIO_ACTIVE_LOW>
* cell的第一位等于3 第二位等于GPIO_ACTIVE_LOW*/
普遍的用法是,用第1个cell来表示哪一个引脚,用第2个cell来表示有效电平:
GPIO_ACTIVE_HIGH : 高电平有效
GPIO_ACTIVE_LOW : 低电平有效
定义GPIO Controller是芯片厂家的事,我们怎么引用某个引脚呢?在自己的设备节点中使用属性"[-]gpios",示例如下:
上图中,可以使用gpios属性,也可以使用name-gpios属性。
2.2.2 驱动代码中调用GPIO子系统
GPIO 子系统有两套接口:基于描述符的(descriptor-based)、老的 (legacy)。前者的函数都有前缀“gpiod”,它使用 gpio_desc 结构体来表示 一个引脚;后者的函数都有前缀“gpio”,它使用一个整数来表示一个引脚。
要操作一个引脚,首先要get引脚,然后设置方向,读值、写值。
头文件
#include <linux/gpio/consumer.h> // descriptor-based
#include <linux/gpio.h> // legacy
常用函数
有前缀“devm_”的含义是“设备资源管理”(Managed Device Resource), 这是一种自动释放资源的机制。它的思想是“资源是属于设备的,设备不存在时资源就可以自动释放”
比如在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 设置的值是“逻辑值”,不一定等于物理值。
举例:LED的驱动方式有两种,具体根据原理图,一种高电平点亮,一种低电平点亮,我们想实现填入1就亮,0就灭的效果,通过在设备树传递flag,如GPIO_ACTIVE_LOW低电平有效,然后GPIO子系统会将传入逻辑值转化为驱动LED对应的物理值
但是,旧的“gpio_”函数没办法根据设备树信息获得引脚,它需要先知道引脚号。
在 GPIO 子系统中,每注册一个 GPIO Controller 时会确定它的“base number”,那么这个控制器里的第n号引脚的号码就是:base number + n。但是如果硬件有变化、设备树有变化,这个base number并不能保证是固定的,应该查看sysfs来确定base number。
2.2.3 sysfs 中的访问方法
在sysfs中访问GPIO,实际上用的就是引脚号,老的方法。
先确定某个GPIO Controller的基准引脚号(base number),再计算出某个引脚的号码。
步骤如下:
1.先在开发板的/sys/class/gpio目录下,找到各个gpiochipXXX目录:
2.然后进入某个gpiochip目录,查看文件label的内容
3.根据label的内容对比设备树
label 内容来自设备树,比如它的寄存器基地址。用来跟设备树(dtsi文件) 比较,就可以知道这对应哪一个GPIO Controller。
示例
以下是在 100asK_imx6ull 上运行的结果,通过对比设备树可知 gpiochip96 对应gpio4:
所以gpio4这组引脚的基准引脚号就是96,这也可以“cat base”来再次确认。
基于sysfs操作引脚:
以100ask_imx6ull 为例,它有一个按键,原理图如下:
那么GPIO4_14的号码是96+14=110,由示例可看出GPIO4基地址为96,加上引脚号14可得出110,可以如下操作读取按键值:
[root@100ask:~]# echo 110 > /sys/class/gpio/export // export 引脚
[root@100ask:~]# echo in > /sys/class/gpio/gpio110/direction // 将引脚设置为输入模式
[root@100ask:~]# cat /sys/class/gpio/gpio110/value // 获取引脚的值
[root@100ask:~]# echo 110 > /sys/class/gpio/unexport // unexport 引脚
export 引脚可得到,unexport则去掉该信息
注意:如果驱动程序已经使用了该引脚,那么将会export失败,会提示下面的错误:
对于输出引脚,假设引脚号为N,可以用下面的方法设置它的值为1:
[root@100ask:~]# echo N > /sys/class/gpio/export // export 引脚
[root@100ask:~]# echo out > /sys/class/gpio/gpioN/direction // 将引脚设置为输出模式
[root@100ask:~]# echo 1 > /sys/class/gpio/gpioN/value // 将引脚的值设置为1
[root@100ask:~]# echo N > /sys/class/gpio/unexport // unexport 引脚