type
date
slug
category
icon
password
前期,通过引入设备数,我们在驱动代码中使用设备树接口,即使设备信息修改了,也可以灵活获取设备信息,极大提高了驱动的复用能力。但我们在前面演示中,还是将寄存器操作具体细节体现在驱动中,比如寄存器置位操作。
下面介绍的pinctrl子系统和GPIO子系统,为驱动提供更通用的操作方法,不涉及具体寄存器操作(对于GPIO外设,具备这样的抽象条件)。
一、pinctrl子系统
- 芯片引脚往往可以做为多个片上外设的功能引脚,编程过程中,首先需要设置引脚复用功能,并且设置引脚的PAD属性。如下面I2C的SCL和SDA的功能引脚不单单只可以使用在I2C上,也可以作为多个外设的功能引脚,如普通的GPIO引脚,串口的接收发送引脚等。

- 若在驱动中配置复用功能,一是增加工作量,二是编写的驱动移植性和重复性差。
- 更糟糕的是缺乏对引脚的统一管理,容易出现引脚的重复定义。 假设我们在I2C1的驱动中将UART4_RX_DATA引脚和UART4_TX_DATA引脚复用为SCL和SDA, 恰好在编写UART4驱动驱动时没有注意到UART4_RX_DATA引脚和UART4_TX_DATA引脚已经被使用, 在驱动中又将其初始化为UART4_RX和UART4_TX,这样IIC1驱动将不能正常工作,并且这种错误很难被发现。
pinctrl子系统是由芯片厂商来实现的,简单来说用于帮助我们管理芯片引脚并自动完成引脚的初始化, 而我们要做的只是在设备树中按照规定的格式写出想要的配置参数即可。
1.1 pinctrl子系统编写格式以及引脚属性详解
1.1.1 iomuxc 节点介绍
在
arch/arm/boot/dts/imx6ull.dtsi
下找到 iomuxc 定义。在iomuxc节点中汇总了所需引脚的配置信息,pinctrl子系统存储使用 iomux节点信息。imx6ull.dtsi
这个文件是芯片厂商官方将芯片的通用的部分单独提出来的一些设备树配置,被100ask_imx6ull-14x14.dts设备树文件包含。
- compatible: 修饰的是与平台驱动做匹配的名字,这里则是与pinctrl子系统的平台驱动做匹配。
- reg: 表示的是引脚配置寄存器的基地址。

在
arch/arm/boot/dts/100ask_imx6ull-14x14.dts
中搜索“&iomuxc”找到设备树中引用“iomuxc”节点的位置如下所示:设备树dts文件下通过引用iomux节点,在下面追加内容。
- “pinctrl-names”标识,指定PIN的状态列表,默认设置为“default”;
- “pinctrl-0 = <&pinctrl_hog_1>”的意思的在默认设置下,将使用pinctrl_hog_1这个设备节点来设置我们的GPIO端口状(pinctrl_hog_1内容是支持热插拔);
- 其余
pinctrl_自定义名字: 自定义名字{}
均是子节点。
1.1.2 pinctrl子节点编写格式
以“
pinctrl_uart1
”节点源码为例介绍 pinctrl
子节点格式规范编写:pinctrl子节点格式规范,格式框架如下:
- 每个芯片厂商的pinctrl子节点的编写格式并不相同
一个引脚可能有多种状态,以串口举例, 在正常使用的时候我们将引脚设置为发送引脚、接收引脚,而在系统进入休眠模式时, 为了节省功耗,我们可以将这两个引脚设置为其他模式,如设置为GPIO功能并设置为高电平等。
- pinctrl-names: 定义引脚状态。
- pinctrl-0: 定义第0种状态需要使用到的引脚配置,可引用其他节点标识。
- pinctrl-1: 定义第1种状态需要使用到的引脚配置。
- pinctrl-2: 定义第2种状态需要使用到的引脚配置
1.1.3 引脚配置信息介绍
引脚配置信息有两部分组成,一个宏定义和一个16进制数,其中:
MX6UL_PAD_UART1_TX_DATA__UART1_DCE_TX
定义在“./arch/arm/boot/dts/imx6ul-pinfunc.h”
- “MX6UL_PAD_UART1_TX_DATA__xxx ”格式宏定义总共有8个,为“UART1_TX_DATA” 引脚的8个复用功能
- 每个宏定义后面有5个参数,名字依次为 mux_reg、conf_reg、input_reg、mux_mode、input_val。
以MX6UL_PAD_UART1_TX_DATA__UART1_DCE_TX为例,宏定义中5个参数参数介绍如下:
- mux_reg 和 mux_mode :mux_reg是引脚复用选择寄存器偏移地址,mux_mode是引脚复用选择寄存器模式选择位的值。 UART1_TX引脚复用选择寄存器 IOMUXC_SW_MUX_CTL_PAD_UART1_TX_DATA 定义如下所示:

mux_reg = 0x0084与IM6ULL用户手册偏移地址一致, mux_mode = 0。 设置复用选择寄存器IOMUXC_SW_MUX_CTL_PAD_UART1_TX_DATA[MUX_MODE] = 0,将其复用为UART1_TX功能。
- conf_reg ,引脚(PAD)属性控制寄存器偏移地址。与引脚复用选择寄存器不同, 引脚属性寄存器应当根据实际需要灵活的配置,所以它的值并不包含在宏定义中, 它的值是我们上面所说的“第六个”参数(宏定义展开的第六个参数,即0x1b0b1)。

从上图可以看到conf_reg = 0x0310对应UART1_TX引脚的引脚属性寄存器的偏移地址。而这个寄存器包含很多配置项 (上图中是部分配置项),这些配置项在裸机部分已有详细介绍,忘记的朋友可以回去再看下裸机部分详细解释。
- input_reg 和 input_val ,input_reg暂且称为输入选择寄存器偏移地址。input_val是输入选择寄存器的值。 这个寄存器只有某些用作输入的引脚才有,正如本例所示,UART1_TX用作输出,所以这两个参数都是零。 “输入选择寄存器”理解稍微有点复杂,结合下图介绍如下。

从上图可以看出,如果引脚用作输出,我们我们只需要配置引脚复用选择寄存器和引脚PAD属性设置寄存器。 如果用作输入时还增加了引脚输入选择寄存器,输入选择寄存器的作用也很明显,在多个可选输入中选择一个连接到片上外设。
- 引脚(PAD)属性值
在pinctrl子系统中一条配置信息由一个宏定义和一个参数组成,将宏定义展开就是六个参数。 结合上面分析我们知道这6个参数就是IOMUX相关的三个寄存器偏移地址和寄存器的值(引脚用作输出时实际只有四个有效, 输入选择寄存器偏移地址和它的值全为0);
至于为什么要将pad属性寄存器的值单独列出,前面也说过了,pad属性配置选项非常多, 配置灵活。在 pinctrl 子系统中添加的PAD属性值就是引脚(PAD)属性设置寄存器的值(16进制)。 有关PAD属性设置内容已经在裸机部分GPIO章节详细介绍,忘记的同学可以回去再回顾下,这里便不再赘述了。
1.2 将RGB灯引脚添加到pinctrl子系统
1.2.1 查找RGB灯使用的引脚
1.2.2 找到引脚配置宏定义
1.2.3 设置引脚属性
1.2.4 在iomuxc节点中添加pinctrl子节点
二、GPIO子系统
在没有使用GPIO子系统之前,如果我们想点亮一个LED,首先要获得 LED 相关的配置寄存器,再手动地读、改、写这些配置寄存器实现 控制LED的目的。有了GPIO子系统之后这部分工作由GPIO子系统帮我们完成,我们只需要调用GPIO子系统提供的API函数即可完成GPIO的 控制动作。
在imx6ull.dtsi文件中的GPIO子节点记录着GPIO控制器的寄存器地址,下面我们以GPIO4为例介绍GPIO子节点相关内容:
- compatible :与GPIO子系统的平台驱动做匹配。
- reg :GPIO寄存器的基地址,GPIO4的寄存器组是的映射地址为0x20a8000-0x20ABFFF
- interrupts :描述中断相关的信息
- clocks :初始化GPIO外设时钟信息
- gpio-controller :表示gpio4是一个GPIO控制器
- #gpio-cells :表示有多少个cells来描述GPIO引脚
- interrupt-controller :表示gpio4也是个中断控制器
- #interrupt-cells :表示用多少个cells来描述一个中断
- gpio-ranges :将gpio编号转换成pin引脚,<&iomuxc 0 94 17>,表示将gpio4的第0个引脚引脚映射为94, 17表示的是引脚的个数。
gpio4这个节点对整个gpio4进行了描述。使用GPIO子系统时需要往设备树中添加设备节点,在驱动程序中使用GPIO子系统提供的API 实现控制GPIO的效果。
2.1 在设备树中添加RGB灯的设备树节点
使用了GPIO子系统的设备树节点样例:
- 第6行,设置“compatible”属性值,与led的平台驱动做匹配;
- 第7行,指定RGB灯的引脚pinctrl信息,上一小节我们定义了pinctrl节点,并且标签设置为“pinctrl_rgb_led”, 在这里我们引用了这个pinctrl信息;
- 第8-10行,指定引脚使用的哪个GPIO,编写格式如下所示0。
- 标号①,设置引脚名字,如果使用GPIO子系统提供的API操作GPIO,在驱动程序中会用到这个名字,名字是自定义的。
- 标号②,指定GPIO组。
- 标号③,指定GPIO编号。
- 编号④,这是一个宏定义,指定有效电平,低电平有效选择“GPIO_ACTIVE_LOW”,高电平有效选择“GPIO_ACTIVE_HIGH”。

2.2 GPIO子系统常用API函数讲解
介绍GPIO子系统常用的几个API函数, 然后就可以使用GPIO子系统编写RGB驱动了。
2.2.1 获取GPIO编号函数of_get_named_gpio
GPIO子系统大多数API函数会用到GPIO编号。GPIO编号可以通过of_get_named_gpio函数从设备树中获取。
- np: 指定设备节点。
- propname: GPIO属性名,与设备树中定义的属性名对应。
- index: 引脚索引值,在设备树中一条引脚属性可以包含多个引脚,该参数用于指定获取那个引脚。
返回值:
- 成功: 获取的GPIO编号(这里的GPIO编号是根据引脚属性生成的一个非负整数),
- 失败: 返回负数。
2.2.2 GPIO申请/释放函数gpio_request
参数:
- gpio: 要申请的GPIO编号,该值是函数of_get_named_gpio的返回值。
- label: 引脚名字,相当于为申请得到的引脚取了个别名。
返回值:
- 成功: 返回0,
- 失败: 返回负数。
2.2.3 GPIO输出设置函数gpio_direction_output
用于将引脚设置为输出模式。
函数参数:
- gpio: 要设置的GPIO的编号。
- value: 输出值,1,表示高电平。0表示低电平。
返回值:
- 成功: 返回0
- 失败: 返回负数。
2.2.4 GPIO输入设置函数gpio_direction_input
用于将引脚设置为输入模式。
函数参数:
- gpio: 要设置的GPIO的编号。
返回值:
- 成功: 返回0
- 失败: 返回负数。
2.2.5 获取GPIO引脚值函数gpio_get_value
函数参数:
- gpio: 要获取的GPIO的编号。
返回值:
- 成功: 获取得到的引脚状态
- 失败: 返回负数
2.2.6 设置GPIO输出值gpio_set_value
该函数只用于那些设置为输出模式的GPIO。
函数参数
- gpio: 设置的GPIO的编号。
- value: 设置的输出值,为1输出高电平,为0输出低电平。
返回值:
- 成功: 返回0
- 失败: 返回负数
三、上机实验
3.1 基于GPIO系统驱动编写
第1步 定义、注册一个 platform_driver;
第2步 在它的 probe 函数里:
- 根据 platform_device 的设备树信息确定 GPIO:gpiod_get。
- 定义、注册一个 file_operations 结构体
- 在 file_operarions 中使用 GPIO 子系统的函数操作 GPIO:gpiod_direction_output、gpiod_set_value
3.2 设备树修改
3.2.1 确定引脚并生成设备树节点
NXP 公司对于 IMX6ULL 芯片,有设备树生成工具(同时提供Win和linux版本)。
安装软件后打开IMX6ULL 的配置文件“MCIMX6Y2xxx08.mex”,就可以在 GUI 界面中选择引脚,配置它的功能,这就可以自动生成 Pinctrl 的子节点信息。
在设备树工具中,操作 GPIO5_3如下图

把自动生成的设备树信息,放到内核源码
arch/arm/boot/dts/100ask_imx6ull-14x14.dts
中。3.2.2 Pinctrl 信息
3.2.3 设备节点信息(放在根节点下)
三、总结
pinctrl系统主要负责统筹管理硬件资源(定义和设置),GPIO子系统则为寄存器读、改、写提供API接口。
在设备树中将RGB灯使用的引脚添加到pinctrl子系统,然后又在设备树中添加了rgb_led设备树节点。
- Author:felixfixit
- URL:http://www.felixmicrospace.top/article/linux_drv_goio_pinctrl
- Copyright:All articles in this blog, except for special statements, adopt BY-NC-SA agreement. Please indicate the source!