1)实验平台:正点原子阿尔法Linux开发板
2)平台购买地址:https://item.taobao.com/item.htm?id=603672744434
2)全套实验源码+手册+视频下载地址:http://www.openedv.com/thread-300792-1-1.html
3)对正点原子Linux感兴趣的同学可以加群讨论:935446741
4)关注正点原子公众号,获取最新资料更新

第四十五章 pinctrl和gpio子系统实验

上一章我们编写了基于设备树的LED驱动,但是驱动的本质还是没变,都是配置LED灯所使用的GPIO寄存器,驱动开发方式和裸机基本没啥区别。Linux是一个庞大而完善的系统,尤其是驱动框架,像GPIO这种最基本的驱动不可能采用“原始”的裸机驱动开发方式,否则就相当于你买了一辆车,结果每天推着车去上班。Linux内核提供了pinctrl和gpio子系统用于GPIO驱动,本章我们就来学习一下如何借助pinctrl和gpio子系统来简化GPIO驱动开发。

45.1 pinctrl子系统
45.1.1 pinctrl子系统简介
Linux驱动讲究驱动分离与分层,pinctrl和gpio子系统就是驱动分离与分层思想下的产物,驱动分离与分层其实就是按照面向对象编程的设计思想而设计的设备驱动框架,关于驱动的分离与分层我们后面会讲。本来pinctrl和gpio子系统应该放到驱动分离与分层章节后面讲解,但是不管什么外设驱动,GPIO驱动基本都是必须的,而pinctrl和gpio子系统又是GPIO驱动必须使用的,所以就将pintrcl和gpio子系统这一章节提前了。
我们先来回顾一下上一章是怎么初始化LED灯所使用的GPIO,步骤如下:
①、修改设备树,添加相应的节点,节点里面重点是设置reg属性,reg属性包括了GPIO相关寄存器。
②、获取reg属性中IOMUXC_SW_MUX_CTL_PAD_GPIO1_IO03和IOMUXC_SW_PAD_CTL_PAD_GPIO1_IO03这两个寄存器地址,并且初始化这两个寄存器,这两个寄存器用于设置GPIO1_IO03这个PIN的复用功能、上下拉、速度等。
③、在②里面将GPIO1_IO03这个PIN复用为了GPIO功能,因此需要设置GPIO1_IO03这个GPIO相关的寄存器,也就是GPIO1_DR和GPIO1_GDIR这两个寄存器。
总结一下,②中完成对GPIO1_IO03这个PIN的初始化,设置这个PIN的复用功能、上下拉等,比如将GPIO_IO03这个PIN设置为GPIO功能。③中完成对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子系统主要工作内容如下:
①、获取设备树中pin信息。
②、根据获取到的pin信息来设置pin的复用功能
③、根据获取到的pin信息来设置pin的电气特性,比如上/下拉、速度、驱动能力等。
对于我们使用者来讲,只需要在设备树里面设置好某个pin的相关属性即可,其他的初始化工作均由pinctrl子系统来完成,pinctrl子系统源码目录为drivers/pinctrl。
45.1.2 I.MX6ULL的pinctrl子系统驱动
1、PIN配置信息详解
要使用pinctrl子系统,我们需要在设备树里面设置PIN的配置信息,毕竟pinctrl子系统要根据你提供的信息来配置PIN功能,一般会在设备树里面创建一个节点来描述PIN的配置信息。打开imx6ull.dtsi文件,找到一个叫做iomuxc的节点,如下所示:
示例代码45.1.2.1 iomuxc节点内容1

756 iomuxc: iomuxc@020e0000 {757             compatible = "fsl,imx6ul-iomuxc";
758             reg = <0x020e0000 0x4000>;
759         };
iomuxc节点就是I.MX6ULL的IOMUXC外设对应的节点,看起来内容很少,没看出什么跟PIN的配置有关的内容啊,别急!打开imx6ull-alientek-emmc.dts,找到如下所示内容:

示例代码45.1.2.2 iomuxc节点内容2

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 };
示例代码45.1.2.2就是向iomuxc节点追加数据,不同的外设使用的PIN不同、其配置也不同,因此一个萝卜一个坑,将某个外设所使用的所有PIN都组织在一个子节点里面。示例代码45.1.2.2中pinctrl_hog_1子节点就是和热插拔有关的PIN集合,比如USB OTG的ID引脚。pinctrl_flexcan1子节点是flexcan1这个外设所使用的PIN,pinctrl_wdog子节点是wdog外设所使用的PIN。如果需要在iomuxc中添加我们自定义外设的PIN,那么需要新建一个子节点,然后将这个自定义外设的所有PIN配置信息都放到这个子节点中。

将其与示例代码45.1.2.1结合起来就可以得到完成的iomuxc节点,如下所示:
示例代码45.1.2.3 完整的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的宏定义内容如下:
示例代码45.1.2.4 UART1_RTS_B 引脚定义

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
示例代码45.1.2.4中一共有8个以“MX6UL_PAD_UART1_RTS_B”开头的宏定义,大家仔细观察应该就能发现,这8个宏定义分别对应UART1_RTS_B这个PIN的8个复用IO。查阅《I.MX6ULL参考手册》可以知UART1_RTS_B的可选复用IO如图45.1.2.1所示:

图45.1.2.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这个寄存器的位域图,如图45.1.2.2所示:

图45.1.2.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的电气特性的,这么重要的寄存器怎么没有值呢?回到示例代码45.1.2.3中,第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。
2、PIN驱动程序讲解
本小节会涉及到Linux驱动分层与分离、平台设备驱动等还未讲解的知识,所以本小节教程可以不用看,不会影响后续的实验。如果对Linux内核的pinctrl子系统实现原理感兴趣的话可以看本小节。
所有的东西都已经准备好了,包括寄存器地址和寄存器值,Linux内核相应的驱动文件就会根据这些值来做相应的初始化。接下来就找一下哪个驱动文件来做这一件事情,iomuxc节点中compatible属性的值为“fsl,imx6ul-iomuxc”,在Linux内核中全局搜索“fsl,imx6ul-iomuxc”字符串就会找到对应的驱动文件。在文件drivers/pinctrl/freescale/pinctrl-imx6ul.c中有如下内容:
示例代码45.1.2.5 pinctrl-imx6ul.c文件代码段

326 static struct of_device_id imx6ul_pinctrl_of_match[] = {327     { .compatible = "fsl,imx6ul-iomuxc", .data =   &imx6ul_pinctrl_info, },
328     { .compatible = "fsl,imx6ull-iomuxc-snvs", .data =    &imx6ull_snvs_pinctrl_info, },
329     { /* sentinel */ }
330 };
331
332 static int imx6ul_pinctrl_probe(struct platform_device *pdev)
333 {334     const struct of_device_id *match;
335     struct imx_pinctrl_soc_info *pinctrl_info;
336
337     match = of_match_device(imx6ul_pinctrl_of_match, &pdev->dev);
338
339     if (!match)
340         return -ENODEV;
341
342     pinctrl_info = (struct imx_pinctrl_soc_info *) match->data;
343
344     return imx_pinctrl_probe(pdev, pinctrl_info);
345 }
346
347 static struct platform_driver imx6ul_pinctrl_driver = {348     .driver = {349         .name = "imx6ul-pinctrl",
350         .owner = THIS_MODULE,
351         .of_match_table = of_match_ptr(imx6ul_pinctrl_of_match),
352     },
353     .probe = imx6ul_pinctrl_probe,
354     .remove = imx_pinctrl_remove,
355 };
第326~330行,of_device_id结构体数组,第四十三章讲解设备树的时候说过了,of_device_id里面保存着这个驱动文件的兼容性值,设备树中的compatible属性值会和of_device_id中的所有兼容性字符串比较,查看是否可以使用此驱动。imx6ul_pinctrl_of_match结构体数组一共有两个兼容性字符串,分别为“fsl,imx6ul-iomuxc”和“fsl,imx6ull-iomuxc-snvs”,因此iomuxc节点与此驱动匹配,所以pinctrl-imx6ul.c会完成I.MX6ULL的PIN配置工作。
第347~355行,platform_driver是平台设备驱动,这个是我们后面章节要讲解的内容,platform_driver是个结构体,有个probe成员变量。在这里大家只需要知道,当设备和驱动匹配成功以后platform_driver的probe成员变量所代表的函数就会执行,在353行设置probe成员变量为imx6ul_pinctrl_probe函数,因此在本章实验中imx6ul_pinctrl_probe这个函数就会执行,可以认为imx6ul_pinctrl_probe函数就是I.MX6ULL这个SOC的PIN配置入口函数。以此为入口,如图45.1.2.3所示的函数调用路径:

图45.1.2.3 imx6ul_pinctrl_probe函数执行流程
在图45.1.2.3中函数imx_pinctrl_parse_groups负责获取设备树中关于PIN的配置信息,也就是我们前面分析的那6个u32类型的值。处理过程如下所示:
示例代码45.1.2.6 imx_pinctrl_parse_groups函数代码段

488 /*
489  * Each pin represented in fsl,pins consists of 5 u32 PIN_FUNC_ID
490  * and 1 u32 CONFIG, so 24 types in total for each pin.
491  */
492 #define FSL_PIN_SIZE 24
493 #define SHARE_FSL_PIN_SIZE 20
494
495 static int imx_pinctrl_parse_groups(struct device_node *np,
496                     struct imx_pin_group *grp,
497                     struct imx_pinctrl_soc_info *info,
498                     u32 index)
499 {500     int size, pin_size;
501     const __be32 *list;
502     int i;
503     u32 config;
......
537
538     for (i = 0; i < grp->npins; i++) {539         u32 mux_reg = be32_to_cpu(*list++);
540         u32 conf_reg;
541         unsigned int pin_id;
542         struct imx_pin_reg *pin_reg;
543         struct imx_pin *pin = &grp->pins[i];
544
......
555
556         pin_id = (mux_reg != -1) ? mux_reg / 4 : conf_reg / 4;
557         pin_reg = &info->pin_regs[pin_id];
558         pin->pin = pin_id;
559         grp->pin_ids[i] = pin_id;
560         pin_reg->mux_reg = mux_reg;
561         pin_reg->conf_reg = conf_reg;
562         pin->input_reg = be32_to_cpu(*list++);
563         pin->mux_mode = be32_to_cpu(*list++);
564         pin->input_val = be32_to_cpu(*list++);
565
566         /* SION bit is in mux register */
567         config = be32_to_cpu(*list++);
568         if (config & IMX_PAD_SION)
569             pin->mux_mode |= IOMUXC_CONFIG_SION;
570         pin->config = config & ~IMX_PAD_SION;
......
574     }
575
576     return 0;
577 }
第496和497行,设备树中的mux_reg和conf_reg值会保存在info参数中,input_reg、mux_mode、input_val和config值会保存在grp参数中。
第560~564行,获取mux_reg、conf_reg、input_reg、mux_mode和input_val值。
第570行,获取config值。
接下来看一下函数pinctrl_register,此函数用于向Linux内核注册一个PIN控制器,此函数原型如下:

struct pinctrl_dev *pinctrl_register(struct pinctrl_desc *pctldesc,
struct device *dev,
void *driver_data)
参数pctldesc非常重要,因为此参数就是要注册的PIN控制器,PIN控制器用于配置SOC的PIN复用功能和电气特性。参数pctldesc是pinctrl_desc结构体类型指针,pinctrl_desc结构体如下所示:
示例代码45.1.2.7 pinctrl_desc结构体

128 struct pinctrl_desc {129     const char *name;
130     struct pinctrl_pin_desc const *pins;
131     unsigned int npins;
132     const struct pinctrl_ops *pctlops;
133     const struct pinmux_ops *pmxops;
134     const struct pinconf_ops *confops;
135     struct module *owner;
136 #ifdef CONFIG_GENERIC_PINCONF
137     unsigned int num_custom_params;
138     const struct pinconf_generic_params *custom_params;
139     const struct pin_config_item *custom_conf_items;
140 #endif
141 };
第132~124行,这三个“_ops”结构体指针非常重要!!!因为这三个结构体就是PIN控制器的“工具”,这三个结构体里面包含了很多操作函数,通过这些操作函数就可以完成对某一个PIN的配置。pinctrl_desc结构体需要由用户提供,结构体里面的成员变量也是用户提供的。但是这个用户并不是我们这些使用芯片的程序员,而是半导体厂商,半导体厂商发布的Linux内核源码中已经把这些工作做完了。比如在imx_pinctrl_probe函数中可以找到如下所示代码:

示例代码45.1.2.8 imx_pinctrl_probe函数代码段

648 int imx_pinctrl_probe(struct platform_device *pdev,
649               struct imx_pinctrl_soc_info *info)
650 {651     struct device_node *dev_np = pdev->dev.of_node;
652     struct device_node *np;
653     struct imx_pinctrl *ipctl;
654     struct resource *res;
655     struct pinctrl_desc *imx_pinctrl_desc;
......
663
664     imx_pinctrl_desc = devm_kzalloc(&pdev->dev,  sizeof(*imx_pinctrl_desc),
665                                           GFP_KERNEL);
666     if (!imx_pinctrl_desc)
667         return -ENOMEM;
......
705
706     imx_pinctrl_desc->name = dev_name(&pdev->dev);
707     imx_pinctrl_desc->pins = info->pins;
708     imx_pinctrl_desc->npins = info->npins;
709     imx_pinctrl_desc->pctlops = &imx_pctrl_ops;
710     imx_pinctrl_desc->pmxops = &imx_pmx_ops;
711     imx_pinctrl_desc->confops = &imx_pinconf_ops;
712     imx_pinctrl_desc->owner = THIS_MODULE;
......
723     ipctl->pctl = pinctrl_register(imx_pinctrl_desc, &pdev->dev,   ipctl);
......
732 }
第655行,定义结构体指针变量imx_pinctrl_desc。
第664行,向指针变量imx_pinctrl_desc分配内存。
第706~712行,初始化imx_pinctrl_desc结构体指针变量,重点是pctlops、pmxops和confops这三个成员变量,分别对应imx_pctrl_ops、imx_pmx_ops和imx_pinconf_ops这三个结构体。
第723行,调用函数pinctrl_register向Linux内核注册imx_pinctrl_desc,注册以后Linux内核就有了对I.MX6ULL的PIN进行配置的工具。
imx_pctrl_ops、imx_pmx_ops和imx_pinconf_ops这三个结构体定义如下:

示例代码45.1.2.9 imx_pctrl_ops、imx_pmx_ops和imx_pinconf_ops结构体

174 static const struct pinctrl_ops imx_pctrl_ops = {175     .get_groups_count = imx_get_groups_count,
176     .get_group_name = imx_get_group_name,
177     .get_group_pins = imx_get_group_pins,
178     .pin_dbg_show = imx_pin_dbg_show,
179     .dt_node_to_map = imx_dt_node_to_map,
180     .dt_free_map = imx_dt_free_map,
181
182 };
......
374 static const struct pinmux_ops imx_pmx_ops = {375     .get_functions_count = imx_pmx_get_funcs_count,
376     .get_function_name = imx_pmx_get_func_name,
377     .get_function_groups = imx_pmx_get_groups,
378     .set_mux = imx_pmx_set,
379     .gpio_request_enable = imx_pmx_gpio_request_enable,
380     .gpio_set_direction = imx_pmx_gpio_set_direction,
381 };
......
481 static const struct pinconf_ops imx_pinconf_ops = {482     .pin_config_get = imx_pinconf_get,
483     .pin_config_set = imx_pinconf_set,
484     .pin_config_dbg_show = imx_pinconf_dbg_show,
485     .pin_config_group_dbg_show = imx_pinconf_group_dbg_show,
486 };
示例代码45.1.2.9中这三个结构体下的所有函数就是I.MX6ULL的PIN配置函数,我们就此打住,不再去分析这些函数了,否则本章就没完没了了,有兴趣的可以去看一下。

45.1.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_”。添加完成以后如下所示:
示例代码45.1.2.10 test设备pinctrl节点
1 pinctrl_test: testgrp {
2 /* 具体的PIN信息 */
3 };
2、添加“fsl,pins”属性
设备树是通过属性来保存信息的,因此我们需要添加一个属性,属性名字一定要为“fsl,pins”,因为对于I.MX系列SOC而言,pinctrl驱动程序是通过读取“fsl,pins”属性值来获取PIN的配置信息,完成以后如下所示:
示例代码45.1.2.11 添加"fsl,pins"属性

1 pinctrl_test: testgrp {2       fsl,pins = <
3       /* 设备所使用的PIN配置信息 */
4       >;
5 };
3、在“fsl,pins”属性中添加PIN配置信息
最后在“fsl,pins”属性中添加具体的PIN配置信息,完成以后如下所示:

示例代码45.1.2.13 完整的test设备pinctrl子节点

1 pinctrl_test: testgrp {2       fsl,pins = <
3       MX6UL_PAD_GPIO1_IO00__GPIO1_IO00  config /*config是具体设置值*/
4       >;
5 };
至此,我们已经在imx6ull-alientek-emmc.dts文件中添加好了test设备所使用的PIN配置信息。

45.2 gpio子系统
45.2.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。
45.2.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设置如下:
示例代码45.2.2.1 SD卡CD引脚PIN配置参数

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卡设备节点,如下所示:

示例代码45.2.2.2 设备树中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,在里面找到如下所示内容:

示例代码45.2.2.2 gpio1节点

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外设寄存器基地址以及兼容属性。关于I.MX系列SOC的GPIO控制器绑定信息请查看文档Documentation/devicetree/bindings/gpio/ fsl-imx-gpio.txt。

第505行,设置gpio1节点的compatible属性有两个,分别为“fsl,imx6ul-gpio”和“fsl,imx35-gpio”,在Linux内核中搜索这两个字符串就可以找到I.MX6UL的GPIO驱动程序。
第506行,reg属性设置了GPIO1控制器的寄存器基地址为0X0209C000,大家可以打开《I.MX6ULL参考手册》找到“Chapter 28:General Purpose Input/Output(GPIO)”章节第28.5小节,有如图45.2.2.1所示的寄存器地址表:

图45.2.2.1 GPIO1寄存器表
从图45.2.2.1可以看出,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)的话表示低电平有效。
2、GPIO驱动程序简介
本小节会涉及到Linux驱动分层与分离、平台设备驱动等还未讲解的知识,所以本小节教程可以不用看,不会影响后续的实验。如果对Linux内核的GPIO子系统实现原理感兴趣的话可以看本小节。
gpio1节点的compatible属性描述了兼容性,在Linux内核中搜索“fsl,imx6ul-gpio”和“fsl,imx35-gpio”这两个字符串,查找GPIO驱动文件。drivers/gpio/gpio-mxc.c就是I.MX6ULL的GPIO驱动文件,在此文件中有如下所示of_device_id匹配表:
示例代码45.2.2.3 mxc_gpio_dt_ids匹配表

152 static const struct of_device_id mxc_gpio_dt_ids[] = {153     { .compatible = "fsl,imx1-gpio", .data =      &mxc_gpio_devtype[IMX1_GPIO], },
154     { .compatible = "fsl,imx21-gpio", .data = &mxc_gpio_devtype[IMX21_GPIO], },
155     { .compatible = "fsl,imx31-gpio", .data = &mxc_gpio_devtype[IMX31_GPIO], },
156     { .compatible = "fsl,imx35-gpio", .data = &mxc_gpio_devtype[IMX35_GPIO], },
157     { /* sentinel */ }
158 };
第156行的compatible值为“fsl,imx35-gpio”,和gpio1的compatible属性匹配,因此gpio-mxc.c就是I.MX6ULL的GPIO控制器驱动文件。gpio-mxc.c所在的目录为drivers/gpio,打开这个目录可以看到很多芯片的gpio驱动文件, “gpiolib”开始的文件是gpio驱动的核心文件,如图45.2.2.2所示:

图45.2.2.2 gpio核心驱动文件
我们重点来看一下gpio-mxc.c这个文件,在gpio-mxc.c文件中有如下所示内容:
示例代码45.2.2.4 mxc_gpio_driver结构体

496 static struct platform_driver mxc_gpio_driver = {497     .driver     = {498         .name   = "gpio-mxc",
499         .of_match_table = mxc_gpio_dt_ids,
500     },
501     .probe      = mxc_gpio_probe,
502     .id_table   = mxc_gpio_devtype,
503 };
可以看出GPIO驱动也是个平台设备驱动,因此当设备树中的设备节点与驱动的of_device_id匹配以后probe函数就会执行,在这里就是mxc_gpio_probe函数,这个函数就是I.MX6ULL的GPIO驱动入口函数。我们简单来分析一下mxc_gpio_probe这个函数,函数内容如下:

示例代码45.2.2.5 mxc_gpio_probe函数

403 static int mxc_gpio_probe(struct platform_device *pdev)
404 {405     struct device_node *np = pdev->dev.of_node;
406     struct mxc_gpio_port *port;
407     struct resource *iores;
408     int irq_base;
409     int err;
410
411     mxc_gpio_get_hw(pdev);
412
413     port = devm_kzalloc(&pdev->dev, sizeof(*port), GFP_KERNEL);
414     if (!port)
415         return -ENOMEM;
416
417     iores = platform_get_resource(pdev, IORESOURCE_MEM, 0);
418     port->base = devm_ioremap_resource(&pdev->dev, iores);
419     if (IS_ERR(port->base))
420         return PTR_ERR(port->base);
421
422     port->irq_high = platform_get_irq(pdev, 1);
423     port->irq = platform_get_irq(pdev, 0);
424     if (port->irq < 0)
425         return port->irq;
426
427     /* disable the interrupt and clear the status */
428     writel(0, port->base + GPIO_IMR);
429     writel(~0, port->base + GPIO_ISR);
430
431     if (mxc_gpio_hwtype == IMX21_GPIO) {432         /*
433          * Setup one handler for all GPIO interrupts. Actually
434          * setting the handler is needed only once, but doing it for
435          * every port is more robust and easier.
436          */
437         irq_set_chained_handler(port->irq, mx2_gpio_irq_handler);
438     } else {439         /* setup one handler for each entry */
440         irq_set_chained_handler(port->irq, mx3_gpio_irq_handler);
441         irq_set_handler_data(port->irq, port);
442         if (port->irq_high > 0) {443             /* setup handler for GPIO 16 to 31 */
444             irq_set_chained_handler(port->irq_high,
445                         mx3_gpio_irq_handler);
446             irq_set_handler_data(port->irq_high, port);
447         }
448     }
449
450     err = bgpio_init(&port->bgc, &pdev->dev, 4,
451              port->base + GPIO_PSR,
452              port->base + GPIO_DR, NULL,
453              port->base + GPIO_GDIR, NULL, 0);
454     if (err)
455         goto out_bgio;
456
457     port->bgc.gc.to_irq = mxc_gpio_to_irq;
458     port->bgc.gc.base = (pdev->id < 0) ? of_alias_get_id(np, "gpio")
459                          * 32 : pdev->id * 32;
460
461     err = gpiochip_add(&port->bgc.gc);
462     if (err)
463         goto out_bgpio_remove;
464
465     irq_base = irq_alloc_descs(-1, 0, 32, numa_node_id());
466     if (irq_base < 0) {467         err = irq_base;
468         goto out_gpiochip_remove;
469     }
470
471     port->domain = irq_domain_add_legacy(np, 32, irq_base, 0,
472                          &irq_domain_simple_ops, NULL);
473     if (!port->domain) {474         err = -ENODEV;
475         goto out_irqdesc_free;
476     }
477
478     /* gpio-mxc can be a generic irq chip */
479     mxc_gpio_init_gc(port, irq_base);
480
481     list_add_tail(&port->node, &mxc_gpio_ports);
482
483     return 0;
......
494 }
第405行,设备树节点指针。
第406行,定义一个结构体指针port,结构体类型为mxc_gpio_port。gpio-mxc.c的重点工作就是维护mxc_gpio_port,mxc_gpio_port就是对I.MX6ULL GPIO的抽象。mxc_gpio_port结构体定义如下:

示例代码45.2.2.6 mxc_gpio_port结构体

61 struct mxc_gpio_port {62      struct list_head node;
63      void __iomem *base;
64      int irq;
65      int irq_high;
66      struct irq_domain *domain;
67      struct bgpio_chip bgc;
68      u32 both_edges;
69 };
mxc_gpio_port的bgc成员变量很重要,因为稍后的重点就是初始化bgc。
继续回到mxc_gpio_probe函数函数,第411行调用mxc_gpio_get_hw函数获取gpio的硬件相关数据,其实就是gpio的寄存器组,函数mxc_gpio_get_hw里面有如下代码:

示例代码45.2.2.7 mxc_gpio_get_hw函数

364 static void mxc_gpio_get_hw(struct platform_device *pdev)
365 {366     const struct of_device_id *of_id =
367             of_match_device(mxc_gpio_dt_ids, &pdev->dev);
368     enum mxc_gpio_hwtype hwtype;
......
383
384     if (hwtype == IMX35_GPIO)
385         mxc_gpio_hwdata = &imx35_gpio_hwdata;
386     else if (hwtype == IMX31_GPIO)
387         mxc_gpio_hwdata = &imx31_gpio_hwdata;
388     else
389         mxc_gpio_hwdata = &imx1_imx21_gpio_hwdata;
390
391     mxc_gpio_hwtype = hwtype;
392 }
注意第385行,mxc_gpio_hwdata是个全局变量,如果硬件类型是IMX35_GPIO的话设置mxc_gpio_hwdat为imx35_gpio_hwdata。对于I.MX6ULL而言,硬件类型就是IMX35_GPIO,imx35_gpio_hwdata是个结构体变量,描述了GPIO寄存器组,内容如下:

示例代码45.2.2.8 imx35_gpio_hwdata结构体

101 static struct mxc_gpio_hwdata imx35_gpio_hwdata = {102     .dr_reg         = 0x00,
103     .gdir_reg   = 0x04,
104     .psr_reg        = 0x08,
105     .icr1_reg       = 0x0c,
106     .icr2_reg       = 0x10,
107     .imr_reg        = 0x14,
108     .isr_reg        = 0x18,
109     .edge_sel_reg   = 0x1c,
110     .low_level      = 0x00,
111     .high_level = 0x01,
112     .rise_edge      = 0x02,
113     .fall_edge      = 0x03,
114 };
大家将imx35_gpio_hwdata中的各个成员变量和图45.2.2.1中的GPIO寄存器表对比就会发现,imx35_gpio_hwdata结构体就是GPIO寄存器组结构。这样我们后面就可以通过mxc_gpio_hwdata这个全局变量来访问 GPIO的相应寄存器了。
继续回到示例代码45.2.2.5的mxc_gpio_probe函数中,第417行,调用函数platform_get_resource获取设备树中内存资源信息,也就是reg属性值。前面说了reg属性指定了GPIO1控制器的寄存器基地址为0X0209C000,在配合前面已经得到的mxc_gpio_hwdata,这样Linux内核就可以访问gpio1的所有寄存器了。
第418行,调用devm_ioremap_resource函数进行内存映射,得到0x0209C000在Linux内核中的虚拟地址。
第422、423行,通过platform_get_irq函数获取中断号,第422行获取高16位GPIO的中断号,第423行获取底16位GPIO中断号。
第428、429行,操作GPIO1的IMR和ISR这两个寄存器,关闭GPIO1所有IO中断,并且清除状态寄存器。
第438~448行,设置对应GPIO的中断服务函数,不管是高16位还是低16位,中断服务函数都是mx3_gpio_irq_handler。
第450~453行,bgpio_init函数第一个参数为bgc,是bgpio_chip结构体指针。bgpio_chip结构体有个gc成员变量,gc是个gpio_chip结构体类型的变量。gpio_chip结构体是抽象出来的GPIO控制器,gpio_chip结构体如下所示(有缩减):

示例代码45.2.2.9 gpio_chip结构体

74  struct gpio_chip {75      const char          *label;
76      struct device       *dev;
77      struct module       *owner;
78      struct list_head    list;
79
80      int                 (*request)(struct gpio_chip *chip,
81                                          unsigned offset);
82      void                (*free)(struct gpio_chip *chip,
83                                          unsigned offset);
84      int                 (*get_direction)(struct gpio_chip *chip,
85                                          unsigned offset);
86      int                 (*direction_input)(struct gpio_chip *chip,
87                                                  unsigned offset);
88      int                 (*direction_output)(struct gpio_chip *chip,
89                                              unsigned offset, int value);
90      int                 (*get)(struct gpio_chip *chip,
91                                      unsigned offset);
92      void                (*set)(struct gpio_chip *chip,
93                                      unsigned offset, int value);
......
145 };
可以看出,gpio_chip大量的成员都是函数,这些函数就是GPIO操作函数。bgpio_init函数主要任务就是初始化bgc->gc。bgpio_init里面有三个setup函数:bgpio_setup_io、bgpio_setup_accessors和bgpio_setup_direction。这三个函数就是初始化bgc->gc中的各种有关GPIO的操作,比如输出,输入等等。第451~453行的GPIO_PSR、GPIO_DR和GPIO_GDIR都是I.MX6ULL的GPIO寄存器。这些寄存器地址会赋值给bgc参数的reg_dat、reg_set、reg_clr和reg_dir这些成员变量。至此,bgc既有了对GPIO的操作函数,又有了I.MX6ULL有关GPIO的寄存器,那么只要得到bgc就可以对I.MX6ULL的GPIO进行操作。
继续回到mxc_gpio_probe函数,第461行调用函数gpiochip_add向Linux内核注册gpio_chip,也就是port->bgc.gc。注册完成以后我们就可以在驱动中使用gpiolib提供的各个API函数。

45.2.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函数就讲这些,这些是我们用的最多的。
45.2.4 设备树中添加gpio节点模板
继续完成45.1.3中的test设备,在45.1.3中我们已经讲解了如何创建test设备的pinctrl节点。本节我们来学习一下如何创建test设备的GPIO节点。
1、创建test设备节点
在根节点“/”下创建test设备子节点,如下所示:
示例代码45.2.4.1 test设备节点

1 test {2   /* 节点内容 */
3 };
2、添加pinctrl信息
在45.1.3中我们创建了pinctrl_test节点,此节点描述了test设备所使用的GPIO1_IO00这个PIN的信息,我们要将这节点添加到test设备节点中,如下所示:

示例代码45.2.4.2 向test节点添加pinctrl信息

1 test {2   pinctrl-names = "default";
3   pinctrl-0 = <&pinctrl_test>;
4   /* 其他节点内容 */
5 };
第2行,添加pinctrl-names属性,此属性描述pinctrl名字为“default”。
第3行,添加pinctrl-0节点,此节点引用45.1.3中创建的pinctrl_test节点,表示tset设备的所使用的PIN信息保存在pinctrl_test节点中。
3、添加GPIO属性信息
我们最后需要在test节点中添加GPIO属性信息,表明test所使用的GPIO是哪个引脚,添加完成以后如下所示:

示例代码45.2.4.3 向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灯。

45.2.5 与gpio相关的OF函数
在示例代码45.2.4.3中,我们定义了一个名为“gpio”的属性,gpio属性描述了test这个设备所使用的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编号,因为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编号;负值,失败。
45.3 硬件原理图分析
本章实验硬件原理图参考8.3小节即可。
45.4 实验程序编写
本实验对应的例程路径为:开发板光盘-> 2、Linux驱动例程-> 5_gpioled。
本章实验我们继续研究LED灯,在第四十四章实验中我们通过设备树向dtsled.c文件传递相应的寄存器物理地址,然后在驱动文件中配置寄存器。本章实验我们使用pinctrl和gpio子系统来完成LED灯驱动。
45.4.1 修改设备树文件
1、添加pinctrl节点
I.MX6U-ALPHA开发板上的LED灯使用了GPIO1_IO03这个PIN,打开imx6ull-alientek-emmc.dts,在iomuxc节点的imx6ul-evk子节点下创建一个名为“pinctrl_led”的子节点,节点内容如下所示:
示例代码45.4.1.1 GPIO1_IO03 pinctrl节点

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”,节点内容如下:

示例代码45.4.1.2 创建LED灯节点

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有没有被其他外设使用包括两个方面:
①、检查pinctrl设置。
②、如果这个PIN配置为GPIO的话,检查这个GPIO有没有被别的外设使用。
在本章实验中LED灯使用的PIN为GPIO1_IO03,因此先检查GPIO_IO03这个PIN有没有被其他的pinctrl节点使用,在imx6ull-alientek-emmc.dts中找到如下内容:

示例代码45.4.1.3 pinctrl_tsc节点

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”,找到如下内容:

示例代码45.4.1.4 tsc节点

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”节点是否存在,如果存在的话就说明设备树基本修改成功(具体还要驱动验证),结果如图45.4.1.1所示:

图45.4.1.1 gpio子节点
45.4.2 LED灯驱动程序编写
设备树准备好以后就可以编写驱动程序了,本章实验在第四十四章实验驱动文件dtsled.c的基础上修改而来。新建名为“5_gpioled”文件夹,然后在5_gpioled文件夹里面创建vscode工程,工作区命名为“gpioled”。工程创建好以后新建gpioled.c文件,在gpioled.c里面输入如下内容:
示例代码45.4.2.1 gpioled.c驱动文件代码

1   #include <linux/types.h>
2   #include <linux/kernel.h>
3   #include <linux/delay.h>
4   #include <linux/ide.h>
5   #include <linux/init.h>
6   #include <linux/module.h>
7   #include <linux/errno.h>
8   #include <linux/gpio.h>
9   #include <linux/cdev.h>
10  #include <linux/device.h>
11  #include <linux/of.h>
12  #include <linux/of_address.h>
13  #include <linux/of_gpio.h>
14  #include <asm/mach/map.h>
15  #include <asm/uaccess.h>
16  #include <asm/io.h>
17  /***************************************************************
18  Copyright © ALIENTEK Co., Ltd. 1998-2029. All rights reserved.
19  文件名    : gpioled.c
20  作者      : 左忠凯
21  版本      : V1.0
22  描述      : 采用pinctrl和gpio子系统驱动LED灯。
23  其他      : 无
24  论坛      : www.openedv.com
25  日志      : 初版V1.0 2019/7/13 左忠凯创建
26  ***************************************************************/
27  #define GPIOLED_CNT         1               /* 设备号个数    */
28  #define GPIOLED_NAME        "gpioled" /* 名字       */
29  #define LEDOFF              0               /* 关灯       */
30  #define LEDON               1               /* 开灯       */
31
32  /* gpioled设备结构体 */
33  struct gpioled_dev{34      dev_t devid;                    /* 设备号      */
35      struct cdev cdev;               /* cdev         */
36      struct class *class;            /* 类            */
37      struct device *device;          /* 设备           */
38      int major;                      /* 主设备号     */
39      int minor;                      /* 次设备号     */
40      struct device_node  *nd;    /* 设备节点         */
41      int led_gpio;                   /* led所使用的GPIO编号        */
42  };
43
44  struct gpioled_dev gpioled; /* led设备 */
45
46  /*
47   * @description    : 打开设备
48   * @param – inode  : 传递给驱动的inode
49   * @param – filp   : 设备文件,file结构体有个叫做private_data的成员变量
50   *                    一般在open的时候将private_data指向设备结构体。
51   * @return         : 0 成功;其他 失败
52   */
53  static int led_open(struct inode *inode, struct file *filp)
54  {55      filp->private_data = &gpioled; /* 设置私有数据 */
56      return 0;
57  }
58
59  /*
60   * @description    : 从设备读取数据
61   * @param – filp   : 要打开的设备文件(文件描述符)
62   * @param - buf    : 返回给用户空间的数据缓冲区
63   * @param - cnt    : 要读取的数据长度
64   * @param – offt   : 相对于文件首地址的偏移
65   * @return         : 读取的字节数,如果为负值,表示读取失败
66   */
67  static ssize_t led_read(struct file *filp, char __user *buf,
size_t cnt, loff_t *offt)
68  {69      return 0;
70  }
71
72  /*
73   * @description    : 向设备写数据
74   * @param - filp   : 设备文件,表示打开的文件描述符
75   * @param - buf    : 要写给设备写入的数据
76   * @param - cnt    : 要写入的数据长度
77   * @param – offt   : 相对于文件首地址的偏移
78   * @return         : 写入的字节数,如果为负值,表示写入失败
79   */
80  static ssize_t led_write(struct file *filp, const char __user *buf,
size_t cnt, loff_t *offt)
81  {82      int retvalue;
83      unsigned char databuf[1];
84      unsigned char ledstat;
85      struct gpioled_dev *dev = filp->private_data;
86
87      retvalue = copy_from_user(databuf, buf, cnt);
88      if(retvalue < 0) {89          printk("kernel write failed!\r\n");
90          return -EFAULT;
91      }
92
93      ledstat = databuf[0];                      /* 获取状态值    */
94
95      if(ledstat == LEDON) {
96          gpio_set_value(dev->led_gpio, 0);   /* 打开LED灯    */
97      } else if(ledstat == LEDOFF) {98          gpio_set_value(dev->led_gpio, 1);   /* 关闭LED灯    */
99      }
100     return 0;
101 }
102
103 /*
104  * @description    : 关闭/释放设备
105  * @param – filp   : 要关闭的设备文件(文件描述符)
106  * @return         : 0 成功;其他 失败
107  */
108 static int led_release(struct inode *inode, struct file *filp)
109 {110     return 0;
111 }
112
113 /* 设备操作函数 */
114 static struct file_operations gpioled_fops = {115     .owner = THIS_MODULE,
116     .open = led_open,
117     .read = led_read,
118     .write = led_write,
119     .release =  led_release,
120 };
121
122 /*
123  * @description    : 驱动入口函数
124  * @param          : 无
125  * @return         : 无
126  */
127 static int __init led_init(void)
128 {129     int ret = 0;
130
131     /* 设置LED所使用的GPIO */
132     /* 1、获取设备节点:gpioled */
133     gpioled.nd = of_find_node_by_path("/gpioled");
134     if(gpioled.nd == NULL) {135         printk("gpioled node cant not found!\r\n");
136         return -EINVAL;
137     } else {138         printk("gpioled node has been found!\r\n");
139     }
140
141     /* 2、 获取设备树中的gpio属性,得到LED所使用的LED编号 */
142     gpioled.led_gpio = of_get_named_gpio(gpioled.nd, "led-gpio", 0);
143     if(gpioled.led_gpio < 0) {144         printk("can't get led-gpio");
145         return -EINVAL;
146     }
147     printk("led-gpio num = %d\r\n", gpioled.led_gpio);
148
149     /* 3、设置GPIO1_IO03为输出,并且输出高电平,默认关闭LED灯 */
150     ret = gpio_direction_output(gpioled.led_gpio, 1);
151     if(ret < 0) {152         printk("can't set gpio!\r\n");
153     }
154
155     /* 注册字符设备驱动 */
156     /* 1、创建设备号 */
157     if (gpioled.major) {                        /*  定义了设备号  */
158         gpioled.devid = MKDEV(gpioled.major, 0);
159         register_chrdev_region(gpioled.devid, GPIOLED_CNT, GPIOLED_NAME);
160     } else {                                    /* 没有定义设备号  */
161         alloc_chrdev_region(&gpioled.devid, 0, GPIOLED_CNT,                     GPIOLED_NAME);  /* 申请设备号        */
162         gpioled.major = MAJOR(gpioled.devid);  /* 获取分配号的主设备号 */
163         gpioled.minor = MINOR(gpioled.devid); /* 获取分配号的次设备号 */
164     }
165     printk("gpioled major=%d,minor=%d\r\n",gpioled.major, gpioled.minor);
166
167     /* 2、初始化cdev */
168     gpioled.cdev.owner = THIS_MODULE;
169     cdev_init(&gpioled.cdev, &gpioled_fops);
170
171     /* 3、添加一个cdev */
172     cdev_add(&gpioled.cdev, gpioled.devid, GPIOLED_CNT);
173
174     /* 4、创建类 */
175     gpioled.class = class_create(THIS_MODULE, GPIOLED_NAME);
176     if (IS_ERR(gpioled.class)) {177         return PTR_ERR(gpioled.class);
178     }
179
180     /* 5、创建设备 */
181     gpioled.device = device_create(gpioled.class, NULL, gpioled.devid, NULL, GPIOLED_NAME);
182     if (IS_ERR(gpioled.device)) {183         return PTR_ERR(gpioled.device);
184     }
185     return 0;
186 }
187
188 /*
189  * @description    : 驱动出口函数
190  * @param          : 无
191  * @return         : 无
192  */
193 static void __exit led_exit(void)
194 {195     /* 注销字符设备驱动 */
196     cdev_del(&gpioled.cdev);            /*  删除cdev */
197     unregister_chrdev_region(gpioled.devid, GPIOLED_CNT); /* 注销 */
198
199     device_destroy(gpioled.class, gpioled.devid);
200     class_destroy(gpioled.class);
201 }
202
203 module_init(led_init);
204 module_exit(led_exit);
205 MODULE_LICENSE("GPL");
206 MODULE_AUTHOR("zuozhongkai");
第41行,在设备结构体gpioled_dev中加入led_gpio这个成员变量,此成员变量保存LED等所使用的GPIO编号。
第55行,将设备结构体变量gpioled设置为filp的私有数据private_data。
第85行,通过读取filp的private_data成员变量来得到设备结构体变量,也就是gpioled。这种将设备结构体设置为filp私有数据的方法在Linux内核驱动里面非常常见。
第96、97行,直接调用gpio_set_value函数来向GPIO写入数据,实现开/关LED的效果。不需要我们直接操作相应的寄存器。
第133行,获取节点“/gpioled”。
第142行,通过函数of_get_named_gpio函数获取LED所使用的LED编号。相当于将gpioled节点中的“led-gpio”属性值转换为对应的LED编号。
第150行,调用函数gpio_direction_output设置GPIO1_IO03这个GPIO为输出,并且默认高电平,这样默认就会关闭LED灯。
可以看出gpioled.c文件中的内容和第四十四章的dtsled.c差不多,只是取消掉了配置寄存器的过程,改为使用Linux内核提供的API函数。在GPIO操作上更加的规范化,符合Linux代码框架,而且也简化了GPIO驱动开发的难度,以后我们所有例程用到GPIO的地方都采用此方法。

44.4.3 编写测试APP
本章直接使用第四十二章的测试APP,将上一章的ledApp.c文件复制到本章实验工程下即可。
45.5 运行测试
45.5.1 编译驱动程序和测试APP
1、编译驱动程序
编写Makefile文件,本章实验的Makefile文件和第四十章实验基本一样,只是将obj-m变量的值改为gpioled.o,Makefile内容如下所示:
示例代码45.5.1.1 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

11 clean:
12 $(MAKE) -C (KERNELDIR)M=(KERNELDIR) M=(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这个应用程序。
45.5.2 运行测试
将上一小节编译出来的gpioled.ko和ledApp这两个文件拷贝到rootfs/lib/modules/4.1.15目录中,重启开发板,进入到目录lib/modules/4.1.15中,输入如下命令加载gpioled.ko驱动模块:
depmod //第一次加载驱动的时候需要运行此命令
modprobe gpioled.ko //加载驱动
驱动加载成功以后会在终端中输出一些信息,如图45.5.2.1所示:

图45.5.2.1 驱动加载成功以后输出的信息
从图45.5.2.1可以看出,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

【正点原子Linux连载】第四十五章 pinctrl和gpio子系统实验 -摘自【正点原子】I.MX6U嵌入式Linux驱动开发指南V1.0相关推荐

  1. 【正点原子MP157连载】第二十五章 pinctrl和gpio子系统实验-摘自【正点原子】STM32MP1嵌入式Linux驱动开发指南V1.7

    1)实验平台:正点原子STM32MP157开发板 2)购买链接:https://item.taobao.com/item.htm?&id=629270721801 3)全套实验源码+手册+视频 ...

  2. 【正点原子FPGA连载】第十五章 RGB LCD彩条显示实验摘自【正点原子】DFZU2EG/4EV MPSoC 之FPGA开发指南V1.0

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

  3. 【正点原子FPGA连载】第十五章 IP核之FIFO实验 -摘自【正点原子】领航者ZYNQ之FPGA开发指南_V2.0

    1)实验平台:正点原子领航者ZYNQ开发板 2)平台购买地址:https://item.taobao.com/item.htm?&id=606160108761 3)全套实验源码+手册+视频下 ...

  4. linux键盘设置的文件在哪个文件夹,「正点原子Linux连载」第十五章按键输入试验...

    原标题:「正点原子Linux连载」第十五章按键输入试验 第十五章按键输入试验 前面几章试验都是讲解如何使用I.MX6U的GPIO输出控制功能,I.MX6U的IO不仅能作为输出,而且也可以作为输入.I. ...

  5. 【正点原子FPGA连载】第四十六章SD卡读写测试实验 -摘自【正点原子】新起点之FPGA开发指南_V2.1

    1)实验平台:正点原子新起点V2开发板 2)平台购买地址:https://detail.tmall.com/item.htm?id=609758951113 2)全套实验源码+手册+视频下载地址:ht ...

  6. 【正点原子Linux连载】第十五章点亮LED-摘自【正点原子】I.MX6U嵌入式Linux C应用编程指南V1.1

    1)实验平台:正点原子阿尔法Linux开发板 2)平台购买地址:https://item.taobao.com/item.htm?id=603672744434 2)全套实验源码+手册+视频下载地址: ...

  7. 【正点原子Linux连载】第三十五章 Linux内核顶层Makefile详解 -摘自【正点原子】I.MX6U嵌入式Linux驱动开发指南V1.0

    1)实验平台:正点原子阿尔法Linux开发板 2)平台购买地址:https://item.taobao.com/item.htm?id=603672744434 2)全套实验源码+手册+视频下载地址: ...

  8. 【正点原子Linux连载】第四十四章 设备树下的LED驱动实验 -摘自【正点原子】I.MX6U嵌入式Linux驱动开发指南V1.0

    1)实验平台:正点原子阿尔法Linux开发板 2)平台购买地址:https://item.taobao.com/item.htm?id=603672744434 2)全套实验源码+手册+视频下载地址: ...

  9. 【正点原子STM32连载】 第二十五章 TFTLCD(MCU屏)实验 摘自【正点原子】MiniPro STM32H750 开发指南_V1.1

    1)实验平台:正点原子MiniPro H750开发板 2)平台购买地址:https://detail.tmall.com/item.htm?id=677017430560 3)全套实验源码+手册+视频 ...

最新文章

  1. 【小白学PyTorch】4.构建模型三要素与权重初始化
  2. android—label窗口——基础编
  3. Docker 健康检查功能
  4. “EncoderDecoder: ‘mit_b1 is not in the backbone registry‘“
  5. jax-rs jax-ws_您的JAX-RS API并非天生就等于:使用动态功能
  6. JavaFX自定义控件– Nest Thermostat第1部分
  7. Linux(14)-正则表达式
  8. flask 安装flask_resultful
  9. LeetCode MySQL 180. 连续出现的数字(cast)
  10. 从移动广告业务“长出”的新增长点,汇量科技进军云服务
  11. c#仿照qq登录界面编辑框内容操作
  12. 虚拟存储器管理c语言_内存管理;虚拟内存
  13. pdf sdk for android,Android 自带PDF SDK
  14. flask+jsonp跨域前后台交互(接口初体验)
  15. html中pt与px的转换,关于网页中pt和px的单位换算!
  16. (1)前言-JsDroid引流脚本混合式开发技术系列教程By飞云脚本学院
  17. MySQL学习笔记.安全管理
  18. STM32中的DMA控制器的使用
  19. (三十)信号——信号产生原因以及信号处理行为的简介
  20. Rust 学习3, 枚举,集合

热门文章

  1. 数据库安全性 完整性
  2. 《信号与系统》笔记·第一章:信号与系统
  3. word2vec-python对词进行相似度计算1
  4. Game Theory: 公平博弈
  5. Java随机生成Mac地址(GitHub源码下载)
  6. 免费杀毒软件推荐下载
  7. Nature子刊:英属哥伦比亚大学揭示植物塑造根际微生物组新机制
  8. JAVA-------封装+Private关键字+权限修饰符+This关键字
  9. Linux服务器修改FTP密码
  10. 《信华广告产品生产管理软件》