Linux驱动-GPIO-动态切换引脚复用
前言
动态切换 引脚复用
一、思考
pinctrl-names = “default”; 默认default 值:默认复用
在Linux驱动-GPIO基本函数api 中,了解了GPIO的基本 函数.这里配置的设备树,如下;
my_gpio:gpio1_a0{
compatible = "mygpio";
my-gpios = <&gpio1 RK_PA0 GPIO_ACTIVE_HIGH>;
pinctrl-names = "default";
pinctrl-0 = <&my_gpio_ctrl>;
};
mygpio{
my_gpio_ctrl:my-gpio-ctrl {
rockchip,pins = <1 RK_PA0 RK_FUNC_GPIO &pcfg_pull_none>;
};
};
核心点: pinctrl-names 值为default , 那么系统解析设备树,自动复用pinctrl-0 下面的 my_gpio_ctrl 标签配置,配置的是gpio 功能。 也就是说 default 情况下默认复用对应的功能。
GPIO Pin-Control 动态切换
pinctrl-names = "myled1", "sleep";
在Linux驱动-GPIO子系统与pinctrl子系统相结合 中,我们理解的知识是:
当 pinctrl-names 配置了具体的值的时候,需要驱动动态配置复用功能。 通过pinctrl_get 方法拿到了pinctrl 实例,通过 pinctrl_lookup_state 方法 查找状态,通过 pinctrl_select_state 方法,
配置状态到硬件,也就是pin脚复用。 这里讲的其实属于GPIO Pin-Control动态切换
对应的设备树,举例人如下;
/ {
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 = “urt”, “i2c”; GPIO 动态引脚复用
上面已经讲到了GPIO Pin-Control 动态切换,现在让pin脚复用不同的功能,比如 GPIO、I2C-SDA、UART-TX 复用功能 ,那么可以在驱动里面根据传参,动态复用到sleep 功能。 或者说 默认是gpio 功能,如何复用到I2C 功能,其实道理是一模一样的。 让驱动程序接收参数,然后动态切换复用功能、同步到硬件。
举例,设备树如下:
my_gpio:gpio1_a0 {
compatible = "mygpio";
my-gpios = <&gpio1 RK_PA0 GPIO_ACTIVE_HIGH>;
pinctrl-names = "mygpio_func1", "mygpio_func2";
pinctrl-0 = <&mygpio_ctrl>;
pinctrl-1 = <&i2c3_sda>;
};
mygpio_func1 {
mygpio_ctrl: my-gpio-ctrl {
rockchip,pins = <1 RK_PA0 RK_FUNC_GPIO &pcfg_pull_none>;
};
};
mygpio_func2 {
i2c3_sda: i2c3_sda {
rockchip,pins = <1 RK_PA0 1 &pcfg_pull_none>;
};
};
二、参考资料
Linux驱动-GPIO基本函数api
Linux驱动-GPIO子系统与pinctrl子系统相结合
实现动态切换引脚复用功能
关于系统属性创建参考:
驱动-设备模型kobject实现属性文件读写终篇 关注:sysfs_create_file、sysfs_create_group 方法
驱动-注册自己的总线并创建属性文件 关注方法 bus_create_file
创建sysfs节点之device_create_file、sysfs_create_group
设备注册流程分析实验
这里核心目的是为了把之前kobject 模型知识点回顾一下,如何创建属性文件:device_create_file
三、核心概念理解
首先,我们简单区分一下这两个紧密相关的概念:
GPIO 动态引脚复用
-
是什么:一个物理引脚在系统运行过程中,可以根据软件配置,在不同的时间扮演不同的角色(如
GPIO、I2C-SDA、UART-TX等)。 -
底层机制:通常由芯片内部的 引脚控制器 管理,通过配置多路复用器来选择引脚的功能。
GPIO Pin-Control 动态切换
-
是什么:在 Linux 内核中,pinctrl 子系统负责管理引脚的复用和电气属性(如上拉/下拉、驱动强度等)。动态切换 就是指在驱动运行时,通过 pinctrl 子系统来改变这些设置。
-
实现方式:在设备树中为同一个设备定义多个
pinctrl状态(如default, sleep, idle),然后在驱动代码中根据需要切换状态。
简单来说,动态引脚复用是目的,而 pinctrl 动态切换是在 Linux 系统中实现这个目的的主要手段。
四、技术实现简介(以 Linux 为例)
在 Linux 设备树中,你会看到这样的定义:
设备树配置
// 在设备节点外定义引脚状态
&iomuxc {
pinctrl_uart2_default: uart2grp_default {
fsl,pins = <
MX6UL_PAD_UART2_TX_DATA__UART2_DCE_TX 0x1b0b1
MX6UL_PAD_UART2_RX_DATA__UART2_DCE_RX 0x1b0b1
>;
};
pinctrl_uart2_sleep: uart2grp_sleep {
fsl,pins = <
// 将TX/RX设置为GPIO输入,并使能内部上拉,以防悬空
MX6UL_PAD_UART2_TX_DATA__GPIO1_IO20 0x4001b8b1
MX6UL_PAD_UART2_RX_DATA__GPIO1_IO21 0x4001b8b1
>;
};
};
// 在设备节点中引用这些状态
&uart2 {
pinctrl-names = "default", "sleep"; // 定义状态名称
pinctrl-0 = <&pinctrl_uart2_default>;
pinctrl-1 = <&pinctrl_uart2_sleep>;
status = "okay";
};
在驱动代码中,切换状态
// 获取pinctrl状态
struct pinctrl *pinctrl = devm_pinctrl_get(dev);
struct pinctrl_state *default_state = pinctrl_lookup_state(pinctrl, "default");
struct pinctrl_state *sleep_state = pinctrl_lookup_state(pinctrl, "sleep");
// 切换到睡眠状态
pinctrl_select_state(pinctrl, sleep_state);
// ... 系统睡眠 ...
// 切换回默认状态
pinctrl_select_state(pinctrl, default_state);
五、具体实验
实验参考:用 将原理图中的一个引脚复用为gpio功能 中的 GPIO1 A0 来实验:原理图、版框图、复用关系参考 之前的文章来并实验。 配置设备树,去掉默认的引用,不要和自定义的设备树节点引用冲突。

设备树修改
// pin脚配置
mygpio_func1 {
mygpio_ctrl: my-gpio-ctrl {
rockchip,pins = <1 RK_PA0 RK_FUNC_GPIO &pcfg_pull_none>;
};
};
mygpio_func2 {
i2c3_sda: i2c3_sda {
rockchip,pins = <1 RK_PA0 1 &pcfg_pull_none>;
};
};
根节点设备数配置
my_gpio:gpio1_a0 {
compatible = "mygpio";
my-gpios = <&gpio1 RK_PA0 GPIO_ACTIVE_HIGH>;
pinctrl-names = "mygpio_func1", "mygpio_func2";
pinctrl-0 = <&mygpio_ctrl>;
pinctrl-1 = <&i2c3_sda>;
};
在 kernel/arch/arm64/boot/dts/rockchip/rk3568-pinctrl.dtsi 看到了GPIO pin脚,如下:
<1 RK_PA1 1 &pcfg_pull_none_smt>,

那么就把i2c3m0_xfer 的引用去掉
就继续检索 i2c3m0_xfer 这个值,发现在rk3568.dtsi文件中,去掉对应的引用,如下:

驱动实验源码
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/mod_devicetable.h>
#include <linux/gpio/consumer.h>
#include <linux/gpio.h>
#include <linux/device.h>
struct pinctrl *gpio_pinctrl; // GPIO pinctrl 实例指针
struct pinctrl_state *func1_state; // 功能1状态
struct pinctrl_state *func2_state; // 功能2状态
int ret;
ssize_t selectmux_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count)
{
unsigned long select;
select = simple_strtoul(buf, NULL, 10);
if (select == 1) {
pinctrl_select_state(gpio_pinctrl, func1_state); // 选择功能1状态
} else if (select == 0) {
pinctrl_select_state(gpio_pinctrl, func2_state); // 选择功能2状态
}
return count;
}
DEVICE_ATTR_WO(selectmux); // 定义可写的设备属性 selectmux
int pinctrl_get_and_lookstate(struct device *dev)
{
gpio_pinctrl = pinctrl_get(dev); // 获取GPIO pinctrl实例
if (IS_ERR(gpio_pinctrl)) {
printk("pinctrl_get is error\n");
return -1;
}
func1_state = pinctrl_lookup_state(gpio_pinctrl, "mygpio_func1"); // 查找功能1状态
if (IS_ERR(func1_state)) {
printk("pinctrl_lookup_state is error\n");
return -2;
}
func2_state = pinctrl_lookup_state(gpio_pinctrl, "mygpio_func2"); // 查找功能2状态
if (IS_ERR(func2_state)) {
printk("pinctrl_lookup_state is error\n");
return -2;
}
return 0;
}
// 平台设备初始化函数
static int my_platform_probe(struct platform_device *dev)
{
printk("This is mydriver_probe\n");
pinctrl_get_and_lookstate(&dev->dev); // 获取并查找GPIO pinctrl实例和状态
device_create_file(&dev->dev, &dev_attr_selectmux); // 在设备上创建属性文件
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引脚复用情况
编译room后,刷固件,然后准备这个驱动文件,实验参考通过:Linux驱动-GPIO子系统与pinctrl子系统相结合 来查看引脚复用:
cat /sys/kernel/debug/pinctrl/pinctrl-rockchip-pinctrl/pinmux-pins | grep 32
通过写属性来更改 设备属性值。
刷固件后,查看模型,如下: 默认是没有我们的属性的,那是因为驱动还没创建,还没有生成属性文件。

查看GPIO引脚复用情况,如下:默认情况下是没有设置的。
cat /sys/kernel/debug/pinctrl/pinctrl-rockchip-pinctrl/pinmux-pins | grep 32

加载驱动,再次查看引脚复用和设备属性
加载驱动,如下:

查看设备模型属性文件:

GPIO 复用情况如下:还是没有复用的

给系统属性selectmux 写0 ,再次查看引脚复用和设备属性

给系统属性selectmux 写1 ,再次查看引脚复用和设备属性

总结
- 这里测试了 引脚复用功能具体实验
- 这里了用了一千的知识点,给设备对象写一个属性,通过属性来判断。当然也可以通过其它方法与驱动通信即可,实现驱动传参来进行公引脚复用功能
- 对于 无符号传参,用到一个方法,可以自行了解:
select = simple_strtoul(buf, NULL, 10);
1324

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



