目前很多对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 过程中,系统会自动处理相应的引脚配置。
配置步骤:
-
定义控制器(一般在 .dtsi 文件中): 定义 SoC 内部的 pinctrl 控制器节点及其包含的功能分组。
-
在设备节点中引用(在 .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 信号
-
系统初始化阶段 :pinctrl 根据设备树中各个设备节点引用的
pinctrl-0,提前将引脚配置好(例如将某个引脚设为 UART 模式,另一个设为 GPIO 模式)。 -
GPIO 驱动请求引脚时 :如果一个引脚当前处于非 GPIO 功能(例如 UART 模式),但是某个 GPIO 驱动通过
gpiod_get()请求它,pinctrl 子系统会自动将那个引脚切换为 GPIO 功能(前提是 pinctrl 驱动实现了gpio_request_enable回调)。 -
运行时动态切换:驱动可以主动调用 pinctrl API 改变引脚功能,再使用 GPIO API 操作;但通常不推荐这样做,容易引起混乱。
调试流程:
在实际开发中,如果遇到引脚不工作的情况,请遵循以下排查顺序:
-
检查设备树: 使用
dtc -I fs /sys/firmware/devicetree/base查看系统最终编译的设备树,确认引脚配置是否冲突。 -
Debugfs 查看: * 查看引脚复用状态:
cat /sys/kernel/debug/pinctrl/pinctrl-handles- 查看 GPIO 状态:
cat /sys/kernel/debug/gpio
- 查看 GPIO 状态:
-
工具集: 使用
libgpiod工具包(如gpiodetect,gpioinfo),在用户空间直接测试引脚电平,验证是否是硬件或逻辑问题。