1)实验平台:正点atom Linux开发板
2)摘自 《正点原子I.MX6U嵌入式Linux驱动开发指南》
关注官方微信号微信官方账号,获取更多信息:守时原子。
上一章我们写了基于设备树的LED驱动,但是驱动的本质没有变。都是配置LED灯使用的GPIO寄存器,驱动开发方法基本和裸机一样。Linux是一个庞大而完善的系统,尤其是驱动框架。不可能使用& quot原创& quot裸机驱动开发方法对于GPIO这种最基础的驱动来说,不然就相当于买了车天天推着上班。Linux内核为gpio驱动程序提供了pinctrl和gpio子系统。在本章中,我们将学习如何使用pinctrl和GPIO子系统简化GPIO驱动程序的开发。
让我们回顾一下上一章中如何初始化LED灯使用的GPIO。步骤如下:
修改设备树,添加相应的节点。该节点的关键点是设置reg属性,包括GPIO相关寄存器。
.获取reg属性中两个寄存器(iomuxc _ SW _ mux _ CTL _ pad _ GPIO1_IO03和(iomuxc _ SW _ pad _ CTL _ pad _ gpio 1 _ IO03)的地址,初始化这两个寄存器,用于设置gpio 1 _ IO03引脚的复用功能、下拉和速度。
在中,GPIO1_IO03的引脚被重用为GPIO功能,因此需要设置GPIO1_IO03,即GPIO1_DR和GPIO1_GDIR寄存器。
综上所述,在中,完成了GPIO1_IO03引脚的初始化,设置了该引脚的复用功能、上拉和下拉。例如,GPIO_IO03的引脚被设置为GPIO功能。完成GPIO的初始化,并将GPIO设置为输入/输出。如果你用过STM32,应该记得STM32也是需要先设置一个管脚的复用功能、速度、上拉下拉,然后再设置该管脚对应的GPIO。其实对于大部分32位SOC来说,管脚设置基本就是这两个方面,所以Linux内核引入了pinctrl子系统进行管脚配置,gpio子系统进行GPIO配置。在本节中,我们将学习pinctrl子系统,然后在下一节中学习gpio子系统。
大多数SOC引脚都支持多路复用,比如I.MX6ULL的GPIO1_IO03,可以作为通用GPIO,也可以作为I2C1的SDA,等等。此外,我们还需要配置pin的电气特性,如上/下、速度、驱动能力等。传统的配置pin的方式是直接操作对应的寄存器,但是这种配置方式比较繁琐,容易出现问题(比如pin功能冲突)。为解决这一问题,引入了Pinctrl子系统。pinctrl子系统的主要工作如下:
获取设备树中的pin信息。
根据获取的pin信息设置pin的复用功能。
根据获取的引脚信息设置引脚的电气特性,如上/下、速度、驱动能力等。
对于我们的用户来说,只需要在设备树中设置一个引脚的相关属性,其他初始化工作由pinctrl子系统完成。pinctrl子系统的源代码目录是drivers/pinctrl。
要使用pinctrl子系统,我们需要在设备树中设置PIN的配置信息。毕竟pinctrl子系统要根据你提供的信息来配置PIN功能,一般会在设备树中创建一个节点来描述PIN的配置信息。打开imx6ull.dtsi文件,找到一个名为iomuxc的节点,如下图所示:
示例代码45.1.2.1iomuxc节点内容1
756 iomuxc:iomuxc @ 020 e 0000{
757兼容=' fsl,imx6ul-io muxc ';
=0x 020 e 00000 x 4000;
p>759};iomuxc节点就是I.MX6ULL的IOMUXC外设对应的节点,看起来内容很少,没看出什么跟PIN的配置有关的内容啊,别急!打开imx6ull-alientek-emmc.dts,找到如下所示内容:
示例代码45.1.2.2iomuxc节点内容2
311&iomuxc{
312pinctrl-names="default";
313pinctrl-0=<&pinctrl_hog_1>;
314imx6ul-evk{
315pinctrl_hog_1:hoggrp-1{
316fsl,pins=<
317MX6UL_PAD_UART1_RTS_B__GPIO1_IO190x17059
318MX6UL_PAD_GPIO1_IO05__USDHC1_VSELECT0x17059
319MX6UL_PAD_GPIO1_IO09__GPIO1_IO090x17059
320MX6UL_PAD_GPIO1_IO00__ANATOP_OTG1_ID0x13058
321>;
322};
......
371pinctrl_flexcan1:flexcan1grp{
372fsl,pins=<
373MX6UL_PAD_UART3_RTS_B__FLEXCAN1_RX0x1b020
374MX6UL_PAD_UART3_CTS_B__FLEXCAN1_TX0x1b020
375>;
376};
......
587pinctrl_wdog:wdoggrp{
588fsl,pins=<
589MX6UL_PAD_LCD_RESET__WDOG1_WDOG_ANY0x30b0
590>;
591};
592};
593};
示例代码45.1.2.2就是向iomuxc节点追加数据,不同的外设使用的PIN不同、其配置也不同,因此一个萝卜一个坑,将某个外设所使用的所有PIN都组织在一个子节点里面。示例代码45.1.2.2中pinctrl_hog_1子节点就是和热插拔有关的PIN集合,比如USBOTG的ID引脚。pinctrl_flexcan1子节点是flexcan1这个外设所使用的PIN,pinctrl_wdog子节点是wdog外设所使用的PIN。如果需要在iomuxc中添加我们自定义外设的PIN,那么需要新建一个子节点,然后将这个自定义外设的所有PIN配置信息都放到这个子节点中。
将其与示例代码45.1.2.1结合起来就可以得到完成的iomuxc节点,如下所示:
示例代码45.1.2.3完整的iomuxc节点
1iomuxc:iomuxc@020e0000{
2compatible="fsl,imx6ul-iomuxc";
3reg=<0x020e00000x4000>;
4pinctrl-names="default";
5pinctrl-0=<&pinctrl_hog_1>;
6imx6ul-evk{
7pinctrl_hog_1:hoggrp-1{
8fsl,pins=<
9MX6UL_PAD_UART1_RTS_B__GPIO1_IO190x17059
10MX6UL_PAD_GPIO1_IO05__USDHC1_VSELECT0x17059
11MX6UL_PAD_GPIO1_IO09__GPIO1_IO090x17059
12MX6UL_PAD_GPIO1_IO00__ANATOP_OTG1_ID0x13058
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_IO190x17059
首先说明一下,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.4UART1_RTS_B引脚定义
190#defineMX6UL_PAD_UART1_RTS_B__UART1_DCE_RTS0x00900x031C0x06200x00x3
191#defineMX6UL_PAD_UART1_RTS_B__UART1_DTE_CTS0x00900x031C0x00000x00x0
192#defineMX6UL_PAD_UART1_RTS_B__ENET1_TX_ER0x00900x031C0x00000x10x0
193#defineMX6UL_PAD_UART1_RTS_B__USDHC1_CD_B0x00900x031C0x06680x20x1
194#defineMX6UL_PAD_UART1_RTS_B__CSI_DATA050x00900x031C0x04CC0x30x1
195#defineMX6UL_PAD_UART1_RTS_B__ENET2_1588_EVENT1_OUT0x00900x031C0x00000x40x0
196#defineMX6UL_PAD_UART1_RTS_B__GPIO1_IO190x00900x031C0x00000x50x0
197#defineMX6UL_PAD_UART1_RTS_B__USDHC2_CD_B0x00900x031C0x06740x80x2
示例代码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.1UART1_RTS_B引脚复用
示例代码196行的宏定义MX6UL_PAD_UART1_RTS_B__GPIO1_IO19表示将UART1_RTS_B这个IO复用为GPIO1_IO19。此宏定义后面跟着5个数字,也就是这个宏定义的具体值,如下所示:
0x00900x031C0x00000x50x0
这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_IO190x17059
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.5pinctrl-imx6ul.c文件代码段
326staticstructof_device_idimx6ul_pinctrl_of_match[]={
327{.compatible="fsl,imx6ul-iomuxc",.data=&imx6ul_pinctrl_info,},
328{.compatible="fsl,imx6ull-iomuxc-snvs",.data=&imx6ull_snvs_pinctrl_info,},
329{}
330};
331
332staticintimx6ul_pinctrl_probe(structplatform_device*pdev)
333{
334conststructof_device_id*match;
335structimx_pinctrl_soc_info*pinctrl_info;
336
337match=of_match_device(imx6ul_pinctrl_of_match,&pdev->dev);
338
339if(!match)
340return-ENODEV;
341
342pinctrl_info=(structimx_pinctrl_soc_info*)match->data;
343
344returnimx_pinctrl_probe(pdev,pinctrl_info);
345}
346
347staticstructplatform_driverimx6ul_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.3imx6ul_pinctrl_probe函数执行流程
在图45.1.2.3中函数imx_pinctrl_parse_groups负责获取设备树中关于PIN的配置信息,也就是我们前面分析的那6个u32类型的值。处理过程如下所示:
示例代码45.1.2.6imx_pinctrl_parse_groups函数代码段
488
492#defineFSL_PIN_SIZE24
493#defineSHARE_FSL_PIN_SIZE20
494
495staticintimx_pinctrl_parse_groups(structdevice_node*np,
496structimx_pin_group*grp,
497structimx_pinctrl_soc_info*info,
498u32index)
499{
500intsize,pin_size;
501const__be32*list;
502inti;
503u32config;
......
537
538for(i=0;i<grp->npins;i++){
539u32mux_reg=be32_to_cpu(*list++);
540u32conf_reg;
541unsignedintpin_id;
542structimx_pin_reg*pin_reg;
543structimx_pin*pin=&grp->pins[i];
544
......
555
556pin_id=(mux_reg!=-1)?mux_reg/4:conf_reg/4;
557pin_reg=&info->pin_regs[pin_id];
558pin->pin=pin_id;
559grp->pin_ids[i]=pin_id;
560pin_reg->mux_reg=mux_reg;
561pin_reg->conf_reg=conf_reg;
562pin->input_reg=be32_to_cpu(*list++);
563pin->mux_mode=be32_to_cpu(*list++);
564pin->input_val=be32_to_cpu(*list++);
565
566
567config=be32_to_cpu(*list++);
568if(config&IMX_PAD_SION)
569pin->mux_mode|=IOMUXC_CONFIG_SION;
570pin->config=config&~IMX_PAD_SION;
......
574}
575
576return0;
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控制器,此函数原型如下:
structpinctrl_dev*pinctrl_register(structpinctrl_desc*pctldesc,
structdevice*dev,
void*driver_data)
参数pctldesc非常重要,因为此参数就是要注册的PIN控制器,PIN控制器用于配置SOC的PIN复用功能和电气特性。参数pctldesc是pinctrl_desc结构体类型指针,pinctrl_desc结构体如下所示:
示例代码45.1.2.7pinctrl_desc结构体
128structpinctrl_desc{
129constchar*name;
130structpinctrl_pin_descconst*pins;
131unsignedintnpins;
132conststructpinctrl_ops*pctlops;
133conststructpinmux_ops*pmxops;
134conststructpinconf_ops*confops;
135structmodule*owner;
136#ifdefCONFIG_GENERIC_PINCONF
137unsignedintnum_custom_params;
138conststructpinconf_generic_params*custom_params;
139conststructpin_config_item*custom_conf_items;
140#endif
141};
第132~124行,这三个“_ops”结构体指针非常重要!!!因为这三个结构体就是PIN控制器的“工具”,这三个结构体里面包含了很多操作函数,通过这些操作函数就可以完成对某一个PIN的配置。pinctrl_desc结构体需要由用户提供,结构体里面的成员变量也是用户提供的。但是这个用户并不是我们这些使用芯片的程序员,而是半导体厂商,半导体厂商发布的Linux内核源码中已经把这些工作做完了。比如在imx_pinctrl_probe函数中可以找到如下所示代码:
示例代码45.1.2.8imx_pinctrl_probe函数代码段
648intimx_pinctrl_probe(structplatform_device*pdev,
649structimx_pinctrl_soc_info*info)
650{
651structdevice_node*dev_np=pdev->dev.of_node;
652structdevice_node*np;
653structimx_pinctrl*ipctl;
654structresource*res;
655structpinctrl_desc*imx_pinctrl_desc;
......
663
664imx_pinctrl_desc=devm_kzalloc(&pdev->dev,sizeof(*imx_pinctrl_desc),
665GFP_KERNEL);
666if(!imx_pinctrl_desc)
667return-ENOMEM;
......
705
706imx_pinctrl_desc->name=dev_name(&pdev->dev);
707imx_pinctrl_desc->pins=info->pins;
708imx_pinctrl_desc->npins=info->npins;
709imx_pinctrl_desc->pctlops=&imx_pctrl_ops;
710imx_pinctrl_desc->pmxops=&imx_pmx_ops;
711imx_pinctrl_desc->confops=&imx_pinconf_ops;
712imx_pinctrl_desc->owner=THIS_MODULE;
......
723ipctl->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.9imx_pctrl_ops、imx_pmx_ops和imx_pinconf_ops结构体
174staticconststructpinctrl_opsimx_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};
......
374staticconststructpinmux_opsimx_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};
......
481staticconststructpinconf_opsimx_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配置函数,我们就此打住,不在去分析这些函数了,否则本章就没完没了了,有兴趣的可以去看一下。
我们已经对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.10test设备pinctrl节点
1pinctrl_test:testgrp{
2
3};
2、添加“fsl,pins”属性
设备树是通过属性来保存信息的,因此我们需要添加一个属性,属性名字一定要为“fsl,pins”,因为对于I.MX系列SOC而言,pinctrl驱动程序是通过读取“fsl,pins”属性值来获取PIN的配置信息,完成以后如下所示:
示例代码45.1.2.11添加"fsl,pins"属性
1pinctrl_test:testgrp{
2fsl,pins=<
3
4>;
5};
3、在“fsl,pins”属性中添加PIN配置信息
最后在“fsl,pins”属性中添加具体的PIN配置信息,完成以后如下所示:
示例代码45.1.2.13完整的test设备pinctrl子节点
1pinctrl_test:testgrp{
2fsl,pins=<
3MX6UL_PAD_GPIO1_IO00__GPIO1_IO00config
4>;
5};
至此,我们已经在imx6ull-alientek-emmc.dts文件中添加好了test设备所使用的PIN配置信息。
上一小节讲解了pinctrl子系统,pinctrl子系统重点是设置PIN(有的SOC叫做PAD)的复用和电气属性,如果pinctrl子系统将一个PIN复用为GPIO的话,那么接下来就要用到gpio子系统了。gpio子系统顾名思义,就是用于初始化GPIO并且提供提供相应的API函数,比如设置GPIO为输入输出,读取GPIO的值等。gpio子系统的主要目的就是方便驱动开发者使用gpio,驱动开发者在设备树中添加gpio相关信息,然后就可以在驱动程序中使用gpio子系统提供的API函数来操作GPIO,Linux内核向驱动开发者屏蔽掉了GPIO的设置过程,极大的方便了驱动开发者使用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.1SD卡CD引脚PIN配置参数
316pinctrl_hog_1:hoggrp-1{
317fsl,pins=<
318MX6UL_PAD_UART1_RTS_B__GPIO1_IO190x17059
......
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{
761pinctrl-names="default","state_100mhz","state_200mhz";
762pinctrl-0=<&pinctrl_usdhc1>;
763pinctrl-1=<&pinctrl_usdhc1_100mhz>;
764pinctrl-2=<&pinctrl_usdhc1_200mhz>;
765
766cd-gpios=<&gpio119GPIO_ACTIVE_LOW>;
767keep-power-in-suspend;
768enable-sdio-wakeup;
769vmmc-supply=<®_sd1_vmmc>;
770status="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.2gpio1节点
504gpio1:gpio@0209c000{
505compatible="fsl,imx6ul-gpio","fsl,imx35-gpio";
506reg=<0x0209c0000x4000>;
507interrupts=<GIC_SPI66IRQ_TYPE_LEVEL_HIGH>,
508<GIC_SPI67IRQ_TYPE_LEVEL_HIGH>;
509gpio-controller;
510#gpio-cells=<2>;
511interrupt-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参考手册》找到“Chapter28:GeneralPurposeInput/Output(GPIO)”章节第28.5小节,有如图45.2.2.1所示的寄存器地址表:
图45.2.2.1GPIO1寄存器表
从图45.2.2.1可以看出,GPIO1控制器的基地址就是0X0209C000。
第509行,“gpio-controller”表示gpio1节点是个GPIO控制器。
第510行,“#gpio-cells”属性和“#address-cells”类似,#gpio-cells应该为2,表示一共有两个cell,第一个cell为GPIO编号,比如“&gpio13”就表示GPIO1_IO03。第二个cell表示GPIO极性如果为0的话表示高电平有效,如果为1的话表示低电平有效。
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.3mxc_gpio_dt_ids匹配表
152staticconststructof_device_idmxc_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{}
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.2gpio核心驱动文件
我们重点来看一下gpio-mxc.c这个文件,在gpio-mxc.c文件中有如下所示内容:
示例代码45.2.2.4mxc_gpio_driver结构体
496staticstructplatform_drivermxc_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.5mxc_gpio_probe函数
403staticintmxc_gpio_probe(structplatform_device*pdev)
404{
405structdevice_node*np=pdev->dev.of_node;
406structmxc_gpio_port*port;
407structresource*iores;
408intirq_base;
409interr;
410
411mxc_gpio_get_hw(pdev);
412
413port=devm_kzalloc(&pdev->dev,sizeof(*port),GFP_KERNEL);
414if(!port)
415return-ENOMEM;
416
417iores=platform_get_resource(pdev,IORESOURCE_MEM,0);
418port->base=devm_ioremap_resource(&pdev->dev,iores);
419if(IS_ERR(port->base))
420returnPTR_ERR(port->base);
421
422port->irq_high=platform_get_irq(pdev,1);
423port->irq=platform_get_irq(pdev,0);
424if(port->irq<0)
425returnport->irq;
426
427
428writel(0,port->base+GPIO_IMR);
429writel(~0,port->base+GPIO_ISR);
430
431if(mxc_gpio_hwtype==IMX21_GPIO){
432
437irq_set_chained_handler(port->irq,mx2_gpio_irq_handler);
438}else{
439
440irq_set_chained_handler(port->irq,mx3_gpio_irq_handler);
441irq_set_handler_data(port->irq,port);
442if(port->irq_high>0){
443
444irq_set_chained_handler(port->irq_high,
445mx3_gpio_irq_handler);
446irq_set_handler_data(port->irq_high,port);
447}
448}
449
450err=bgpio_init(&port->bgc,&pdev->dev,4,
451port->base+GPIO_PSR,
452port->base+GPIO_DR,NULL,
453port->base+GPIO_GDIR,NULL,0);
454if(err)
455gotoout_bgio;
456
457port->bgc.gc.to_irq=mxc_gpio_to_irq;
458port->bgc.gc.base=(pdev->id<0)?of_alias_get_id(np,"gpio")
459*32:pdev->id*32;
460
461err=gpiochip_add(&port->bgc.gc);
462if(err)
463gotoout_bgpio_remove;
464
465irq_base=irq_alloc_descs(-1,0,32,numa_node_id());
466if(irq_base<0){
467err=irq_base;
468gotoout_gpiochip_remove;
469}
470
471port->domain=irq_domain_add_legacy(np,32,irq_base,0,
472&irq_domain_simple_ops,NULL);
473if(!port->domain){
474err=-ENODEV;
475gotoout_irqdesc_free;
476}
477
478
479mxc_gpio_init_gc(port,irq_base);
480
481list_add_tail(&port->node,&mxc_gpio_ports);
482
483return0;
......
494}
第405行,设备树节点指针。
第406行,定义一个结构体指针port,结构体类型为mxc_gpio_port。gpio-mxc.c的重点工作就是维护mxc_gpio_port,mxc_gpio_port就是对I.MX6ULLGPIO的抽象。mxc_gpio_port结构体定义如下:
示例代码45.2.2.6mxc_gpio_port结构体
61structmxc_gpio_port{
62structlist_headnode;
63void__iomem*base;
64intirq;
65intirq_high;
66structirq_domain*domain;
67structbgpio_chipbgc;
68u32both_edges;
69};
mxc_gpio_port的bgc成员变量很重要,因为稍后的重点就是初始化bgc。
继续回到mxc_gpio_probe函数函数,第411行调用mxc_gpio_get_hw函数获取gpio的硬件相关数据,其实就是gpio的寄存器组,函数mxc_gpio_get_hw里面有如下代码:
示例代码45.2.2.7mxc_gpio_get_hw函数
364staticvoidmxc_gpio_get_hw(structplatform_device*pdev)
365{
366conststructof_device_id*of_id=
367of_match_device(mxc_gpio_dt_ids,&pdev->dev);
368enummxc_gpio_hwtypehwtype;
......
383
384if(hwtype==IMX35_GPIO)
385mxc_gpio_hwdata=&imx35_gpio_hwdata;
386elseif(hwtype==IMX31_GPIO)
387mxc_gpio_hwdata=&imx31_gpio_hwdata;
388else
389mxc_gpio_hwdata=&imx1_imx21_gpio_hwdata;
390
391mxc_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.8imx35_gpio_hwdata结构体
101staticstructmxc_gpio_hwdataimx35_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};