您当前的位置: 首页 >  嵌入式

正点原子

暂无认证

  • 1浏览

    0关注

    382博文

    0收益

  • 0浏览

    0点赞

    0打赏

    0留言

私信
关注
热门博文

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

正点原子 发布时间:2021-09-01 10:28:49 ,浏览量:1

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 = ;
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 = ;
314     imx6ul-evk {
315         pinctrl_hog_1: hoggrp-1 {
316             fsl,pins = ;
322         };
......
371         pinctrl_flexcan1: flexcan1grp{
372             fsl,pins = ;
376         };
...... 
587         pinctrl_wdog: wdoggrp {
588             fsl,pins = ;
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 = ;
4   	pinctrl-names = "default";
5   	pinctrl-0 = ;
6   	imx6ul-evk {
7       		pinctrl_hog_1: hoggrp-1 {
8           		fsl,pins = ;
......
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个值的含义如下所示: 综上所述可知: 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 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 = ;
5 };
3、在“fsl,pins”属性中添加PIN配置信息
最后在“fsl,pins”属性中添加具体的PIN配置信息,完成以后如下所示:

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

1 pinctrl_test: testgrp {
2 		fsl,pins = ;
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 = ;
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 = ;
763     pinctrl-1 = ;
764     pinctrl-2 = ;
765     /* pinctrl-3 = ; */
766     cd-gpios = ;
767     keep-power-in-suspend;
768     enable-sdio-wakeup;
769     vmmc-supply = ;
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 = ”这一行,也就是说并没有指定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 = ;
507     interrupts = ,
508              ;
509     gpio-controller;
510     #gpio-cells = ;
511     interrupt-controller;
512     #interrupt-cells = ;
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 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 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 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 = ;
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 = ;
4   gpio = ;
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 = ; 上述代码的“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编号,此函数会将设备树中类似的属性信息转换为对应的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 = ;
5 };
第3行,将GPIO1_IO03这个PIN复用为GPIO1_IO03,电气属性值为0X10B0。 
2、添加LED设备节点
在根节点“/”下创建LED灯节点,节点名为“gpioled”,节点内容如下:

示例代码45.4.1.2 创建LED灯节点

1 gpioled {
2     #address-cells = ;
3     #size-cells = ;
4     compatible = "atkalpha-gpioled";
5     pinctrl-names = "default";
6     pinctrl-0 = ;
7     led-gpio = ;
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 = ;
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 = ;
726     xnur-gpio = ;
727     measure-delay-time = ;
728     pre-charge-time = ;
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 
2   #include 
3   #include 
4   #include 
5   #include 
6   #include 
7   #include 
8   #include 
9   #include 
10  #include 
11  #include 
12  #include 
13  #include 
14  #include 
15  #include 
16  #include 
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 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             
关注
打赏
1665308814
查看更多评论
0.1278s