本帖最后由 正点原子运营 于 2023-6-6 17:25 编辑
第二十二章 gpio子系统简介
1)实验平台:正点原子 DFZU2EG_4EV MPSoC开发板
2) 章节摘自【正点原子】DFZU2EG_4EV MPSoC开发板之嵌入式Linux 驱动开发指南 V1.0
6)Linux技术交流QQ群:299746173
上一章我们编写了基于设备树的LED驱动,但是驱动的本质还是没变,都是配置LED灯所使用的GPIO寄存器,驱动开发方式和裸机基本没啥区别。在驱动程序用到了GPIO就直接去读写GPIO相关的寄存器,这样会引发一个问题,大家有没有想过,如果另外一个驱动工程师写了一个驱动也用到这个相同的管脚,那么它也会去操作这些GPIO寄存器,也就是说多个驱动代码中都用了这个GPIO,那么这会乱套的,对于linux系统来说是绝对不允许的事情!
内核维护者在内核中设计了一些统一管控系统资源的体系,这些体系让内核能够对系统资源在各个驱动之间的使用统一协调和分配,保证整个内核的稳定健康运行。例如系统中所有的GPIO就属于系统资源,每个驱动模块如果要使用某个GPIO就要先调用特殊的接口先申请,申请到后使用,使用完后要释放。又譬如中断号也是一种资源,驱动在使用前也必须去申请,只能申请成功之后才能使用,否则不能使用。
所以本章就引入了gpio子系统,那么gpio子系统其实就是内核中管控GPIO资源的一套体系。
22.1 gpio子系统1.1.1 gpio子系统简介gpio子系统是linux内核当中用于管理GPIO资源的一套系统,它提供了很多GPIO相关的API接口。驱动程序中使用GPIO之前需要向gpio子系统申请,申请成功之后才可以使用,例如设置GPIO的输入、输出方向,设置GPIO输出高或低电平、读取GPIO输入电平等等。
gpio子系统的主要目的就是方便驱动开发者使用gpio,驱动开发者在设备树中添加gpio相关信息,然后就可以在驱动程序中使用gpio子系统提供的API函数来操作GPIO,Linux内核向驱动开发者屏蔽掉了GPIO的设置过程,极大的方便了驱动开发者使用GPIO。
22.1.2 ZYNQ 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等于0,GPIO_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 = <016 4>;
- 654 interrupt-controller;
- 655 #interrupt-cells =<2>;
- 656 reg = <0x00xff0a0000 0x00x1000>;
- 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 PeripheralsRegisters小节,如图 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驱动的核心文件,如下图所示: gpio-zynq.c文件的内容这里先不分析,等后面时机成熟的时候会专门介绍gpio-zynq.c程序以及如何编写一个GPIO驱动程序。
22.1.3 gpio子系统API函数对于驱动开发人员,设置好设备树以后就可以使用gpio子系统提供的API函数来操作指定的GPIO,gpio子系统向驱动开发人员屏蔽了具体的读写寄存器过程。这就是驱动分层与分离的好处,大家各司其职,做好自己的本职工作即可。gpio子系统提供的常用的API函数有下面几个: 1、gpio_request函数 gpio_request函数用于申请一个GPIO管脚,在使用一个GPIO之前一定要使用gpio_request进行申请,函数原型如下: - intgpio_request(unsigned gpio, const char*label)
复制代码函数参数和返回值含义如下: gpio:要申请的gpio标号,使用of_get_named_gpio函数从设备树获取指定GPIO属性信息,此函数会返回这个GPIO的标号。 label:给gpio设置个名字。 返回值:0,申请成功;其他值,申请失败。
2、gpio_free函数 如果不使用某个GPIO了,那么就可以调用gpio_free函数进行释放。函数原型如下: - voidgpio_free(unsigned gpio)
复制代码函数参数和返回值含义如下: gpio:要释放的gpio标号。 返回值:无。
3、gpio_direction_input函数 此函数用于设置某个GPIO为输入,函数原型如下所示: - intgpio_direction_input(unsigned gpio)
复制代码函数参数和返回值含义如下: gpio:要设置为输入的GPIO标号。 返回值:0,设置成功;负值,设置失败。
4、gpio_direction_output函数 此函数用于设置某个GPIO为输出,并且设置默认输出值,函数原型如下: - intgpio_direction_output(unsigned gpio, int value)
复制代码函数参数和返回值含义如下: gpio:要设置为输出的GPIO标号。 value:GPIO默认输出值。 返回值:0,设置成功;负值,设置失败。
5、gpio_get_value函数 此函数用于获取某个GPIO的值(0或1),此函数是个宏,定义所示: - #definegpio_get_value __gpio_get_value
- int__gpio_get_value(unsigned gpio)
复制代码函数参数和返回值含义如下: gpio:要获取的GPIO标号。 返回值:非负值,得到的GPIO值;负值,获取失败。
6、gpio_set_value函数 此函数用于设置某个GPIO的值,此函数是个宏,定义如下 - #definegpio_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个,此函数原型如下: - intof_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信息,函数原型如下所示: - intof_gpio_count(struct device_node *np)
复制代码函数参数和返回值含义如下: np:设备节点。 返回值:正值,统计到的GPIO数量;负值,失败。
3、of_get_named_gpio函数 此函数获取GPIO编号,gpio子系统为了方便管理系统中的GPIO资源,每一个GPIO管脚都有一个对应的编号,Linux内核中关于GPIO的API函数都要使用GPIO编号,此函数会将设备树中类似<&gpio 38GPIO_ACTIVE_LOW>的属性信息转换为对应的GPIO编号,此函数在驱动中使用很频繁!函数原型如下: - intof_get_named_gpio(struct device_node *np, const char *propname, int index)
复制代码函数参数和返回值含义如下: np:设备节点。 propname:包含要获取GPIO信息的属性名。 index:GPIO索引,因为一个属性里面可能包含多个GPIO,此参数指定要获取哪个GPIO的编号,如果只有一个GPIO信息的话此参数为0。 返回值:正值,获取到的GPIO编号;负值,失败。
22.2 pinctrl子系统gpio子系统是用于管理系统中的GPIO资源的,那么pinctrl子系统又是做什么的呢?pinctrl其实就是PIN control的一个缩写形式。
如果使用过STM32的话应该都记得,STM32也是要先设置某个PIN的复用功能以及电气特性,例如IO速率、上下拉等,其实对于大多数SOC而言,PIN都是需要设置复用功能和电气特性,因为大多数SOC的pin都是支持复用的,同一个PIN可以作为多种功能,例如vivado中配置SD1时,可以使用MIO46~51,也可以选择MIO71~76等,以及配置它们的电气特性,如所示: 因此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来说,我们可以忽略它! |