第4章 GPIO驱动移植:从框架到实战
GPIO,说白了就是芯片最基础的“手脚”。你让它高它就高,让它低它就低。但到了Linux内核里,事情就没那么简单了。我刚开始接触RK平台时,也被GPIO子系统绕得有点晕。今天咱们就把这块彻底捋清楚。
本章核心脉络:GPIO子系统框架 → RK GPIO控制器驱动 → DTS配置实战 → 中断调试
4.1 GPIO子系统框架:三层结构
Linux内核的GPIO子系统,我习惯把它看成三层:
- GPIO库(gpiolib):最底层,直接操作硬件寄存器
- GPIO控制器驱动:芯片厂商实现,比如RK的gpio-rockchip.c
- GPIO消费者接口:给其他驱动用的API,比如gpiod_get_value()
你想想看,为什么搞这么复杂?直接读写寄存器不香吗?
嗯,我在一个项目里吃过亏。当时一个外设驱动直接操作GPIO寄存器,结果内核升级后寄存器地址变了,代码全废。用gpiolib的话,换个平台只需要改DTS,驱动代码不用动。
4.2 RK GPIO控制器驱动分析
瑞芯微的GPIO控制器,每个bank有32个引脚。以RK3588为例,有4个GPIO bank:GPIO0~GPIO3。驱动文件在 drivers/gpio/gpio-rockchip.c。
我建议你重点关注这几个回调函数:
| 回调函数 | 作用 | 我在项目中踩过的坑 |
|---|---|---|
direction_input |
设置引脚为输入 | 忘记配置上拉/下拉,导致电平不确定 |
direction_output |
设置引脚为输出并赋值 | 输出低电平时驱动能力不足,外设不响应 |
get |
读取引脚电平 | 中断上下文里调用这个函数,导致调度异常 |
set |
设置引脚电平 | 多个驱动同时操作同一个GPIO,竞争条件 |
to_irq |
GPIO号转中断号 | 返回的中断号不对,request_irq失败 |
小技巧:调试GPIO驱动时,我习惯先在gpio-rockchip.c里加printk,打印出bank基地址和偏移量。这样能快速确认驱动是否正确映射了寄存器。
4.3 DTS中GPIO配置实战
DTS配置是GPIO移植的重头戏。我见过太多人在这里翻车。先看一个标准配置:
&gpio1 {
status = "okay";
};
my_device: my-device {
compatible = "my,device";
pinctrl-names = "default", "sleep";
pinctrl-0 = <&my_device_pins>;
pinctrl-1 = <&my_device_sleep_pins>;
enable-gpios = <&gpio1 RK_PB2 GPIO_ACTIVE_HIGH>;
irq-gpios = <&gpio1 RK_PB3 GPIO_ACTIVE_LOW>;
};
这里有几个关键点:
- pinctrl-names:定义不同状态,比如default和sleep
- pinctrl-0:引用pinctrl节点,配置复用功能
- enable-gpios:直接引用GPIO控制器,指定bank和引脚
我曾经在一个项目里,把RK_PB2写成了RK_PB3,结果设备死活不工作。查了两天才发现是引脚号错了。所以,我建议你每次写完DTS,先用dtc -I dts -O dtb编译,再用fdtdump反编译确认。
注意:RK平台中,GPIO号的计算方式是:bank * 32 + pin。比如GPIO1_B2就是1*32 + 8 = 40。但DTS里直接用宏RK_PB2更安全,别自己算。
4.4 GPIO中断调试
GPIO中断调试,是驱动开发里最让人头疼的部分之一。我总结了一套调试流程:
- 确认中断号:
cat /proc/interrupts | grep gpio,看有没有注册成功 - 检查触发条件:上升沿、下降沿、高电平、低电平,别搞混
- 测试中断响应:手动短接GPIO到VCC或GND,看中断计数是否增加
- 排查中断处理函数:加printk,确认是否进入了handler
嗯,这里要注意。中断处理函数里不能做耗时操作。我有个同事在中断里调用了msleep,结果系统直接死锁。中断上下文里只能用tasklet或workqueue推迟处理。
看一个典型的中断注册代码:
static irqreturn_t my_gpio_irq_handler(int irq, void *dev_id)
{
struct my_device *dev = dev_id;
/* 读取GPIO状态 */
int val = gpiod_get_value(dev->gpio);
/* 注意:这里不能sleep! */
schedule_work(&dev->work);
return IRQ_HANDLED;
}
static int my_probe(struct platform_device *pdev)
{
struct my_device *dev;
int irq, ret;
dev = devm_kzalloc(&pdev->dev, sizeof(*dev), GFP_KERNEL);
dev->gpio = devm_gpiod_get(&pdev->dev, "irq", GPIOD_IN);
irq = gpiod_to_irq(dev->gpio);
ret = devm_request_irq(&pdev->dev, irq, my_gpio_irq_handler,
IRQF_TRIGGER_FALLING, "my_device", dev);
return ret;
}
调试时,我常用的命令:
echo 1 > /sys/class/gpio/gpioN/value:手动控制GPIOcat /sys/kernel/debug/gpio:查看所有GPIO状态cat /proc/interrupts:查看中断统计
核心经验:GPIO中断不触发,90%的原因是DTS里pinctrl配置错了。先检查复用功能是不是设成了GPIO模式,再检查中断触发条件。我曾经在RK3399上折腾了一整天,最后发现是pinctrl节点里忘了加bias-pull-up。
4.5 常见故障与排查
| 故障现象 | 可能原因 | 排查方法 |
|---|---|---|
| GPIO输出电平不对 | 驱动能力不足、上拉/下拉配置错误 | 用示波器看波形,检查DTS中drive-strength |
| 中断不触发 | 中断号错误、触发条件不对、pinctrl未配置 | cat /proc/interrupts,手动触发测试 |
| 中断频繁触发 | 没有去抖、电平不稳定 | 硬件加电容,软件加延迟或使用IRQF_TRIGGER_NONE |
| GPIO被其他驱动占用 | DTS中多个节点引用同一个GPIO | 检查DTS,使用gpio-reserved-ranges |
说实话,GPIO驱动移植本身不难,难的是排查。我个人的习惯是:先确认硬件没问题,再查DTS,最后看驱动代码。顺序别搞反了,否则容易做无用功。
调试利器:在RK平台上,我经常用io命令直接读写GPIO寄存器。比如io -4 0xfec20000读GPIO0的SWPORTA_DR寄存器,能快速确认硬件状态。
好了,GPIO这块就聊到这儿。记住我说的三层框架、DTS配置要点、中断调试流程,你在RK平台上基本不会在GPIO上栽跟头。