100字范文,内容丰富有趣,生活中的好帮手!
100字范文 > 「正点原子Linux连载」第四十五章 pinctrl和gpio子系统实验一

「正点原子Linux连载」第四十五章 pinctrl和gpio子系统实验一

时间:2019-02-20 21:35:49

相关推荐

「正点原子Linux连载」第四十五章 pinctrl和gpio子系统实验一

第四十五章 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 =<0x020e00000x4000>;

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 =<0x020e00000x4000>;

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 0x00900x031C0x06200x00x3

191 #define MX6UL_PAD_UART1_RTS_B__UART1_DTE_CTS 0x00900x031C0x00000x00x0

192 #define MX6UL_PAD_UART1_RTS_B__ENET1_TX_ER 0x00900x031C0x00000x10x0

193 #define MX6UL_PAD_UART1_RTS_B__USDHC1_CD_B 0x00900x031C0x06680x20x1

194 #define MX6UL_PAD_UART1_RTS_B__CSI_DATA05 0x00900x031C0x04CC0x30x1

195 #define MX6UL_PAD_UART1_RTS_B__ENET2_1588_EVENT1_OUT 0x00900x031C0x00000x40x0

196 #define MX6UL_PAD_UART1_RTS_B__GPIO1_IO19 0x00900x031C0x00000x50x0

197 #define MX6UL_PAD_UART1_RTS_B__USDHC2_CD_B 0x00900x031C0x06740x80x2

示例代码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文件代码段

326staticstruct 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

332staticint imx6ul_pinctrl_probe(struct platform_device *pdev)

333{

334conststruct of_device_id *match;

335struct imx_pinctrl_soc_info *pinctrl_info;

336

337 match =of_match_device(imx6ul_pinctrl_of_match,&pdev->dev);

338

339if(!match)

340return-ENODEV;

341

342 pinctrl_info =(struct imx_pinctrl_soc_info *)match->data;

343

344returnimx_pinctrl_probe(pdev, pinctrl_info);

345}

346

347staticstruct 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

495staticint imx_pinctrl_parse_groups(struct device_node *np,

496struct imx_pin_group *grp,

497struct imx_pinctrl_soc_info *info,

498 u32 index)

499{

500int size,pin_size;

501const __be32 *list;

502int i;

503 u32 config;

......

537

538for(i =0;i < grp->npins; i++){

539 u32 mux_reg =be32_to_cpu(*list++);

540 u32 conf_reg;

541unsignedint pin_id;

542struct imx_pin_reg *pin_reg;

543struct 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++);

568if(config &IMX_PAD_SION)

569 pin->mux_mode |=IOMUXC_CONFIG_SION;

570 pin->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控制器,此函数原型如下:

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结构体

128struct pinctrl_desc {

129constchar*name;

130struct pinctrl_pin_desc const*pins;

131unsignedint npins;

132conststruct pinctrl_ops *pctlops;

133conststruct pinmux_ops *pmxops;

134conststruct pinconf_ops *confops;

135struct module *owner;

136 #ifdef CONFIG_GENERIC_PINCONF

137unsignedint num_custom_params;

138conststruct pinconf_generic_params *custom_params;

139conststruct 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函数代码段

648int imx_pinctrl_probe(struct platform_device *pdev,

649struct imx_pinctrl_soc_info *info)

650{

651struct device_node *dev_np =pdev->dev.of_node;

652struct device_node *np;

653struct imx_pinctrl *ipctl;

654struct resource *res;

655struct pinctrl_desc *imx_pinctrl_desc;

......

663

664 imx_pinctrl_desc = devm_kzalloc(&pdev->dev,sizeof(*imx_pinctrl_desc),

665 GFP_KERNEL);

666if(!imx_pinctrl_desc)

667return-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结构体

174staticconststruct 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};

......

374staticconststruct 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};

......

481staticconststruct 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 =<0x0209c0000x4000>;

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的话表示高电平有效,如果为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.3 mxc_gpio_dt_ids匹配表

152staticconststruct 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结构体

496staticstruct 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函数

403staticint mxc_gpio_probe(struct platform_device *pdev)

404{

405struct device_node *np =pdev->dev.of_node;

406struct mxc_gpio_port *port;

407struct resource *iores;

408int irq_base;

409int err;

410

411 mxc_gpio_get_hw(pdev);

412

413 port =devm_kzalloc(&pdev->dev,sizeof(*port), GFP_KERNEL);

414if(!port)

415return-ENOMEM;

416

417 iores =platform_get_resource(pdev, IORESOURCE_MEM,0);

418 port->base =devm_ioremap_resource(&pdev->dev, iores);

419if(IS_ERR(port->base))

420returnPTR_ERR(port->base);

421

422 port->irq_high =platform_get_irq(pdev,1);

423 port->irq =platform_get_irq(pdev,0);

424if(port->irq <0)

425returnport->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

431if(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);

442if(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);

454if(err)

455gotoout_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);

462if(err)

463gotoout_bgpio_remove;

464

465 irq_base =irq_alloc_descs(-1,0,32, numa_node_id());

466if(irq_base <0){

467 err =irq_base;

468gotoout_gpiochip_remove;

469}

470

471 port->domain =irq_domain_add_legacy(np,32, irq_base,0,

472&irq_domain_simple_ops,NULL);

473if(!port->domain){

474 err =-ENODEV;

475gotoout_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

483return0;

......

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结构体

61struct 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函数

364staticvoid mxc_gpio_get_hw(struct platform_device *pdev)

365{

366conststruct of_device_id *of_id =

367 of_match_device(mxc_gpio_dt_ids,&pdev->dev);

368enum mxc_gpio_hwtype hwtype;

......

383

384if(hwtype ==IMX35_GPIO)

385 mxc_gpio_hwdata =&imx35_gpio_hwdata;

386elseif(hwtype ==IMX31_GPIO)

387 mxc_gpio_hwdata =&imx31_gpio_hwdata;

388else

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结构体

101staticstruct 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};

本内容不代表本网观点和政治立场,如有侵犯你的权益请联系我们处理。
网友评论
网友评论仅供其表达个人看法,并不表明网站立场。