目录
pinctrl子系统简介
I.MX6ULL的pinctrl子系统驱动
PIN驱动程序讲解
设备树中添加pinctrl节点模板
gpio子系统简介
I.MX6ULL的gpio子系统驱动
GPIO驱动程序简介
gpio子系统API函数
设备树中添加gpio节点模板
与gpio相关的OF函数
LED实验
LED灯驱动程序编写
运行测试
pinctrl子系统简介
Linux驱动讲究驱动分离与分层,pinctrl和gpio子系统就是驱动分离与分层思想下的产物,驱动分离与分层其实就是按照面向对象编程的设计思想而设计的设备驱动框架。
回顾一下怎么初始化LED灯所使用的GPIO,步骤如下:
1.修改设备树,添加相应的节点,节点里面重点是设置reg属性, reg属性包括了GPIO相关寄存器。
2获取reg属性中
IOMUXC_SW_MUX_CTL_PAD_GPIO1_IO03和"IOMUXC-SW-PAD-CTL-PAD-GPIO1-IO03这两个寄存器地址,并且初始化这两个寄存器,这两个寄存器用于设置GPIO1_IO03这个PIN的复用功能、上下拉、速度等。
3.在2里面将GPIO1_IO03这个PIN复用为了GPIO功能,因此需要设置GPIO1_IO03这个 GPIO相关的寄存器,也就是GPIO1_DR和GPIO1_GDIR这两个寄存器。
总结一下, 2中完成对GPIO1_IO03这个PIN的初始化,设置这个PIN的复用功能、上下拉等,比如将GPIO_IO03这个PIN设置为GPIO功能。③中完成对GPIO的初始化,设置GPIO为输入/输出等。如果使用过STM32的话应该都记得, STM32也是要先设置某个PIN的复用功能、速度、上下拉等,然后再设置PIN所对应的GPIO。其实对于大多数的32位SOC而言,引脚的设置基本都是这两方面,因此Linux内核针对PIN的配置推出了pinctrl子系统,对于GPIO的配置推出了gpio子系统。
大多数SOC的pin 都是支持复用的,比如I.MX6ULL的GPIO1_IO03既可以作为普通的GPIO使用,也可以作为I2C1的SDA等等。此外我们还需要配置pin的电气特性,比如上/下拉、速度、驱动能力等等。传统的配置pin的方式就是直接操作相应的寄存器,但是这种配置方式比较繁琐、而且容易出问题(比如pin功能冲突)。pinctrl子系统就是为了解决这个问题而引入的, pinctrl子系统主要工作内容如下:
1.获取设备树中pin信息。
2.根据获取到的pin信息来设置pin的复用功能。
3.根据获取到的pin信息来设置pin的电气特性,比如上/下拉、速度、驱动能力等。
对于我们使用者来讲,只需要在设备树里面设置好某个pin的相关属性即可,其他的初始化工作均由pinctrl子系统来完成,pinctrl子系统源码目录为drivers/pinctrl。
I.MX6ULL的pinctrl子系统驱动
PIN配置信息详解
要使用pinctrl子系统,我们需要在设备树里面设置PIN的配置信息,毕竟pinctrl子系统要根据你提供的信息来配置PIN功能,一般会在设备树里面创建一个节点来描述PIN的配置信息。打开imx6ull.dtsi文件,找到一个叫做iomuxc的节点,如下所示:
iomuxc节点就是I.MX6ULL的IOMUXC外设对应的节点,看起来内容很少,没看出什么跟PIN 的配置有关的内容啊,别急!打开imx6ull-alientek-emmc.dts,找到如下所示内容:
示例代码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节点,如下所示:
第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的配置信息如下:
首先说明一下,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的复用功能,一个是用来设置UARTI_RTS_B的电气特性。
首先来看一下MX6UL_PAD_UARTI_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中一共有8个以"MX6UL_PAD_UART1_RTS_B"开头的宏定义,大家仔细观察应该就能发现,这8个宏定义分别对应UART1_RTS_B这个PIN的8个复用IO。查阅《I.MX6LL参考手册》可以知UART1_RTS_B的可选复用IO如图45.1.2.1所示:
这5个值的含义如下所示:
综上所述可知:
0x0090: mux_reg寄存器偏移地址,设备树中的iomuxc节点就是IOMUXC外设对应的节点,根据其reg属性可知IOMUXC外设寄存器起始地址为0x020e0000。因此Ox020e0000+0x0090=0x020e0090, IOMUXC_SW_MUX_CTL_PAD_UART1_RTS_B
寄存器地址正好是0x020e0090,大家可以在《IMX6ULL参考手册》中找到IOMUXC_SW-MUX_CTL_PAD_UART1_RTS_B这个寄存器的位域图,如图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_UARTI_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,反应快的同学应该已经猜出来了, 0x17059就是conf_reg寄存器值!此值由用户自行设置,通过此值来设置一个IO的上/下拉、驱动能力和速度等。在这里就相当于设置寄存器IOMUXC_SW_PAD_CTL_PAD_UART1_RTS_B的值为0x17059。
PIN驱动程序讲解
所有的东西都已经准备好了,包括寄存器地址和寄存器值,Linux内核相应的驱动文件就会根据这些值来做相应的初始化。接下来就找一下哪个驱动文件来做这一件事情, iomuxc节点中compatible属性的值为"fsl,imx6ul-iomuxc",在Linux内核中全局搜索"fsl,imx6ul-iomuxc"字符串就会找到对应的驱动文件。在文件drivers/pinctrl/freescale/pinctrl-imx6ul.c中有如下内容:
第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中函数imx_pinctrl_parse_groups负责获取设备树中关于PIN的配置信息,也就是我们前面分析的那6个u32类型的值。处理过程如下所示:
第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控制器,此函数原型如下:
参数petldesc非常重要,因为此参数就是要注册的PIN控制器, PIN控制器用于配置SOC·的PIN复用功能和电气特性。参数pctldesc是pinctrl_desc结构体类型指针, pinctrl_desc结构体如下所示:
第132-124行,这三个"ops”结构体指针非常重要!!!因为这三个结构体就是PIN控制器的“工具”,这三个结构体里面包含了很多操作函数,通过这些操作函数就可以完成对某一个PIN的配置。pinctrl_desc结构体需要由用户提供,结构体里面的成员变量也是用户提供的。但是这个用户并不是我们这些使用芯片的程序员,而是半导体厂商,半导体厂商发布的Linux内核源码中已经把这些工作做完了。比如在imx pinctrl probe函数中可以找到如下所示代码:
第655行,定义结构体指针变量imx_pinctrl_desc。
第664行,向指针变量imx_pinctrl_desc分配内存。
第706-712行,初始化imx_pinctrl_desc结构体指针变量,重点是pctlops、pmxops和confops这三个成员变量,分别对应imx_petrl_ops、imx_pmx_ops和imx_pinconfops这三个结构体。
第723行,调用函数pinctrl_register向Linux内核注册imx_pinctrl_desc,注册以后Linux内核就有了对I.MX6ULL的PIN进行配置的工具。
Imx_pctrl_ops、imx_pmx_ops和imx_pinconf_ops这三个结构体定义如下:
示例代码45.1.2.9中这三个结构体下的所有函数就是I.MX6ULL的PIN配置函数,就此打住,不再去分析这些函数了,否则就没完没了了,有兴趣的可以去看一下。
设备树中添加pinctrl节点模板
创建对应的节点
同一个外设的PIN都放到一个节点里面,打开imx6ull-alientek-emme.dts,在iomuxc节点中的“imx6ul-evk”子节点下添加“pinctrl_test”节点。添加完成以后如下所示:
添加“fsl, pins”属性
设备树是通过属性来保存信息的,因此我们需要添加一个属性,属性名字一定要为“fsl,pins”,因为对于I.MX系列SOC而言, pinctrl驱动程序是通过读取"fsl,pins"属性值来获取PIN的配置信息,完成以后如下所示:
在"fsl,pins"属性中添加PIN配置信息
最后在“fsl,pins”属性中添加具体的PIN配置信息,完成以后如下所示:
至此,已经在imx6ull-alientek-emmc.dts文件中添加好了test设备所使用的PIN配置信息。
gpio子系统简介
讲解了pinctrl子系统, pinctrl子系统重点是设置PIN(有的SOC叫做PAD)的复用和电气属性,如果pinctrl子系统将一个PIN复用为GPIO的话,那么接下来就要用到gpio子系统了。gpio子系统顾名思义,就是用于初始化GPIO并且提供相应的API函数,比如设置GPIO为输入输出,读取GPIO的值等。gpio子系统的主要目的就是方便驱动开发者使用gpio,驱动开发者在设备树中添加gpio相关信息,然后就可以在驱动程序中使用gpio子系统提供的API函数来操作GPIO, Linux内核向驱动开发者屏蔽掉了GPIO的设置过程,极大的方便了驱动开发者使用 GPIO。
I.MX6ULL的gpio子系统驱动
设备树中的gpio信息
I.MX6ULL-ALPHA开发板上的UART1_RTS_B做为SD卡的检测引脚, UART1_RTS_B复用为GPIO1_IO19,通过读取这个GPIO的高低电平就可以知道SD卡有没有插入。首先肯定是将 UARTI_RTS_B 这个 PIN 复用为 GPIO1_IO19,并且设置电气属性,也就是上面讲的pinctrl节点。打开imx6ull-alientek-emmc.dts,UART1_RTS_B这个PIN的pinert设置如下:
第318行,设置UARTI_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 中找到名为"usdhcl"的节点,这个节点就是SD卡设备节点,如下所示:
第765行,此行本来没有,是作者添加的, usdhcl节点作为SD卡设备总节点, usdhc1节点需要描述SD卡所有的信息,因为驱动要使用。本行就是描述SD卡的CD引脚pinctrl信息所在的子节点,因为SD卡驱动需要根据pinertl节点信息来设置CD引脚的复用功能等。762~764行的pinctrl-0~2都是SD卡其他PIN的pinertl节点信息。但是大家会发现,其实在usdhc1节点中并没有“pinctrl-3=<&pinctrl_hog_l>”这一行,也就是说并没有指定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,在里面找到如下所示内容:
gpio1节点信息描述了GPIO1控制器的所有信息,重点就是 GPIO1外设寄存器基地址以及兼容属性。关于I.MX系列SOC的GPIO控制器绑定信息请查看文档
Documentation/devicetree/bindings/gpio/ fsl-imx-gpio.txt。
第505行,设置gpio1节点的compatible属性有两个,分别为"fsl,imx6ul-gpio"和"fsl,imx35gpio”,在Linux内核中搜索这两个字符串就可以找到I.MX6UL的GPIO驱动程序。
第506行, reg属性设置了GPIO1控制器的寄存器基地址为0X0209C000,大家可以打开《I.MX6ULL参考手册》找到"Chapter 28:General Purpose Input/Output(GPIO)"章节第28.5小节,有如图所示的寄存器地址表:
从图可以看出, 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)的话表示低电平有效。
GPIO驱动程序简介
Gpio1节点的compatible属性描述了兼容性,在Linux内核中搜索“fsl,imx6ul-gpio"和 "fsl,imx35-gpio"这两个字符串,查找GPIO驱动文件。drivers/gpio/gpio-mxc.c就是 I.MX6ULL的GPIO驱动文件,在此文件中有如下所示of_device_id 匹配表:
第156行的compatible值为"fsl,imx35-gpio",和gpio1的compatible属性匹配,因此 gpiomxc.c就是I.MX6ULL的GPIO控制器驱动文件。gpio-mxc.c所在的目录为drivers/gpio,打开这个目录可以看到很多芯片的gpio驱动文件,“gpiolib”开始的文件是gpio驱动的核心文件,如图所示:
重点来看一下gpio-mxc.c这个文件,在gpio-mxc.c文件中有如下所示内容:
可以看出GPIO驱动也是个平台设备驱动,因此当设备树中的设备节点与驱动的of_device_id 匹配以后probe函数就会执行,在这里就是mxc_gpio_probe函数,这个函数就是I.MX6ULL的GPIO驱动入口函数。简单来分析一下 mxc_gpio_probe 这个函数,函数内容如下:
第405行,设备树节点指针。
第406行,定义一个结构体指针port,结构体类型为mxc_gpio_port。gpio-mxc.c的重点工作就是维护mxe_gpio_port, mxe_gpio_port就是对I.MX6ULL_GPIO的抽象。mxe_gpio_port结构体定义如下:
mxc_gpio_port的bgc成员变量很重要,因为稍后的重点就是初始化 bgc。
继续回到mxc_gpio_probe函数函数,第411行调用mxc_gpio_get_hw函数获取gpio的硬件相关数据,其实就是gpio的寄存器组,函数mxc_gpio_get_hw里面有如下代码:
注意第385行,mxc_gpio_hwdata是个全局变量,如果硬件类型是IMX35_GPIO的话设置mxc_gpio_hwdat为imx35_gpio_hwdata。
对于I.MX6ULL而言,硬件类型就是IMX35_GPIO,imx35_gpio_hwdata是个结构体变量,描述了 GPIO寄存器组,内容如下:
大家将imx35_gpio_hwdata中的各个成员变量和图中的GPIO 寄存器表对比就会发现, imx35_gpio_hwdata结构体就是GPIO寄存器组结构。
这样后面就可以通过mxc_gpio_hwdata这个全局变量来访问GPIO的相应寄存器了。
继续回到示例代码45.2.2.5的mxe_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所有1O中断,并且清除状态寄存器。
第438-448行,设置对应GPIO的中断服务函数,不管是高16位还是低16位,中断服务函数都是mx3_gpio_irq_handler。
第450-453行, bgpio_init函数第一个参数为bgc,是bgpio_chip结构体指针。bgpio_chip
结构体有个ge成员变量,ge是个gpio_chip结构体类型的变量。gpio_chip结构体是抽象出来的GPIO控制器,gpio_chip结构体如下所示(有缩减):
可以看出, 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->bge.ge。注册完成以后我们就可以在驱动中使用gpiolib提供的各个AP1函数。
gpio子系统API函数
对于驱动开发人员,设置好设备树以后就可以使用gpio子系统提供的API 函数来操作指定的GPIO, gpio子系统向驱动开发人员屏蔽了具体的读写寄存器过程。这就是驱动分层与分离的好处,大家各司其职,做好自己的本职工作即可。gpio子系统提供的常用的API函数有下面几个:
gpio_request函数
gpio_request函数用于申请一个GPIO管脚,在使用一个GPIO之前一定要使用gpio_request进行申请,函数原型如下:
函数参数和返回值含义如下:
gpio:要申请的gpio标号,使用of_get_named_gpio函数从设备树获取指定GPIO属性信·息,此函数会返回这个GPIO的标号。
Label:给gpio设置个名字。
返回值:0,申请成功:其他值,申请失败。
gpio_free函数
如果不使用某个GPIO了,那么就可以调用gpio_free函数进行释放。函数原型如下:
函数参数和返回值含义如下:
Gpio:要释放的gpio标号。
返回值:无。
gpio_direction_input函数
此函数用于设置某个GPIO为输入,函数原型如下所示:
函数参数和返回值含义如下:
gpio:要设置为输入的 GPIO标号。
返回值:0,设置成功:负值,设置失败。
gpio_direction_output函数
此函数用于设置某个GPIO为输出,并且设置默认输出值,函数原型如下:
函数参数和返回值含义如下:
Gpio:要设置为输出的GPIO标号。
Value:GPIO默认输出值。
返回值:0,设置成功;负值,设置失败。
gpio_get_value函数
此函数用于获取某个GPIO的值(0或1),此函数是个宏,定义所示:
函数参数和返回值含义如下:
Gpio:要获取的GPIO标号。
返回值:非负值,得到的GPIO值;负值,获取失败。
gpio_set_value函数
此函数用于设置某个GPIO的值,此函数是个宏,定义如下:
函数参数和返回值含义如下:
Gpio:要设置的GPIO标号。
Value:要设置的值。
返回值:无关于gpio子系统常用的API函数就讲这些,这些是用的最多的。
设备树中添加gpio节点模板
继续完成的test设备,在中我们已经讲解了如何创建test设备的pinctrl节点。
创建test设备节点
在根节点“/”下创建test设备子节点,如下所示:
添加pinctrl信息
在中我们创建了pinctrl_test节点,此节点描述了test设备所使用的GPIO1_IO00这个PIN的信息,要将这节点添加到test设备节点中,如下所示:
第2行,添加pinctrl-names属性,此属性描述pinctrl名字为“default”。
第3行,添加pinctrl-0节点,此节点引用45.1.3中创建的pinctrl_test节点,表示tset设备的所使用的PIN信息保存在pinctrl_test节点中。
添加GPIO属性信息
我们最后需要在test节点中添加GPIO属性信息,表明test所使用的GPIO是哪个引脚,添加完成以后如下所示:
第4行,test设备所使用的gpio。关于pinctrl子系统和gpio子系统就讲解到这里,接下来就使用pinctrl和gpio子系统来驱动I.MX6ULL-ALPHA开发板上的LED灯。
与gpio相关的OF函数
在示例代码 45.2.4.3中,定义了一个名为“gpio”的属性,gpio属性描述了test这个设备所使用的GPIO。在驱动程序中需要读取gpio属性内容, Linux内核提供了几个与GPIO有关的OF函数,常用的几个OF函数如下所示:
of_gpio_named_count函数
of_gpio_named_count函数用于获取设备树某个属性里面定义了几个GPIO信息,要注意的是空的GPIO信息也会被统计到,比如:
上述代码的“gpios”节点一共定义了4个GPIO,但是有2个是空的,没有实际的含义。通过of _gpio_named_count函数统计出来的GPIO数量就是4个,此函数原型如下:
函数参数和返回值含义如下:
Np:设备节点。
返回值:正值,统计到的GPIO数量;负值,失败。
of_get_named_gpio函数
此函数获取GPIO编号,因为Linux内核中关于GPIO的API函数都要使用GPIO编号,此函数会将设备树中类似<&gpio5 7 GPIO_ACTIVE_LOW>的属性信息转换为对应的GPIO 编号,此函数在驱动中使用很频繁!函数原型如下:
函数参数和返回值含义如下:
np:设备节点。
propname:包含要获取GPIO信息的属性名。
Index:GPIO索引,因为一个属性里面可能包含多个GPIO,此参数指定要获取哪个GPIO的编号,如果只有一个GPIO信息的话此参数为0。
返回值:正值,获取到的GPIO编号;负值,失败。
LED实验
修改设备树文件
添加pinctrl节点
I.MX6U-ALPHA开发板上的 LED 灯使用了GPIO1_IO03这个PIN,打开imx6ull-alientekemmc.dts,在iomuxc节点的imx6ul-evk子节点下创建一个名为“pinctrl_led”的子节点,节点内容如下所示:
第3行,将GPIO1_1003这个PIN复用为GPIO1_1O03,电气属性值为0X10B0。
添加LED设备节点
在根节点“/”下创建LED灯节点,节点名为“gpioled”,节点内容如下:
第6行,pinctrl-0属性设置LED灯所使用的PIN对应的pinctrl节点。
第7行, led-gpio属性指定了LED灯所使用的GPIO,在这里就是GPIO1的IO03,低电平有效。稍后编写驱动程序的时候会获取 led-gpio属性的内容来得到GPIO编号,因为gpio子系统的 API操作函数需要GPIO编号。
设备树编写完成以后使用“make dtbs”命令重新编译设备树,然后使用新编译出来的imx6ull-alientek-emmc.dtb文件启动Linux系统。启动成功以后进入“/proc/device-tree"目录中查看“gpioled”节点是否存在,如果存在的话就说明设备树基本修改成功(具体还要驱动验证),结果如图所示:
LED灯驱动程序编写
第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_1003这个GPIO为输出,并且默认高电平,这样默认就会关闭LED灯。
运行测试
驱动加载成功以后会在终端中输出一些信息,如图所示: