Linxu 驱动 中 GPIO 与 pinctrl 子系统
前言
如果对于一个初学者来说,GPIO 子系统已经了解了部分,这里目标就是GPIO子系统结合pinctrl 子系统的知识理解。
必须具备知识点:
- gpio 子系统理解,包括基本GPIO的API
- pinctrl 子系统的理解,包括pinctrl 子系统的基本API
一、参考资料
GPIO子系统GPIO的调试方法
GPIO的调试方法
Pinctrl子系统和GPIO子系统——LED实验
gpio子系统与pinctrl子系统一:关系
Linux驱动-GPIO基本函数api
Linux 驱动-GPIO 三级节点获取和控制相关API
GPIO子系统-第135章 GPIO子系统与pinctrl子系统相结合实验
Linux驱动设备树-pinctrl篇
深入思考
首先,对于初学者来说,gpio结合pinctrl 这个需求 或者 说这个意思到底是什么? 上面提供的参考资料如果说深入研究、自己结合自己之前的知识点 理解后。 回过头来看 就像一个圈圈一样 知识点实现了闭环。
在 Linux驱动-GPIO基本函数api 中,我就提到过一个问题,为什么 抛出问题-为什么定义一个自己的节点,需要在设备树根节点里面创建一个并在pin-ctrl 里面在创建一个节点呢?为什么必须分两步? 在 Linux 驱动-GPIO 三级节点获取和控制相关API 学习的内容中也 列出了实现所需要修改的设备树 ,反问道为什么设备树需要这么修改。 在 Linux驱动设备树-pinctrl篇 中,我们实验过 gpio+pinctrl 实现默认复用gpio 脚具体功能。
回过头来看,其实我们已经学习过gpio+pinctrl 的使用方法和最基本的理论知识并实践过。 但是 知识点都是零散的,突然对知识点总结起来,发现一脸懵逼,再思考一下,其实自己还是理解的,这样知识就形成了闭环。
理解指引-目标
上面思考,其实理解了gpio、pinctrl 子系统的知识点,使用方案,这里再次加深理解,并搞清楚我们这里需要加深理解的知识点是什么?
问题:pinctrl-names = “myled1”; 指定名称和默认default 有什么区别
在设备树(DTS)配置中,pinctrl-names 配置 default 和指定名称的主要区别如下:
default 名称 - 特点
pinctrl-names = "default";
pinctrl-0 = <&mygpio_ctrl>;
特点
- 系统在设备初始化时会自动加载对应的 pinctrl 状态
- 不需要在驱动代码中显式调用状态切换
- 适用于设备只需要一种固定的引脚配置状态
我们之前学过的知识点 Linux驱动设备树-pinctrl篇 已经配置过自己定义的led 灯相关的 pinctrl 实例编写-将 led 的控制引脚复用为 GPIO 模式 的知识点。
指定名称-特点
pinctrl-names = "myled1", "sleep";
pinctrl-0 = <&mygpio_ctrl>;
pinctrl-1 = <&mygpio_sleep>;
特点
- 需要在驱动代码中手动切换状态
- 支持多种工作模式(如:正常模式、睡眠模式等)
- 提供更灵活的引脚状态管理
目标
所以,这里我们就是在指定pinctrl-names 情况下,在驱动中去指定pin-ctrl 的功能到硬件。
二、pin-ctrl 相关函数
在Linux驱动开发中,pinctrl 子系统用于管理引脚的复用和电气属性。下面详细解释 pinctrl_get、pinctrl_put、pinctrl_lookup_state 和 pinctrl_select_state 这几个核心函数。
pinctrl_get
pinctrl_get 函数用于获取与指定设备关联的 pinctrl 控制句柄。
struct pinctrl *pinctrl_get(struct device *dev);
dev:指向要获取引脚控制器的设备对象指针
功能:根据设备对象 dev,获取该设备关联的 pinctrl(引脚控制器)结构体指针。
返回值:
成功时返回一个指向 struct pinctrl 的指针。
失败(如设备不支持)返回 NULL
使用场景
在设备驱动探测(probe)函数中,通常首先调用此函数来获取设备的引脚控制句柄,为后续的引脚状态配置做准备。
pinctrl_put
pinctrl_put 函数用于释放通过 pinctrl_get 获取的 pinctrl 资源。
函数原型与参数
void pinctrl_put(struct pinctrl *p);
p:要释放的 pinctrl 结构体指针
功能:释放通过 pinctrl_get() 获取的 pinctrl 资源,避免内存泄漏。
返回值:无返回值
使用场景
-
通常在驱动移除(remove)函数或探测失败时调用,用于释放之前获取的 pinctrl 资源。必须与 pinctrl_get 配对使用。
-
对于 devm_pinctrl_get 获取的句柄,应使用 devm_pinctrl_put 释放,但通常可以不显式调用,由设备资源管理机制自动处理。
pinctrl_lookup_state
pinctrl_lookup_state 函数用于在指定的 pinctrl 实例中查找对应的引脚配置状态。
函数原型与参数
struct pinctrl_state *pinctrl_lookup_state(struct pinctrl *p, const char *name);
p:要查找状态的 pinctrl 结构体指针。
name:要查找的状态名称。
功能与返回值
功能:根据名称查找对应的引脚配置状态(如引脚模式、电气属性)。
返回值:
成功时返回该状态的指针。
未找到或出错返回 NULL
常用状态名称
- PINCTRL_STATE_DEFAULT 或 “default”:默认状态
- PINCTRL_STATE_IDLE 或 “idle”:空闲状态
- PINCTRL_STATE_SLEEP 或 “sleep”:睡眠状态
使用场景
在设备驱动中,需要查找特定工作模式对应的引脚状态时使用。例如,查找设备的默认状态、睡眠状态或空闲状态。
pinctrl_select_state
pinctrl_select_state 函数用于将指定的引脚配置状态应用到硬件。
函数原型与参数
int pinctrl_select_state(struct pinctrl *p, struct pinctrl_state *s);
p:要操作的 pinctrl 结构体指针。
s:要应用的引脚配置状态指针。
功能与返回值
功能:将指定的引脚配置状态 s 应用到硬件,更新引脚的实际设置(例如切换引脚功能)。
返回值:
成功返回 0。
失败返回负数错误码(如 -EINVAL)。
使用场景
- 当需要动态切换设备引脚状态时使用,例如:
- 设备进入或退出低功耗模式时切换睡眠状态
- 设备工作模式改变时需要切换引脚功能
- 运行时根据操作需求切换引脚配置
API 总结与简易对比
| 函数 | 作用 | 调用时机 |
|---|---|---|
| pinctrl_get | 获取引脚控制句柄 | 驱动探测时 |
| pinctrl_lookup_state | 查找引脚状态 | 获取句柄后 |
| pinctrl_select_state | 应用引脚状态 | 需要切换状态时 |
| pinctrl_put | 释放引脚控制句柄 | 驱动卸载或出错时 |
三、GPIO+pinctrl 子系统结合-动态配置pin引脚功能
上面提到过 指定pinctrl 名称的特点:下面就通过实际实验来说明使用方案、步骤、方法。
指定名称-特点
pinctrl-names = "myled1", "sleep";
pinctrl-0 = <&mygpio_ctrl>;
pinctrl-1 = <&mygpio_sleep>;
特点
- 需要在驱动代码中手动切换状态
- 支持多种工作模式(如:正常模式、睡眠模式等)
- 提供更灵活的引脚状态管理
修改设备树
位置:kernel/arch/arm64/boot/dts/rockchip/rk3568-evb1-ddr4-v10.dtsi,两点:
/ {
model = "Rockchip RK3568 EVB1 DDR4 V10 Board";
compatible = "rockchip,rk3568-evb1-ddr4-v10", "rockchip,rk3568";
....................................
my_gpio:gpio1_a0 {
compatible = "mygpio";
my-gpios = <&gpio1 RK_PA0 GPIO_ACTIVE_HIGH>;
pinctrl-names = "myled1";
pinctrl-0 = <&my_gpio_ctrl>;
};
};
&pinctrl {
.......................
mygpio{
my_gpio_ctrl:my-gpio-ctrl {
rockchip,pins = <1 RK_PA0 RK_FUNC_GPIO &pcfg_pull_none>;
};
};
..................................
};
在第四行中的pinctrl-names参数并不是default,这就需要用到我们前面pinctrl子系统中的知识来查找并设置相应的pinctrl状态了
驱动程序测试源码
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/mod_devicetable.h>
#include <linux/gpio/consumer.h>
#include <linux/gpio.h>
struct pinctrl *led_pinctrl; // pinctrl 实例指针
struct pinctrl_state *led_state;// pinctrl 状态指针
int ret;
//平台设备初始化函数
static int my_platform_probe(struct platform_device *dev)
{
printk("This is mydriver_probe\n");
led_pinctrl = pinctrl_get(&dev->dev);// 获取 pinctrl 实例
if (IS_ERR(led_pinctrl)) {
printk("pinctrl_get is error\n");
return -1;
}
led_state = pinctrl_lookup_state(led_pinctrl, "myled1");// 查找状态
if (IS_ERR(led_state)) {
printk("pinctrl_lookup_state is error\n");
return -2;
}
ret = pinctrl_select_state(led_pinctrl, led_state);// 设置状态到硬件
if (ret < 0) {
printk("pinctrl_select_state is error\n");
return -3;
}
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");
测试结果
先搞清楚,pin 脚是哪个脚 GPIO1_A0 ,所以它的pin脚号 就是 1*32+(A-A)*8+0=32
调试GPIO 方法,详细可参考 GPIO的调试方法
GPIO 调试-编译固件之前查看指定pin脚GPIO信息
cat /sys/kernel/debug/pinctrl/pinctrl-rockchip-pinctrl/pinmux-pins | grep 32
结果如下:

驱动运行-加载驱动
如下,说明驱动已经运行起来了。

再次进行 GPIO 调试-编译固件之前查看指定pin脚GPIO信息
cat /sys/kernel/debug/pinctrl/pinctrl-rockchip-pinctrl/pinmux-pins | grep 32
结果如下:

总结
- 这里只是对gpio-pinctrl 两个子系统结合起来用的一个总结、规整知识点而已,实际上我们一直都是这么用的。
- 多总结,知识点 多分析,多思考。
1349

被折叠的 条评论
为什么被折叠?



