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

相关推荐
Rocktech_ruixun2 分钟前
机器人数据采集能力取决于什么?瑞迅科技RK3588/3576核心板方案深度解析
人工智能·嵌入式硬件·机器人
水果里面有苹果5 分钟前
28-DRV8825PWPR步进电机驱动器
单片机
恒锐丰科技韩生32 分钟前
率能 SS6625E|3.0~20V 单通道 H 桥直流电机驱动 SOP8 电子锁玩具机器人分享
嵌入式硬件·硬件工程
小马同学-33 分钟前
高可用-Keepalived全解析
linux·运维
zlinear数据采集卡37 分钟前
D223 ADC数据处理流水线:从原始采样值到工程单位的完整转换链
开发语言·arm开发·嵌入式硬件·fpga开发·开源·c#
Forever Nore1 小时前
LeetCode 13 罗马数字转整数 - 按规则处理
linux·服务器·leetcode
我不会插花弄玉1 小时前
2.Linux基础指令下【由浅入深-Linux】
linux·服务器
Dr.kangder2 小时前
嵌入式面试总结(十八)——JTAG调试
嵌入式硬件·面试·职场和发展·架构·嵌入式
雯宝2 小时前
3. 驱动框架(块设备)
linux
恒锐丰科技韩生3 小时前
率能 SS6226|2.4~7.2V 低压双通道双 H 桥电机驱动 SOP16 玩具小家电专用芯片
嵌入式硬件·硬件工程