往期内容
本专栏往期内容:
- Pinctrl子系统和其主要结构体引入
- Pinctrl子系统pinctrl_desc结构体进一步介绍
- Pinctrl子系统中client端设备树相关数据结构介绍和解析
- inctrl子系统中Pincontroller构造过程驱动分析:imx_pinctrl_soc_info结构体
- Pinctrl子系统中client端使用pinctrl过程的驱动分析
- Pinctrl子系统中Pincontroller和client驱动程序的编写
- GPIO子系统层次与数据结构详解-CSDN博客
input子系统专栏:
- 专栏地址:input子系统
- input角度:I2C触摸屏驱动分析和编写一个简单的I2C驱动程序
– 末片,有往期内容观看顺序I2C子系统专栏:
- 专栏地址:IIC子系统
- 具体芯片的IIC控制器驱动程序分析:i2c-imx.c-CSDN博客
– 末篇,有往期内容观看顺序总线和设备树专栏:
- 专栏地址:总线和设备树
- 设备树与 Linux 内核设备驱动模型的整合-CSDN博客
– 末篇,有往期内容观看顺序
目录
- 往期内容
- 前言
- 1.设备树
- 2.驱动程序
- 2.1 分配gpio_chip
- 2.2 设置gpio_chip
- 2.3 注册gpio_chip
- 总结
前言
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-mxc.c📎gpio-mxc.c
- Linux-4.9.88\arch\arm\boot\dts\imx6ull.dtsi
本文主要讲解GPIO子系统中,内核源码提供的GPIO控制器驱动程序大概是如何去编写的,总体就是如何去分配、设置、注册gpio_chip的。
建议先看一下上一篇关于GPIO子系统的相关结构体介绍:GPIO子系统层次与数据结构详解-CSDN博客
1.设备树
Linux-4.9.88\arch\arm\boot\dts\imx6ull.dtsi:进入Linux-4.9.88\arch\arm\boot\dts\目录下,使用dtc -I dtb -o dts 100ask_imx6ull-14x14.dtb > 1.dts,反汇编为dts文件,在该文件中可以搜索到gpio@0209c000设备节点,在imx6ull.dtsi中也可以找到
aliases {can0 = &flexcan1;can1 = &flexcan2;ethernet0 = &fec1;ethernet1 = &fec2;gpio0 = &gpio1;
};gpio1: gpio@0209c000 {compatible = "fsl,imx6ul-gpio", "fsl,imx35-gpio"; /*在Linux-4.9.88\drivers\下通过grep “” -r可以找到对应的驱动程序*/reg = <0x0209c000 0x4000>;interrupts = <GIC_SPI 66 IRQ_TYPE_LEVEL_HIGH>,<GIC_SPI 67 IRQ_TYPE_LEVEL_HIGH>;gpio-controller;#gpio-cells = <2>;interrupt-controller;#interrupt-cells = <2>;
};
GPIO控制器的设备树中,有两项是必须的:
- gpio-controller : 表明这是一个GPIO控制器
- gpio-cells : 指定使用多少个cell(就是整数)来描述一个引脚
当解析设备节点中的GPIO信息时,需要用到上面的属性。
比如下面的led-gpios
,在#gpio-cells = <2>
的情况下,它表示的引脚数量是1。
myled {compatible = "100ask,leddrv";led-gpios = <&gpio1 10 GPIO_ACTIVE_LOW>;};
2.驱动程序
📎gpio-mxc.c
2.1 分配gpio_chip
\Linux-4.9.88\drivers\gpio\gpio-mxc.c
static int mxc_gpio_probe(struct platform_device *pdev)
{// 获取设备树中的设备节点,用于设备信息的解析struct device_node *np = pdev->dev.of_node;// 定义 GPIO 端口结构的指针,将存储 GPIO 控制器的特定信息struct mxc_gpio_port *port;// 用于保存设备的资源信息(IO 地址、内存区域等)struct resource *iores;// IRQ 基地址,用于分配 GPIO 中断编号int irq_base = 0;// 错误状态码int err;// 确定当前平台设备的硬件类型,初始化硬件描述符mxc_gpio_get_hw(pdev);// 分配内存空间给 GPIO 端口结构,初始化为 0,释放自动管理port = devm_kzalloc(&pdev->dev, sizeof(*port), GFP_KERNEL);if (!port) // 如果分配失败,返回错误码return -ENOMEM;// 获取设备的资源(内存地址等),以进行 IO 映射iores = platform_get_resource(pdev, IORESOURCE_MEM, 0);// 将物理地址映射为虚拟地址,用于后续访问寄存器port->base = devm_ioremap_resource(&pdev->dev, iores);if (IS_ERR(port->base)) // 如果映射失败,返回错误码return PTR_ERR(port->base);// 获取 GPIO 控制器的两个中断号port->irq_high = platform_get_irq(pdev, 1);port->irq = platform_get_irq(pdev, 0);if (port->irq < 0) // 如果获取失败,返回错误码return port->irq;// 获取控制器时钟(可选),用于驱动 GPIO 模块port->clk = devm_clk_get(&pdev->dev, NULL);if (IS_ERR(port->clk)) // 若无法获取时钟,不返回错误,仅置 NULLport->clk = NULL;// 准备并启用 GPIO 时钟(若存在),并检查是否成功err = clk_prepare_enable(port->clk);if (err) {dev_err(&pdev->dev, "Unable to enable clock.\n");return err;}// 设置设备的电源状态为活跃,启用运行时电源管理pm_runtime_set_active(&pdev->dev);pm_runtime_enable(&pdev->dev);// 请求设备电源,如果失败则跳转到错误处理err = pm_runtime_get_sync(&pdev->dev);if (err < 0)goto out_pm_dis;// 禁用中断,清除中断状态writel(0, port->base + GPIO_IMR);writel(~0, port->base + GPIO_ISR);// 针对不同硬件类型,设置 GPIO 中断的处理程序if (mxc_gpio_hwtype == IMX21_GPIO) {// 为所有 GPIO 中断设置一个通用的中断处理程序irq_set_chained_handler(port->irq, mx2_gpio_irq_handler);} else {// 单独为每个中断号设置处理程序irq_set_chained_handler_and_data(port->irq,mx3_gpio_irq_handler, port);if (port->irq_high > 0)// 设置 GPIO 16 到 31 的中断处理程序irq_set_chained_handler_and_data(port->irq_high,mx3_gpio_irq_handler,port);}// 初始化基本 GPIO 操作接口,绑定 GPIO 控制寄存器err = 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);if (err) // 如果失败,跳转到错误处理goto out_bgio;// 读取设备树属性,判断是否包含 "gpio_ranges" 属性if (of_property_read_bool(np, "gpio_ranges"))port->gpio_ranges = true;elseport->gpio_ranges = false;// 设置 GPIO 控制器的操作接口,如请求、释放 GPIO 引脚等port->gc.request = mxc_gpio_request;port->gc.free = mxc_gpio_free;port->gc.parent = &pdev->dev;port->gc.to_irq = mxc_gpio_to_irq;// 设置 GPIO 基地址,便于统一管理 GPIO 编号空间port->gc.base = (pdev->id < 0) ? of_alias_get_id(np, "gpio") * 32 :pdev->id * 32;// 注册 GPIO 控制器到内核系统,并将端口数据与控制器关联err = devm_gpiochip_add_data(&pdev->dev, &port->gc, port);if (err)goto out_bgio;// 分配 IRQ 描述符,用于 GPIO 的中断分配irq_base = irq_alloc_descs(-1, 0, 32, numa_node_id());if (irq_base < 0) {err = irq_base;goto out_bgio;}// 添加 IRQ 域,将硬件 IRQ 与 Linux IRQ 映射port->domain = irq_domain_add_legacy(np, 32, irq_base, 0,&irq_domain_simple_ops, NULL);if (!port->domain) {err = -ENODEV;goto out_irqdesc_free;}// 将 GPIO 控制器初始化为通用 IRQ 芯片,支持中断处理err = mxc_gpio_init_gc(port, irq_base, &pdev->dev);if (err < 0)goto out_irqdomain_remove;// 将当前 GPIO 端口加入 mxc_gpio_ports 链表中,便于管理list_add_tail(&port->node, &mxc_gpio_ports);// 保存端口数据指针到平台设备的数据域,便于后续访问platform_set_drvdata(pdev, port);// 释放运行时电源管理引用pm_runtime_put(&pdev->dev);// 返回成功return 0;// 错误处理部分:若电源获取失败,禁用电源管理并释放时钟
out_pm_dis:pm_runtime_disable(&pdev->dev);clk_disable_unprepare(port->clk);// 移除 IRQ 域并释放描述符
out_irqdomain_remove:irq_domain_remove(port->domain);out_irqdesc_free:irq_free_descs(irq_base, 32);out_bgio:// 打印错误信息,显示错误来源和错误码dev_info(&pdev->dev, "%s failed with errno %d\n", __func__, err);return err; // 返回错误码
}
2.2 设置gpio_chip
\Linux-4.9.88\drivers\gpio\gpio-mxc.c
static int mxc_gpio_probe(struct platform_device *pdev)
{//........................// 初始化基本 GPIO 操作接口,绑定 GPIO 控制寄存器err = 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);if (err) // 如果失败,跳转到错误处理goto out_bgio;// 读取设备树属性,判断是否包含 "gpio_ranges" 属性if (of_property_read_bool(np, "gpio_ranges"))port->gpio_ranges = true;elseport->gpio_ranges = false;// 设置 GPIO 控制器的操作接口,如请求、释放 GPIO 引脚等port->gc.request = mxc_gpio_request;port->gc.free = mxc_gpio_free;port->gc.parent = &pdev->dev;port->gc.to_irq = mxc_gpio_to_irq;// 设置 GPIO 基地址,便于统一管理 GPIO 编号空间port->gc.base = (pdev->id < 0) ? of_alias_get_id(np, "gpio") * 32 :pdev->id * 32;// 注册 GPIO 控制器到内核系统,并将端口数据与控制器关联err = devm_gpiochip_add_data(&pdev->dev, &port->gc, port);if (err)goto out_bgio;//........................
}
其中的bgpio_init
函数中提到了GPIO_PSR
、GPIO_DR
、GPIO_GDIR
,这是三个寄存器的地址
GPIO_DR
:gpio pad status register,读取到的data更加准确,比如输出引脚,设置DR为1,想让引脚输出1,但由于某些原因被拉低了电平,变成输出0了此时去读DR该引脚的电平,实际上是1,而读PSD的话则是引脚实际输出的值,也就是0GPIO_DR
:gpio data register,输出方向:输出高低电平,读取引脚输出值;输入方向:读取引脚输入值GPIO_GDIR
:gpio direction register,设置引脚输出/输入方向
来进入这个函数看看:
\Linux-4.9.88\drivers\gpio\gpio-mxc.c
int bgpio_init(struct gpio_chip *gc, struct device *dev,unsigned long sz, void __iomem *dat, void __iomem *set,void __iomem *clr, void __iomem *dirout, void __iomem *dirin,unsigned long flags)
{// 返回值,表示函数执行结果int ret;// 检查 GPIO 位宽是否为 2 的幂,如果不是,返回错误码 -EINVALif (!is_power_of_2(sz))return -EINVAL;// 设置 GPIO 控制器的总位数,sz 表示字节数,乘以 8 转换为位数gc->bgpio_bits = sz * 8;// 如果 GPIO 位数超过 `BITS_PER_LONG`(通常是 32 或 64 位),返回错误码if (gc->bgpio_bits > BITS_PER_LONG)return -EINVAL;// 初始化自旋锁,用于 GPIO 操作的同步保护spin_lock_init(&gc->bgpio_lock);// 将 GPIO 控制器的父设备设置为 dev(GPIO 控制器所属的设备)gc->parent = dev;// 设置 GPIO 控制器的标签名称,便于识别gc->label = dev_name(dev);// 设置 GPIO 基地址为 -1,表示由系统动态分配gc->base = -1;// 设置 GPIO 数量为 bgpio_bits 位宽的大小gc->ngpio = gc->bgpio_bits;// 将 GPIO 请求函数指针设置为 bgpio_requestgc->request = bgpio_request;// 设置 GPIO 数据寄存器和清除寄存器,配置 I/O 访问模式ret = bgpio_setup_io(gc, dat, set, clr, flags);if (ret) // 如果设置失败,返回错误码return ret;// 根据 flags 标志位设置访问器(字节序),以便处理大端和小端模式ret = bgpio_setup_accessors(dev, gc, flags & BGPIOF_BIG_ENDIAN,flags & BGPIOF_BIG_ENDIAN_BYTE_ORDER);if (ret) // 如果设置失败,返回错误码return ret;// 设置 GPIO 引脚的方向寄存器(输入/输出)和方向控制标志ret = bgpio_setup_direction(gc, dirout, dirin, flags);if (ret) // 如果设置失败,返回错误码return ret;// 读取 GPIO 数据寄存器的初始值,保存在 `bgpio_data` 中gc->bgpio_data = gc->read_reg(gc->reg_dat);// 检查是否使用 SET 寄存器来设置 GPIO 数据,更新 bgpio_dataif (gc->set == bgpio_set_set &&!(flags & BGPIOF_UNREADABLE_REG_SET))gc->bgpio_data = gc->read_reg(gc->reg_set);// 检查方向寄存器是否可读,若可读则将值存入 bgpio_dirif (gc->reg_dir && !(flags & BGPIOF_UNREADABLE_REG_DIR))gc->bgpio_dir = gc->read_reg(gc->reg_dir);// 返回成功或最后一个错误码return ret;
}
ret = bgpio_setup_io(gc, dat, set, clr, flags);
该函数就是对相关寄存器进行配置,参数也是bgpio_init
函数传进来的相关寄存器的的地址:
/** 函数:bgpio_setup_io* 功能:根据传入的寄存器和标志配置 GPIO 芯片的输入/输出功能。** 参数:* - gc: 指向 GPIO 芯片的结构体 gpio_chip,包含 GPIO 的操作函数和寄存器指针。* - dat: GPIO 数据寄存器指针,用于读写 GPIO 数据。* - set: GPIO 设置寄存器指针,用于设置 GPIO 的值。* - clr: GPIO 清除寄存器指针,用于清除 GPIO 的值。* - flags: 标志位,用于指定 GPIO 芯片的特定属性,如是否包含输出寄存器。** 返回值:* - 成功时返回 0,若缺少关键的 GPIO 数据寄存器,返回 -EINVAL。*/
static int bgpio_setup_io(struct gpio_chip *gc,void __iomem *dat,void __iomem *set,void __iomem *clr,unsigned long flags)
{// 将数据寄存器的地址存储在 gc->reg_dat 中,用于 GPIO 数据的读写gc->reg_dat = dat;if (!gc->reg_dat)return -EINVAL; // 若数据寄存器缺失,返回 -EINVAL 错误// 根据提供的寄存器设置不同的 GPIO 配置模式if (set && clr) { // 设置寄存器和清除寄存器都存在的情况// 支持通过设置/清除配对寄存器进行 GPIO 设置gc->reg_set = set; // 设置寄存器gc->reg_clr = clr; // 清除寄存器gc->set = bgpio_set_with_clear; // 设置操作函数为 bgpio_set_with_cleargc->set_multiple = bgpio_set_multiple_with_clear; // 多路 GPIO 设置函数} else if (set && !clr) { // 仅有设置寄存器存在// 支持通过单一设置寄存器进行 GPIO 设置gc->reg_set = set;gc->set = bgpio_set_set; // 设置操作函数为 bgpio_set_setgc->set_multiple = bgpio_set_multiple_set;} else if (flags & BGPIOF_NO_OUTPUT) { // 若无输出支持// 标志位 BGPIOF_NO_OUTPUT 表示无输出功能gc->set = bgpio_set_none; // 禁止 GPIO 设置gc->set_multiple = NULL; // 禁止多路 GPIO 设置} else { // 默认 GPIO 设置gc->set = bgpio_set; // 设置操作函数为 bgpio_setgc->set_multiple = bgpio_set_multiple;}// 配置 GPIO 的获取函数,用于读取 GPIO 数据if (!(flags & BGPIOF_UNREADABLE_REG_SET) && (flags & BGPIOF_READ_OUTPUT_REG_SET))// 若设置了 BGPIOF_READ_OUTPUT_REG_SET 且未设置 BGPIOF_UNREADABLE_REG_SET// 选择使用 bgpio_get_set 读取输出寄存器gc->get = bgpio_get_set;else// 默认使用 bgpio_get 获取数据寄存器的值gc->get = bgpio_get;return 0; // 配置成功返回 0
}
2.3 注册gpio_chip
static int mxc_gpio_probe(struct platform_device *pdev)
{// 获取设备树中的设备节点,用于设备信息的解析struct device_node *np = pdev->dev.of_node;// 定义 GPIO 端口结构的指针,将存储 GPIO 控制器的特定信息struct mxc_gpio_port *port;//........................// 注册 GPIO 控制器到内核系统,并将端口数据与控制器关联err = devm_gpiochip_add_data(&pdev->dev, &port->gc, port);if (err)goto out_bgio;//.......................
}
其中devm_gpiochip_add_data
就是注册gpio_chip:
\Linux-4.9.88\drivers\gpio\gpiolib.c:/*** devm_gpiochip_add_data() - 使用资源管理器的 gpiochip_add_data() 函数* @dev: IRQ 控制器所属的设备指针* @chip: 要注册的 GPIO 控制器芯片,chip->base 应已初始化* @data: 附加到 GPIO 控制器的数据指针** 返回值:* 如果 GPIO 芯片注册失败(如 `chip->base` 无效或已与其他芯片相关联),则返回负错误码;* 否则返回 0 表示成功。** 说明:* 该函数将 GPIO 芯片与设备绑定,在设备解绑时会自动释放该 GPIO 芯片。*/
int devm_gpiochip_add_data(struct device *dev, struct gpio_chip *chip,void *data)
{// 定义一个指向 gpio_chip 的指针 ptr,用于保存 GPIO 控制器struct gpio_chip **ptr;int ret;// 分配内存给 ptr,该内存会被设备资源管理器管理,释放函数为 devm_gpio_chip_releaseptr = devres_alloc(devm_gpio_chip_release, sizeof(*ptr),GFP_KERNEL);if (!ptr) // 如果分配失败,返回内存不足的错误码return -ENOMEM;// 调用 gpiochip_add_data 函数将 GPIO 控制器注册到系统ret = gpiochip_add_data(chip, data);if (ret < 0) { // 如果注册失败,释放之前分配的 ptr 内存devres_free(ptr);return ret;}// 注册成功后,将 ptr 指向 GPIO 控制器 `chip`*ptr = chip;// 将 ptr 添加到 dev 设备的资源管理器列表中,// 这样当 dev 被解绑时,ptr 指向的 GPIO 控制器会自动释放devres_add(dev, ptr);// 成功返回 0return 0;
}
其中ret = gpiochip_add_data(chip, data);
不仅仅是将 GPIO 控制器注册到系统,还有设置注册gpio_chip:
/*** gpiochip_add_data() - 注册一个 gpio_chip* @chip: 要注册的 gpio_chip,chip->base 必须已经初始化* * 上下文: 可能在中断请求(irq)可用之前调用** 返回值: 如果无法注册芯片,将返回负的错误码,例如* 因为 chip->base 无效或已与不同的芯片关联。否则返回零表示成功。** 当 gpiochip_add_data() 在启动过程中被早期调用,以便可以* 自由使用 GPIO 时,chip->parent 设备必须在 gpio 框架的* arch_initcall() 之前注册。否则,GPIO 的 sysfs 初始化将会失败。** gpiochip_add_data() 必须在 gpiolib 初始化之后调用,* 也就是在 core_initcall() 之后。** 如果 chip->base 为负,则请求动态分配一段有效的 GPIO 范围。*/
int gpiochip_add_data(struct gpio_chip *chip, void *data)
{unsigned long flags; // 用于保存中断状态的变量int status = 0; // 状态变量,默认为0(成功)unsigned i; // 循环变量int base = chip->base; // 获取芯片的基地址struct gpio_device *gdev; // 定义 gpio_device 结构体的指针/** 第一步: 分配并填充内部状态容器,并* 设置结构体设备。*/gdev = kzalloc(sizeof(*gdev), GFP_KERNEL); // 分配内存if (!gdev) // 检查内存分配是否成功return -ENOMEM; // 内存分配失败,返回错误码gdev->dev.bus = &gpio_bus_type; // 设置设备总线类型为 GPIOgdev->chip = chip; // 将 gpio_chip 关联到 gdevchip->gpiodev = gdev; // 将 gdev 关联到 chip// 如果芯片有父设备,设置父设备信息if (chip->parent) {gdev->dev.parent = chip->parent; // 设置父设备gdev->dev.of_node = chip->parent->of_node; // 设置设备树节点}#ifdef CONFIG_OF_GPIO/* 如果 gpiochip 有分配的 OF 节点,则优先使用 */if (chip->of_node)gdev->dev.of_node = chip->of_node; // 设置 OF 节点
#endif// 获取唯一的设备 IDgdev->id = ida_simple_get(&gpio_ida, 0, 0, GFP_KERNEL);if (gdev->id < 0) { // 检查 ID 获取是否成功status = gdev->id; // 记录错误状态goto err_free_gdev; // 释放 gdev 并返回错误}// 设置设备名称dev_set_name(&gdev->dev, "gpiochip%d", gdev->id);device_initialize(&gdev->dev); // 初始化设备dev_set_drvdata(&gdev->dev, gdev); // 绑定驱动数据// 设置设备所有者if (chip->parent && chip->parent->driver)gdev->owner = chip->parent->driver->owner; // 从父设备获取驱动所有者else if (chip->owner)// TODO: 移除 chip->ownergdev->owner = chip->owner; // 使用芯片的所有者elsegdev->owner = THIS_MODULE; // 默认使用当前模块// 分配 GPIO 描述符数组gdev->descs = kcalloc(chip->ngpio, sizeof(gdev->descs[0]), GFP_KERNEL);if (!gdev->descs) { // 检查内存分配是否成功status = -ENOMEM; // 内存分配失败,返回错误码goto err_free_gdev; // 释放 gdev 并返回错误}// 检查 GPIO 数量是否为零if (chip->ngpio == 0) {chip_err(chip, "tried to insert a GPIO chip with zero lines\n");status = -EINVAL; // 参数无效,返回错误码goto err_free_descs; // 释放描述符数组并返回错误}// 设置设备标签if (chip->label)gdev->label = kstrdup(chip->label, GFP_KERNEL); // 复制标签elsegdev->label = kstrdup("unknown", GFP_KERNEL); // 默认标签为 "unknown"if (!gdev->label) { // 检查内存分配是否成功status = -ENOMEM; // 内存分配失败,返回错误码goto err_free_descs; // 释放描述符数组并返回错误}gdev->ngpio = chip->ngpio; // 设置 GPIO 数量gdev->data = data; // 关联数据spin_lock_irqsave(&gpio_lock, flags); // 获取自旋锁并保存中断状态/** TODO: 为这个芯片在全局 GPIO 编号空间中分配一个 Linux GPIO 编号基。* 从长远来看,我们希望摆脱这个编号空间,仅使用描述符,* 但这可能是一个不切实际的想法。* 在我们摆脱 sysfs 接口之前,它不会发生。*/if (base < 0) { // 如果基地址为负,则请求分配一个基地址base = gpiochip_find_base(chip->ngpio); // 查找可用的基地址if (base < 0) { // 检查基地址分配是否成功status = base; // 记录错误状态spin_unlock_irqrestore(&gpio_lock, flags); // 解锁自旋锁goto err_free_label; // 释放标签并返回错误}/** TODO: 不应该有必要将分配的基地址反映到 GPIO 子系统之外。* 检查驱动程序,看看是否有人使用这个,其他情况下丢弃这个,* 并分配一个毒值。*/chip->base = base; // 设置芯片的基地址}gdev->base = base; // 设置 gdev 的基地址// 将 gdev 添加到 GPIO 设备列表中status = gpiodev_add_to_list(gdev);if (status) { // 检查是否成功添加spin_unlock_irqrestore(&gpio_lock, flags); // 解锁自旋锁goto err_free_label; // 释放标签并返回错误}spin_unlock_irqrestore(&gpio_lock, flags); // 解锁自旋锁// 初始化每个 GPIO 描述符for (i = 0; i < chip->ngpio; i++) {struct gpio_desc *desc = &gdev->descs[i]; // 获取当前 GPIO 描述符desc->gdev = gdev; // 关联描述符到 gdev/** REVISIT: 大多数硬件初始化 GPIO 时将其设置为输入(通常启用上拉),* 从而最小化功耗。Linux 代码应该首先设置 GPIO 方向;* 但在此之前,如果 chip->get_direction 未设置,可能会在 sysfs 中暴露错误的方向。*/if (chip->get_direction) { // 如果提供了获取方向的回调/** 如果有 .get_direction,设置初始方向标志从硬件中获取。*/int dir = chip->get_direction(chip, i); // 获取当前 GPIO 的方向if (!dir) // 如果方向为输入set_bit(FLAG_IS_OUT, &desc->flags); // 设置为输出} else if (!chip->direction_input) { // 如果没有设置方向输入回调/** 如果芯片缺少 .direction_input 回调,* 我们逻辑上假设所有 GPIO 都是输出。*/set_bit(FLAG_IS_OUT, &desc->flags); // 设置为输出}}#ifdef CONFIG_PINCTRLINIT_LIST_HEAD(&gdev->pin_ranges); // 初始化引脚范围列表
#endif// 设置 GPIO 描述符名称status = gpiochip_set_desc_names(chip);if (status) // 检查是否成功设置名称goto err_remove_from_list;// 初始化 GPIO 中断芯片有效掩码status = gpiochip_irqchip_init_valid_mask(chip);if (status) // 检查是否成功初始化goto err_remove_from_list;// 添加 GPIO 设备树支持status = of_gpiochip_add(chip);if (status) // 检查是否成功添加goto err_remove_chip;acpi_gpiochip_add(chip); // 添加 ACPI 支持/** 首先添加字符设备,然后添加设备,* 我们可以在 sysfs 中的* /sys/bus/gpio/devices/gpiochipN/dev 下创建设备节点条目,* 可以用于冷启动设备节点和其他 udev 业务。* 我们只有在 gpiolib 初始化后才能这样做。* 否则,将推迟到后面。*/if (gpiolib_initialized) { // 如果 gpiolib 已初始化status = gpiochip_setup_dev(gdev); // 设置 GPIO 设备if (status) // 检查设置是否成功goto err_remove_chip; // 释放资源并返回错误}return 0; // 成功返回// 错误处理部分
err_remove_chip:acpi_gpiochip_remove(chip); // 移除 ACPI 设备gpiochip_free_hogs(chip); // 释放 GPIO 设备的资源of_gpiochip_remove(chip); // 移除设备树相关资源gpiochip_irqchip_free_valid_mask(chip); // 释放中断掩码资源
err_remove_from_list:spin_lock_irqsave(&gpio_lock, flags); // 获取自旋锁list_del(&gdev->list); // 从设备列表中删除 gdevspin_unlock_irqrestore(&gpio_lock, flags); // 解锁自旋锁
err_free_label:kfree(gdev->label); // 释放标签内存
err_free_descs:kfree(gdev->descs); // 释放描述符数组内存
err_free_gdev:ida_simple_remove(&gpio_ida, gdev->id); // 移除设备 ID/* 此处失败可能会导致系统无法启动... */pr_err("%s: GPIOs %d..%d (%s) failed to register\n", __func__,gdev->base, gdev->base + gdev->ngpio - 1,chip->label ? : "generic"); // 打印错误信息kfree(gdev); // 释放 gdev 内存return status; // 返回错误状态
}
总结
总结了Linux 4.9.88内核中GPIO子系统的使用与配置,包括设备树中的GPIO节点定义、GPIO控制器的配置、及其驱动程序的编写过程。内容涵盖了GPIO控制器的初始化步骤、关键的硬件资源管理和内核资源分配。通过对gpio-mxc.c源码的深入解析,展示了如何在GPIO设备树中定义gpio-controller和#gpio-cells等必要属性以标识控制器类型与引脚数量。驱动代码重点阐述了mxc_gpio_probe函数的工作流程,包括GPIO端口结构分配、时钟资源的启用、GPIO控制寄存器的映射等。同时,通过对bgpio_init函数的分析,详细解释了GPIO寄存器(如GPIO_PSR、GPIO_DR和GPIO_GDIR)的作用与配置方式,为GPIO控制器的开发提供了理论基础和实践参考。