Linux 驱动-GPIO 三级节点获取和控制相关API

前言:gpio 三级节点获取和控制相关API函数

文章目录


一、 需求

gpio 配置在三级目录下,如何获取和控制,相关API了解熟悉。

gpio 配置,并不是都是配置在二级目录下,而是配置在三级目录下,下面列举二级目录的配置和三级目录的配置,如下:

  • 二级目录配置,gpio 配置在设备树根节点二级目录节点下
  • 可能存在三级目录配置,gpio 配置在设备树根节点三级目录节点下,如下:

二、参考文档

GPIO子系统三级节点操作函数

三、多级 GPIO 节点操作核心函数

函数名 作用描述 所属头文件 关键参数说明 返回值说明
device_get_child_node_count 统计设备节点的直接子节点数量 <linux/device.h> dev: 父设备节点 子节点数量 (失败返回0)
device_get_next_child_node 遍历获取下一个子节点的对象地址 <linux/device.h> dev: 父设备节点 child: 当前子节点 下一个子节点的 fwnode_handle (没有则返回NULL)
fwnode_get_named_gpiod 从指定节点和属性名获取GPIO描述符 <linux/gpio/consumer.h> fwnode: 节点对象 propname: 属性名 index: 属性中GPIO的索引 dflags: 初始化配置 label: GPIO描述标签 GPIO描述符指针 (失败返回NULL)

四、关键操作流程与技巧

  • **遍历子节点:**通常先用device_get_child_node_count了解子节点数量,然后结合 device_get_next_child_node 循环遍历所有子节点。

  • **获取GPIO描述符: fwnode_get_named_gpiod 功能强大,可通过 index 参数指定获取属性中的第几个GPIO引脚,dflags 参数能直接设置GPIO的初始状态(如输入、输出高/低电平)。

  • **GPIO配置与读写: 获取到 struct gpio_desc * 后,可以使用 gpiod_direction_input/output、gpiod_get/set_value 等函数进行配置和读写。操作完成后,记得用 gpiod_put 释放资源。

设备树节点更改

位置:kernel/arch/arm64/boot/dts/rockchip/rk3568-evb1-ddr4-v10.dtsi,两点:

  • 在根节点下定义led1/led2 两个二级节点,在二级节点下创建my-gpios 三级节点。其中一个 pinctrl ,设置pinctrl-0 的引用
  • 在pin-ctrl 设备树节点中定义 my_gpio 节点。

代码如下:

java 复制代码
/ {
	model = "Rockchip RK3568 EVB1 DDR4 V10 Board";
	compatible = "rockchip,rk3568-evb1-ddr4-v10", "rockchip,rk3568";

....................................
my_gpio:gpio1_a0 {
    compatible = "mygpio";
	led1{
		my-gpios = <&gpio1 RK_PA0 GPIO_ACTIVE_HIGH>, <&gpio1 RK_PB1 GPIO_ACTIVE_HIGH>;
		pinctrl-names = "default";
		pinctrl-0 = <&my_gpio_ctrl>;		
	};
	led2{
		my-gpios = <&gpio1 RK_PB0 GPIO_ACTIVE_HIGH>;	
	};
};
	

};
java 复制代码
&pinctrl {
 .......................
  	mygpio{
		my_gpio_ctrl:my-gpio-ctrl {
			rockchip,pins = <1 RK_PA0 RK_FUNC_GPIO &pcfg_pull_none>;
		};
	};
..................................
};

注意点:

  • 节点已经定义了 gpio1_a0 ,标签为my_gpio ,为什么还要在&pinctrl 里面定义关联的my_gpio_ctrl 呢?
  • 根节点里面的设备树配置如何和pin-ctrl 里面关联的呢? 就是通过 pinctrl-0 属性值关联的。

驱动代码

java 复制代码
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/mod_devicetable.h>
#include <linux/gpio/consumer.h>
#include <linux/gpio.h>

unsigned int count;
struct fwnode_handle *child_fw = NULL;
struct gpio_desc *led[2];
int i = 0;
int num = 0;

// 平台设备初始化函数
static int my_platform_probe(struct platform_device *dev)
{
    printk("This is my_platform_probe\n");

    // 获取父设备节点的子设备节点数量
    count = device_get_child_node_count(&dev->dev);
    printk("count is %d\n", count);

    for (i = 0; i < count; i++)
    {
        // 获取下一个子设备节点
        child_fw = device_get_next_child_node(&dev->dev, child_fw);

        if (child_fw)
        {
            // 获取子设备节点中名为 "my-gpios" 的 GPIO 描述
            led[i] = fwnode_get_named_gpiod(child_fw, "my-gpios", 0, 0, "LED");
        }

        // 将 GPIO 描述转换为 GPIO 号
        num = desc_to_gpio(led[i]);
        printk("num is %d\n", num);
    }

    return 0;
}

// 平台设备的移除函数
static int my_platform_remove(struct platform_device *pdev)
{
    printk(KERN_INFO "my_platform_remove: Removing platform device\n");

    // 清理设备特定的操作
    // ...

    return 0;
}

const struct of_device_id of_match_table_id[] = {
    {.compatible = "mygpio"},
};

// 定义平台驱动结构体
static struct platform_driver my_platform_driver = {
    .probe = my_platform_probe,
    .remove = my_platform_remove,
    .driver = {
        .name = "my_platform_device",
        .owner = THIS_MODULE,
        .of_match_table = of_match_table_id,
    },
};

// 模块初始化函数
static int __init my_platform_driver_init(void)
{
    int ret;

    // 注册平台驱动
    ret = platform_driver_register(&my_platform_driver);
    if (ret)
    {
        printk(KERN_ERR "Failed to register platform driver\n");
        return ret;
    }

    printk(KERN_INFO "my_platform_driver: Platform driver initialized\n");

    return 0;
}

// 模块退出函数
static void __exit my_platform_driver_exit(void)
{
    // 注销平台驱动
    platform_driver_unregister(&my_platform_driver);

    printk(KERN_INFO "my_platform_driver: Platform driver exited\n");
}

module_init(my_platform_driver_init);
module_exit(my_platform_driver_exit);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("fangchen");

实验结果

五、GPIO 三级节点获取和控制相关API 总结

在 Linux 内核中,操作 GPIO(通用输入输出)主要有两套 API:一套是基于描述符(descriptor-based) 的现代接口,另一套是基于整数(legacy) 的传统接口。

主要 API

下面这个表格汇总了基于描述符的 GPIO 三级节点获取和控制相关的主要 API:

类别 函数名 功能描述
获取GPIO描述符 gpiod_get() 基础函数,通过设备节点和名称获取GPIO描述符
gpiod_get_index() 获取属性中包含多个GPIO引脚时的指定索引引脚
gpiod_get_optional() 可选获取,GPIO不存在时返回NULL而不报错
fwnode_get_named_gpiod() 通过节点对象地址和属性名获取GPIO结构描述
释放GPIO描述符 gpiod_put() 释放之前申请的GPIO资源
方向控制 gpiod_direction_input() 配置GPIO为输入模式
gpiod_direction_output() 配置GPIO为输出模式,并可指定初始输出值
电平读写 gpiod_get_value() 读取GPIO的当前电平状态
gpiod_set_value() 设置GPIO的输出电平状态
状态查询 gpiod_get_direction() 查询GPIO当前是输入还是输出模式

关键API详解

  • 获取GPIO描述符:这是控制GPIO的第一步。gpiod_get() 是最基础的函数,你需要提供设备结构体指针、在设备树中定义的GPIO名称以及初始标志。如果设备树属性包含多个GPIO引脚,可以使用 gpiod_get_index() 指定索引。fwnode_get_named_gpiod() 则更底层,直接通过节点对象地址和属性名获取。

  • 配置GPIO方向:获取描述符后,需要设置方向。使用 gpiod_direction_input() 设置为输入(如读取按键),使用gpiod_direction_output()设置为输出(如控制LED)并可同时设置初始电平。

  • 电平读写与控制:配置好方向后,对于输入GPIO,使用gpiod_get_value()读取当前电平;对于输出GPIO,使用 gpiod_set_value() 设置输出电平。

  • 释放资源:当不再使用某个GPIO时,务必调用 gpiod_put() 释放资源。

三级节点操作辅助函数

  • 在设备树中,GPIO控制器可能包含多级子节点。以下辅助函数有助于遍历和操作这些节点:

  • device_get_next_child_node():用于遍历父设备节点下的所有子设备节点。传入父设备指针和当前子节点(首次调用可传入NULL),函数返回下一个子节点的指针,方便逐个处理。

  • device_get_child_node_count():用于统计指定设备节点下直接子节点的数量,有助于在遍历前了解子设备规模。

总结

  • 核心内容,对于二级节点下的三级节点的gpio 属性相关获取的基本方法,讨论、验证 api.
  • 获取到了三级节点下的gpio 描述符,剩下的就是递归、基本gpio 调用方法了。