news 2026/5/26 9:15:55

嵌入式Linux驱动开发——GPIO 子系统架构深度解析

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
嵌入式Linux驱动开发——GPIO 子系统架构深度解析

嵌入式Linux驱动开发——GPIO 子系统架构深度解析

仓库已经开源!所有教程,主线内核移植,跑新版本imx-linux/uboot都在这里,或者一起来尝试跑7.0的Linux!欢迎各位大佬观摩!喜欢的话点个⭐!

仓库地址:https://github.com/Awesome-Embedded-Learning-Studio/imx-forge

静态网页:https://awesome-embedded-learning-studio.github.io/imx-forge/

前言:比 pinctrl 更简单但同样重要

在上一章我们分析了 pinctrl 子系统,说实话,那部分真的挺复杂的。好消息是,GPIO 子系统会简单一些。它的核心思想很清晰:提供一个统一的 API 来控制 GPIO 引脚,不管底层是什么芯片。

GPIO 子系统的分层设计

GPIO 子系统采用了经典的分层设计:

┌─────────────────────────────────────────────────────────────┐ │ 设备驱动 (你的代码) │ │ gpio_set_value(gpio, 1) │ └──────────────────────────┬──────────────────────────────────┘ │ GPIO API ▼ ┌─────────────────────────────────────────────────────────────┐ │ GPIO 子系统核心层 │ │ (gpiolib.c - gpiolib.h) │ │ 提供统一的 API: gpio_request, gpio_set_value │ └──────────────────────────┬──────────────────────────────────┘ │ gpio_chip ops ▼ ┌─────────────────────────────────────────────────────────────┐ │ GPIO 控制器驱动 (平台特定) │ │ gpio-mxc.c, gpio-pl061.c, ... │ │ 实现 struct gpio_chip 的各种回调函数 │ └──────────────────────────┬──────────────────────────────────┘ │ 寄存器操作 ▼ ┌─────────────────────────────────────────────────────────────┐ │ 硬件寄存器 │ │ GPIO_DR, GPIO_GDIR, GPIO_PSR, ... │ └─────────────────────────────────────────────────────────────┘

这种分层设计的好处是:设备驱动不需要关心底层是什么芯片,只要调用 GPIO API 就行。如果换了芯片,只需要修改底层的 GPIO 控制器驱动,设备驱动代码完全不用改。

核心数据结构:gpio_chip

GPIO 子系统的核心是struct gpio_chip,这个结构体描述了一个 GPIO 控制器的能力和操作函数。

structgpio_chip{constchar*label;// 控制器名称structdevice*dev;// 关联的设备structmodule*owner;// 模块所有者intbase;// GPIO 编号的起始值intngpio;// GPIO 的数量constchar*const*names;// GPIO 名称数组// 方向操作int(*get_direction)(structgpio_chip*chip,unsignedoffset);int(*direction_input)(structgpio_chip*chip,unsignedoffset);int(*direction_output)(structgpio_chip*chip,unsignedoffset,intvalue);// 值操作int(*get)(structgpio_chip*chip,unsignedoffset);void(*set)(structgpio_chip*chip,unsignedoffset,intvalue);// 其他操作...int(*request)(structgpio_chip*chip,unsignedoffset);void(*free)(structgpio_chip*chip,unsignedoffset);int(*to_irq)(structgpio_chip*chip,unsignedoffset);// ...};

每个 GPIO 控制器驱动都需要实现这个结构体,填充各种回调函数。当设备驱动调用 GPIO API 时,GPIO 核心层会找到对应的 gpio_chip,然后调用这些回调函数。

i.MX 的 GPIO 控制器驱动

现在让我们来看看 i.MX 的 GPIO 控制器驱动是怎么实现的。源码在drivers/gpio/gpio-mxc.c

硬件描述结构:mxc_gpio_hwdata

i.MX 系列有多个芯片代际,每个代际的 GPIO 寄存器布局可能不同。为了兼容这些差异,驱动定义了一个mxc_gpio_hwdata结构体:

structmxc_gpio_hwdata{unsigneddr_reg;// 数据寄存器偏移unsignedgdir_reg;// 方向寄存器偏移unsignedpsr_reg;// 状态寄存器偏移unsignedicr1_reg;// 中断控制寄存器1unsignedicr2_reg;// 中断控制寄存器2unsignedimr_reg;// 中断屏蔽寄存器unsignedisr_reg;// 中断状态寄存器intedge_sel_reg;// 边沿选择寄存器// ...};

每个芯片代际都有一个这样的结构体:

staticstructmxc_gpio_hwdataimx35_gpio_hwdata={.dr_reg=0x00,.gdir_reg=0x04,.psr_reg=0x08,.icr1_reg=0x0c,.icr2_reg=0x10,.imr_reg=0x14,.isr_reg=0x18,.edge_sel_reg=0x1c,// ...};

这些偏移值和我们在硬件章节讲的寄存器地址是对应的。比如dr_reg = 0x00表示数据寄存器在 GPIO 模块基址的偏移 0 处。

端口结构:mxc_gpio_port

structmxc_gpio_port{structlist_headnode;void__iomem*base;// 寄存器基地址structclk*clk;// 时钟intirq;// 中断号structgpio_chipgc;// gpio_chip 结构体structdevice*dev;// 设备指针conststructmxc_gpio_hwdata*hwdata;// 硬件描述// ...};

这个结构体描述了一个 GPIO 端口(比如 GPIO1、GPIO2)。i.MX 6ULL 有 5 个 GPIO 端口,每个端口最多 32 个 GPIO。

设备树匹配

驱动通过设备树的 compatible 属性来匹配:

staticconststructof_device_idmxc_gpio_dt_ids[]={{.compatible="fsl,imx1-gpio",.data=&imx1_imx21_gpio_hwdata},{.compatible="fsl,imx21-gpio",.data=&imx1_imx21_gpio_hwdata},{.compatible="fsl,imx31-gpio",.data=&imx31_gpio_hwdata},{.compatible="fsl,imx35-gpio",.data=&imx35_gpio_hwdata},{.compatible="fsl,imx7d-gpio",.data=&imx35_gpio_hwdata},{.compatible="fsl,imx8dxl-gpio",.data=&imx35_gpio_hwdata},{/* sentinel */}};

i.MX 6ULL 对应的是fsl,imx7d-gpio(实际上 6ULL 和 7D 的 GPIO 模块兼容)。

probe 函数流程

当 GPIO 控制器驱动加载时,probe 函数会被调用。流程大致是这样的:

staticintmxc_gpio_probe(structplatform_device*pdev){// 1. 分配端口结构体port=devm_kzalloc(&pdev->dev,sizeof(*port),GFP_KERNEL);// 2. 获取硬件描述port->hwdata=device_get_match_data(&pdev->dev);// 3. 映射寄存器地址port->base=devm_platform_ioremap_resource(pdev,0);// 4. 获取中断号port->irq=platform_get_irq(pdev,0);// 5. 获取并使能时钟port->clk=devm_clk_get_optional_enabled(&pdev->dev,NULL);// 6. 初始化 gpio_chiperr=bgpio_init(&port->gc,&pdev->dev,4,port->base+GPIO_PSR,// 读取port->base+GPIO_DR,// 写入NULL,port->base+GPIO_GDIR,// 方向NULL,BGPIOF_READ_OUTPUT_REG_SET);// 7. 设置自定义函数port->gc.request=mxc_gpio_request;port->gc.free=mxc_gpio_free;port->gc.to_irq=mxc_gpio_to_irq;// 8. 设置 GPIO 编号基数port->gc.base=of_alias_get_id(np,"gpio")*32;// 9. 注册 gpio_chiperr=devm_gpiochip_add_data(&pdev->dev,&port->gc,port);}

这里有几个关键点:

  1. bgpio_init:这是一个辅助函数,用于初始化基于寄存器的 GPIO 控制器。它会设置基本的读写函数。
  2. GPIO 编号基数:每个 GPIO 端口有 32 个 GPIO。GPIO1 的编号是 0-31,GPIO2 是 32-63,以此类推。
  3. devm_gpiochip_add_data:注册 gpio_chip 到 GPIO 核心层。

GPIO API:设备驱动如何使用

现在让我们看看设备驱动是怎么使用 GPIO API 的。这些 API 定义在linux/gpio/consumer.hlinux/gpio.h中。

获取 GPIO 编号

从设备树获取 GPIO 编号:

intgpio=of_get_named_gpio(dev->of_node,"led-gpio",0);if(gpio<0){pr_err("Failed to get GPIO\n");returngpio;}

of_get_named_gpio会从设备树的led-gpio属性解析 GPIO 编号。

申请 GPIO

在使用 GPIO 之前,需要先申请:

err=gpio_request(gpio,"aes-led");if(err){pr_err("Failed to request GPIO %d\n",gpio);returnerr;}

gpio_request的作用是检查这个 GPIO 是否已经被其他驱动占用了。

设置方向

// 设置为输出,初始值为 1err=gpio_direction_output(gpio,1);if(err){pr_err("Failed to set GPIO direction\n");returnerr;}// 或设置为输入err=gpio_direction_input(gpio);

设置/获取值

// 设置值(0 或 1)gpio_set_value(gpio,0);// 获取值intvalue=gpio_get_value(gpio);

释放 GPIO

gpio_free(gpio);

GPIO 编号空间:全局编号 vs 控制器编号

这里有个容易混淆的概念:GPIO 有两种编号方式。

全局编号

全局编号是整个系统范围内的唯一编号。GPIO1_IO03 的全局编号是:

gpio1_base + 3 = 0 + 3 = 3

因为 GPIO1 的基数是 0,GPIO1_IO03 是第 3 号引脚,所以全局编号是 3。

控制器编号

控制器编号是相对于特定控制器的编号。GPIO1_IO03 在 GPIO1 控制器内的编号是 3。

设备树里使用的是控制器编号:

led-gpio = <&gpio1 3 GPIO_ACTIVE_LOW>;

这里&gpio1表示 GPIO1 控制器,3是控制器内的编号。

主线内核与 imx 内核的差异对比

代码量对比

主线内核 gpio-mxc.c: 733 行 imx 内核 gpio-mxc.c: 739 行

两个版本非常接近,只差 6 行。这说明 GPIO 驱动已经相当稳定了。

API 差异

两个内核的 GPIO API 完全兼容。主要的差异可能在于:

  1. 设备树绑定:主线内核的设备树绑定文档可能更完整,格式也更新(从 .txt 转到 .yaml)。

  2. 错误处理:主线内核可能有更严格的错误检查。

  3. devm_API*:主线内核更倾向于使用devm_前缀的资源管理 API,这些 API 会在设备卸载时自动释放资源。

数据结构差异

两个内核的核心数据结构(struct gpio_chipstruct mxc_gpio_port)完全一致。

GPIO 子系统与 pinctrl 子系统的协作

GPIO 子系统和 pinctrl 子系统是紧密协作的。当你调用gpio_direction_output时,实际上发生了这些事情:

1. GPIO 子系统检查 GPIO 是否已经被申请 2. GPIO 子系统调用 gpio_chip 的 direction_output 回调 3. gpio-mxc.c 驱动写 GDIR 寄存器,设置方向 4. (可能)GPIO 子系统请求 pinctrl 子系统配置引脚 5. pinctrl 子系统检查引脚是否已被配置 6. (如果需要)pinctrl 子系统配置引脚复用

在大多数情况下,pinctrl 子系统会在设备加载时自动配置好引脚,GPIO 子系统只需要操作 GPIO 寄存器就行了。

小结

GPIO 子系统是 Linux 内核里相对简单但非常重要的子系统。它的核心思想是:提供一个统一的 API,屏蔽不同芯片的硬件差异

从硬件角度看,GPIO 子系统是对 GPIO 模块寄存器的软件抽象。

从软件角度看,GPIO 子系统提供了一套标准的 API,让设备驱动不需要关心具体的寄存器操作。

从架构角度看,GPIO 子系统采用了"核心层 + 平台驱动层"的分层设计,和 pinctrl 子系统类似。

说实话,GPIO 子系统的源码相对容易理解。如果你想深入学习,建议从gpio-mxc.c的 probe 函数开始,然后追踪bgpio_initdevm_gpiochip_add_data的调用链。


相关阅读

  1. 嵌入式Linux嵌入式Linux驱动开发:设备树驱动改造——从硬编码到设备树的实战之旅 - 相似度 100%
  2. 嵌入式Linux驱动开发pinctrl篇(1)——从寄存器到子系统:驱动演进之路 - 相似度 100%
  3. 通用GUI编程技术——图形渲染实战(四十五)——D3D12资源与堆管理:从上传到驻留 - 相似度 100%
版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/5/26 9:09:36

如何快速开启中兴光猫工厂模式:网络管理员的完整指南

如何快速开启中兴光猫工厂模式&#xff1a;网络管理员的完整指南 【免费下载链接】zteOnu A tool that can open ZTE onu device factory mode 项目地址: https://gitcode.com/gh_mirrors/zt/zteOnu 中兴光猫终极管理工具zteOnu是一款专为中兴光纤猫设备设计的开源管理工…

作者头像 李华
网站建设 2026/5/26 9:07:42

.NET 10 API 鉴权体系:从原理到实践

一、什么是 API 鉴权 1.1 问题的起点&#xff1a;HTTP 是无状态的 HTTP 协议本身对"请求者是谁"毫无记忆。每一个请求到达服务器时&#xff0c;服务器看到的都是一个陌生的连接。这意味着&#xff1a; 没有鉴权&#xff1a;任何人 → GET /api/salary/records → …

作者头像 李华
网站建设 2026/5/26 9:06:21

RV1126/RV1109 EVB板SDK v2.2.5保姆级配置指南:从虚拟机到烧录全流程避坑

RV1126/RV1109 EVB板SDK v2.2.5全流程开发实战&#xff1a;从环境搭建到固件烧录第一次接触瑞芯微RV1126/RV1109平台时&#xff0c;面对复杂的开发环境和陌生的工具链&#xff0c;很多开发者都会感到无从下手。本文将带你完整走通从虚拟机配置到最终固件烧录的全流程&#xff0…

作者头像 李华
网站建设 2026/5/26 9:04:21

Seraphine:5分钟快速上手的英雄联盟智能助手完整指南

Seraphine&#xff1a;5分钟快速上手的英雄联盟智能助手完整指南 【免费下载链接】Seraphine 英雄联盟战绩查询工具 项目地址: https://gitcode.com/gh_mirrors/se/Seraphine Seraphine是一款基于英雄联盟官方LCU API开发的智能游戏助手工具&#xff0c;专为英雄联盟玩家…

作者头像 李华
网站建设 2026/5/26 9:02:16

【方法论+案例】物流企业数字化转型的EA全景实施路线图:业务战略理解与现状评估、蓝图架构规划、实施路径设计

基于“企业架构&#xff08;EA&#xff09;”方法&#xff0c;从战略与业务理解出发&#xff0c;了解需求、分析现状、明确蓝图、设计路径&#xff0c;以确保信息化与业务战略的一致。本次规划的提升点&#xff1a;一是从聚焦于物流业务运营向聚焦于企业综合管理的提升&#xf…

作者头像 李华