Linux内核与驱动:pinctl子系统和GPIO子系统

目前很多对pinctl子系统和GPIO子系统的讲解一般都是讲他们关系密切,甚至将他们一起讲,所以容易将这两个子系统划分为一类,但是我个人认为这样理解并不方便,我更趋向于把GPIO子系统和I2C子系统,SPI子系统等功能性子系统划分为同一类别,无论是GPIO还是I2C或SPI等所描述的都是这个pin口实现的功能与通信的方式,而pinctl子系统是服务于他们的,是选择我当前使用的pin口的功能到底是GPIO还是I2C还是SPI。

所以在讲述每个子系统之前,我们先区分这两个子系统:

**pinctl子系统:**用于引脚复用(功能选择)与引脚电气特性配置,将引脚配置为 UART、I2C、SPI、PWM 或 GPIO 模式;设置上下拉、驱动能力、开漏等。

**GPIO子系统:**进行GPIO 信号的方向控制、电平读写、中断处理等。

1.pinctl子系统

1.1 核心作用

pinctrl 子系统主要解决以下两个核心问题:

  • 引脚复用 (Pin Muxing): 将物理引脚分配给特定的外设功能。

  • 引脚配置 (Pin Configuration): 设置引脚的电气属性,如:

    • 上拉/下拉电阻(Pull-up/Pull-down)

    • 驱动能力(Drive Strength)

    • 施密特触发器(Schmitt Trigger)

    • 高阻态/浮空状态

pinctl子系统中拥有引脚控制器(Pin Controller),它内部有一组寄存器,每个引脚对应若干位(mux bits),用来选择功能。

1.2设备树配置

现代 Linux 内核中,pinctrl 通常通过设备树进行配置。驱动程序在 Probe 过程中,系统会自动处理相应的引脚配置。

配置步骤:
  1. 定义控制器(一般在 .dtsi 文件中): 定义 SoC 内部的 pinctrl 控制器节点及其包含的功能分组。

  2. 在设备节点中引用(在 .dts 文件中): 驱动程序节点使用 pinctrl-namespinctrl-0 属性来引用这些定义。

示例:

如在rk3506.dtsi文件中定义:

bash 复制代码
&pinctrl {
    uart0_pins: uart0-pins {
        pins = "GPIO1_A0", "GPIO1_A1";
        function = "uart0";
        bias-disable;
    };
};

在xxx(板商)-RK3506-xxx.dts文件中添加某设备节点:

bash 复制代码
&uart0 {
    pinctrl-names = "default";
    pinctrl-0 = <&uart0_pins>;
    status = "okay";
};

含义为此设备节点的pin口使用"uart0_pins"的默认配置。

1.3 pinctrl 的状态机制

pinctrl 支持 状态(state)概念,例如:

  • default:正常工作状态

  • sleep:系统休眠时的引脚状态(可节省功耗)

  • idle:空闲状态

1.4驱动编写常用API

如果编写驱动程序需要动态调整引脚,会使用 pinctrl_get() 等接口,但在大多数情况下,通过设备树绑定自动完成:

  • devm_pinctrl_get(): 获取指定设备的 pinctrl 处理句柄。

  • pinctrl_lookup_state(): 查找指定的状态(如 "default", "sleep")。

  • pinctrl_select_state(): 激活指定的引脚状态。

2.GPIO子系统

Linux GPIO (General Purpose Input/Output) 子系统是内核中用于控制通用输入/输出引脚的框架。它允许驱动程序以统一的方式操作引脚,而无需直接接触底层的寄存器。

在现代 Linux 中,GPIO 子系统不仅处理引脚的高低电平读写,还负责引脚方向配置、中断触发、甚至引脚复用管理(与 pinctrl 配合)。

区分于引脚控制器 :当引脚被选为 GPIO 功能后,它的电平由另一个硬件模块 ------ GPIO 控制器 来管理。GPIO 控制器有自己的寄存器(方向、数据输入、数据输出、中断使能等)。

2.1设备树配置

与pinctl子系统一样,GPIO子系统的设备树配置依然分为两部分,GPIO控制器节点和GPIO设备节点:

GPIO 控制器节点:标明自己是一个 GPIO 控制器,以及引用时需要几个参数。

bash 复制代码
gpio0: gpio@fdd60000 {
    compatible = "snps,dw-apb-gpio";
    reg = <0xfdd60000 0x1000>;
    gpio-controller;
    #gpio-cells = <2>;     // 第一个参数:引脚偏移,第二个:标志(高/低有效等)
    interrupt-controller;
    #interrupt-cells = <2>;
};

使用 GPIO 的设备节点 :通过 [name]-gpios 属性引用。

bash 复制代码
leds {
    compatible = "gpio-leds";
    led0 {
        label = "heartbeat";
        gpios = <&gpio0 5 GPIO_ACTIVE_LOW>;
        linux,default-trigger = "heartbeat";
    };
};

2.2驱动编写常用API

常见操作步骤:
  • 获取引脚: gpiod_get()devm_gpiod_get()

  • 配置方向: gpiod_direction_input()gpiod_direction_output()

  • 读写数据: gpiod_get_value()gpiod_set_value()

  • 中断处理: gpiod_to_irq()

3.Pinctl子系统和GPIO子系统的协作与区别

协作流程:从引脚到 GPIO 信号

  1. 系统初始化阶段 :pinctrl 根据设备树中各个设备节点引用的 pinctrl-0,提前将引脚配置好(例如将某个引脚设为 UART 模式,另一个设为 GPIO 模式)。

  2. GPIO 驱动请求引脚时 :如果一个引脚当前处于非 GPIO 功能(例如 UART 模式),但是某个 GPIO 驱动通过 gpiod_get() 请求它,pinctrl 子系统会自动将那个引脚切换为 GPIO 功能(前提是 pinctrl 驱动实现了 gpio_request_enable 回调)。

  3. 运行时动态切换:驱动可以主动调用 pinctrl API 改变引脚功能,再使用 GPIO API 操作;但通常不推荐这样做,容易引起混乱。

调试流程:

在实际开发中,如果遇到引脚不工作的情况,请遵循以下排查顺序:

  1. 检查设备树: 使用 dtc -I fs /sys/firmware/devicetree/base 查看系统最终编译的设备树,确认引脚配置是否冲突。

  2. Debugfs 查看: * 查看引脚复用状态:cat /sys/kernel/debug/pinctrl/pinctrl-handles

    • 查看 GPIO 状态:cat /sys/kernel/debug/gpio
  3. 工具集: 使用 libgpiod 工具包(如 gpiodetect, gpioinfo),在用户空间直接测试引脚电平,验证是否是硬件或逻辑问题。

相关推荐
电气_空空1 小时前
基于 LabVIEW 的单片机串口通信设计
单片机·嵌入式硬件·毕业设计·labview
无足鸟ICT2 小时前
【RHCA+】查找与替换
linux
RisunJan2 小时前
Linux命令-pmap(进程内存映射报告工具)
linux·服务器·网络
郝学胜-神的一滴2 小时前
CMake 017:彩色日志输出实战
linux·c语言·开发语言·c++·软件工程·软件构建·cmake
暗影天帝3 小时前
BPI-R3 Mini NAND 刷机教程(Webfailsafe 方案)
linux
Full Stack Developme3 小时前
Linux rm-rf 执行后,硬盘空间变化
linux·运维·服务器
caimouse3 小时前
Reactos 第 9 章 设备驱动 — 9.10 磁盘的Miniport驱动模块
windows·嵌入式硬件
xiangw@GZ3 小时前
WiFi系统BCC与LDPC纠错编码技术性能对比
单片机·嵌入式硬件
插件开发3 小时前
vs2015 cuda c++ cdpSimplePrint范例,递归功能实现演示
linux·c++·算法