Linux驱动开发基础(pinctrl子系统和GPIO子系统)

 所学来自百问网

目录

1. Pinctrl 子系统 

1.1 引入

1.2 概念

1.2.1 pin controller

1.2.2 client device

2. GPIO 子系统

2.1 引入

2.2 GPIO的使用

2.2.1 设备树指定引脚

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

2.2.3 sysfs 中的访问方法


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 引脚

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值