Lazy loaded image
🥳嵌入式Linux开发
Linux驱动基础07-GPIO和Pinctrl
Words 5560Read Time 14 min
2024-9-11
2025-12-25
type
date
slug
category
icon
password
前期文章通过引入设备树,并在驱动代码中使用设备树接口,因此即使设备信息修改,也可灵活获取设备信息,极大提高了驱动的复用能力。但在前面演示中,还是将寄存器具体操作细节编写在驱动中,比如寄存器置位操作。下面介绍的pinctrl子系统和GPIO子系统,为驱动提供更通用的操作方法,不涉及具体寄存器操作(对于GPIO外设,具备这样的抽象条件)。

一、pinctrl子系统

  1. 芯片引脚往往可以作为多个片上外设的功能引脚。编程时,需要设置引脚复用功能和PAD属性。例如,I2C的SCL和SDA引脚不仅可以用于I2C,还可以作为GPIO引脚、串口收发引脚等。
    1. notion image
  1. 若在驱动中配置复用功能,一是增加工作量,二是驱动的移植性和重用性差。
  1. 更糟糕的是缺乏对引脚的统一管理,容易出现引脚重复定义。假设在I2C1驱动中将UART4_RX_DATA和UART4_TX_DATA引脚复用为SCL和SDA,而编写UART4驱动时没有注意到这两个引脚已被使用,又将其初始化为UART4_RX和UART4_TX,这会导致I2C1驱动无法正常工作,且这种错误很难发现。
pinctrl子系统由芯片厂商实现,用于帮助我们管理芯片引脚自动完成引脚初始化。我们只需在设备树中按规定格式写出配置参数即可。

1.1 pinctrl子系统编写格式以及引脚属性详解

1.1.1 iomuxc 节点介绍

arch/arm/boot/dts/imx6ull.dtsi 文件中找到 iomuxc 节点定义。该节点汇总了所需引脚的配置信息,供 pinctrl 子系统存储和使用。
imx6ull.dtsi 这个文件是芯片厂商官方将芯片的通用的部分单独提出来的一些设备树配置,被100ask_imx6ull-14x14.dts设备树文件包含。
pinctrl引脚定义放在iomux节点下,主要有以下几个原因:
  • 硬件对应关系:iomuxc节点对应芯片的IOMUX控制器硬件模块,该模块负责管理所有引脚的复用和配置。iomux节点的reg属性指向引脚配置寄存器的基地址。
  • 统一管理:iomuxc节点汇总了所需引脚的配置信息,供pinctrl子系统存储和使用。将所有引脚配置集中在iomuxc节点下,便于统一管理和避免引脚冲突。
  • 设备树结构规范:通过"&iomuxc"引用的方式,在设备树中扩展引脚配置。这种层次化的组织方式符合设备树的设计规范,让引脚配置信息与对应的硬件控制器关联。
  • 平台驱动匹配:iomuxc节点通过compatible属性与pinctrl子系统的平台驱动做匹配,这样pinctrl驱动就能正确识别和管理这些引脚配置。
这种设计使得引脚配置既有清晰的硬件归属,又便于驱动程序统一调用和管理。
  • compatible: 修饰的是与平台驱动做匹配的名字,这里则是与pinctrl子系统的平台驱动做匹配。
  • reg: 表示的是引脚配置寄存器的基地址。
    • notion image
 
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”
notion image
  • “MX6UL_PAD_UART1_TX_DATA__xxx ”格式宏定义总共有8个,为“UART1_TX_DATA” 引脚的8个复用功能
  • 每个宏定义后面有5个参数,名字依次为 mux_regconf_reginput_regmux_modeinput_val
     
    以MX6UL_PAD_UART1_TX_DATA__UART1_DCE_TX为例,宏定义中5个参数参数介绍如下:
    1. mux_reg 和 mux_mode :mux_reg是引脚复用选择寄存器偏移地址,mux_mode是引脚复用选择寄存器模式选择位的值。 UART1_TX引脚复用选择寄存器 IOMUXC_SW_MUX_CTL_PAD_UART1_TX_DATA 定义如下所示:
      1. notion image
        mux_reg = 0x0084与IM6ULL用户手册偏移地址一致, mux_mode = 0。 设置复用选择寄存器IOMUXC_SW_MUX_CTL_PAD_UART1_TX_DATA[MUX_MODE] = 0,将其复用为UART1_TX功能。
    1. conf_reg ,引脚(PAD)属性控制寄存器偏移地址。与引脚复用选择寄存器不同, 引脚属性寄存器应当根据实际需要灵活的配置,所以它的值并不包含在宏定义中, 它的值是我们上面所说的“第六个”参数(宏定义展开的第六个参数,即0x1b0b1)。
      1. notion image
        从上图可以看到conf_reg = 0x0310对应UART1_TX引脚的引脚属性寄存器的偏移地址。而这个寄存器包含很多配置项 (上图中是部分配置项),这些配置项在裸机部分已有详细介绍,忘记的朋友可以回去再看下裸机部分详细解释。
    1. input_reg 和 input_val ,input_reg暂且称为输入选择寄存器偏移地址。input_val是输入选择寄存器的值。 这个寄存器只有某些用作输入的引脚才有,正如本例所示,UART1_TX用作输出,所以这两个参数都是零。 “输入选择寄存器”理解稍微有点复杂,结合下图介绍如下。
      1. notion image
        从上图可以看出,如果引脚用作输出,我们我们只需要配置引脚复用选择寄存器和引脚PAD属性设置寄存器。 如果用作输入时还增加了引脚输入选择寄存器,输入选择寄存器的作用也很明显,在多个可选输入中选择一个连接到片上外设。
    1. 引脚(PAD)属性值
      1. 在pinctrl子系统中一条配置信息由一个宏定义和一个参数组成,将宏定义展开就是六个参数。 结合上面分析我们知道这6个参数就是IOMUX相关的三个寄存器偏移地址和寄存器的值(引脚用作输出时实际只有四个有效, 输入选择寄存器偏移地址和它的值全为0);
        至于为什么要将pad属性寄存器的值单独列出,前面也说过了,pad属性配置选项非常多, 配置灵活。在 pinctrl 子系统中添加的PAD属性值就是引脚(PAD)属性设置寄存器的值(16进制)。下面对PAD属性值进行补充说明。
        PAD属性值详解
        PAD属性寄存器控制引脚的电气特性,一个16位的配置值可以设置以下属性:
        • HYS (bit 16): 迟滞功能,0-禁用,1-使能
        • PUS (bit 15-14): 上拉/下拉配置
          • 00: 100K欧姆下拉
          • 01: 47K欧姆上拉
          • 10: 100K欧姆上拉
          • 11: 22K欧姆上拉
        • PUE (bit 13): 上拉/下拉使能,0-禁用,1-使能
        • PKE (bit 12): 保持器或上拉/下拉使能,0-禁用,1-使能
        • ODE (bit 11): 开漏输出,0-禁用,1-使能
        • SPEED (bit 7-6): 速度配置
          • 00: 低速(50MHz)
          • 01: 中速(100MHz)
          • 10: 中速(100MHz)
          • 11: 最高速(200MHz)
        • DSE (bit 5-3): 驱动强度
          • 000: 输出关闭
          • 001: R0(260欧姆@3.3V, 150欧姆@1.8V)
          • 010: R0/2
          • 011: R0/3
          • 100: R0/4
          • 101: R0/5
          • 110: R0/6
          • 111: R0/7
        • SRE (bit 0): 压摆率,0-慢速,1-快速
         
        0x17059配置值示例
        以常用的PAD属性值0x17059为例进行说明:
        各bit位含义解析:
        • bit 16 (HYS): 1 - 使能迟滞功能
        • bit 15-14 (PUS): 01 - 47K欧姆上拉
        • bit 13 (PUE): 1 - 使能上拉/下拉
        • bit 12 (PKE): 1 - 使能保持器或上拉/下拉
        • bit 11 (ODE): 0 - 禁用开漏输出
        • bit 7-6 (SPEED): 01 - 中速(100MHz)
        • bit 5-3 (DSE): 011 - R0/3驱动强度
        • bit 0 (SRE): 1 - 快速压摆率
        因此,0x17059配置表示:使能迟滞、47K上拉、中速、中等驱动强度、快速压摆率。这是一个较为通用的GPIO输出配置,适合大多数应用场景。

    1.2 将RGB灯引脚添加到pinctrl子系统

    1.2.1 查找RGB灯使用的引脚

    根据开发板原理图,我们可以找到RGB灯使用的引脚信息:
    • 红灯:GPIO1_IO04
    • 绿灯:GPIO4_IO20
    • 蓝灯:GPIO4_IO19
    这些引脚需要配置为GPIO功能,并设置相应的PAD属性。

    1.2.2 找到引脚配置宏定义

    在"./arch/arm/boot/dts/imx6ul-pinfunc.h"文件中查找对应引脚的宏定义:
    这些宏定义包含了引脚复用和配置所需的寄存器地址信息。

    1.2.3 设置引脚属性

    根据RGB灯的电路特性,我们需要设置合适的PAD属性值。通常RGB灯需要配置为:
    • 上拉/下拉配置
    • 驱动能力设置
    • 速度配置
    常用的PAD属性值为0x10B0或0x000010B0,具体数值需要根据硬件要求调整。

    1.2.4 在iomuxc节点中添加pinctrl子节点

    在设备树的iomuxc节点中添加RGB灯的pinctrl配置:
    这样就完成了RGB灯引脚在pinctrl子系统中的配置,后续可以在设备节点中通过标签"pinctrl_rgb_led"引用这个配置。

    二、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,编写格式如下图所示。
      • notion image
      • 标号①:设置引脚名字。如果使用GPIO子系统提供的API操作GPIO,驱动程序中会用到这个名字。名字可自定义。
      • 标号②:指定GPIO组。
      • 标号③:指定GPIO编号。
      • 标号④:这是一个宏定义,指定有效电平。低电平有效选择"GPIO_ACTIVE_LOW",高电平有效选择"GPIO_ACTIVE_HIGH"。

    2.2 GPIO子系统常用API函数讲解(GPIO描述符接口)

    本节介绍GPIO子系统推荐使用的GPIO描述符(Descriptor)API函数。相比传统的基于整数编号的接口,GPIO描述符接口具有以下优点:
    • 更好的类型安全性,使用结构体指针而非整数
    • 支持设备资源自动管理(devm_系列函数)
    • 自动处理GPIO属性(如active-low配置)
    • API命名更清晰直观
    • 是Linux内核当前推荐的标准接口
    掌握这些API函数后,就可以使用GPIO子系统编写RGB驱动了。

    2.2.1 获取GPIO描述符函数gpiod_get

    从设备树中获取GPIO描述符,这是使用GPIO的第一步。推荐使用带资源管理的devm_版本。
    • dev:设备结构体指针,通常为&pdev->dev。
    • con_id:连接ID,对应设备树中GPIO属性名的前缀(如"led-gpios"中的"led")。
    • flags:GPIO标志,可选值包括:
      • GPIOD_ASIS:不改变GPIO状态
      • GPIOD_IN:配置为输入
      • GPIOD_OUT_LOW:配置为输出并设为低电平
      • GPIOD_OUT_HIGH:配置为输出并设为高电平
    返回值:
    • 成功:返回gpio_desc结构体指针。
    • 失败:返回错误指针,可用IS_ERR()判断。

    2.2.2 GPIO释放函数gpiod_put

    参数:
    • desc:要释放的GPIO描述符指针。
    使用devm_gpiod_get获取的GPIO在设备卸载时会自动释放,这是推荐的做法。

    2.2.3 GPIO输出设置函数gpiod_direction_output

    用于将引脚设置为输出模式并设置初始值。
    函数参数:
    • desc:GPIO描述符指针。
    • value:初始输出值,1表示高电平,0表示低电平(会自动处理active-low属性)。
    返回值:
    • 成功:返回0
    • 失败:返回负数。

    2.2.4 GPIO输入设置函数gpiod_direction_input

    用于将引脚设置为输入模式。
    函数参数:
    • desc:GPIO描述符指针。
    返回值:
    • 成功:返回0
    • 失败:返回负数。

    2.2.5 获取GPIO引脚值函数gpiod_get_value

    函数参数:
    • desc:GPIO描述符指针。
    返回值:
    • 成功:返回引脚状态(0或1,已考虑active-low属性)
    • 失败:返回负数

    2.2.6 设置GPIO输出值gpiod_set_value

    该函数用于设置输出模式GPIO的电平值。
    函数参数:
    • desc:GPIO描述符指针。
    • value:输出值,1表示高电平,0表示低电平(会自动处理active-low属性)。
    注意:此函数无返回值。GPIO描述符接口会自动根据设备树中的GPIO_ACTIVE_LOW/HIGH标志处理电平极性,使代码更简洁可靠。

    三、上机实验

    以下是该Linux驱动程序中各组件的关系和编写顺序的Mermaid流程图:
    编写顺序说明:
    1. 设备树配置
        • 在iomuxc_snvs节点下定义pinctrl_test_led,配置引脚复用和电气特性
        • 在根节点下创建testled设备节点,指定compatible、pinctrl和GPIO信息
    1. Platform驱动框架
        • 定义of_device_id匹配表,与设备树compatible对应
        • 定义platform_driver结构体,包含probe/remove函数
        • 在模块入口函数中注册platform_driver
    1. Probe函数实现
        • 使用gpiod_get从GPIO子系统获取GPIO描述符
        • 注册字符设备获取主设备号
        • 创建设备类和设备节点
    1. 字符设备操作
        • 定义file_operations结构体
        • 在open函数中配置GPIO为输出模式
        • 在write函数中使用gpiod_set_value控制GPIO电平
    关键点:Pinctrl子系统负责引脚复用配置,GPIO子系统提供统一的GPIO操作接口,Platform总线机制实现设备与驱动的匹配,字符设备为用户空间提供访问接口。

    3.1 基于GPIO系统驱动编写

    第1步 定义、注册一个 platform_driver;
    第2步 在它的 probe 函数里:
    1. 根据 platform_device 的设备树信息确定 GPIO:gpiod_get。
      1. 定义、注册一个 file_operations 结构体
        1. 在 file_operarions 中使用 GPIO 子系统的函数操作 GPIO:gpiod_direction_output、gpiod_set_value

          3.2 设备树修改

          3.2.1 确定引脚并生成设备树节点

          NXP公司为IMX6ULL芯片提供了设备树生成工具(支持Windows和Linux版本)。安装软件后,打开IMX6ULL的配置文件"MCIMX6Y2xxx08.mex",即可在GUI界面中选择引脚并配置其功能,工具会自动生成Pinctrl的子节点信息。
          在设备树工具中,操作GPIO5_3如下图:
          notion image
          把自动生成的设备树信息,放到内核源码arch/arm/boot/dts/100ask_imx6ull-14x14.dts 中。

          3.2.2 Pinctrl 信息

          3.2.3 设备节点信息(放在根节点下"/")

          三、总结

          pinctrl子系统主要负责硬件资源的统筹管理(定义和配置),GPIO子系统则提供寄存器读写操作的API接口。
          在设备树中,将RGB灯使用的引脚添加到pinctrl子系统,然后添加rgb_led设备树节点。
          上一篇
          Linux驱动基础06-设备树引入和使用
          下一篇
          Linux驱动基础08-详解GPIO和pinctrl子系统设备树

          Comments
          Loading...