|
本帖最后由 正点原子 于 2020-9-7 11:53 编辑
1)实验平台:正点原子领航者ZYNQ开发板
2)平台购买地址:https://item.taobao.com/item.htm?&id=606160108761
3)全套实验源码+手册+视频下载地址:http://www.openedv.com/docs/boards/fpga/zdyz_linhanz.html
4)对正点原子FPGA感兴趣的同学可以加群讨论:876744900 点击加入:
5)关注正点原子公众号,获取最新资料
第二十六章gpio子系统简介
上一章我们编写了基于设备树的LED驱动,但是驱动的本质还是没变,都是配置LED灯所使用的GPIO寄存器,驱动开发方式和裸机基本没啥区别。在驱动程序用到了GPIO就直接去读写GPIO相关的寄存器,这样会引发一个问题,大家有没有想过,如果另外一个驱动工程师写了一个驱动也用到这个相同的管脚,那么它也会去操作这些GPIO寄存器,也就是说多个驱动代码中都用了这个GPIO,那么这会乱套的,对于linux系统来说是绝对不允许的事情!
内核维护者在内核中设计了一些统一管控系统资源的体系,这些体系让内核能够对系统资源在各个驱动之间的使用统一协调和分配,保证整个内核的稳定健康运行。例如系统中所有的GPIO就属于系统资源,每个驱动模块如果要使用某个GPIO就要先调用特殊的接口先申请,申请到后使用,使用完后要释放。又譬如中断号也是一种资源,驱动在使用前也必须去申请,只能申请成功之后才能使用,否则不能使用。
所以本章就引入了gpio子系统,那么gpio子系统其实就是内核中管控GPIO资源的一套体系。
26.1gpio子系统
26.1.1gpio子系统简介
gpio子系统是linux内核当中用于管理GPIO资源的一套系统,它提供了很多GPIO相关的API接口。驱动程序中使用GPIO之前需要向gpio子系统申请,申请成功之后才可以使用,例如设置GPIO的输入、输出方向,设置GPIO输出高或低电平、读取GPIO输入电平等等。
gpio子系统的主要目的就是方便驱动开发者使用gpio,驱动开发者在设备树中添加gpio相关信息,然后就可以在驱动程序中使用gpio子系统提供的API函数来操作GPIO,Linux内核向驱动开发者屏蔽掉了GPIO的设置过程,极大的方便了驱动开发者使用GPIO。
26.1.2ZYNQ的gpio子系统驱动
gpio子系统虽然方便了驱动开发者使用gpio,但是最终还是得去操作硬件寄存器;所以在使用gpio子系统之前,我们需要向内核gpio子系统注册这一套操作硬件寄存器的“方法”,关于这个后面再说,我们先来看看在设备树中是如何描述gpio信息。
1、设备树中的gpio信息
我们来看个示例,如下所示:
示例代码26.1.2.1 gpio信息
- 1 led {
- 2 compatible = "alientek,led";
- 3 status = "okay";
- 4 default-state = "on";
- 5 led-gpio = <&gpio0 7 GPIO_ACTIVE_HIGH>;
- 6 };
- 7
- 8 beeper {
- 9 compatible = "alientek,beeper";
- 10 status = "okay";
- 11 default-state = "off";
- 12 beeper-gpio = <&gpio0 60 GPIO_ACTIVE_HIGH>;
- 13 };
复制代码
在上面的示例当中,定义了两个节点led和beeper,其中led节点是对应了一个led设备,beeper节点对应了一个蜂鸣器设备;第5行,led节点中定义了一个属性“led-gpio”,该属性用于描述LED设备由那个GPIO控制,属性值一共有三个,我们来看一下这三个属性值的含义,“&gpio0”表示led引脚所使用的IO属于gpio0,“7”表示gpio0的第7号IO,通过这两个值led驱动程序就知道led引脚使用了GPIO0_IO07这GPIO。“GPIO_ACTIVE_HIGH”表示高电平有效,如果改为“GPIO_ACTIVE_LOW”就表示高电平有效(GPIO_ACTIVE_HIGH和GPIO_ACTIVE_LOW其实是宏定义,GPIO_ACTIVE_HIGH等于0,GPIO_ACTIVE_LOW等于1);高电平有效的意思就是,当GPIO0_IO07管脚输出高电平时led才会被点亮。
根据上面这些信息,LED驱动程序就可以使用GPIO0_IO07来控制LED灯的亮灭状态了,打开zynq-7000.dtsi,在里面找到如下所示内容:
示例代码26.1.2.2 gpio0节点
- 110 gpio0: gpio@e000a000 {
- 111 compatible = "xlnx,zynq-gpio-1.0";
- 112 #gpio-cells = <2>;
- 113 clocks = <&clkc 42>;
- 114 gpio-controller;
- 115 interrupt-controller;
- 116 #interrupt-cells = <2>;
- 117 interrupt-parent = <&intc>;
- 118 interrupts = <0 20 4>;
- 119 reg = <0xe000a000 0x1000>;
- 120 };
复制代码
Gpio0节点信息描述了ZYNQ PS GPIO控制器的所有信息,重点就是GPIO外设寄存器基地址以及兼容属性。关于ZYNQ系列SOC的PS GPIO控制器绑定信息请查看文档Documentation/devicetree/bindings/gpio/gpio-zynq.txt。
第111行,设置gpio0节点的compatible属性为“xlnx,zynq-gpio-1.0”,在Linux内核中搜索这两个字符串就可以找到ZYNQ的GPIO驱动程序。
第119行,reg属性设置了GPIO控制器的寄存器基地址为0xE000A000,大家可以打开ug585-Zynq-7000-TRM.pdf手册,找到“Appx. B: Register Details”章节的General Purpose I/O (gpio)小节,如图 37.1.1所示:
图 37.1.1 ZYNQ GPIO寄存器基地址
从图 37.1.1可以看出,GPIO控制器的基地址就是0xE000A000。
第114行,“gpio-controller”表示gpio0节点是个GPIO控制器,表示这个节点对应的驱动程序是gpio驱动。
第112行,“#gpio-cells”属性和“#address-cells”类似,在gpio0节点中#gpio-cells的值等于2,表示一共有两个cell,大家可以这样理解,使用gpio0的时候,需要传递2个参数过去,第一个参数为GPIO编号,比如“&gpio0 7”就表示GPIO0_IO07。第二个参数表示GPIO极性,如果为0(GPIO_ACTIVE_HIGH)的话表示高电平有效,如果为1(GPIO_ACTIVE_LOW)的话表示低电平有效。
所以在led节点中“led-gpio = <&gpio0 7 GPIO_ACTIVE_HIGH>;”就表示使用了GPIO0_IO07这个管脚,并且是高电平有效。
以上就讲解了在设备树如何指定、描述一个gpio。
2、GPIO驱动程序简介
在前面给大家说过,gpio子系统虽然方便了驱动开发者使用gpio,但是最终还是得去操作硬件寄存器;所以在使用gpio子系统之前,我们需要向内核gpio子系统注册这一套操作硬件寄存器的“方法”,那么这套“方法”就是由GPIO驱动程序去实现、并注册到gpio子系统,所以GPIO驱动程序就负责实现GPIO操控硬件寄存器的代码,并注册到内核gpio子系统中由gpio子系统进行统一管控。
所以怎么去控制GPIO的高低电平、怎么去设置输入输出方向,怎么读取GPIO高低电平等这些代码都是在GPIO驱动程序中实现的。
gpio0节点的compatible属性描述了兼容性,在Linux内核中搜索“xlnx,zynq-gpio-1.0”这个字符串,找打我们ZYNQ的GPIO驱动程序代码。drivers/gpio/gpio-zynq.c就是ZYNQ的PS端GPIO驱动程序,在此文件中有如下所示of_device_id匹配表:
示例代码26.1.2.3 zynq_gpio_of_match配置表
- 781 static const struct of_device_id zynq_gpio_of_match[] = {
- 782 { .compatible = "xlnx,zynq-gpio-1.0", .data = &zynq_gpio_def },
- 783 { .compatible = "xlnx,zynqmp-gpio-1.0", .data = &zynqmp_gpio_def },
- 784 { /* end of table */ }
- 785 };
- 786 MODULE_DEVICE_TABLE(of, zynq_gpio_of_match);
复制代码
第782行的compatible值为“xlnx,zynq-gpio-1.0”,和gpio0节点的compatible属性匹配,因此gpio-zynq.c就是ZYNQ的GPIO控制器驱动文件。gpio-zynq.c所在的目录为drivers/gpio,进入到这个目录下可以看到很多芯片的gpio驱动文件,“gpiolib”开始的文件是gpio驱动的核心文件,如图 38.4.1所示:
图 37.1.2 gpio驱动核心文件
gpio-zynq.c文件的内容这里先不分析,等后面时机成熟的时候会专门介绍gpio-zynq.c程序以及如何编写一个GPIO驱动程序。
26.1.3gpio子系统API函数
对于驱动开发人员,设置好设备树以后就可以使用gpio子系统提供的API函数来操作指定的GPIO,gpio子系统向驱动开发人员屏蔽了具体的读写寄存器过程。这就是驱动分层与分离的好处,大家各司其职,做好自己的本职工作即可。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标号。
value:GPIO默认输出值。
返回值: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函数就讲这些,这些是我们用的最多的。
26.1.4与gpio相关的OF函数
示例代码26.1.2.1中使用led-gpio属性指定了LED灯对应的GPIO,使用beeper-gpio属性指定了蜂鸣器对应的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编号,此函数会将设备树中类似<&gpio0 7 GPIO_ACTIVE_LOW>的属性信息转换为对应的GPIO编号,此函数在驱动中使用很频繁!函数原型如下:
- int of_get_named_gpio(struct device_node *np, const char *propname, int index)
复制代码
函数参数和返回值含义如下:
np:设备节点。
propname:包含要获取GPIO信息的属性名。
index:GPIO索引,因为一个属性里面可能包含多个GPIO,此参数指定要获取哪个GPIO的编号,如果只有一个GPIO信息的话此参数为0。
返回值:正值,获取到的GPIO编号;负值,失败。
26.2pinctrl子系统
gpio子系统是用于管理系统中的GPIO资源的,那么pinctrl子系统又是做什么的呢?pinctrl其实就是PIN control的一个缩写形式。
如果使用过STM32的话应该都记得,STM32也是要先设置某个PIN的复用功能以及电气特性,例如IO速率、上下拉等,其实对于大多数的32位SOC而言,PIN都是需要设置复用功能和电气特性,因为大多数SOC的pin都是支持复用的,同一个PIN可以作为多种功能,例如vivado中配置SD0时,可以使用MIO40~45,也可以选择MIO16~21等,以及配置它们的电气特性,如所示:
图 37.2.1 vivado中配置SD0
因此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来说,pinctrl-zynq.c就是它的pinctrl驱动程序文件。pinctrl-zynq.c文件的内容这里先不分析,等后面时机成熟的时候会专门介绍pinctrl-zynq.c程序以及如何编写一个pinctrl驱动程序。
对于ZYNQ来说,我们使用了vivado图形化完成了对PIN的配置并在fsbl阶段将配置信息写入了硬件寄存器中(具体的过程就不分析了),所以不需要在内核阶段进行配置,所以就不详细介绍pinctrl子系统了,本小节的只是告诉大家pinctrl子系统的存在以及它的作用,对于ZYNQ来说,我们可以忽略它!
|
阿莫论坛20周年了!感谢大家的支持与爱护!!
曾经有一段真挚的爱情摆在我的面前,我没有珍惜,现在想起来,还好我没有珍惜……
|