控制 GPIO 通用化:从寄存器到 gpiod
课程位置:驱动初级 → 平台设备 → 控制 GPIO 通用化。依据:2026-10-13 录音、学习系统
03_控制GPIO 通用化的设备树/驱动/验证笔记,以及老师的gpiod_led/led.c。录音转写中的函数名以示例源码为准。本篇记录课程思路,未在你的板子上执行。
1. 为什么还要改
上一节把 0x6000D200、0x70003150 两个基地址 移进设备树,但驱动仍自己写 ioremap()、readl/writel(),并写死 GPIO 位号 1 << 7 和 CNF/OE/OUT 寄存器偏移。换到另一组管脚或另一款芯片,这些细节还可能要改。老师这节改用内核的 GPIO 描述符接口(gpiod_*):设备树写"用哪根 GPIO",驱动只说"输出有效或无效"。(录音约 00:37---03:43)
| 上一节:直接操作寄存器 | 这一节:GPIO 子系统 |
|---|---|
DTS 提供两段 reg 地址;驱动取资源、映射地址、算偏移和位。 |
DTS 用 gpios 指向 GPIO 控制器及具体引脚;驱动取得 gpio_desc,调用 gpiod_*。 |
驱动知道 GPIO3、PJ.07 的寄存器细节。 |
驱动不直接碰这些寄存器;GPIO 控制器驱动负责底层操作。 |
2. 设备树:告诉驱动"哪一根、什么极性"
dts
yhai-led-gpiod {
compatible = "yhai,led_gpiod";
status = "okay";
gpios = <&gpio TEGRA_GPIO(J, 7) GPIO_ACTIVE_HIGH>;
};
compatible要与下方驱动的of_device_id[].compatible完全一致 。匹配不是比较yhai-led-gpiod这个节点名。gpios中的&gpio引用板上已有的 GPIO 控制器节点;TEGRA_GPIO(J, 7)是 Tegra 的 J 组第 7 号线 。该控制器的#gpio-cells = <2>决定引用后跟两个参数:线号、极性。GPIO_ACTIVE_HIGH表示逻辑值1对应物理高电平;若写GPIO_ACTIVE_LOW,逻辑值1对应物理低电平。是否点亮还取决于 LED 接线,不能把"逻辑 1"永远背成"高电平"。Linux GPIO 消费者接口
本例不用前面的两段 reg。gpiod_get(&pdev->dev, NULL, ...) 的第二个参数是 NULL,对应这个无功能名前缀 的 gpios 属性;若以后写 led-gpios,通常用 "led" 作 con_id。Linux GPIO 映射说明
3. 驱动:谁调用谁
text
insmod led.ko
→ module_platform_driver() 注册 led_gpiod_drv
→ 平台总线匹配 compatible = "yhai,led_gpiod"
→ 内核调用 led_gpiod_probe(pdev)
→ gpiod_get(&pdev->dev, NULL, GPIOD_OUT_HIGH)
→ 取得并申请 GPIO,设为输出,初值为逻辑 1
→ platform_set_drvdata(pdev, gpiod) 保存指针
rmmod led
→ 内核调用 led_gpiod_remove(pdev)
→ platform_get_drvdata(pdev) 找回指针
→ gpiod_set_value(gpiod, 0) 输出逻辑 0
→ gpiod_put(gpiod) 释放 GPIO
这就是老师演示的加载模块时亮、卸载模块时灭 。platform_set_drvdata/get_drvdata 只是把本设备的 gpiod 指针存起来、再取回来;不是设置引脚值。Linux GPIO 消费者接口(录音约 13:21---21:16)
记住四个 API
| API | 本例作用 |
|---|---|
gpiod_get(&pdev->dev, NULL, GPIOD_OUT_HIGH) |
取得并申请该设备的 GPIO,直接设为输出且初值为逻辑 1。 |
gpiod_direction_output(gpiod, 1) |
显式设置输出及初值。老师源码在上一步之后又调用一次,功能重复;保留时应检查返回值。 |
gpiod_set_value(gpiod, 0) |
改成逻辑 0;会按设备树的 GPIO_ACTIVE_HIGH/LOW 转成物理电平。 |
gpiod_put(gpiod) |
与 gpiod_get() 配对,释放已申请的 GPIO 描述符。 |
纠正老师示例的一处判断: gpiod_get() 失败返回错误指针,不是 NULL。此例的核心写法可简化为:
c
gpiod = gpiod_get(&pdev->dev, NULL, GPIOD_OUT_HIGH);
if (IS_ERR(gpiod))
return PTR_ERR(gpiod);
platform_set_drvdata(pdev, gpiod);
这样也保留了实际错误码。因为 GPIOD_OUT_HIGH 已设置方向和初值,最简代码无需再调用 gpiod_direction_output(gpiod, 1)。老师原写法只是重复配置,并且忽略了第二次调用可能返回的错误。Linux 4.17 GPIO 接口说明
4. 与以前 /dev/led 的关系
本课的 gpiod_led/led.c 是独立的最简模块 ,只展示 probe() 点亮、remove() 关闭:没有注册设备号、cdev、/dev/led,也不需要运行之前的 ioctl 应用 。若以后要恢复应用控制,可保留上一节的字符设备外壳,把 led_on()/led_off() 里的 readl/writel 换成 gpiod_set_value();这是下一步改造,不是本课已经完成的功能。
GPIO 描述符接口屏蔽了 GPIO 控制器的寄存器操作,但不保证只写 gpios 就完成管脚复用 。若引脚仍被设成 UART/I²S 等功能、被其他驱动占用,仍要检查板级 pinctrl 配置和占用情况。Linux pinctrl 与 GPIO 关系
5. 最短验证顺序
- 在课程对应板卡的 DTS 中加入节点,编译 DTB,让板子实际加载新 DTB。只改 DTS 文本而未更新启动使用的 DTB,不会生效。
- 用匹配板上运行内核的构建目录编译
gpiod_led/led.c得到led.ko。老师把 KO 放进 NFS 根文件系统、把 DTB 放进 TFTP 目录;这只是他的传输方式。 - 板上
sudo insmod ./led.ko,用sudo dmesg | tail -n 30找led_gpiod_probe ok,并看 LED;再sudo rmmod led,看led_gpiod_remove ok与 LED 状态。printk()可能只出现在dmesg,不直接显示在命令行。(录音约 21:40---25:30)
如果没进 probe,先核对正在运行的 DTB 、compatible 是否一致;若 gpiod_get 失败,再查 gpios 引用、pinctrl 和 GPIO 是否被其他模块占用。此前的字符设备模块也叫 led,实验时不要同时加载两个同名模块。
**一句话记忆:**设备树选 GPIO → 平台总线找到驱动 → probe 用 gpiod_get 申请并点亮 → remove 用 gpiod_set_value 关闭、gpiod_put 释放。
上一节:字符设备改成平台设备。