设备树GPIO控制器:从原理到实战
大家好,我是老李。做嵌入式Linux驱动开发这么多年,GPIO这块儿可以说是打交道最多的外设之一了。今天咱们就来聊聊设备树里GPIO控制器的配置,说白了就是怎么告诉内核:我这颗芯片的GPIO长什么样、怎么用。
我记得刚入行那会儿,看设备树文件就像看天书。什么gpio-controller、#gpio-cells,完全搞不懂是干嘛的。后来踩了不少坑,才慢慢摸清楚这里面的门道。今天我把这些经验整理出来,希望能帮你少走弯路。
GPIO控制器属性:两个核心标志
在设备树里,一个GPIO控制器节点必须有两个关键属性。少了任何一个,内核都没法正常工作。
gpio-controller:声明身份
这个属性是个空属性,没有值。它的作用就一个:告诉内核“我是一个GPIO控制器”。
gpio0: gpio@ff310000 {
compatible = "rockchip,gpio-bank";
reg = <0x0 0xff310000 0x0 0x1000>;
gpio-controller; // 我就是GPIO控制器
#gpio-cells = <2>;
...
};
嗯,这里要注意:如果你忘了加gpio-controller,其他设备想引用这个GPIO的时候,内核会直接报错。我在项目里就犯过这个低级错误,排查了半天才发现是少写了这个属性。
#gpio-cells:定义GPIO描述格式
这个属性告诉内核:引用一个GPIO需要几个参数。最常见的是2个,也有用1个或3个的。
#gpio-cells = <2> 是最常见的配置,两个参数分别是:
- 第一个参数:GPIO编号(相对于控制器的偏移)
- 第二个参数:GPIO标志(高低电平有效、上拉下拉等)
为什么会这样设计?你想想看,一个GPIO控制器可能有32个引脚,每个引脚又有不同的电气特性。用两个cell来描述,既简洁又够用。
GPIO号与引脚映射:从软件到硬件的桥梁
这部分是很多人的困惑点。设备树里的GPIO号,跟芯片数据手册上的引脚号,到底怎么对应?
说白了,设备树里的GPIO号是相对编号,不是绝对编号。什么意思呢?
- 每个GPIO控制器内部,引脚从0开始编号
- 比如控制器有32个引脚,编号就是0到31
- 具体哪个编号对应芯片的哪个物理引脚,由芯片厂商的BSP代码决定
我的经验:拿到一个新板子,我习惯先看芯片数据手册的GPIO章节,找到引脚复用表。然后对照设备树里的GPIO控制器节点,确认每个bank的起始引脚号。这一步做错了,后面全白搭。
实战:一个完整的GPIO控制器节点
咱们来看一个实际项目中用到的配置。这是某款ARM芯片的GPIO控制器,有4个bank,每个bank管理32个引脚。
pinctrl: pinctrl@ff770000 {
compatible = "mychip,pinctrl";
reg = <0x0 0xff770000 0x0 0x10000>;
gpio0: gpio@ff770000 {
compatible = "mychip,gpio-bank";
reg = <0x0 0xff770000 0x0 0x1000>;
gpio-controller;
#gpio-cells = <2>;
gpio-ranges = <&pinctrl 0 0 32>;
interrupts = <GIC_SPI 64 IRQ_TYPE_LEVEL_HIGH>;
};
gpio1: gpio@ff771000 {
compatible = "mychip,gpio-bank";
reg = <0x0 0xff771000 0x0 0x1000>;
gpio-controller;
#gpio-cells = <2>;
gpio-ranges = <&pinctrl 32 32 32>;
interrupts = <GIC_SPI 65 IRQ_TYPE_LEVEL_HIGH>;
};
};
这里有个gpio-ranges属性,它建立了GPIO号和引脚复用器之间的映射关系。第一个参数是引脚控制器,第二个是GPIO控制器的起始编号,第三个是引脚控制器的起始编号,第四个是引脚数量。
我曾经踩过的坑:gpio-ranges这个属性很容易写错。有一次我把第二个和第三个参数搞反了,结果GPIO0的引脚全部映射到了错误的物理引脚上。LED灯怎么点都不亮,查了两天才发现是这里的问题。
GPIO引用方式:消费者视角
其他设备怎么使用这些GPIO呢?看个例子:
leds {
compatible = "gpio-leds";
led-red {
label = "red-led";
gpios = <&gpio0 15 GPIO_ACTIVE_LOW>;
linux,default-trigger = "heartbeat";
};
led-green {
label = "green-led";
gpios = <&gpio1 7 GPIO_ACTIVE_HIGH>;
default-state = "off";
};
};
注意看gpios属性的写法:
&gpio0:引用gpio0这个控制器15:控制器内部的引脚编号GPIO_ACTIVE_LOW:低电平有效
这里GPIO_ACTIVE_LOW是个宏定义,展开后就是1。如果#gpio-cells是2,那第二个参数就是标志位。常用的标志位有:
| 宏定义 | 值 | 说明 |
|---|---|---|
| GPIO_ACTIVE_HIGH | 0 | 高电平有效 |
| GPIO_ACTIVE_LOW | 1 | 低电平有效 |
| GPIO_PULL_UP | 2 | 内部上拉 |
| GPIO_PULL_DOWN | 4 | 内部下拉 |
核心逻辑:一张图看懂
说了这么多,咱们用一张图来总结GPIO控制器的核心逻辑。我画了个流程图,把整个映射关系理清楚了。
这张图把整个流程串起来了。从设备树节点开始,经过属性解析、GPIO号映射,最后到消费者设备使用和内核处理,最终控制物理引脚。每一步都环环相扣,缺一不可。
几个实用技巧
最后分享几个我在项目中积累的小技巧:
- 调试时先看dmesg:内核启动时会打印GPIO控制器的注册信息,看看有没有报错
- 用/sys/kernel/debug/gpio查看状态:这个虚拟文件系统能显示所有GPIO的当前状态,调试利器
- 注意引脚复用冲突:同一个物理引脚可能被多个功能模块使用,设备树里要确保不冲突
- #gpio-cells的值要跟驱动匹配:如果驱动期望3个cell,你只给了2个,驱动解析时会出错
我个人的习惯:每次写完设备树,我都会用dtc工具反编译一下,看看编译后的二进制文件是不是我想要的。有时候语法错误不会报错,但实际效果完全不对。
好了,关于GPIO控制器的核心属性就讲到这里。这些内容看起来简单,但实际项目中很容易出问题。记住:gpio-controller声明身份,#gpio-cells定义格式,gpio-ranges建立映射。把这三点搞清楚了,GPIO配置这块儿就稳了。