4. GPIO驱动与调试:从框架到实战
GPIO,说白了就是芯片的“手脚”。你要让灯亮、让电机转、让传感器报数,最终都得落到这几个引脚上。RK3568 的 GPIO 子系统不算复杂,但坑不少。我这些年调过的板子,至少有一半的硬件问题都是通过 GPIO 先暴露出来的。这一章,咱们就把 GPIO 的底裤扒干净。
4.1 GPIO子系统框架:三层的“传话筒”
Linux 的 GPIO 子系统分三层,你想想看:
- 最底层:硬件寄存器操作,由 pinctrl 驱动和 GPIO 控制器驱动完成。
- 中间层:gpiolib,提供统一的 API 接口,屏蔽硬件差异。
- 最上层:用户空间或内核其他驱动,通过 sysfs、libgpiod 或标准 API 来用 GPIO。
我在项目中遇到过一位同事,直接在应用层用 devmem 去读写 GPIO 寄存器。嗯,当时我就告诉他:“你这样搞,pinctrl 的复用配置根本没生效,引脚功能是乱的。” 所以,理解这三层关系,是定位问题的第一步。
4.2 引脚控制 (pinctrl):别让引脚“精神分裂”
pinctrl 干的事很简单:管好每个引脚该干什么。是当 GPIO 用,还是当 I2C 的 SDA 用,还是当 UART 的 TX 用?都由它说了算。
RK3568 的 pinctrl 在设备树里配置,典型写法如下:
&pinctrl {
led_pins: led-pins {
rockchip,pins = <0 RK_PA5 RK_FUNC_GPIO &pcfg_pull_none>;
};
};
这里 RK_PA5 就是 GPIO0_A5。RK_FUNC_GPIO 表示把它配成普通 GPIO 功能。pcfg_pull_none 是不上下拉。
4.3 GPIO操作API:别再用过时的老接口
内核里操作 GPIO,现在推荐用 descriptor-based 的接口。老式的 gpio_request() 和 gpio_set_value() 虽然还能用,但新驱动就别用了。
我习惯的写法是这样的:
#include <linux/gpio/consumer.h>
struct gpio_desc *led;
// 从设备树获取 GPIO
led = devm_gpiod_get(&pdev->dev, "led", GPIOD_OUT_LOW);
if (IS_ERR(led)) {
dev_err(&pdev->dev, "failed to get led gpio\n");
return PTR_ERR(led);
}
// 输出高电平
gpiod_set_value(led, 1);
// 读取电平
int val = gpiod_get_value(led);
你看,用 gpiod_ 系列函数,连 GPIO 号都不用记,直接通过设备树里的名字来引用。代码可读性高,也容易移植。
| 老接口 (deprecated) | 新接口 (推荐) | 说明 |
|---|---|---|
| gpio_request() | devm_gpiod_get() | 自动资源管理,不用手动释放 |
| gpio_set_value() | gpiod_set_value() | 支持 active low 自动反转 |
| gpio_get_value() | gpiod_get_value() | 返回实际电平,考虑极性 |
| gpio_direction_output() | GPIOD_OUT_HIGH/LOW | 在获取时直接指定方向 |
/sys/kernel/debug/gpio 查看所有 GPIO 的当前状态。我每次遇到 GPIO 不干活,第一件事就是 cat 这个文件,看看引脚方向对不对、电平对不对。
4.4 常见GPIO问题定位:三板斧
GPIO 出问题,无非三种情况:没反应、电平不对、功能错乱。我的定位思路是这样的:
- 查设备树:pinctrl 配对了没?GPIO 的 phandle 引对了没?我曾经见过有人把 GPIO0_A5 写成了 GPIO0_B5,差一个 bit,完全不是同一个引脚。
- 查驱动:gpiod_get 成功了没?返回值是不是 IS_ERR?很多驱动没做错误检查,直接往下跑,结果操作的是个空指针。
- 查硬件:用示波器或万用表量一下引脚电平。软件说输出高了,但硬件量出来是低,那多半是外部上拉/下拉电阻在打架。
嗯,说到这我想起一个案例。有次客户说他们的按键检测不准,按下去有时候没反应。我查了一圈,发现是 GPIO 内部上拉没使能,按键松开时引脚电平不确定。在 pinctrl 里加上 &pcfg_pull_up 就解决了。
GPIO 调试,说白了就是“软件配功能,硬件量电平”。把这两头都卡住,问题就跑不掉。