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-names 和 pinctrl-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),在用户空间直接测试引脚电平,验证是否是硬件或逻辑问题。

相关推荐
不悔哥3 天前
开源OV-Watch:怎么做一个智能手表
单片机·开源·嵌入式
未济3 天前
linux 配置环境变量
linux
傲世仙尊3 天前
目录即文件-Ext文件系统收尾篇
linux·c语言
XiHongShi20163 天前
STM32F407 RTC定时器例程,建议保存
stm32·单片机·学习
_艾伦 耶格尔.3 天前
进程间通信
linux
深圳老胡3 天前
STM32F407 控制 L6470 步进电机驱动 —— 控制过程简介
笔记·stm32·单片机·嵌入式硬件·代码规范
Liuqy-053 天前
Linux IO编程——静态库、动态库
linux
女神下凡3 天前
嵌入式设计的各种存储芯片的硬件/软件设计规范,非常全面。
arm开发·单片机·嵌入式硬件
彧azz3 天前
Linux 环境下 Redis 学习总结:数据类型、持久化、锁、事务、主从与缓存问题
linux·redis·笔记·学习·面试
-梅3 天前
linux(8) 软硬链接
linux·运维·服务器