【正点原子FPGA连载】第二十六章gpio子系统简介 摘自【正点原子】DFZU2EG

阿里云国内75折 回扣 微信号:monov8
阿里云国际,腾讯云国际,低至75折。AWS 93折 免费开户实名账号 代冲值 优惠多多 微信号:monov8 飞机:@monov6

1实验平台正点原子MPSoC开发板
2平台购买地址https://detail.tmall.com/item.htm?id=692450874670
3全套实验源码+手册+视频下载地址 http://www.openedv.com/thread-340252-1-1.html

第二十六章gpio子系统简介

上一章我们编写了基于设备树的LED驱动但是驱动的本质还是没变都是配置LED灯所使用的GPIO寄存器驱动开发方式和裸机基本没啥区别。在驱动程序用到了GPIO就直接去读写GPIO相关的寄存器这样会引发一个问题大家有没有想过如果另外一个驱动工程师写了一个驱动也用到这个相同的管脚那么它也会去操作这些GPIO寄存器也就是说多个驱动代码中都用了这个GPIO那么这会乱套的对于linux系统来说是绝对不允许的事情
内核维护者在内核中设计了一些统一管控系统资源的体系这些体系让内核能够对系统资源在各个驱动之间的使用统一协调和分配保证整个内核的稳定健康运行。例如系统中所有的GPIO就属于系统资源每个驱动模块如果要使用某个GPIO就要先调用特殊的接口先申请申请到后使用使用完后要释放。又譬如中断号也是一种资源驱动在使用前也必须去申请只能申请成功之后才能使用否则不能使用。
所以本章就引入了gpio子系统那么gpio子系统其实就是内核中管控GPIO资源的一套体系。

22.1gpio子系统

22.1.1gpio子系统简介
gpio子系统是linux内核当中用于管理GPIO资源的一套系统它提供了很多GPIO相关的API接口。驱动程序中使用GPIO之前需要向gpio子系统申请申请成功之后才可以使用例如设置GPIO的输入、输出方向设置GPIO输出高或低电平、读取GPIO输入电平等等。
gpio子系统的主要目的就是方便驱动开发者使用gpio驱动开发者在设备树中添加gpio相关信息然后就可以在驱动程序中使用gpio子系统提供的API函数来操作GPIOLinux内核向驱动开发者屏蔽掉了GPIO的设置过程极大的方便了驱动开发者使用GPIO。

22.1.2ZYNQ MPSoC的gpio子系统驱动

gpio子系统虽然方便了驱动开发者使用gpio但是最终还是得去操作硬件寄存器所以在使用gpio子系统之前我们需要向内核gpio子系统注册这一套操作硬件寄存器的“方法”关于这个后面再说我们先来看看在设备树中是如何描述gpio信息。
1、设备树中的gpio信息
我们来看个示例如下所示
示例代码22.1.2.1 gpio信息

11 gpio-led0 {
12     label = "ps_led1";
13     gpios = <&gpio 38 GPIO_ACTIVE_HIGH>;
14     linux,default-trigger = "timer";
15 };
 ……
41 gpio-key1 {
42     label = "ps_key1";
43     gpios = <&gpio 40 GPIO_ACTIVE_LOW>;
44     linux,code = <KEY_UP>;
45 };

在上面的示例当中定义了两个节点gpio-led0和gpio-key1其中gpio-led0节点是对应了一个led设备gpio-key1节点对应了一个按键设备第13行gpio-led0节点中定义了一个属性“gpios”该属性用于描述LED设备由那个GPIO控制属性值一共有三个我们来看一下这三个属性值的含义“&gpio”表示led引脚所使用的IO属于gpio“38”表示gpio的第38号IO通过这两个值led驱动程序就知道led引脚使用了GPIO MIO38。“GPIO_ACTIVE_HIGH”表示高电平有效如果改为“GPIO_ACTIVE_LOW”就表示低电平有效GPIO_ACTIVE_HIGH和GPIO_ACTIVE_LOW其实是宏定义GPIO_ACTIVE_HIGH等于0GPIO_ACTIVE_LOW等于1高电平有效的意思就是当GPIO MIO38管脚输出高电平时led才会被点亮。
根据上面这些信息LED驱动程序就可以使用GPIO MIO38来控制LED灯的亮灭状态了打开zynqmp.dtsi在里面找到如下所示内容
示例代码22.1.2.2 gpio0节点

648 gpio: gpio@ff0a0000 {
649         compatible = "xlnx,zynqmp-gpio-1.0";
650         status = "disabled";
651         #gpio-cells = <0x2>;
652         interrupt-parent = <&gic>;
653         interrupts = <0 16 4>;
654         interrupt-controller;
655         #interrupt-cells = <2>;
656         reg = <0x0 0xff0a0000 0x0 0x1000>;
657         gpio-controller;
658         power-domains = <&zynqmp_firmware 46>;
659 };

gpio节点信息描述了ZYNQ MPSoC器件PS GPIO控制器的所有信息重点就是GPIO外设寄存器基地址以及兼容属性。关于ZYNQ MPSoC的PS GPIO控制器绑定信息请查看内核源码中的文档Documentation/devicetree/bindings/gpio/gpio-zynq.txt。
第649行设置gpio节点的compatible属性为“xlnx,zynqmp-gpio-1.0”在Linux内核中搜索这两个字符串就可以找到ZYNQ MPSoC的GPIO驱动程序。
第656行reg属性设置了GPIO控制器的寄存器基地址为0xFF0A0000大家可以打开ug1085-zynq-ultrascale-trm.pdf手册找到“Ch.10 System Addresses”章节的PS I/O Peripherals Registers小节如图 22.1.1所示
在这里插入图片描述

图 22.1.1 ZYNQ MPSoC GPIO寄存器基地址
从图 22.1.1可以看出GPIO控制器的基地址就是0xFF0A0000。
第657行“gpio-controller”表示gpio节点是个GPIO控制器表示这个节点对应的驱动程序是gpio驱动。
第651行“#gpio-cells”属性和“#address-cells”类似在gpio节点中#gpio-cells的值等于0x2表示一共有两个cell大家可以这样理解使用gpio的时候需要传递2个参数过去第一个参数为GPIO编号比如“&gpio 38”就表示GPIO MIO38。第二个参数表示GPIO极性如果为0(GPIO_ACTIVE_HIGH)的话表示高电平有效如果为1(GPIO_ACTIVE_LOW)的话表示低电平有效。
所以在led节点中“led-gpio = <&gpio 38 GPIO_ACTIVE_HIGH>;”就表示使用了GPIO Bank1 MIO38这个管脚并且是高电平有效。
以上就讲解了在设备树如何指定、描述一个gpio。
2、GPIO驱动程序简介
在前面给大家说过gpio子系统虽然方便了驱动开发者使用gpio但是最终还是得去操作硬件寄存器所以在使用gpio子系统之前我们需要向内核gpio子系统注册这一套操作硬件寄存器的“方法”那么这套“方法”就是由GPIO驱动程序去实现、并注册到gpio子系统所以GPIO驱动程序就负责实现GPIO操控硬件寄存器的代码并注册到内核gpio子系统中由gpio子系统进行统一管控。
所以怎么去控制GPIO的高低电平、怎么去设置输入输出方向怎么读取GPIO高低电平等这些代码都是在GPIO驱动程序中实现的。
gpio节点的compatible属性描述了兼容性在Linux内核中搜索“xlnx,zynqmp-gpio-1.0”这个字符串找到我们ZYNQ MPSoC的GPIO驱动程序代码。drivers/gpio/gpio-zynq.c就是ZYNQ MPSoC的GPIO驱动程序在此文件中有如下所示of_device_id匹配表
示例代码22.1.2.3 zynq_gpio_of_match配置表

870 static const struct of_device_id zynq_gpio_of_match[] = {
871         { .compatible = "xlnx,zynq-gpio-1.0", .data = &zynq_gpio_def },
872         { .compatible = "xlnx,zynqmp-gpio-1.0", .data = &zynqmp_gpio_def },
873         { .compatible = "xlnx,versal-gpio-1.0", .data = &versal_gpio_def },
874         { .compatible = "xlnx,pmc-gpio-1.0", .data = &pmc_gpio_def },
875         { /* end of table */ }
876 };

第872行的compatible值为“xlnx,zynqmp-gpio-1.0”和gpio节点的compatible属性匹配因此gpio-zynq.c就是ZYNQ MPSoC的GPIO控制器驱动文件。gpio-zynq.c所在的目录为drivers/gpio进入到这个目录下可以看到很多芯片的gpio驱动文件“gpiolib”开始的文件是gpio驱动的核心文件如下图所示
在这里插入图片描述

图 22.1.2 gpio驱动核心文件
gpio-zynq.c文件的内容这里先不分析等后面时机成熟的时候会专门介绍gpio-zynq.c程序以及如何编写一个GPIO驱动程序。

22.1.3gpio子系统API函数

对于驱动开发人员设置好设备树以后就可以使用gpio子系统提供的API函数来操作指定的GPIOgpio子系统向驱动开发人员屏蔽了具体的读写寄存器过程。这就是驱动分层与分离的好处大家各司其职做好自己的本职工作即可。gpio子系统提供的常用的API函数有下面几个
1、gpio_request函数
gpio_request函数用于申请一个GPIO管脚在使用一个GPIO之前一定要使用gpio_request进行申请函数原型如下
int gpio_request(unsigned gpio, const char *label)
函数参数和返回值含义如下
gpio要申请的gpio标号使用of_get_named_gpio函数从设备树获取指定GPIO属性信息此函数会返回这个GPIO的标号。
label给gpio设置个名字。
返回值0申请成功其他值申请失败。
2、gpio_free函数
如果不使用某个GPIO了那么就可以调用gpio_free函数进行释放。函数原型如下
void gpio_free(unsigned gpio)
函数参数和返回值含义如下
gpio要释放的gpio标号。
返回值无。
3、gpio_direction_input函数
此函数用于设置某个GPIO为输入函数原型如下所示
int gpio_direction_input(unsigned gpio)
函数参数和返回值含义如下
gpio要设置为输入的GPIO标号。
返回值0设置成功负值设置失败。
4、gpio_direction_output函数
此函数用于设置某个GPIO为输出并且设置默认输出值函数原型如下
int gpio_direction_output(unsigned gpio, int value)
函数参数和返回值含义如下
gpio要设置为输出的GPIO标号。
valueGPIO默认输出值。
返回值0设置成功负值设置失败。
5、gpio_get_value函数
此函数用于获取某个GPIO的值(0或1)此函数是个宏定义所示
#define gpio_get_value __gpio_get_value
int __gpio_get_value(unsigned gpio)
函数参数和返回值含义如下
gpio要获取的GPIO标号。
返回值非负值得到的GPIO值负值获取失败。
6、gpio_set_value函数
此函数用于设置某个GPIO的值此函数是个宏定义如下
#define gpio_set_value __gpio_set_value
void __gpio_set_value(unsigned gpio, int value)
函数参数和返回值含义如下
gpio要设置的GPIO标号。
value要设置的值。
返回值无
关于gpio子系统常用的API函数就讲这些这些是我们用的最多的。
22.1.4与gpio相关的OF函数
示例代码22.1.2.1中使用gpios属性指定了LED灯对应的GPIO那么在驱动程序中就需要去读取这些属性的内容Linux内核提供了几个与GPIO有关的OF函数常用的几个OF函数如下所示
1、of_gpio_named_count函数
of_gpio_named_count函数用于获取设备树某个属性里面定义了几个GPIO信息要注意的是空的GPIO信息也会被统计到比如
gpios = <0
&gpio1 1 2
0
&gpio2 3 4>;
上述代码的“gpios”节点一共定义了4个GPIO但是有2个是空的没有实际的含义。通过of_gpio_named_count函数统计出来的GPIO数量就是4个此函数原型如下
int of_gpio_named_count(struct device_node *np, const char *propname)
函数参数和返回值含义如下
np设备节点。
propname要统计的GPIO属性。
返回值正值统计到的GPIO数量负值失败。
2、of_gpio_count函数
和of_gpio_named_count函数一样但是不同的地方在于此函数统计的是“gpios”这个属性的GPIO数量而of_gpio_named_count函数可以统计任意属性的GPIO信息函数原型如下所示
int of_gpio_count(struct device_node *np)
函数参数和返回值含义如下
np设备节点。
返回值正值统计到的GPIO数量负值失败。
3、of_get_named_gpio函数
此函数获取GPIO编号gpio子系统为了方便管理系统中的GPIO资源每一个GPIO管脚都有一个对应的编号Linux内核中关于GPIO的API函数都要使用GPIO编号此函数会将设备树中类似<&gpio 38 GPIO_ACTIVE_LOW>的属性信息转换为对应的GPIO编号此函数在驱动中使用很频繁函数原型如下
int of_get_named_gpio(struct device_node *np, const char *propname, int index)
函数参数和返回值含义如下
np设备节点。
propname包含要获取GPIO信息的属性名。
indexGPIO索引因为一个属性里面可能包含多个GPIO此参数指定要获取哪个GPIO的编号如果只有一个GPIO信息的话此参数为0。
返回值正值获取到的GPIO编号负值失败。
22.2pinctrl子系统
gpio子系统是用于管理系统中的GPIO资源的那么pinctrl子系统又是做什么的呢pinctrl其实就是PIN control的一个缩写形式。
如果使用过STM32的话应该都记得STM32也是要先设置某个PIN的复用功能以及电气特性例如IO速率、上下拉等其实对于大多数SOC而言PIN都是需要设置复用功能和电气特性因为大多数SOC的pin都是支持复用的同一个PIN可以作为多种功能例如vivado中配置SD1时可以使用MIO4651也可以选择MIO7176等以及配置它们的电气特性如所示
在这里插入图片描述

图 22.2.1 vivado中配置SD1
因此Linux内核针对PIN的配置推出了pinctrl子系统对于GPIO的配置推出了gpio子系统所以说到这里就知道了pinctrl子系统是内核中专门用于管理、配置PIN的一套子系统。
配置PIN的复用功能和电器特性也是通过控制硬件寄存器来实现的所以pinctrl子系统最终也是如此。有gpio驱动程序那必然也有pinctrl驱动程序pinctrl驱动程序中实现了PIN的配置方法并并注册到pinctrl子系统所以pinctrl驱动程序就负责实现配置PIN的底层代码主要就是寄存器控制并注册到内核pinctrl子系统中由pinctrl子系统进行统一管理。
在内核源码drivers/pinctrl目录下有很多的pinctrl-xxx.c文件它们都是不同SoC所对应的pinctrl驱动程序源文件例如对于ZYNQ MPSoC来说pinctrl-zynqmp.c就是它的pinctrl驱动程序文件。pinctrl-zynqmp.c文件的内容这里先不分析等后面时机成熟的时候会专门介绍pinctrl-zynqmp.c程序以及如何编写一个pinctrl驱动程序。
对于ZYNQ MPSoC来说我们使用了vivado图形化完成了对PIN的配置并在fsbl阶段将配置信息写入了硬件寄存器中具体的过程就不分析了所以不需要在内核阶段进行配置所以就不详细介绍pinctrl子系统了本小节的只是告诉大家pinctrl子系统的存在以及它的作用对于ZYNQ MPSoC来说我们可以忽略它

阿里云国内75折 回扣 微信号:monov8
阿里云国际,腾讯云国际,低至75折。AWS 93折 免费开户实名账号 代冲值 优惠多多 微信号:monov8 飞机:@monov6