目录

1.pinctrl子系统

    1)pinctrl子系统简介

    2)I.MX6ULL的的pinctrl子系统驱动

    3)设备树中添加pinctrl节点模板

2.gpio子系统

    1)gpio子系统简介

    2)I.MX6ULL的的gpio子系统驱动

    3)gpio子系统API函数

    4)设备树中添加gpio节点模板

           1、创建test设备节点

           2、添加pinctrl信息

           3、添加GPIO属性信息

    5)与gpio相关的OF函数

4.实验程序编写

    1)修改设备树文件

           1、添加pinctrl节点

           2、添加LED设备节点

           3、检查PIN是否被其他外设使用

5.运行测试

    1)编译驱动程序和测试APP

           1、编译驱动程序

            2、编译测试APP

    2)运行测试


      Linux内核提供了pinctrl和gpio子系统用于GPIO驱动,本章我们就来学习一下如何借助pinctrl和

      gpio子系统来简化GPIO驱动开发。

1.pinctrl子系统

    1)pinctrl子系统简介

              Linux驱动讲究驱动分离与分层,pinctrl和gpio子系统就是驱动分离与分层思想下的产物,

              驱动分离与分层其实就是按照面向对象编程的设计思想而设计的设备驱动框架,关于驱动

              的分离与分层我们后面会讲。

              本来pinctrl和gpio子系统应该放到驱动分离与分层章节后面讲解,但是不管什么外设驱

              动,GPIO驱动基本都是必须的,而pinctrl和gpio子系统又是GPIO驱动必须使用的,所以

              就将pintrcl和gpio子系统这一章节提前了。

              我们先来回顾一下上一章是怎么初始化LED灯所使用的GPIO,步骤如下:

              1、修改设备树,添加相应的节点,节点里面重点是设置reg属性,reg属性包括了GPIO相

                    关寄存器。

              2、获取reg属性中IOMUXC_SW_MUX_CTL_PAD_GPIO1_IO03和

                    IOMUXC_SW_PAD_CTL_PAD_GPIO1_IO03这两个寄存器地址,并且初始化这两个

                   寄存器,这两个寄存器用于设置GPIO1_IO03这个PIN的复用功能、上下拉、速度等。

              3、在步骤2里面将GPIO1_IO03这个PIN复用为了GPIO功能,因此需要设置GPIO1_IO03

                    这个GPIO相关的寄存器,也就是GPIO1_DR和GPIO1_GDIR这两个寄存器。

              总结一下,步骤2中完成对GPIO1_IO03这个PIN的初始化,设置这个PIN的复用功能、上

              下拉等,比如将GPIO_IO03这个PIN设置为GPIO功能。步骤3中完成对GPIO的初始化,

              设置GPIO为输入/输出等。

              如果使用过STM32的话应该都记得,STM32也是要先设置某个PIN的复用功能、速度、上

              下拉等,然后再设置PIN所对应的GPIO。其实对于大多数的32位SOC而言,引脚的设置

              基本都是这两方面,因此Linux内核针对PIN的配置推出了pinctrl子系统对于GPIO的配

              置推出了gpio子系统。本节我们来学习pinctrl子系统,下一节再学习gpio子系统。

              大多数SOC的pin都是支持复用的,比如I.MX6ULL的GPIO1_IO03既可以作为普通的

              GPIO使用,也可以作为I2C1的SDA等等。此外我们还需要配置pin的电气特性,比如上/

              下拉、速度、驱动能力等等。

              传统的配置pin的方式就是直接操作相应的寄存器,但是这种配置方式比较繁琐、而且容

              易出问题(比如pin功能冲突)。pinctrl子系统就是为了解决这个问题而引入的,pinctrl子系

              统主要工作内容如下:

              1、获取设备树中pin信息。

              2、根据获取到的pin信息来设置pin的复用功能

              3、根据获取到的pin信息来设置pin的电气特性,比如上/下拉、速度、驱动能力等。

              对于我们使用者来讲,只需要在设备树里面设置好某个pin的相关属性即可,其他的初始

              化工作均由pinctrl子系统来完成,pinctrl子系统源码目录为drivers/pinctrl。

    2)I.MX6ULL的的pinctrl子系统驱动

              1、PIN配置信息详解

                   要使用pinctrl子系统,我们需要在设备树里面设置PIN的配置信息,毕竟pinctrl子系统

                   要根据你提供的信息来配置PIN功能,一般会在设备树里面创建一个节点来描述PIN的

                   配置信息。打开imx6ull.dtsi文件,找到一个叫做iomuxc的节点,如下所示:

756 iomuxc: iomuxc@020e0000 {
757 	compatible = "fsl,imx6ul-iomuxc";
758 	reg = <0x020e0000 0x4000>;
759 };

                    iomuxc节点就是I.MX6ULL的IOMUXC外设对应的节点,看起来内容很少,没看出什

                   么跟PIN的配置有关的内容啊,别急!打开imx6ull-alientek-emmc.dts,找到如下所示

                    内容:

311 &iomuxc {
312 	pinctrl-names = "default";
313 	pinctrl-0 = <&pinctrl_hog_1>;
314 	imx6ul-evk {
315 		pinctrl_hog_1: hoggrp-1 {
316 			fsl,pins = <
317 				MX6UL_PAD_UART1_RTS_B__GPIO1_IO19 0x17059
318 				MX6UL_PAD_GPIO1_IO05__USDHC1_VSELECT 0x17059
319 				MX6UL_PAD_GPIO1_IO09__GPIO1_IO09 0x17059
320 				MX6UL_PAD_GPIO1_IO00__ANATOP_OTG1_ID 0x13058
321 				>;
322	 		};
......
371 		pinctrl_flexcan1: flexcan1grp{
372 			fsl,pins = <
373 				MX6UL_PAD_UART3_RTS_B__FLEXCAN1_RX 0x1b020
374 				MX6UL_PAD_UART3_CTS_B__FLEXCAN1_TX 0x1b020
375 			>;
376 		};
......
587 		pinctrl_wdog: wdoggrp {
588 			fsl,pins = <
589 				MX6UL_PAD_LCD_RESET__WDOG1_WDOG_ANY 0x30b0
590 			>;
591 		};
592 	};
593 };

                   以上代码就是向iomuxc节点追加数据,不同的外设使用的PIN不同、其配置也不同,因

                   此一个萝卜一个坑,将某个外设所使用的所有PIN都组织在一个子节点里面。以上代码

                   中pinctrl_hog_1子节点就是和热插拔有关的PIN集合,比如USBOTG的ID引脚。

                    pinctrl_flexcan1子节点是flexcan1这个外设所使用的PIN,pinctrl_wdog子节点是wdog

                   外设所使用的PIN。如果需要在iomuxc中添加我们自定义外设的PIN,那么需要新建一

                   个子节点,然后将这个自定义外设的所有PIN配置信息都放到这个子节点中。将其与以

                   上代码结合起来就可以得到完成的iomuxc节点,如下所示:

1 iomuxc: iomuxc@020e0000 {
2 		compatible = "fsl,imx6ul-iomuxc";
3 		reg = <0x020e0000 0x4000>;
4 		pinctrl-names = "default";
5 		pinctrl-0 = <&pinctrl_hog_1>;
6 		imx6ul-evk {
7 			pinctrl_hog_1: hoggrp-1 {
8 				fsl,pins = <
9 						MX6UL_PAD_UART1_RTS_B__GPIO1_IO19 0x17059
10 						MX6UL_PAD_GPIO1_IO05__USDHC1_VSELECT 0x17059
11 						MX6UL_PAD_GPIO1_IO09__GPIO1_IO09 0x17059
12 						MX6UL_PAD_GPIO1_IO00__ANATOP_OTG1_ID 0x13058
13				 >;
......
16 			};
17		};
18 };

                   第2行,compatible属性值为“fsl,imx6ul-iomuxc”,前面讲解设备树的时候说过,Linux

                                内核会根据compatbile属性值来查找对应的驱动文件,所以我们在Linux内核源

                                码中全局搜索字符串“fsl,imx6ul-iomuxc”就会找到I.MX6ULL这颗SOC的pinctrl

                                驱动文件。稍后我们会讲解这个pinctrl驱动文件。

                   第9~12行,pinctrl_hog_1子节点所使用的PIN配置信息,我们就以第9行的

                                      UART1_RTS_B这个PIN为例,讲解一下如何添加PIN的配置信息,

                                       UART1_RTS_B的配置信息如下:

MX6UL_PAD_UART1_RTS_B__GPIO1_IO19 0x17059

                   首先说明一下,UART1_RTS_B这个PIN是作为SD卡的检测引脚,也就是通过此PIN就

                   可以检测到SD卡是否有插入。UART1_RTS_B的配置信息分为两部分:

                   MX6UL_PAD_UART1_RTS_B__GPIO1_IO19和0x17059,我们重点来看一下这两部

                   分是什么含义,前面说了,对于一个PIN的配置主要包括两方面,一个是设置这个PIN

                   的复用功能,另一个就是设置这个PIN的电气特性。所以我们可以大胆的猜测

                   UART1_RTS_B的这两部分配置信息一个是设置UART1_RTS_B的复用功能,一个是

                   用来设置UART1_RTS_B的电气特性。

                   首先来看一下MX6UL_PAD_UART1_RTS_B__GPIO1_IO19,这是一个宏定义,定义

                   在文件arch/arm/boot/dts/imx6ul-pinfunc.h中,imx6ull.dtsi会引用imx6ull-pinfunc.h这个

                   头文件,而imx6ull-pinfunc.h又会引用imx6ul-pinfunc.h这个头文件(绕啊绕!)。从这

                   里可以看出,可以在设备树中引用C语言中.h文件中的内容。

                   MX6UL_PAD_UART1_RTS_B__GPIO1_IO19的宏定义内容如下:

190 #define MX6UL_PAD_UART1_RTS_B__UART1_DCE_RTS 	0x0090 0x031C 0x0620 0x0 0x3
191 #define MX6UL_PAD_UART1_RTS_B__UART1_DTE_CTS 	0x0090 0x031C 0x0000 0x0 0x0
192 #define MX6UL_PAD_UART1_RTS_B__ENET1_TX_ER 		0x0090 0x031C 0x0000 0x1 0x0
193 #define MX6UL_PAD_UART1_RTS_B__USDHC1_CD_B 		0x0090 0x031C 0x0668 0x2 0x1
194 #define MX6UL_PAD_UART1_RTS_B__CSI_DATA05 		0x0090 0x031C 0x04CC 0x3 0x1
195 #define MX6UL_PAD_UART1_RTS_B__ENET2_1588_EVENT1_OUT 	0x0090 0x031C 0x0000 0x4 0x0
196 #define MX6UL_PAD_UART1_RTS_B__GPIO1_IO19 				0x0090 0x031C 0x0000 0x5 0x0
197 #define MX6UL_PAD_UART1_RTS_B__USDHC2_CD_B 				0x0090 0x031C 0x0674 0x8 0x2

                   以上代码中一共有8个以“MX6UL_PAD_UART1_RTS_B”开头的宏定义,大家仔细观察

                   应该就能发现,这8个宏定义分别对应UART1_RTS_B这个PIN的8个复用IO。查阅

                 《I.MX6ULL参考手册》可以知UART1_RTS_B的可选复用IO如图1所示:

pinctrl和gpio子系统-编程知识网

 图1 UART1_RTS_B引脚复用

                   以上代码196行的宏定义MX6UL_PAD_UART1_RTS_B__GPIO1_IO19表示将

                   UART1_RTS_B这个IO复用为GPIO1_IO19。此宏定义后面跟着5个数字,也就是这个

                   宏定义的具体值,如下所示:

0x0090 0x031C 0x0000 0x5 0x0

                   这5个值的含义如下所示:

<mux_reg conf_reg input_reg mux_mode input_val>

                   综上所述可知:

                   0x0090:mux_reg寄存器偏移地址,设备树中的iomuxc节点就是IOMUXC外设对应的

                                   节点,根据其reg属性可知IOMUXC外设寄存器起始地址为0x020e0000。因

                                   此0x020e0000+0x0090=0x020e0090,

                                   IOMUXC_SW_MUX_CTL_PAD_UART1_RTS_B寄存器地址正好是

                                   0x020e0090,大家可以在《IMX6ULL参考手册》中找到

                                   IOMUXC_SW_MUX_CTL_PAD_UART1_RTS_B这个寄存器的位域图,如

                                    图2所示: 

pinctrl和gpio子系统-编程知识网

 图2 寄存器位域图

                   因此可知,0x020e0000+mux_reg就是PIN的复用寄存器地址

                   0x031C:conf_reg寄存器偏移地址,和mux_reg一样,

                                    0x020e0000+0x031c=0x020e031c,这个就是寄存器

                                    IOMUXC_SW_PAD_CTL_PAD_UART1_RTS_B的地址。

                   0x0000:input_reg寄存器偏移地址,有些外设有input_reg寄存器,有input_reg寄存

                                   器的外设需要配置input_reg寄存器。没有的话就不需要设置,

                                   UART1_RTS_B这个PIN在做GPIO1_IO19的时候是没有input_reg寄存器,

                                   因此这里intput_reg是无效的。

                   0x5:mux_reg寄存器值,在这里就相当于设置

                             IOMUXC_SW_MUX_CTL_PAD_UART1_RTS_B寄存器为0x5,也即是设置

                             UART1_RTS_B这个PIN复用为GPIO1_IO19。

                   0x0:input_reg寄存器值,在这里无效。

                   这就是宏MX6UL_PAD_UART1_RTS_B__GPIO1_IO19的含义,看的比较仔细的同学

                   应该会发现并没有conf_reg寄存器的值,config_reg寄存器是设置一个PIN的电气特性

                   的,这么重要的寄存器怎么没有值呢?回到以上代码中,第9行的内容如下所示:

MX6UL_PAD_UART1_RTS_B__GPIO1_IO19 0x17059

                   MX6UL_PAD_UART1_RTS_B__GPIO1_IO19我们上面已经分析了,就剩下了一个

                   0x17059,反应快的同学应该已经猜出来了,0x17059就是conf_reg寄存器值!此值

                  由用户自行设置,通过此值来设置一个IO的上/下拉、驱动能力和速度等。在这里就相

                  当于设置寄存器IOMUXC_SW_PAD_CTL_PAD_UART1_RTS_B的值为0x17059

    3)设备树中添加pinctrl节点模板

              我们已经对pinctrl有了比较深入的了解,接下来我们学习一下如何在设备树中添加某个外

              设的PIN信息。关于I.MX系列SOC的pinctrl设备树绑定信息可以参考文档

              Documentation/devicetree/bindings/pinctrl/fsl,imx-pinctrl.txt。这里我们虚拟一个名为

              “test”的设备,test使用了GPIO1_IO00这个PIN的GPIO功能,pinctrl节点添加过程如下:

              1、创建对应的节点

                    同一个外设的PIN都放到一个节点里面,打开imx6ull-alientek-emmc.dts,在iomuxc节

                    点中的“imx6ul-evk”子节点下添加“pinctrl_test”节点,注意!节点前缀一定要为

                    “pinctrl_”。添加完成以后如下所示:

1 pinctrl_test: testgrp {
2     /* 具体的 PIN 信息 */
3 };

              2、添加“fsl,pins”属性

                    设备树是通过属性来保存信息的,因此我们需要添加一个属性属性名字一定要为

                    “fsl,pins”,因为对于I.MX系列SOC而言,pinctrl驱动程序是通过读取“fsl,pins”属性值

                    来获取PIN的配置信息,完成以后如下所示:

1 pinctrl_test: testgrp {
2 		fsl,pins = <
3 			/* 设备所使用的 PIN 配置信息 */
4 		>;
5 };

              3、在“fsl,pins”属性中添加PIN配置信息

                   最后在“fsl,pins”属性中添加具体的PIN配置信息,完成以后如下所示:

1 pinctrl_test: testgrp {
2 		fsl,pins = <
3 			MX6UL_PAD_GPIO1_IO00__GPIO1_IO00 config /*config 是具体设置值*/
4 		>;
5 };

2.gpio子系统

    1)gpio子系统简介

              上一小节讲解了pinctrl子系统,pinctrl子系统重点是设置PIN(有的SOC叫做PAD)的复用和

              电气属性,如果pinctrl子系统将一个PIN复用为GPIO的话,那么接下来就要用到gpio子系

              统了。gpio子系统顾名思义,就是用于初始化GPIO并且提供相应的API函数比如设置

              GPIO为输入输出,读取GPIO的值等gpio子系统的主要目的就是方便驱动开发者使用

              gpio,驱动开发者在设备树中添加gpio相关信息,然后就可以在驱动程序中使用gpio子

             系统提供的API函数来操作GPIO,Linux内核向驱动开发者屏蔽掉了GPIO的设置过程,极

             大的方便了驱动开发者使用GPIO。

    2)I.MX6ULL的的gpio子系统驱动

              1、设备树中的gpio信息

                    I.MX6ULL-ALPHA开发板上的UART1_RTS_B做为SD卡的检测引脚,UART1_RTS_B

                    复用为GPIO1_IO19,通过读取这个GPIO的高低电平就可以知道SD卡有没有插

                    入。首先肯定是将UART1_RTS_B这个PIN复用为GPIO1_IO19,并且设置电气属性,

                    也就是上一小节讲的pinctrl节点。打开imx6ull-alientek-emmc.dts,UART1_RTS_B这

                    个PIN的pincrtl设置如下:

316 pinctrl_hog_1: hoggrp-1 {
317 	fsl,pins = <
318 		MX6UL_PAD_UART1_RTS_B__GPIO1_IO19 0x17059 /* SD1 CD */
......
322 	>;
323 };

                    第318行,设置UART1_RTS_B这个PIN为GPIO1_IO19。

                    pinctrl配置好以后就是设置gpio了,SD卡驱动程序通过读取GPIO1_IO19的值来判断

                    SD卡有没有插入,但是SD卡驱动程序怎么知道CD引脚连接的GPIO1_IO19呢?肯定

                    是需要设备树告诉驱动啊!在设备树中SD卡节点下添加一个属性来描述SD卡的CD引

                    脚就行了,SD卡驱动直接读取这个属性值就知道SD卡的CD引脚使用的是哪个GPIO

                    了。SD卡连接在I.MX6ULL的usdhc1接口上,在imx6ull-alientek-emmc.dts中找到名

                    为“usdhc1”的节点,这个节点就是SD卡设备节点,如下所示:

760 &usdhc1 {
761 	pinctrl-names = "default", "state_100mhz", "state_200mhz";
762 	pinctrl-0 = <&pinctrl_usdhc1>;
763 	pinctrl-1 = <&pinctrl_usdhc1_100mhz>;
764 	pinctrl-2 = <&pinctrl_usdhc1_200mhz>;
765 	/* pinctrl-3 = <&pinctrl_hog_1>; */
766 	cd-gpios = <&gpio1 19 GPIO_ACTIVE_LOW>;
767 	keep-power-in-suspend;
768 	enable-sdio-wakeup;
769 	vmmc-supply = <&reg_sd1_vmmc>;
770 	status = "okay";
771 };

                   第765行,此行本来没有,是作者添加的,usdhc1节点作为SD卡设备总节点,usdhc1

                   节点需要描述SD卡所有的信息,因为驱动要使用。本行就是描述SD卡的CD引脚

                   pinctrl信息所在的子节点,因为SD卡驱动需要根据pincrtl节点信息来设置CD引脚的复

                   用功能等。762~764行的pinctrl-0~2都是SD卡其他PIN的pincrtl节点信息。但是大家

                   会发现,其实在usdhc1节点中并没有“pinctrl-3=<&pinctrl_hog_1>”这一行,也就是说

                   并没有指定CD引脚的pinctrl信息,那么SD卡驱动就没法设置CD引脚的复用功能啊?

                   这个不用担心,因为在“iomuxc”节点下引用了pinctrl_hog_1这个节点,所以Linux内

                   核中的iomuxc驱动就会自动初始化pinctrl_hog_1节点下的所有PIN。

                   第766行,属性“cd-gpios”描述了SD卡的CD引脚使用的哪个IO。属性值一共有三个,

                   我们来看一下这三个属性值的含义,“&gpio1”表示CD引脚所使用的IO属于GPIO1组,

                    “19”表示GPIO1组的第19号IO,通过这两个值SD卡驱动程序就知道CD引脚使用了

                   GPIO1_IO19这GPIO。“GPIO_ACTIVE_LOW”表示低电平有效,如果改为

                   “GPIO_ACTIVE_HIGH”就表示高电平有效。

                   根据上面这些信息,SD卡驱动程序就可以使用GPIO1_IO19来检测SD卡的CD信号

                   了,打开imx6ull.dtsi,在里面找到如下所示内容:

504 gpio1: gpio@0209c000 {
505 	compatible = "fsl,imx6ul-gpio", "fsl,imx35-gpio";
506 	reg = <0x0209c000 0x4000>;
507 	interrupts = <GIC_SPI 66 IRQ_TYPE_LEVEL_HIGH>,
508 		<GIC_SPI 67 IRQ_TYPE_LEVEL_HIGH>;
509 	gpio-controller;
510 	#gpio-cells = <2>;
511 	interrupt-controller;
512 	#interrupt-cells = <2>;
513 };

                    gpio1节点信息描述了GPIO1控制器的所有信息,重点就是GPIO1外设寄存器基地址以

                    及兼容属性。

                    第505行,设置gpio1节点的compatible属性有两个,分别为“fsl,imx6ul-gpio”和

                    “fsl,imx35-gpio”,在Linux内核中搜索这两个字符串就可以找到I.MX6UL的GPIO驱动

                    程序。

                    第506行,reg属性设置了GPIO1控制器的寄存器基地址为0X0209C000,大家可以打

                    开《I.MX6ULL参考手册》找到“Chapter28:General Purpose Input/Output(GPIO)”章节

                    第28.5小节,有如图3所示的寄存器地址表:

pinctrl和gpio子系统-编程知识网

图3 GPIO1寄存器表

                    从图3可以看出,GPIO1控制器的基地址就是0X0209C000。

                    第509行,“gpio-controller”表示gpio1节点是个GPIO控制器。

                    第510行,“#gpio-cells”属性和“#address-cells”类似,#gpio-cells应该为2,表示一共有

                                      两个cell,第一个cell为GPIO编号,比如“&gpio1 3”就表示GPIO1_IO03。

                                      第二个cell表示GPIO极性,如果为0(GPIO_ACTIVE_HIGH)的话表示高电

                                       平有效,如果为1(GPIO_ACTIVE_LOW)的话表示低电平有效。

    3)gpio子系统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函数就讲这些,这些是我们用的最多的。

    4)设备树中添加gpio节点模板

               继续完成之前的test设备,在之前中我们已经讲解了如何创建test设备的pinctrl节点。本

               节我们来学习一下如何创建test设备的GPIO节点。

           1、创建test设备节点

                        在根节点“/”下创建test设备子节点,如下所示:

1 test {
2 	/* 节点内容 */
3 };

           2、添加pinctrl信息

                       在之前我们创建了pinctrl_test节点,此节点描述了test设备所使用的GPIO1_IO00这

                       个PIN的信息,我们要将这节点添加到test设备节点中,如下所示:

1 test {
2 		pinctrl-names = "default";
3 		pinctrl-0 = <&pinctrl_test>;
4 		/* 其他节点内容 */
5 };

                       第2行,添加pinctrl-names属性,此属性描述pinctrl名字为“default”。

                       第3行,添加pinctrl-0节点,此节点引用之前创建的pinctrl_test节点,表示tset设备的

                                     所使用的PIN信息保存在pinctrl_test节点中。

           3、添加GPIO属性信息

                       我们最后需要在test节点中添加GPIO属性信息表明test所使用的GPIO是哪个引

                       脚,添加完成以后如下所示:

1 test {
2 		pinctrl-names = "default";
3 		pinctrl-0 = <&pinctrl_test>;
4 		gpio = <&gpio1 0 GPIO_ACTIVE_LOW>;
5 };

                        第4行,test设备所使用的gpio。

                       关于pinctrl子系统和gpio子系统就讲解到这里,接下来就使用pinctrl和gpio子系统来

                       驱动I.MX6ULL-ALPHA开发板上的LED灯。

    5)与gpio相关的OF函数

               在之前的代码中,我们定义了一个名为“gpio”的属性,gpio属性描述了test这个设备所使

               用的GPIO。在驱动程序中需要读取gpio属性内容,Linux内核提供了几个与GPIO有关的

               OF函数,常用的几个OF函数如下所示:

               1、of_gpio_named_count函数

                     of_gpio_named_count函数用于获取设备树某个属性里面定义了几个GPIO信息,要

                      注意的是空的GPIO信息也会被统计到,比如:

gpios = <0&gpio1 1 20&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编号,因为Linux内核中关于GPIO的API函数都要使用GPIO编号,

                      此函数会将设备树中类似<&gpio5 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编号。负值,失败。

4.实验程序编写

    1)修改设备树文件

           1、添加pinctrl节点

                       I.MX6U-ALPHA开发板上的LED灯使用了GPIO1_IO03这个PIN,打开imx6ull-

                       alientek-emmc.dts,在iomuxc节点的imx6ul-evk子节点下创建一个名为

                       “pinctrl_led”的子节点,节点内容如下所示:

1 pinctrl_led: ledgrp {
2 		fsl,pins = <
3 				MX6UL_PAD_GPIO1_IO03__GPIO1_IO03 0x10B0 /* LED0 */
4 		>;
5 };

                      第3行,将GPIO1_IO03这个PIN复用为GPIO1_IO03,电气属性值为0X10B0。

           2、添加LED设备节点

                       在根节点“/”下创建LED灯节点,节点名为“gpioled”,节点内容如下:

1 gpioled {
2 		#address-cells = <1>;
3 		#size-cells = <1>;
4 		compatible = "atkalpha-gpioled";
5 		pinctrl-names = "default";
6 		pinctrl-0 = <&pinctrl_led>;
7 		led-gpio = <&gpio1 3 GPIO_ACTIVE_LOW>;
8 		status = "okay";
9 }

                       第6行,pinctrl-0属性设置LED灯所使用的PIN对应的pinctrl节点。

                       第7行,led-gpio属性指定了LED灯所使用的GPIO,在这里就是GPIO1的IO03,低

                                    电平有效。

                       稍后编写驱动程序的时候会获取led-gpio属性的内容来得到GPIO编号,因为gpio子

                       系统的API操作函数需要GPIO编号。

           3、检查PIN是否被其他外设使用

                       这一点非常重要!!!

                      很多初次接触设备树的驱动开发人员很容易因为这个小问题栽了大跟头!因为我们所

                      使用的设备树基本都是在半导体厂商提供的设备树文件基础上修改而来的,而半导体

                      厂商提供的设备树是根据自己官方开发板编写的,很多PIN的配置和我们所使用的开

                      发板不一样。比如A这个引脚在官方开发板接的是I2C的SDA,而我们所使用的硬件

                      可能将A这个引脚接到了其他的外设,比如LED灯上,接不同的外设,A这个引脚的

                      配置就不同。一个引脚一次只能实现一个功能,如果A引脚在设备树中配置为了I2C

                      的SDA信号,那么A引脚就不能再配置为GPIO,否则的话驱动程序在申请GPIO的时

                      候就会失败。检查PIN有没有被其他外设使用包括两个方面:

                      1.检查pinctrl设置。

                      2.如果这个PIN配置为GPIO的话,检查这个GPIO有没有被别的外设使用。

                      在本章实验中LED灯使用的PIN为GPIO1_IO03,因此先检查GPIO_IO03这个PIN有

                      没有被其他的pinctrl节点使用,在imx6ull-alientek-emmc.dts中找到如下内容:

480 pinctrl_tsc: tscgrp {
481 	fsl,pins = <
482 		MX6UL_PAD_GPIO1_IO01__GPIO1_IO01 0xb0
483 		MX6UL_PAD_GPIO1_IO02__GPIO1_IO02 0xb0
484 		MX6UL_PAD_GPIO1_IO03__GPIO1_IO03 0xb0
485 		MX6UL_PAD_GPIO1_IO04__GPIO1_IO04 0xb0
486 	>;
487 };

                      pinctrl_tsc节点是TSC(电阻触摸屏接口)的pinctrl节点,从第484行可以看出,默认情

                      况下GPIO1_IO03作为了TSC外设的PIN。所以我们需要将第484行屏蔽掉!和C语言

                      一样,在要屏蔽的内容前后加上“/*”和“*/”符号即可。其实在I.MX6U-ALPHA开发板上

                      并没有用到TSC接口,所以第482~485行的内容可以全部屏蔽掉。

                      因为本章实验我们将GPIO1_IO03这个PIN配置为了GPIO,所以还需要查找一下有没

                      有其他的外设使用了GPIO1_IO03,在imx6ull-alientek-emmc.dts中搜索“gpio1 3”,

                       找到如下内容:

723 &tsc {
724 	pinctrl-names = "default";
725 	pinctrl-0 = <&pinctrl_tsc>;
726 	xnur-gpio = <&gpio1 3 GPIO_ACTIVE_LOW>;
727 	measure-delay-time = <0xffff>;
728 	pre-charge-time = <0xfff>;
729 	status = "okay";
730 };

                       tsc是TSC的外设节点,从726行可以看出,tsc外设也使用了GPIO1_IO03,同样我

                       们需要将这一行屏蔽掉。然后在继续搜索“gpio1 3”,看看除了本章的LED灯以外还

                       有没有其他的地方也使用了GPIO1_IO03,找到一个屏蔽一个。

                       设备树编写完成以后使用“make dtbs”命令重新编译设备树,然后使用新编译出来的

                       imx6ull-alientek-emmc.dtb文件启动Linux系统。启动成功以后进入“/proc/device-

                       tree”目录中查看“gpioled”节点是否存在,如果存在的话就说明设备树基本修改成

                       功 (具体还要驱动验证),结果如图4所示:

pinctrl和gpio子系统-编程知识网

 图4  gpio子节点

5.运行测试

    1)编译驱动程序和测试APP

           1、编译驱动程序

                        编写Makefile文件,本章实验的Makefile文件和之前

                        实验基本一样,只是将obj-m变量的值改为gpioled.o,Makefile内容如下所示:

1 KERNELDIR := /home/zuozhongkai/linux/IMX6ULL/linux/temp/linux-imx-rel_imx_4.1.15_2.1.0_ga_alientek
......
4 obj-m := gpioled.o .o
......
11 clean:
12 $(MAKE) -C $(KERNELDIR) M=$(CURRENT_PATH) clean

                         第4行,设置obj-m变量的值为gpioled.o。

                          输入如下命令编译出驱动模块文件:

make -j32

                         编译成功以后就会生成一个名为“gpioled.ko”的驱动模块文件。

            2、编译测试APP

                         输入如下命令编译测试ledApp.c这个测试程序:

arm-linux-gnueabihf-gcc ledApp.c -o ledApp

                          编译成功以后就会生成 ledApp这个应用程序。

    2)运行测试

               将上一小节编译出来的gpioled.ko和ledApp这两个文件拷贝到rootfs/lib/modules/4.1.15目

               录中,重启开发板,进入到目录lib/modules/4.1.15中,输入如下命令加载gpioled.ko驱动

               模块:

depmod //第一次加载驱动的时候需要运行此命令
modprobe gpioled.ko //加载驱动

               驱动加载成功以后会在终端中输出一些信息,如图5所示:

pinctrl和gpio子系统-编程知识网

 图5 驱动加载成功以后输出的信息

               从图5可以看出,gpioled这个节点找到了,并且GPIO1_IO03这个GPIO的编号为3。驱动

               加载成功以后就可以使用ledApp软件来测试驱动是否工作正常,输入如下命令打开LED

               灯: 

./ledApp /dev/gpioled 1 //打开 LED 灯

               输入上述命令以后观察I.MX6U-ALPHA开发板上的红色LED灯是否点亮,如果点亮的话说

               明驱动工作正常。在输入如下命令关闭LED灯:

./ledApp /dev/gpioled 0 //打开 LED 灯

               输入上述命令以后观察I.MX6U-ALPHA开发板上的红色LED灯是否熄灭。如果要卸载驱动

               的话输入 如下命令即可:

rmmod gpioled.ko