GPIO子系统中Controller驱动源码分析

往期内容

本专栏往期内容:

  1. Pinctrl子系统和其主要结构体引入
  2. Pinctrl子系统pinctrl_desc结构体进一步介绍
  3. Pinctrl子系统中client端设备树相关数据结构介绍和解析
  4. inctrl子系统中Pincontroller构造过程驱动分析:imx_pinctrl_soc_info结构体
  5. Pinctrl子系统中client端使用pinctrl过程的驱动分析
  6. Pinctrl子系统中Pincontroller和client驱动程序的编写
  7. GPIO子系统层次与数据结构详解-CSDN博客

input子系统专栏:

  1. 专栏地址:input子系统
  2. input角度:I2C触摸屏驱动分析和编写一个简单的I2C驱动程序
    – 末片,有往期内容观看顺序

I2C子系统专栏:

  1. 专栏地址:IIC子系统
  2. 具体芯片的IIC控制器驱动程序分析:i2c-imx.c-CSDN博客
    – 末篇,有往期内容观看顺序

总线和设备树专栏:

  1. 专栏地址:总线和设备树
  2. 设备树与 Linux 内核设备驱动模型的整合-CSDN博客
    – 末篇,有往期内容观看顺序

img

目录

  • 往期内容
  • 前言
  • 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中也可以找到

img

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

img

\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

img

\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_PSRGPIO_DRGPIO_GDIR,这是三个寄存器的地址

  • GPIO_DR:gpio pad status register,读取到的data更加准确,比如输出引脚,设置DR为1,想让引脚输出1,但由于某些原因被拉低了电平,变成输出0了此时去读DR该引脚的电平,实际上是1,而读PSD的话则是引脚实际输出的值,也就是0
  • GPIO_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

img

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控制器的开发提供了理论基础和实践参考。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.xdnf.cn/news/4196.html

如若内容造成侵权/违法违规/事实不符,请联系一条长河网进行投诉反馈,一经查实,立即删除!

相关文章

【系统架构设计师】六、UML建模与架构文档化

在20世纪70年代&#xff0c;陆续出现了面向对象的建模方法&#xff0c;UML&#xff08;统一建模语言&#xff09;的出现&#xff0c;以融合了多种面向对象建模方法&#xff0c;简介的图形和符号&#xff0c;直观的表示和强大的表示能力&#xff0c;得到了工业界与学术界认可。它…

【实用技能】在 SQL Server 中使用 LIMIT 子句的替代方案

在数据库管理中&#xff0c;有效限制查询结果对于优化性能和确保检索相关数据至关重要。许多 SQL 数据库系统&#xff08;例如 MySQL 和 PostgreSQL&#xff09;都使用LIMIT子句来指定查询返回的记录数。但是&#xff0c;SQL Server 不支持该LIMIT子句&#xff0c;而是选择诸如…

Apache-Hive数据库使用学习

前期准备 Hadoop-分布式部署&#xff08;服务全部在线&#xff09; Mysql-node1节点部署&#xff08;确认安装正常&#xff09; apache-hive -node1节点部署&#xff08;需要与MySQL元数据联动存储&#xff09; 参考博客&#xff1a; Hadoop Hadoop集群搭建-完全分布式_hadoop完…

Webserver(3.2)锁

目录 互斥量死锁未解锁重复加锁多个锁 读写锁案例 互斥量 接上一章&#xff0c;卖票存在线程安全问题。 #include<stdio.h> #include<pthread.h> #include<unistd.h> int tickets1000;//局部变量就是每个人卖100张&#xff0c;全局变量就是一起卖100张&…

105. UE5 GAS RPG 搭建主菜单

在这一篇&#xff0c;我们将实现对打开游戏显示的主菜单进行搭建&#xff0c;主菜单将显示游戏主角&#xff0c;游戏名称和进入游戏和退出游戏两个按钮。 搭建菜单场景 我们将主菜单设置为一个单独的场景&#xff0c;前面可以显示对应的UI控件&#xff0c;用于玩家操作&#…

语义分割——U-Net

U-Net是继FCN之后又一个经典的语义分割网络模型&#xff0c;并且也是很多后续语义分割模型的“祖宗”。这个网络模型是2015年提出来的&#xff0c;它具有一个非常对称的结构&#xff0c;很像字母“U”&#xff0c;所以被称作U-Net。U-Net被广泛应用于医学影像领域&#xff0c;如…

AI之硬件对比:据传英伟达Nvidia2025年将推出RTX 5090-32GB/RTX 5080-24GB、华为2025年推出910C/910D

AI之硬件对比&#xff1a;据传英伟达Nvidia2025年将推出RTX 5090-32GB/RTX 5080-24GB、华为2025年推出910C/910D 目录 Nvidia的显卡 Nvidia的5090/5080/4090/4080&#xff1a;据传传英伟达Nvidia RTX 5090后续推出32GB版且RTX 5080后续或推出24GB版 RTX 5090相较于RTX 4090&…

Android无限层扩展多级recyclerview列表+实时搜索弹窗

业务逻辑&#xff1a; 点击选择&#xff0c;弹出弹窗&#xff0c;列表数据由后台提供&#xff0c;不限层级&#xff0c;可叠加无限层子级&#xff1b; 点击item展开收起&#xff0c;点击尾部icon单选选中&#xff0c;点击[确定]为最终选中&#xff0c;收起弹窗&#xff1b; 搜索…

SpringBoot+ClickHouse集成

前面已经完成ClickHouse的搭建&#xff0c;创建账号&#xff0c;创建数据库&#xff0c;保存数据库等&#xff0c;接下来就是在SpringBoot项目中集成ClickHouse。 一&#xff0c;引入依赖 <!-- SpringBoot集成ClickHouse --> <dependency><groupId>com.baom…

【基于轻量型架构的WEB开发】课程 12.5 数据回写 Java EE企业级应用开发教程 Spring+SpringMVC+MyBatis

12.5 数据回写 12.5.1 普通字符串的回写 接下来通过HttpServletResponse输出数据的案例&#xff0c;演示普通字符串的回写&#xff0c;案例具体实现步骤如下。 1 创建一个数据回写类DataController&#xff0c;在DataController类中定义 showDataByResponse()方法&#xff…

Java实现JWT登录认证

文章目录 什么是JWT?为什么需要令牌?如何实现?添加依赖&#xff1a;JwtUtils.java&#xff08;生成、解析Token的工具类&#xff09;jwt配置&#xff1a;登录业务逻辑&#xff1a;其他关联代码&#xff1a;测试&#xff1a; 什么是JWT? JWT&#xff08;Json Web Token&…

光伏无人机踏勘,照亮光伏未来!

光伏电站选址地分散在各地&#xff0c;想要精准获取该地的地形特点与屋顶面积等信息&#xff0c;传统的人工踏勘耗时耗力且精度无法保证&#xff0c;难以满足现代光伏项目的规模快发发展需求。光伏无人机踏勘&#xff0c;照亮光伏未来&#xff01; 在光伏无人机智能踏勘设计系统…

Vue全栈开发旅游网项目(7)-搜索界面开发及其接口联调

1.搜索界面开发 1.1 模糊查询 文件地址&#xff1a;pycharm- class SightListView(ListView):paginate_by 5def get_queryset(self):#is_validTrue&#xff1a;表中is_valid列&#xff0c;有值则被查询出来query Q(is_validTrue)#1.获得热门景点is_hot self.request.GET.…

『 Linux 』网络传输层 - TCP(二)

文章目录 TCP六个标志位TCP的连接三次握手 四次挥手为什么是三次握手和四次挥手 重传机制 TCP六个标志位 在TCP协议报文的报头中存在一个用于标志TCP报文类型的标志位(不考虑保留标志位),这些标志位以比特位选项的方式存在,即对应标志位为0则表示为假,对应标志位为1则为真; SYN…

Django学习-项目部署

WSGI定义&#xff1a; uWSGI定义&#xff1a; 安装uWSGI&#xff1a; 配置uWSGI&#xff1a; uWSGI常见问题汇总&#xff1a; 安装nginx&#xff1a; 配置&#xff1a; 启动/停止dnginx 修改uWSGI配置&#xff1a; 常见问题解决方法&#xff1a; nginx静态文件配置&#xff…

迅为RK3588开发板Android多屏显示之多屏同显和多屏异显

迅为RK3588开发板是一款低功耗、高性能的处理器&#xff0c;适用于基于arm的PC和Edge计算设备、个人移动互联网设备等数字多媒体应用&#xff0c;RK3588支持8K视频编解码&#xff0c;内置GPU可以完全兼容OpenGLES 1.1、2.0和3.2。RK3588引入了新一代完全基于硬件的最大4800万像…

QML项目实战:自定义Button

目录 一.添加模块 ​1.QtQuick.Controls 2.1 2.QtGraphicalEffects 1.12 二.自定义Button 1.颜色背景设置 2.设置渐变色背景 3.文本设置 4.点击设置 5.阴影设置 三.效果 1.当enabled为true 2.按钮被点击时 3.当enabled为false 四.代码 一.添加模块 1.QtQuick.Con…

基于C#实现Windows后台窗口操作与图像处理技术分析

在Windows编程中&#xff0c;操作后台窗口是一项复杂而有用的技术。它可以用来自动化用户界面测试、应用程序机器人等场景。本文将深入探讨如何在C#中绑定后台窗口、获取后台窗口界面图片&#xff0c;以及在图片中寻找指定图标并获取坐标。本技术文章结合最先进的资料与实践经验…

数据库基础(1) . 关系型数据库

1.数据库 database 1.1.数据持久化 数据持久化&#xff08;Data Persistence&#xff09;指的是将程序中的数据保存到某种持久化的存储介质&#xff08;如硬盘、SSD、磁带等&#xff09;上的过程&#xff0c;使得即使在程序终止后&#xff0c;数据依然可以被保留下来并在下次…