GPIO子系统层次与数据结构详解_gpio子系统详解
往期内容
本专栏往期内容:
- Pinctrl子系统和其主要结构体引入
- Pinctrl子系统pinctrl_desc结构体进一步介绍
- Pinctrl子系统中client端设备树相关数据结构介绍和解析
- inctrl子系统中Pincontroller构造过程驱动分析:imx_pinctrl_soc_info结构体
- Pinctrl子系统中client端使用pinctrl过程的驱动分析
- Pinctrl子系统中Pincontroller和client驱动程序的编写
input子系统专栏:
- 专栏地址:input子系统
- input角度:I2C触摸屏驱动分析和编写一个简单的I2C驱动程序
– 末片,有往期内容观看顺序I2C子系统专栏:
- 专栏地址:IIC子系统
- 具体芯片的IIC控制器驱动程序分析:i2c-imx.c-CSDN博客
– 末篇,有往期内容观看顺序总线和设备树专栏:
- 专栏地址:总线和设备树
- 设备树与 Linux 内核设备驱动模型的整合-CSDN博客
– 末篇,有往期内容观看顺序
前言
Linux 4.x内核文档
- Linux-4.9.88\\Documentation\\gpio📎drivers-on-gpio.txt📎gpio.txt📎gpio-legacy.txt📎sysfs.txt📎board.txt📎consumer.txt📎driver.txt
- Linux-4.9.88\\Documentation\\devicetree\\bindings\\gpio\\gpio.txt📎gpio.txt
- Linux-4.9.88\\drivers\\gpio\\gpio-74x164.c📎gpio-74x164.c
本文主要讲解GPIO子系统的层次结构体、提供的相关API接口以及相关数据结构,同时通过对内核的示例驱动代码添加一些注释使能更好的理解GPIO子系统。
1.GPIO子系统的层次
1.1 层次
1.2 GPIOLIB向上提供的接口
GPIO子系统有两套接口:基于描述符的(descriptor-based)、老的(legacy)。前者的函数都有前缀“gpiod_”,它使用gpio_desc结构体来表示一个引脚;后者的函数都有前缀“gpio_”,它使用一个整数来表示一个引脚。
有前缀“devm_”的含义是“设备资源管理”(Managed Device Resource),这是一种自动释放资源的机制。它的思想是“资源是属于设备的,设备不存在时资源就可以自动释放”。
实际上会调用到gpio_chip中的相关函数:
对应这两种方法有啥区别,下面在介绍gpio_desc中会提到。
1.3 GPIOLIB向下提供的接口
如果chip无法被注册(例如chip->base无效或者已经和其它不同的chip关联),则返回负数的errno。否侧返回0。
当函数在开机早期被调用,以便GPIOs可以自由适用于时,chip->parent设备必须在gpio框架的arch_initcall()调用之前注册好。否则,GPIOs的sysfs初始化将失败。
函数只能在core_initcall()初始化之后(即在core_initcall之后)调用。
如果chip_base为负,则要求动态分配一系列有效的GPIO。
先自己构造好GPIO控制器对应的gpio_chip(里面有对GPIO控制器对应引脚控制和中断的相关操作函数),通过上面该函数来构造gpio_device来表示一个GPIO控制器
2.重要的3个核心数据机构
记住GPIO Controller的要素,这有助于理解它的驱动程序:
- 一个GPIO Controller里有多少个引脚?有哪些引脚?
- 需要提供函数,设置引脚方向、读取/设置数值
- 需要提供函数,把引脚转换为中断
以Linux面向对象编程的思想,一个GPIO Controller必定会使用一个结构体来表示,这个结构体必定含有这些信息:
- GPIO引脚信息
- 控制引脚的函数
- 中断相关的函数
2.1 gpio_device
每个GPIO Controller用一个gpio_device来表示:
- 里面每一个gpio引脚用一个gpio_desc来表示
- gpio引脚的函数(引脚控制、中断相关),都放在gpio_chip里
/** * struct gpio_device - GPIO 设备的内部状态容器 * * @id: GPIO 芯片的数字 ID。 * @dev: GPIO 设备对应的 `device` 结构体,表示 Linux 设备模型中的该 GPIO 设备。 * @chrdev: GPIO 设备的字符设备结构,用于设备节点访问 GPIO。 * @mockdev: 类设备,用于过时的 sysfs 接口;如果该接口不使用,可能为 NULL。 * @owner: 模块的引用计数,当 GPIO 正在使用时,防止模块被卸载。 * @chip: 指向 `gpio_chip` 的指针,保存 GPIO 芯片的静态数据,包括 GPIO 操作函数。 * @descs: 描述符数组,包含该 GPIO 设备的所有 GPIO 线的描述符,数组大小为 `ngpio`。 * @ngpio: GPIO 设备中的 GPIO 线数量,与 `descs` 数组大小相同。 * @base: GPIO 的基础编号(已弃用),全局编号空间中的起始值,在设备创建时分配。 * @label: GPIO 设备的描述性名称,例如 SoC 内的 IP 组件的部件号或名称。 * @data: 驱动程序分配的每个实例的数据,用于存储驱动特定的自定义数据。 * @list: 将多个 `gpio_device` 结构连接在一起,用于遍历。 * * 该状态容器保存了一个 GPIO 设备的大多数运行时变量数据, * 在 GPIO 芯片被移除后,它仍可以被保留并在用户空间使用。 */struct gpio_device { int id; // GPIO 芯片的数字 ID struct device dev; // GPIO 设备的 device 结构 struct cdev chrdev; // 字符设备结构,用于字符设备节点 struct device *mockdev; // 用于过时的 sysfs 接口的类设备,可能为 NULL struct module *owner; // 模块引用计数,防止模块卸载 struct gpio_chip *chip; // 指向 gpio_chip 的指针,保存 GPIO 芯片的静态信息 struct gpio_desc *descs; // GPIO 描述符数组 int base; // 在全局 GPIO 空间中的基础编号(已弃用) u16 ngpio; // GPIO 设备中的 GPIO 线数量 char *label; // 描述性名称 void *data; // 驱动程序分配的自定义数据 struct list_head list; // 链表结构,用于将多个 `gpio_device` 串联#ifdef CONFIG_PINCTRL /* * 如果 CONFIG_PINCTRL 被启用,GPIO 控制器可以描述其在 SoC 中实际服务的管脚范围。 * 该信息将由 pinctrl 子系统使用,以配置 GPIO 管脚使用。 */ struct list_head pin_ranges; // pinctrl 的 GPIO 管脚范围#endif};
以1号GPIO Controller,如下图,那么其就有一个gpio_device
结构体来表示,可以看出他有四个引脚,每个引脚就是用其成员struct gpio_desc *descs
来表示,这是个数组来着,具体是什么下文会讲解。
-
id
就是1,因为这个1号GPIO控制器。 -
base
就是引脚编号基值,在这里就是0,在传统legacy中有用,需要注意的是它是全局的。- 比如第二个GPIO控制器,有32个引脚(编号范围是pin0pin31),那么加上1号控制器全部就有36个引脚是吧,此时我通过legacy的方式去使用**编号**为10的引脚(也就是第11根pin)。可以知道10号引脚不在1号GPIO控制器内(因为其只有4根pin,pin03),那就会去2号控制器找,那么注意了,在2号控制器的base就是4(全局,注意是编号值,编号是从0开始的!),10引脚对应其哪一个???10-4=6,也就是编号值为6的引脚(在全局中对应第11根pin)
- descriptor-based中,就弃用了好像,按照上面的情景,我还是去找编号为10的引脚,在1号控制器没找到,因为其大于
ngpio-1
,那么就会去第二个控制器找,有32根pin(pin0pin31),那么要找的编号为10的引脚就直接对应到pin0pin31中的pin10,从全局上来看就是第4+11 = 15根引脚,是不是就和legacy不一样了
还有疑惑的可以看一下下面的图:
📎leddrv.c
2.2 gpio_chip
并不需要自己创建gpio_device,编写驱动时要创建的是gpio_chip,里面提供了:
- 控制引脚的函数
- 中断相关的函数
- 引脚信息:支持多少个引脚?各个引脚的名字?
/** * struct gpio_chip - 抽象 GPIO 控制器 * * @label: GPIO 设备的功能性名称,如部件号或 SoC 中实现该 GPIO 的 IP 模块名称。 * @gpiodev: 内部状态结构的指针,用于 `gpio_device` 结构。 * @parent: 可选的父设备,提供 GPIO 的来源。 * @owner: 引用模块,防止在 GPIO 使用时移除模块。 * * @request: 可选的钩子函数,用于激活 GPIO 控制器模块(如开启模块电源和时钟);可能会导致休眠。 * @free: 可选的钩子函数,用于释放 GPIO 控制器模块(如关闭电源和时钟);也可能休眠。 * @get_direction: 获取 GPIO 方向(0 表示输出,1 表示输入),返回负数表示错误。 * @direction_input: 将 GPIO 配置为输入;返回错误代码(如果失败)。 * @direction_output: 将 GPIO 配置为输出,并设置初始输出值;返回错误代码。 * @get: 获取 GPIO 信号的当前值,0 表示低电平,1 表示高电平,负值表示错误。 * @set: 设置 GPIO 的输出值(高或低)。 * @set_multiple: 为多个 GPIO 信号设置输出值,使用位掩码 `mask` 定义多个信号。 * @set_debounce: 设置特定 GPIO 的去抖时间(仅限支持中断触发的 GPIO 芯片)。 * @set_single_ended: 设置 GPIO 线为开漏、开源或非单端(例如从开漏恢复到正常模式),适用于支持这些功能的硬件。 * @to_irq: 支持非静态 `gpio_to_irq()` 映射的可选钩子,不能在实现中休眠。 * @dbg_show: 可选的调试显示例程,显示 GPIO 芯片的状态。 * * @base: 该芯片处理的第一个 GPIO 编号;如果设置为负数,则动态分配编号。 * 建议使用动态分配方式(base = -1),以避免全局静态 GPIO 编号空间。 * @ngpio: 该控制器管理的 GPIO 数量。 * @names: 可选的 GPIO 名称数组,为 GPIO 芯片的 GPIO 提供别名,数组长度必须为 `ngpio`。 * @can_sleep: 若 `get()` 和 `set()` 函数会休眠(如通过 I2C 或 SPI 访问 GPIO 扩展芯片),则该标志应为真。 * @irq_not_threaded: 若 `can_sleep` 为真但 IRQ 不需要线程化,则应设置此标志。 * * 通用 GPIO 控制器寄存器的接口: * * @read_reg: 通用 GPIO 的读取寄存器。 * @write_reg: 通用 GPIO 的写入寄存器。 * @pin2mask: GPIO 引脚到位掩码的转换器回调。 * @reg_dat: 输入数据寄存器。 * @reg_set: 输出设定寄存器(设为高电平)。 * @reg_clr: 输出清除寄存器(设为低电平)。 * @reg_dir: GPIO 方向寄存器。 * @bgpio_bits: 通用 GPIO 的寄存器位数。 * @bgpio_lock: 用于锁定 `bgpio_data`,保证数据写入的原子性。 * @bgpio_data: 通用 GPIO 的数据寄存器,用于安全地清除/设置位。 * @bgpio_dir: 方向寄存器的镜像。 * * 与 GPIO IRQ 相关的字段: * * @irqchip: GPIO IRQ 芯片实现,通常由 GPIO 驱动提供。 * @irqdomain: 中断翻译域,用于在硬件 IRQ 与 Linux IRQ 之间映射。 * @irq_base: GPIO IRQ 芯片的第一个 Linux IRQ 编号(已弃用)。 * @irq_handler: GPIO 中断的处理程序(通常为预定义的中断核心函数)。 * @irq_default_type: GPIO 驱动初始化期间应用的默认中断触发类型。 * @irq_parent: GPIO IRQ 芯片的父中断编号。 * @irq_need_valid_mask: 如果设置为真,核心会分配 `irq_valid_mask`,且所有位均为 1。 * @irq_valid_mask: 允许包含在 IRQ 域的有效 GPIO 位掩码。 * @lock_key: 每个 GPIO IRQ 芯片的 lockdep 类。 * * 设备树相关的字段(CONFIG_OF_GPIO): * * @of_node: 设备树中的节点指针。 * @of_gpio_n_cells: GPIO 节点的单元数量。 * @of_xlate: 用于 OF(设备树)的 GPIO 芯片转换回调。 * * 该结构体使平台能够抽象各种来源的 GPIO,提供一致的接口调用, * 并支持不同硬件架构的 GPIO 访问和控制。 */struct gpio_chip {const char*label;struct gpio_device*gpiodev;struct device*parent;struct module*owner;int(*request)(struct gpio_chip *chip,unsigned offset);void(*free)(struct gpio_chip *chip,unsigned offset);int(*get_direction)(struct gpio_chip *chip,unsigned offset);int(*direction_input)(struct gpio_chip *chip,unsigned offset);int(*direction_output)(struct gpio_chip *chip,unsigned offset, int value);int(*get)(struct gpio_chip *chip,unsigned offset);void(*set)(struct gpio_chip *chip,unsigned offset, int value);void(*set_multiple)(struct gpio_chip *chip,unsigned long *mask,unsigned long *bits);int(*set_debounce)(struct gpio_chip *chip,unsigned offset,unsigned debounce);int(*set_single_ended)(struct gpio_chip *chip,unsigned offset,enum single_ended_mode mode);int(*to_irq)(struct gpio_chip *chip,unsigned offset);void(*dbg_show)(struct seq_file *s,struct gpio_chip *chip);intbase;u16ngpio;const char*const *names;boolcan_sleep;boolirq_not_threaded;#if IS_ENABLED(CONFIG_GPIO_GENERIC)unsigned long (*read_reg)(void __iomem *reg);void (*write_reg)(void __iomem *reg, unsigned long data);unsigned long (*pin2mask)(struct gpio_chip *gc, unsigned int pin);void __iomem *reg_dat;void __iomem *reg_set;void __iomem *reg_clr;void __iomem *reg_dir;int bgpio_bits;spinlock_t bgpio_lock;unsigned long bgpio_data;unsigned long bgpio_dir;#endif#ifdef CONFIG_GPIOLIB_IRQCHIPstruct irq_chip*irqchip;struct irq_domain*irqdomain;unsigned intirq_base;irq_flow_handler_tirq_handler;unsigned intirq_default_type;intirq_parent;boolirq_need_valid_mask;unsigned long*irq_valid_mask;struct lock_class_key*lock_key;#endif#if defined(CONFIG_OF_GPIO)struct device_node *of_node;int of_gpio_n_cells;int (*of_xlate)(struct gpio_chip *gc,const struct of_phandle_args *gpiospec, u32 *flags);#endif};
内核中给了详细的英文注释,这里按照个人理解将其转译出来。
2.3 gpio_desc
使用GPIO子系统时,首先是获得某个引脚对应的gpio_desc。
gpio_device表示一个GPIO Controller,里面支持多个GPIO。
在gpio_device中有一个gpio_desc数组,每一引脚有一项gpio_desc。
struct gpio_desc {struct gpio_device*gdev; /* 所属 GPIO 设备的指针 */unsigned longflags; /* GPIO 的状态标志位 *//* 状态标志的位定义 */#define FLAG_REQUESTED0 /* 标记 GPIO 已被请求 */#define FLAG_IS_OUT1 /* 标记 GPIO 为输出方向 */#define FLAG_EXPORT2 /* 通过 sysfs_lock 保护的 GPIO 导出标志 */#define FLAG_SYSFS3 /* GPIO 是否通过 /sys/class/gpio 导出 */#define FLAG_ACTIVE_LOW6 /* GPIO 为低电平有效 */#define FLAG_OPEN_DRAIN7 /* GPIO 为开漏类型 */#define FLAG_OPEN_SOURCE 8 /* GPIO 为开源类型 */#define FLAG_USED_AS_IRQ 9 /* GPIO 已用作中断 */#define FLAG_IS_HOGGED11 /* GPIO 被占用 *//* 连接标签,用于标识 GPIO 功能或用途 */const char*label;/* GPIO 的名称 */const char*name;};
3.怎么编写GPIO Controller驱动程序
分配、设置、注册gpioc_chip结构体,示例:drivers\\gpio\\gpio-74x164.c
📎gpio-74x164.c,内核提供的 对 74x164 移位寄存器的 SPI 控制功能,将寄存器的位作为 GPIO 使用。通过实现 GPIO 接口函数,它可以与 Linux 的 GPIO 子系统交互,使用户空间可以访问和控制寄存器的引脚状态。 下面是个人对代码添加的一些注释
/* * 74Hx164 - 通用串入/并出 8位移位寄存器 GPIO 驱动 * * 该驱动程序实现了对 74x164 类的串入/并出移位寄存器的控制,提供 GPIO 接口。 */#include #include #include #include #include #include #include #define GEN_74X164_NUMBER_GPIOS 8 // 每个移位寄存器有 8 个 GPIO 引脚// 定义 gen_74x164_chip 结构体,表示一个 GPIO 扩展器设备实例struct gen_74x164_chip { struct gpio_chip gpio_chip; // GPIO 控制器结构 struct mutex lock; // 保护移位寄存器缓冲区的互斥锁 u32 registers; // 移位寄存器数量 u8 buffer[0]; // 缓冲区,用于存储所有移位寄存器的状态,大小动态分配};// 将缓冲区内容写入硬件寄存器static int __gen_74x164_write_config(struct gen_74x164_chip *chip){ return spi_write(to_spi_device(chip->gpio_chip.parent), chip->buffer, chip->registers);}// 获取指定 GPIO 引脚的值static int gen_74x164_get_value(struct gpio_chip *gc, unsigned offset){ struct gen_74x164_chip *chip = gpiochip_get_data(gc); u8 bank = chip->registers - 1 - offset / 8; // 计算目标寄存器 u8 pin = offset % 8; // 计算目标寄存器的引脚位置 int ret; mutex_lock(&chip->lock); // 获取锁,防止并发访问 ret = (chip->buffer[bank] >> pin) & 0x1; // 获取指定引脚的值 mutex_unlock(&chip->lock); // 释放锁 return ret;}// 设置指定 GPIO 引脚的值static void gen_74x164_set_value(struct gpio_chip *gc, unsigned offset, int val){ struct gen_74x164_chip *chip = gpiochip_get_data(gc); u8 bank = chip->registers - 1 - offset / 8; // 计算目标寄存器 u8 pin = offset % 8; // 计算目标寄存器的引脚位置 mutex_lock(&chip->lock); // 获取锁 if (val) chip->buffer[bank] |= (1 << pin); // 设置引脚为高电平 else chip->buffer[bank] &= ~(1 << pin); // 设置引脚为低电平 __gen_74x164_write_config(chip); // 将配置写入硬件寄存器 mutex_unlock(&chip->lock); // 释放锁}// 设置多个 GPIO 引脚的值,使用掩码确定设置的目标引脚static void gen_74x164_set_multiple(struct gpio_chip *gc, unsigned long *mask, unsigned long *bits){ struct gen_74x164_chip *chip = gpiochip_get_data(gc); unsigned int i, idx, shift; u8 bank, bankmask; mutex_lock(&chip->lock); // 获取锁 for (i = 0, bank = chip->registers - 1; i < chip->registers; i++, bank--) { idx = i / sizeof(*mask); shift = i % sizeof(*mask) * BITS_PER_BYTE; bankmask = mask[idx] >> shift; if (!bankmask) continue; chip->buffer[bank] &= ~bankmask; // 清除当前引脚 chip->buffer[bank] |= bankmask & (bits[idx] >> shift); // 设置新值 } __gen_74x164_write_config(chip); // 写入寄存器 mutex_unlock(&chip->lock); // 释放锁}// 配置 GPIO 引脚为输出模式,并设置初始值static int gen_74x164_direction_output(struct gpio_chip *gc, unsigned offset, int val){ gen_74x164_set_value(gc, offset, val); // 设置引脚值 return 0;}// SPI 驱动探测函数,初始化 74x164 设备static int gen_74x164_probe(struct spi_device *spi){ struct gen_74x164_chip *chip; u32 nregs; // 寄存器数量 int ret; spi->bits_per_word = 8;// 配置 SPI 传输位宽 ret = spi_setup(spi); // 设置 SPI 设备 if (ret < 0) return ret; // 从设备树读取 \"registers-number\" 属性 if (of_property_read_u32(spi->dev.of_node, \"registers-number\", &nregs)) { dev_err(&spi->dev, \"设备树中缺少 registers-number 属性.\\n\"); return -EINVAL; } // 为 chip 结构体分配内存 chip = devm_kzalloc(&spi->dev, sizeof(*chip) + nregs, GFP_KERNEL); if (!chip) return -ENOMEM; spi_set_drvdata(spi, chip); // 设置驱动程序数据 // 初始化 GPIO 控制器参数 chip->gpio_chip.label = spi->modalias; chip->gpio_chip.direction_output = gen_74x164_direction_output; chip->gpio_chip.get = gen_74x164_get_value; chip->gpio_chip.set = gen_74x164_set_value; chip->gpio_chip.set_multiple = gen_74x164_set_multiple; chip->gpio_chip.base = -1; chip->registers = nregs; // 设置寄存器数量 chip->gpio_chip.ngpio = GEN_74X164_NUMBER_GPIOS * chip->registers; // 从设备树中读取默认寄存器值并初始化缓冲区 of_property_read_u8_array(spi->dev.of_node, \"registers-default\", chip->buffer, chip->registers); chip->gpio_chip.can_sleep = true; chip->gpio_chip.parent = &spi->dev; chip->gpio_chip.owner = THIS_MODULE; mutex_init(&chip->lock); // 初始化互斥锁 // 将初始配置写入硬件寄存器 ret = __gen_74x164_write_config(chip); if (ret) { dev_err(&spi->dev, \"写入失败: %d\\n\", ret); goto exit_destroy; } // 注册 GPIO 控制器 ret = gpiochip_add_data(&chip->gpio_chip, chip); if (!ret) return 0;exit_destroy: mutex_destroy(&chip->lock); // 清理互斥锁 return ret;}// SPI 驱动移除函数static int gen_74x164_remove(struct spi_device *spi){ struct gen_74x164_chip *chip = spi_get_drvdata(spi); gpiochip_remove(&chip->gpio_chip); // 删除 GPIO 控制器 mutex_destroy(&chip->lock); // 销毁互斥锁 return 0;}// 设备树匹配表,列出支持的设备static const struct of_device_id gen_74x164_dt_ids[] = { { .compatible = \"fairchild,74hc595\" }, { .compatible = \"nxp,74lvc594\" }, {},};MODULE_DEVICE_TABLE(of, gen_74x164_dt_ids);// SPI 驱动程序结构static struct spi_driver gen_74x164_driver = { .driver = { .name = \"74x164\", .of_match_table = gen_74x164_dt_ids, }, .probe = gen_74x164_probe, .remove = gen_74x164_remove,};module_spi_driver(gen_74x164_driver); // 注册 SPI 驱动程序MODULE_AUTHOR(\"Gabor Juhos \");MODULE_AUTHOR(\"Miguel Gaio \");MODULE_DESCRIPTION(\"74X164 8位移位寄存器的 GPIO 扩展器驱动\");MODULE_LICENSE(\"GPL v2\");