前言:
本文是根据哔哩哔哩网站上"正点原子【第四期】手把手教你学Linux系列课程之 Linux驱动开发篇"视频的学习笔记,该课程配套开发板为正点原子alpha/mini Linux开发板。在这里会记录下正点原子 I.MX6ULL 开发板的配套视频教程所作的实验和学习笔记内容。本文大量引用了正点原子教学视频和链接中的内容。
引用:
正点原子IMX6U仓库 (GuangzhouXingyi) - Gitee.com
正点原子【第四期】手把手教你学 Linux之驱动开发篇_哔哩哔哩_bilibili
《【正点原子】I.MX6U嵌入式Linux驱动开发指南V1.5.2.pdf》
正点原子资料下载中心 --- 正点原子资料下载中心 1.0.0 文档
正点原子imx6ull-mini-Linux驱动之Linux I2C 驱动实验(21)-CSDN博客
uboot移植(4)--在NXP官方uboot适配ALPHA开发板网络_uboot sr8201f-CSDN博客
正文:
本文是 "正点原子【第四期】手把手教你学 Linux之驱动开发篇-1.1 Linux驱动开发与裸机开发的区别"。本节将参考正点原子的视频教程和配套的正点原子开发指南文档进行学习。
0. 概述
上一章我们编写了基于设备树的 LED驱动,但是驱动的本质还是没变,都是配置 LED灯所使用的 GPIO寄存器,驱动开发方式和裸机基本没啥区别。 Linux是一个庞大而完善的系统,尤其是驱动框架,像 GPIO这种最基本的驱动不可能采用"原始"的裸机驱动开发方式,否则就相当于你买了一辆车,结果每天推着车去上班。 Linux内核提供了 pinctrl和 gpio子系统用于GPIO驱动,本章我们就来学习一下如何借助 pinctrl和 gpio子系统来简化 GPIO驱动开发。
1 pinctrl子系统
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。
drivers/pinctrl/
1.2 I.MX6ULL的 pinctrl子系统驱动
1、 PIN配置信息详解
要使用 pinctrl子系统,我们需要在设备树里面设置 PIN的配置信息,毕竟 pinctrl子系统要根据你提供的信息来配置 PIN功能,一般会在设备树里面创建一个节点来描述 PIN的配置信息。打开 imx6ull.dtsi文件,找到一个叫做 iomuxc的节点,如下所示:


iomuxc节点就是 I.MX6ULL的 IOMUXC外设对应的节点,看起来内容很少,没看出什么跟 PIN的配置有关的内容啊,别急!打开 imx6ull-alientek-emmc.dts,找到如下所示内容
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的配置信息如下:
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的宏定义内容如下:
imx6ull.dtsi
imx6ull-pinfunc.h
arch/arm/boot/dts/imx6ul-pinfunc.h


示例代码 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所示:

示例代码 196行的宏定义 MX6UL_PAD_UART1_RTS_B__GPIO1_IO19表示将UART1_RTS_B这个 IO复用为 GPIO1_IO19。此宏定义后面跟着 5个数字,也就是这个宏定义的具体值,如下所示:
bash
0x0090 0x031C 0x0000 0x5 0x0
这 5个值的含义如下所示:
bash
<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所示:

因此可知, 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行的内容如下所示:
bash
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中有如下内容:
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控制器,此函数原型如下:
struct pinctrl_dev *pinctrl_register(struct pinctrl_desc *pctldesc, struct device *dev, void *driver_data)
参数 pctldesc非常重要,因为此参数就是要注册的 PIN控制器, PIN控制器用于配置 SOC的 PI复用功能和电气特性。参数 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_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中这三个结构体下的所有函数就是 I.MX6ULL的 PIN配置函数,我们就此打住,不 再 去分析这些函数了,否则本章就没完没了了,有兴趣的可以去看一下。
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_"。添加完成以后如下所示:

2 、添加" fsl ,pins "属性
设备树是通过属性来保存信息的,因此我们需要添加一个属性,属性名字一定要为" fsl,pins因为对于 I.MX系列 SOC而言, pinctrl驱动程序是通过读取" fsl,pins"属性值来获取 PIN的配置信息,完成以后如下所示:

3、在" fsl,pins"属性中添加 PIN配置信息
最后在" fsl,pins"属性中添加具体的 PIN配置信息,完成以后如下所示:

至此,我们已经在 imx6ull-alientek-emmc.dts文件中添加好了 test设备所使用的 PIN配置信息。