嵌入式Linux驱动——6 Pinctrl和GPIO子系统
难道每个引脚都通过一个一个配置寄存器吗?这不可能因此需要使用因为一般都是把引脚配置为 GPIO,因此这里把也讲了(
目录
2.1 利用 GPIO Controller 来指定使用哪个引脚:
1.1 在设备树 iomuxc_snvs 节点中添加 pin controller
1.2 在 led 设备节点添加 client device 信息
3.GPIO Controller 不需要改动,厂商在.dtsi文件中已经设置好了
完整的 led 设备节点(包含 Pinctrl 和 GPIO 子系统信息):
配置一个 pin 引脚十分复杂,以配置 GPIO 为例:
- 配置引脚复用功能(IOMUX)为 GPIO
- 配置引脚参数(上拉、下拉...)
- 配置 GPIO (方向、数据...)
难道每个引脚都通过一个一个配置寄存器吗?这不可能
因此需要使用 Pinctrl 子系统(用于配置引脚复用功能)
因为一般都是把引脚配置为 GPIO,因此这里把 GPIO 子系统也讲了(跟 Pinctrl 类似,用于配置 GPIO)
1.Pinctrl
Pinctrl 跟设备树是紧密相连的,一般涉及分为两个部分:
1.1 pin controller
一个软件上的概念,可实现 IOMUX 的功能作用:选择引脚复用功能、配置引脚(比如上下拉电阻等)
例如可以把 pin 配置复用为 GPIO 功能和引脚参数,但 GPIO 的配置(方向、数据...)得使用后面讲的 GPIO 子系统
它是设备树中的一个节点,其中有多个子节点,每个子节点声明了:
- 该节点使用了哪些引脚
- 把这些引脚复用为了什么功能
- 把引脚参数配置为了多少
1.2 client device
“客户设备”,谁的客户?Pinctrl 系统的客户(理解为 pin controller 的客户更准确)
它是一个在设备树根目录下的设备节点,节点中各属性内容主要是:
- 用 pinctrl-names 声明设备的状态(可以有多个)
- 声明每个状态分别使用 pin controller 的哪个子节点来配置引脚(pinctrl-x = < &pin conctrl >,x 代表第几个状态)
图示:

在图中,
当设备是 default 状态时,Pinctrl 子系统会自动配置好 pincontroller 中 state_0_node_a 节点的引脚和功能
当设备是 sleep 状态时,Pinctrl 子系统会自动配置好 pincontroller 中 state_1_node_a 节点的引脚和功能
1.3 补充说明
pin controller 节点的格式没有统一的标准,但内容都是一样的:该子节点使用哪些引脚、复用成什么功能
示例:不同设备的 pin conctrller 和 client device

2.GPIO 子系统
先通过 Pinctrl 把 pin 配置为 GPIO 功能,接着通过 GPIO 子系统配置 GPIO:引脚方向...等
一般也分为两部分 GPIO Controller 和 device client(跟Pinctrl是同一个,配置好引脚的复用功能后紧接着就是配置GPIO)
在使用 GPIO 子系统之前,就要先确定:它是哪组的?组里的哪一个?
2.1 利用 GPIO Controller 来指定使用哪个引脚:
(注意,只是指定使用哪个引脚,配置引脚要在后面使用函数配置)
在设备树中,GPIO按组来分,一个 “GPIO 组” 就是一个 GPIO Controller,它们一般都是厂家定 义好,在 xxx.dtsi 文件中:

可以看到,在设备树中,GPIO 节点按组来分,需要重点关注 GPIO 组节点的两个属性:
gpio-controller、#gpio-cells=<2>
- gpio-controller:声明该组GPIO节点是一个 GPIO Controller ,
- #gpio-cells = <2>”:表明在设备节点 device client 中每一个引脚要用 2 个 32 位的数(cell)来描述。
(一般是一个数描述用哪个引脚,另一个数描述该引脚是高电平有效还是低电平有效)
定义 GPIO Controller 是芯片厂家的事,我们只需要在设备树中引用该 .dtsi 文件就行
2.2 怎么在设备节点引用某个引脚呢?
在自己的设备节点中使用属性 “[<name>-]gpios” ,示例如下:

可以使用 gpios 属性,也可以使用 <name>-gpios 属性
一个实现了 Pinctrl 和 GPIO 子系统的设备节点示例:
myled {
compatible = "100ask,leddrv";
/*pinctrl部分*/
pinctrl-names = "default"; //状态
pinctrl-0 = <&myled_for_gpio_subsys>; //状态0对应的引脚复用
/*GPIO子系统部分*/
//指定gpio组,并用两个参数描述引脚:3:pin3;GPIO_ACTIVE_LOW:低电平有效
led-gpios = <&gpio5 3 GPIO_ACTIVE_LOW>;
};
在这里虽然 Pinctrl 和 GPIO 子系统配置的引脚是同一个,但这是两个不同的操作,所以要引用 2 次
2.3 配置 GPIO
引用好gpio引脚后,就要开始对其进行配置了
GPIO 子系统有两套接口:
- 一套是新的,基于描述符(descriptor-based)(不用管描述符是什么),函数前缀为 “gpiod_”
- 一套是老的(legacy),函数前缀为 “gpio_”
PS:建议使用新的
在驱动程序中使用这些函数要包含头文件:
- 新接口(descriptor-based):#include <linux/gpio/consumer.h>
- 老接口(legacy):#include <linux/gpio.h>
常见函数如下图:

一般的操作流程:
- 通过 get 函数获取引脚
- 使用上示各种函数配置该引脚(在函数中传入获取的引脚)
具体新接口和老接口的区别:
1. 新接口
新接口用 gpio_desc 结构体表示一个引脚
新接口先要通过 get 函数获得一个引脚的 gpio_desc 结构体,其他的配置函数就是通过传入这个 gpio_desc 结构体实现具体引脚的配置
示例:
假设在设备树中有如下节点:
foo_device {
compatible = "acme,foo";
...
led-gpios = <&gpio 15 GPIO_ACTIVE_HIGH>, /* red */
<&gpio 16 GPIO_ACTIVE_HIGH>, /* green */
<&gpio 17 GPIO_ACTIVE_HIGH>; /* blue */
power-gpios = <&gpio 1 GPIO_ACTIVE_LOW>;
};
那么可以使用下面的函数获得引脚:
struct gpio_desc *red, *green, *blue, *power;
/*解释这里的dev参数:
*当plaform device和plaform driver配对成功后,
*会自动把plaform device结构体传入到plaform driver的.probe函数中
*即.probe函数中的*pdev参数,这个参数就是传入的plaform device结构体
*而struct device *dev = &pdev->dev;
*即这里的dev是pdev的dev成员
*pdev->dev 是内嵌的 struct device 成员,代表设备的基类。
*
*/
red = gpiod_get_index(dev, "led", 0, GPIOD_OUT_HIGH);
green = gpiod_get_index(dev, "led", 1, GPIOD_OUT_HIGH);
blue = gpiod_get_index(dev, "led", 2, GPIOD_OUT_HIGH);
power = gpiod_get(dev, "power", GPIOD_OUT_HIGH);
解释这里的dev参数:
当 plaform device 和 plaform driver 配对成功后,会自动把 plaform device 结构体传入到 plaform driver 的 .probe函数 中,即 .probe 函数中的 *pdev 参数,这个参数就是传入的 plaform device 结构体,而 dev 是 pdev 的成员,pdev->dev 是内嵌的 struct device 成员,代表设备的基类。其最主要的作用:
与设备树(DT)交互:通过
dev->of_node访问设备树节点:
在代码中,因为 led-gpios 中引用了多个引脚,因此使用 gpiod_get_index 获取 led-gpios 中的不同引脚的 gpio_desc
当 gpios 中只引用了一个引脚时候,可以直接使用 gpiod_get 获取该引脚的 gpio_desc
后续使用配置函数,就可以通过传入不同引脚的 gpio_desc,实现对不同引脚的配置
gpiod_get 函数可以顺便设置引脚的电平(上示第3个参数,第三个参数即可用作索引,也可以用作设置引脚输入输出模式及初始电平),这里设为0,表示用作索引,引用 led-gpios中的第一个引脚
也可以通过 gpiod_set_value 设置
注意:gpiod_set_value 设置的值是“逻辑值”,不一定等于物理值(高电平有效时逻辑1=物理1,低电平有效时逻辑1=物理0)
2.老接口
老接口用引脚编号代表一个接口
旧的 “gpio_” 函数没办法根据设备树信息获得引脚,它需要先知道引脚号。
老接口就是通过向配置函数传入引脚编号实现具体引脚的配置
注意:需要先对引脚进行 gpio_request 申请(也是传入引脚号)之后,才能开始使用各种配置函数
引脚号怎么确定?
在 GPIO 子系统中,每注册一个 GPIO Controller 时会确定它的 “base number”那么这个控制器里的第 n 号引脚的号码就是:base number + n。
可以通过查看 sysfs 来确定 base number:
- 先在开发板的/sys/class/gpio 目录下,找到各个 gpiochipXXX 目录(这里每个目录代表一组GPIO Controller ):

- 进入某个 gpiochip 目录,查看文件 label 的内容
- 根据 label 的内容对比设备树,lable中一般包含 GPIO Controller 的寄存器基地址
可以通过在设备树(dtsi 文件)中查找该基地址,就可以知道这对应哪一个 GPIO Controller,就可以把该组 GPIO Controller 与 base number 匹配起来了
如下图所示:

这样就确定了 96 是 gpio4 的 base number,base number + n(第几个引脚)就得到引脚号了
还可以基于 sysfs 操作引脚(除“gpio_” 函数外的另外一种操作方式):
以 100ask_imx6ull 为例,它有一个按键,原理图如下:

那么 GPIO4_14 的号码是 96+14=110,可以如下操作读取按键值:
[root@100ask:~]# echo 110 > /sys/class/gpio/export //获取110号引脚
[root@100ask:~]# echo in > /sys/class/gpio/gpio110/direction //设置引脚方向为in
[root@100ask:~]# cat /sys/class/gpio/gpio110/value //打印出引脚值
[root@100ask:~]# echo 110 > /sys/class/gpio/unexport //释放110号引脚
注意:如果驱动程序已经使用了该引脚,那么将会 export 失败,会提示下面的错误:

3.基于 GPIO 子系统的 LED 驱动程序
流程思路:
- 先把某个引脚配置为 GPIO 功能
这要使用 Pinctrl 子系统,只需要在设备树中的我们的设备节点里指定就可以。在驱动代码上不需要做任何事情。
- 设备节点中还需要写出 gpios 属性,声明GPIO子系统引用的引脚
- 设备树节点会被内核转换为 platform_device,对应的,驱动代码中要注册一个 platform_driver,在 probe 函数中:获取在GPIO子系统引用的引脚、注册 file_operations。
- 在 file_operations 中就可以通过调用上面 GPIO 子系统的配置函数实现设置GIPO方向、读值/写值等操作
具体步骤(以led为例):
1.在设备树中添加 Pinctrl 信息
1.1 在设备树 iomuxc_snvs 节点中添加 pin controller
安装“Pins_Tool_for_i.MX_Processors_v6_x64.exe”后运行,打开 IMX6ULL 的配置文件“MCIMX6Y2xxx08.mex”,就可以在 GUI 界面中选择引脚生成 Pinctrl 的 pin controller 信息

可以看到,生成的信息提示我们要把该引脚的 pin contronller 信息放到设备树 iomuxc_snvs 节点下
&iomuxc_snvs {
……
myled_for_gpio_subsys: myled_for_gpio_subsys{
fsl,pins = <
//第一个宏表示了把GPIO5_3配置为GPIO,第二个数表示了了要设置的参数(上下拉电阻等)
MX6ULL_PAD_SNVS_TAMPER3__GPIO5_IO03 0x000110A0
>;
};
1.2 在 led 设备节点添加 client device 信息
声明:
- pinctrl-names:设备的各种状态
- pinctrl-<x>:不同状态对应引脚复用的配置
2.在 led 设备节点中添加 GPIO 信息:
根据 GPIO Controller 节点里的 “#gpio-cells” 属性值,在 led 设备节点中添加 “[name]-gpios” 属性
指定:使用的是哪一个 GPIO Controller 里的哪一个引脚,还有其他 Flag 信息(高低电平有效等)
3.GPIO Controller 不需要改动,厂商在.dtsi文件中已经设置好了
完整的 led 设备节点(包含 Pinctrl 和 GPIO 子系统信息):
myled {
//用于与platform driver相匹配
compatible = "100ask,leddrv";
//状态
pinctrl-names = "default";
//0号状态pinctrl要复用的引脚及其功能
pinctrl-0 = <&myled_for_gpio_subsys>;
//GPIO子系统要引用的引脚
led-gpios = <&gpio5 3 GPIO_ACTIVE_LOW>;
};
4.驱动的编写
流程:
(1)定义 platform driver 结构体
- 在 .of_match_table 中定义好和 led 设备节点相匹配的 compatible
- 定义好 probe 函数
在 probe 函数中:
- 通过 gpiod_get 获取 gpio 引脚
- 注册 file_operations 结构体
- 创建类和设备节点
源码示例:
static int chip_demo_gpio_probe(struct platform_device *pdev) { //int err; printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__); /*获取GPIO引脚*/ /*设备树中定义了:led-gpios=<...>,因此通过“led”在pdev->dev获取*/ led_gpio = gpiod_get(&pdev->dev, "led", 0); if (IS_ERR(led_gpio)) { dev_err(&pdev->dev, "Failed to get GPIO for led\n"); return PTR_ERR(led_gpio); } /*注册file_operations*/ major = register_chrdev(0, "100ask_led", &led_drv); /* /dev/led */ /*创建类*/ led_class = class_create(THIS_MODULE, "100ask_led_class"); if (IS_ERR(led_class)) { printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__); unregister_chrdev(major, "led"); gpiod_put(led_gpio); return PTR_ERR(led_class); } /*创建设备节点*/ device_create(led_class, NULL, MKDEV(major, 0), NULL, "100ask_led%d", 0); /* /dev/100ask_led0 */ return 0; }
(2)定义 file_operations 结构体
在 file_operations 结构体中实现对GPIO的配置
源码示例:
/*.read函数,这里没作操作*/
static ssize_t led_drv_read (struct file *file, char __user *buf, size_t size, loff_t *offset)
{
printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
return 0;
}
/*.write函数:向GPIO引脚写入status值*/
static ssize_t led_drv_write (struct file *file, const char __user *buf, size_t size, loff_t *offset)
{
int err;
char status;
printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
err = copy_from_user(&status, buf, 1);
/* 根据GPIO引脚,写入status值,控制LED */
gpiod_set_value(led_gpio, status);
return 1;
}
/*.open函数:设置引脚方向*/
static int led_drv_open (struct inode *node, struct file *file)
{
printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
/* 根据GPIO引脚,设置引脚方向,初始化LED */
gpiod_direction_output(led_gpio, 0);
return 0;
}
/*.release函数:没做任何操作*/
static int led_drv_close (struct inode *node, struct file *file)
{
printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
return 0;
}
/*定义自己的file_operations结构*/
static struct file_operations led_drv = {
.owner = THIS_MODULE,
.open = led_drv_open,
.read = led_drv_read,
.write = led_drv_write,
.release = led_drv_close,
};
完整源码:
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/fs.h>
#include <linux/errno.h>
#include <linux/miscdevice.h>
#include <linux/kernel.h>
#include <linux/major.h>
#include <linux/mutex.h>
#include <linux/proc_fs.h>
#include <linux/seq_file.h>
#include <linux/stat.h>
#include <linux/init.h>
#include <linux/device.h>
#include <linux/tty.h>
#include <linux/kmod.h>
#include <linux/gfp.h>
#include <linux/gpio/consumer.h>
#include <linux/of.h>
/* 1. 确定主设备号 */
static int major = 0;
static struct class *led_class;
static struct gpio_desc *led_gpio;
/*.read函数,这里没作操作*/
static ssize_t led_drv_read (struct file *file, char __user *buf, size_t size, loff_t *offset)
{
printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
return 0;
}
/*.write函数:向GPIO引脚写入status值*/
static ssize_t led_drv_write (struct file *file, const char __user *buf, size_t size, loff_t *offset)
{
int err;
char status;
printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
err = copy_from_user(&status, buf, 1);
/* 根据GPIO引脚,写入status值,控制LED */
gpiod_set_value(led_gpio, status);
return 1;
}
/*.open函数:设置引脚方向*/
static int led_drv_open (struct inode *node, struct file *file)
{
printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
/* 根据GPIO引脚,设置引脚方向,初始化LED */
gpiod_direction_output(led_gpio, 0);
return 0;
}
/*.release函数:没做任何操作*/
static int led_drv_close (struct inode *node, struct file *file)
{
printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
return 0;
}
/*定义自己的file_operations结构*/
static struct file_operations led_drv = {
.owner = THIS_MODULE,
.open = led_drv_open,
.read = led_drv_read,
.write = led_drv_write,
.release = led_drv_close,
};
static int chip_demo_gpio_probe(struct platform_device *pdev)
{
printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
/*获取GPIO引脚*/
/*设备树中定义了:led-gpios=<...>,因此通过“led”在pdev->dev获取*/
led_gpio = gpiod_get(&pdev->dev, "led", 0);
if (IS_ERR(led_gpio)) {
dev_err(&pdev->dev, "Failed to get GPIO for led\n");
return PTR_ERR(led_gpio);
}
/*注册file_operations*/
major = register_chrdev(0, "100ask_led", &led_drv); /* /dev/led */
/*创建类*/
led_class = class_create(THIS_MODULE, "100ask_led_class");
if (IS_ERR(led_class)) {
printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
unregister_chrdev(major, "led");
gpiod_put(led_gpio);
return PTR_ERR(led_class);
}
/*创建设备节点*/
device_create(led_class, NULL, MKDEV(major, 0), NULL, "100ask_led%d", 0); /* /dev/100ask_led0 */
return 0;
}
static int chip_demo_gpio_remove(struct platform_device *pdev)
{
device_destroy(led_class, MKDEV(major, 0));
class_destroy(led_class);
unregister_chrdev(major, "100ask_led");
gpiod_put(led_gpio);
return 0;
}
/*定义和设备节点匹配的compatible*/
static const struct of_device_id ask100_leds[] = {
{ .compatible = "100ask,leddrv" },
{ },
};
/* 定义platform_driver */
static struct platform_driver chip_demo_gpio_driver = {
.probe = chip_demo_gpio_probe,
.remove = chip_demo_gpio_remove,
.driver = {
.name = "100ask_led",
.of_match_table = ask100_leds,
},
};
/* 在入口函数:注册platform_driver */
static int __init led_init(void)
{
int err;
printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
err = platform_driver_register(&chip_demo_gpio_driver);
return err;
}
/* 出口函数:卸载platform_driver */
static void __exit led_exit(void)
{
printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);
platform_driver_unregister(&chip_demo_gpio_driver);
}
module_init(led_init);
module_exit(led_exit);
MODULE_LICENSE("GPL");
openvela 操作系统专为 AIoT 领域量身定制,以轻量化、标准兼容、安全性和高度可扩展性为核心特点。openvela 以其卓越的技术优势,已成为众多物联网设备和 AI 硬件的技术首选,涵盖了智能手表、运动手环、智能音箱、耳机、智能家居设备以及机器人等多个领域。
更多推荐



所有评论(0)