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.rhkb.cn/news/465121.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【Linux系统编程】第四十二弹---多线程编程全攻略:涵盖线程创建、异常处理、用途、进程对比及线程控制

✨个人主页&#xff1a; 熬夜学编程的小林 &#x1f497;系列专栏&#xff1a; 【C语言详解】 【数据结构详解】【C详解】【Linux系统编程】 目录 1、线程创建 2、线程异常 3、线程用途 4、进程 VS 线程 5、线程控制 5.1、创建和等待线程 1、线程创建 线程能看到进程的大…

Qt入门基础分享

文章目录 学习 Qt 语言之前的基本知识1. 编程基础语法:面向对象编程(OOP):基本数据结构:了解数组、链表、栈、队列、树(如二叉树、平衡树)、图(如邻接矩阵、邻接表)等。算法:熟悉常见的排序算法(如快速排序、归并排序、冒泡排序)和查找算法(如线性查找、二分查找)…

超萌!HTMLCSS:超萌卡通熊猫头

效果演示 创建了一个卡通风格的熊猫头 HTML <div class"box"><div class"head"><div class"head-copy"></div><div class"ears-left"></div><div class"ears-right"></di…

使用 AMD GPU 的 ChatGLM-6B 双语语言模型

Using the ChatGLM-6B bilingual language model with AMD GPUs — ROCm Blogs 2024 年 4 月 4 日&#xff0c;作者&#xff1a; Phillip Dang. ChatGLM-6B 是一个开源的中英双语语言模型&#xff0c;拥有 62 亿参数。它基于通用语言模型 (GLM) 架构&#xff0c;针对中文对话进…

计算并联电阻的阻值

计算并联电阻的阻值 C语言代码C代码Java代码Python代码 &#x1f490;The Begin&#x1f490;点点关注&#xff0c;收藏不迷路&#x1f490; 对于阻值为r1和r2的电阻&#xff0c;其并联电阻阻值公式计算如下&#xff1a; R1/(1/r11/r2) 输入 两个电阻阻抗大小&#xff0c;浮…

创建线程时传递参数给线程

在C中&#xff0c;可以使用 std::thread 来创建和管理线程&#xff0c;同时可以通过几种方式将参数传递给线程函数。这些方法包括使用值传递、引用传递和指针传递。下面将对这些方法进行详细讲解并给出相应的代码示例。 1. 值传递参数 当你创建线程并希望传递参数时&#xff…

AAA 数据库事务隔离级别及死锁

目录 一、事务的四大特性&#xff08;ACID&#xff09; 1. 原子性(atomicity)&#xff1a; 2. 一致性(consistency)&#xff1a; 3. 隔离性(isolation)&#xff1a; 4. 持久性(durability)&#xff1a; 二、死锁的产生及解决方法 三、事务的四种隔离级别 0 .封锁协议 …

数字后端零基础入门系列 | Innovus零基础LAB学习Day9

Module 16 Wire Editing 这个章节的学习目标是学习如何在innovus中手工画线&#xff0c;切断一根线&#xff0c;换孔&#xff0c;更改一条net shape的layer和width等等。这个技能是每个数字IC后端工程师必须具备的。因为项目后期都需要这些技能来修复DRC和做一些手工custom走线…

前后端交互通用排序策略

目录 排序场景 排序实现思路 1. 静态代码排序实现 2.数据库驱动排序实现 3. 基于Java反射的动态排序实现 通用排序工具 SortListUtil 结语 排序场景 在面向前端数据展示的应用场景中&#xff0c;我们旨在实现一个更加灵活的排序机制&#xff0c;该机制能够支持对从后端传递…

前端入门一之CSS知识详解

前言 CSS是前端三件套之一&#xff0c;在MarkDown中也完美兼容这些语法&#xff1b;这篇文章是本人大一学习前端的笔记&#xff1b;欢迎点赞 收藏 关注&#xff0c;本人将会持续更新。 文章目录 Emmet语法&#xff1a;CSS基本语法&#xff1a;css语法结构只有3种&#xff1a…

leetcode | 88. 合并两个有序数组

题目描述 88. 合并两个有序数组 分析 题目不允许更改nums1的长度&#xff0c;要求原地更改。 题目其实不难&#xff0c;如果记住可以从后往前合并的解法&#xff0c;但是正向遍历的问题是什么呢&#xff1f; ——元素覆盖。那为什么负向遍历就不会有这个问题呢&#xff1f;…

跳蚤市场之商品发布功能

一 商品类别和小类的联动 以下是一个示例代码&#xff0c;展示了如何实现商品类别中大类和小类的联动。 商品大类选择框、小类选择框 的设计 html部分 <form id"category-form"><label for"major-category">大类&#xff1a;</label&g…

OpenAI 发布了新的事实性基准——SimpleQA

SimpleQA 简介 名为 SimpleQA 的事实性基准&#xff0c;用于衡量语言模型回答简短的事实性问题的能力。 人工智能领域的一个悬而未决的问题是如何训练模型&#xff0c;使其产生符合事实的回答。 目前的语言模型有时会产生错误的输出或没有证据证明的答案&#xff0c;这个问题…

Android camera2

一、序言 为了对阶段性的知识积累、方便以后调查问题&#xff0c;特做此文档&#xff01; 将以camera app 使用camera2 api进行分析。 (1)、打开相机 openCamera (2)、创建会话 createCaptureSession (3)、开始预览 setRepeatingRequest (4)、停止预览 stopRepeating (5)、关闭…

Javascript属性遮蔽问题

先了解一下Object.defineProperty()方法 Object.defineProperty() 静态方法会直接在一个对象上定义一个新属性&#xff0c;或修改其现有属性&#xff0c;并返回此对象。 //obj&#xff1a;要定义的对象 //prop&#xff1a;一个字符串或 Symbol&#xff0c;指定了要定义或修改…

vue3项目history模式部署404处理,使用 historyApiFallback 中间件支持单页面应用路由

vue3项目history模式部署404处理&#xff0c;使用 historyApiFallback 中间件支持单页面应用路由 在现代的 web 开发中&#xff0c;单页面应用&#xff08;SPA&#xff09;变得越来越流行。这类应用通常依赖于客户端路由来提供流畅的用户体验&#xff0c;但在服务器端&#xf…

【vim文本编辑器gcc编译器gdb调试器】

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 一、vimvim安装vim常用快捷键vim使用vimtutor zh文档 二、gcc编译器安装gcc工具编译源代码 三、gdb调试器gdb安装gdb常用指令gdb简单上手使用gdb的单步调试功能 总结…

企业数字化转型的架构治理策略:核心问题、深度分析与优化路径

在当今的商业环境中&#xff0c;企业数字化转型已成为实现可持续发展、增强竞争力的战略选择。企业架构治理&#xff08;Enterprise Architecture Governance Capability, EAGC&#xff09;在数字化转型中扮演着保障架构一致性、提升变革效能的关键角色。本指南深入解析了如何通…

基于springboot+vue实现的农产品物流系统

基于springbootvue实现的农产品物流系统 &#xff08;源码L文ppt&#xff09;4-107 摘 要 随着现代信息技术的迅猛发展&#xff0c;农产品物流系统应运而生&#xff0c;成为连接生产者与消费者的重要桥梁。该系统采用java语言&#xff0c; Spring Boot框架&#xff0c;结合My…