GPIO 子系统和 pinctrl 子系统
Linux 中的 GPIO 子系统与 pinctrl 子系统详解
一、引言
工作流程如下:
- 通过 pinctrl 子系统将硬件引脚配置为 GPIO 功能
- 通过 GPIO 子系统设置该 GPIO 的方向(输入 / 输出)
- 进行实际的电平读写操作
二、GPIO 子系统与 pinctrl 子系统中节点和寄存器偏移地址的关系
在硬件层面,每个 GPIO 引脚和复用功能都与特定的寄存器相关联。在设备树中,通过节点来描述硬件信息。pinctrl 子系统的节点用于指定引脚的复用配置,例如将某个引脚配置为 GPIO 功能或者其他功能(如 UART、I2C 等)。
GPIO 控制器节点则用于描述 GPIO 控制器的相关信息,如“gpio - controller”属性标识该节点是一个 GPIO 控制器,“#gpio - cells = <2>”属性表示该控制器下每一个引脚要用 2 个 32 位的数(cell)来描述,其中第一个 cell 表示引脚编号,第二个 cell 表示有效电平。
设备树中的 pinctrl 和 GPIO 节点是硬件寄存器的抽象表示:
- pinctrl 节点的 reg 属性指定了引脚控制器的寄存器基地址和范围
- 节点中的每个引脚配置对应到具体寄存器的特定位段
- GPIO 控制器节点的 reg 属性定义了 GPIO 寄存器组的基地址
- 引脚编号通过基地址 + 偏移地址的方式映射到实际寄存器
例如,当设备树中定义 reg = <0x12340000 0x1000> 时,0x12340000 是寄存器基地址,0x1000 是地址范围,控制器内部的各个功能寄存器通过相对基地址的偏移量来访问。
三、在设备树添加的 pinctrl 节点模板和gpio节点模板
pinctrl@0 {compatible = "example,pinctrl - device";reg = <0x12340000 0x1000>; // 假设寄存器基地址和大小pinctrl - names = "default";pinctrl - 0 = <&gpio_pins>;gpio_pins: gpio - pins {pinmux = <&gpio1 0 GPIO_ACTIVE_HIGH>, // 假设复用为 GPIO1 引脚 0,高电平有效<&gpio1 1 GPIO_ACTIVE_LOW>;bias - pull - up; // 上拉电阻配置,可根据需求选择bias - pull - down; // 下拉电阻配置,可根据需求选择drive - strength = <16>; // 驱动强度配置};
};
上述模板中,pinctrl@0
节点表示一个 pinctrl 设备,compatible
属性用于设备树匹配,reg
属性指定了寄存器地址范围。pinctrl - names
和 pinctrl - 0
用于指定默认的引脚配置组。在 gpio_pins
子节点中,通过 pinmux
属性指定引脚的复用和电平配置,同时还可以设置上拉/下拉电阻以及驱动强度等属性。
/* GPIO 控制器节点 */
pio: gpio@0x01c20800 {compatible = "allwinner,sun8i-h3-gpio";reg = <0x01c20800 0x400>;gpio-controller; /* 标识为 GPIO 控制器 */#gpio-cells = <3>; /* 每个 GPIO 需 3 个 cell 描述 *//* cell 含义: 引脚号, 标志位, 有效电平 */
};
四、GPIO 子系统常用的 API 函数
(一)基于描述符的接口(descriptor - based)
gpiod_get
- 功能:从设备树中获取指定的 GPIO 描述符。
- 原型:
struct gpio_desc *gpiod_get(struct device *dev, const char *con_id, enum gpiod_flags flags)
- 参数:
dev
:设备指针,通常为平台设备的指针。con_id
:设备树中定义的 GPIO 控制器的名称。flags
:标志位,可用于指定一些特殊的行为,如GPIOD_AS_IS
表示不进行额外的初始化。
- 返回值:成功时返回 GPIO 描述符指针,失败时返回 ERR_PTR。
gpiod_direction_output
- 功能:将指定的 GPIO 设置为输出模式,并可设置初始输出值。
- 原型:
int gpiod_direction_output(struct gpio_desc *desc, int value)
- 参数:
desc
:GPIO 描述符指针。value
:初始输出值,通常为 0(低电平)或 1(高电平)。
- 返回值:成功时返回 0,失败时返回负数错误码。
gpiod_set_value
- 功能:设置指定 GPIO 的输出值。
- 原型:
void gpiod_set_value(struct gpio_desc *desc, int value)
- 参数:
desc
:GPIO 描述符指针。value
:要设置的输出值,0 或 1。
(二)老的接口(legacy)
gpio_request
- 功能:向系统申请一个 GPIO 引脚。
- 原型:
int gpio_request(unsigned gpio, const char *label)
- 参数:
gpio
:要申请的 GPIO 引脚编号。label
:该引脚的描述标签,方便在调试和管理时识别。
- 返回值:成功时返回 0,失败时返回负数错误码。
gpio_free
- 功能:释放已经申请的 GPIO 引脚。
- 原型:
void gpio_free(unsigned gpio)
- 参数:
gpio
:要释放的 GPIO 引脚编号。
gpio_direction_input
- 功能:将指定的 GPIO 引脚设置为输入模式。
- 原型:
int gpio_direction_input(unsigned gpio)
- 参数:
gpio
:要设置的 GPIO 引脚编号。 - 返回值:成功时返回 0,失败时返回负数错误码。
gpio_direction_output
- 功能:将指定的 GPIO 引脚设置为输出模式,并可以同时设置该引脚的初始输出值。
- 原型:
int gpio_direction_output(unsigned gpio, int value)
- 参数:
gpio
:要设置的 GPIO 引脚编号。value
:初始输出值,通常为 0(低电平)或 1(高电平)。
- 返回值:成功时返回 0,失败时返回负数错误码。
gpio_get_value
- 功能:获取指定 GPIO 引脚的输入值。
- 原型:
int gpio_get_value(unsigned gpio)
- 参数:
gpio
:要获取值的 GPIO 引脚编号。 - 返回值:返回引脚的输入值,0 或 1。
gpio_set_value
- 功能:设置指定 GPIO 引脚的输出值。
- 原型:
void gpio_set_value(unsigned gpio, int value)
- 参数:
gpio
:要设置的 GPIO 引脚编号。value
:要设置的输出值,0 或 1。
五、设备树中添加 gpio 节点模板
gpio - device@0 {compatible = "example,gpio - device";reg = <0x56780000 0x2000>; // 假设 GPIO 控制器寄存器基地址和大小gpio - controller;#gpio - cells = <2>;interrupt - controller;#interrupt - cells = <2>;gpio_led: led - gpio {label = "led - gpio";gpios = <&gpio1 10 GPIO_ACTIVE_LOW>; // 假设使用 GPIO1 的第 10 引脚,低电平点亮 LEDlinux, default - state = "off";};
};
在这个模板中,gpio - device@0
节点表示一个 GPIO 设备,compatible
属性用于设备树匹配,reg
属性指定了 GPIO 控制器的寄存器地址范围。gpio - controller
和 #gpio - cells = <2>
属性用于标识这是一个 GPIO 控制器,并定义了引脚描述方式。gpio_led
子节点用于描述具体的 GPIO 应用,这里是控制一个 LED 灯,gpios
属性指定了使用的 GPIO 引脚及其有效电平。
六、与 gpio 相关的 OF 函数的用法
(一)of_gpio_get_by_name
- 功能:根据设备树中指定的名称获取 GPIO 编号。
- 原型:
int of_gpio_get_by_name(struct device_node *np, const char *propname, const char *con_id)
- 参数:
np
:设备树节点指针。propname
:包含 GPIO 信息的属性名称,如gpios
。con_id
:GPIO 控制器的名称。
- 返回值:成功时返回 GPIO 编号,失败时返回负数错误码。
(二)of_gpio_count
- 功能:获取设备树节点中指定属性的 GPIO 引脚数量。
- 原型:
int of_gpio_count(struct device_node *np, const char *propname)
- 参数:
np
:设备树节点指针。propname
:包含 GPIO 信息的属性名称。
- 返回值:返回 GPIO 引脚数量,失败时返回负数错误码。
(三)of_gpio_get_flags
- 功能:获取指定 GPIO 引脚的标志位。
- 原型:
int of_gpio_get_flags(struct device_node *np, int index, enum of_gpio_flags *flags)
- 参数:
np
:设备树节点指针。index
:GPIO 引脚在属性中的索引。flags
:用于存储获取到的标志位的指针。
- 返回值:成功时返回 0,失败时返回负数错误码。
七、sysfs 中 GPIO 的用户空间访问方法
(一)、导出 GPIO 引脚
在对某个 GPIO 引脚进行操作之前,首先需要将其导出,这样才能在用户空间对其进行访问。假设我们要操作编号为 12 的 GPIO 引脚,执行以下命令:
echo 12 > /sys/class/gpio/export
执行该命令后,系统会在 /sys/class/gpio/
目录下创建一个名为 gpio12
的子目录,后续对该 GPIO 引脚的操作都将在这个子目录下进行。
(二)、配置 GPIO 方向
GPIO 引脚可以配置为输入模式或者输出模式,具体配置方法如下:
- 设置为输出模式:
echo out > /sys/class/gpio/gpio12/direction
- 设置为输入模式:
echo in > /sys/class/gpio/gpio12/direction
(三)、读写 GPIO 电平值
根据 GPIO 引脚配置的方向不同,读写电平值的操作也有所区别。
- 设置输出高电平(GPIO 配置为输出模式时):
echo 1 > /sys/class/gpio/gpio12/value
- 读取输入电平(GPIO 配置为输入模式时):
cat /sys/class/gpio/gpio12/value
执行 cat
命令后,会返回 0
(低电平)或者 1
(高电平)。
(四)、查看 GPIO 控制器信息
我们可以查看系统中 GPIO 控制器的相关信息,了解其管理的引脚范围等内容。
- 列出所有 GPIO 控制器:
ls /sys/class/gpio/
执行该命令后,会列出系统中所有的 GPIO 控制器,通常以 gpiochipXXX
的形式命名,例如 gpiochip0
、gpiochip1
等。
- 查看特定控制器管理的引脚范围:
以gpiochip0
为例,执行以下命令:
cat /sys/class/gpio/gpiochip0/label
cat /sys/class/gpio/gpiochip0/base
cat /sys/class/gpio/gpiochip0/ngpio
其中,label
是 GPIO 控制器的标识;base
是该控制器管理的起始 GPIO 引脚编号;ngpio
是该控制器管理的 GPIO 引脚数量。
(五)、取消导出 GPIO 引脚
当不再需要操作某个 GPIO 引脚时,为了释放资源,需要取消导出该引脚,命令如下:
echo 12 > /sys/class/gpio/unexport
执行该命令后,/sys/class/gpio/
目录下对应的 gpio12
子目录会被删除。
通过 sysfs
提供的这些接口,用户在不需要编写复杂驱动程序的情况下,就能方便地在用户空间对 GPIO 引脚进行操作,非常适合用于简单的硬件测试和原型开发。
八、编程示例
以下是一个简单的基于平台设备驱动的示例,展示了如何使用 GPIO 子系统和 pinctrl 子系统控制一个 LED 灯。
(一)设备树部分
led - device {compatible = "example,led - device";pinctrl - names = "default";pinctrl - 0 = <&led_pins>;led - gpio = <&gpio1 10 GPIO_ACTIVE_LOW>; // 假设使用 GPIO1 的第 10 引脚,低电平点亮 LED
};pinctrl@0 {compatible = "example,pinctrl - device";reg = <0x12340000 0x1000>;pinctrl - names = "default";pinctrl - 0 = <&led_pins>;led_pins: led - pins {pinmux = <&gpio1 10 GPIO_ACTIVE_LOW>;bias - pull - up;};
};
(二)驱动代码部分
#include <linux/init.h>
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/gpio/consumer.h>
#include <linux/of_gpio.h>
#include <linux/of.h>
#include <linux/fs.h>
#include <linux/uaccess.h>#define DEVICE_NAME "led_device"
static struct class *led_class;
static struct device *led_device;
static struct gpio_desc *led_gpio;static ssize_t led_write(struct file *filp, const char __user *buf, size_t count, loff_t *f_pos)
{char value;if (count!= 1) {return -EINVAL;}if (copy_from_user(&value, buf, 1)) {return -EFAULT;}gpiod_set_value(led_gpio, value - '0');return 1;
}static const struct file_operations led_fops = {.write = led_write,
};static int led_probe(struct platform_device *pdev)
{struct device_node *np = pdev->dev.of_node;int ret;// 获取 GPIO 描述符led_gpio = gpiod_get(&pdev->dev, "led - gpio", GPIOD_AS_IS);if (IS_ERR(led_gpio)) {dev_err(&pdev->dev, "Failed to get LED GPIO\n");return PTR_ERR(led_gpio);}// 设置为输出模式ret = gpiod_direction_output(led_gpio, 0);if (ret < 0) {dev_err(&pdev->dev, "Failed to set LED GPIO as output\n");gpiod_put(led_gpio);return ret;}// 创建字符设备led_class = class_create(THIS_MODULE, DEVICE_NAME);if (IS_ERR(led_class)) {dev_err(&pdev->dev, "Failed to create class\n");gpiod_put(led_gpio);return PTR_ERR(led_class);}led_device = device_create(led_class, &pdev->dev, MKDEV(0, 0), NULL, DEVICE_NAME);if (IS_ERR(led_device)) {dev_err(&pdev->dev, "Failed to create device\n");class_destroy(led_class);gpiod_put(led_gpio);return PTR_ERR(led_device);}// 注册文件操作ret = register_chrdev(0, DEVICE_NAME, &led_fops);if (ret < 0) {dev_err(&pdev->dev, "Failed to register chrdev\n");device_destroy(led_class, MKDEV(0, 0));class_destroy(led_class);gpiod_put(led_gpio);return ret;}return 0;
}static int led_remove(struct platform_device *pdev)
{unregister_chrdev(0, DEVICE_NAME);device_destroy(led_class, MKDEV(0, 0));class_destroy(led_class);gpiod_put(led_gpio);return 0;
}static const struct of_device_id led_of_match[] = {{.compatible = "example,led - device" },{},
};
MODULE_DEVICE_TABLE(of, led_of_match);static struct platform_driver led_driver = {.probe = led_probe,.remove = led_remove,.driver = {.name = DEVICE_NAME,.of_match_table = led_of_match,},
};static int __init led_driver_init(void)
{return platform_driver_register(&led_driver);
}static void __exit led_driver_exit(void)
{platform_driver_unregister(&led_driver);
}module_init(led_driver_init);
module_exit(led_driver_exit);MODULE_AUTHOR("Your Name");
MODULE_DESCRIPTION("LED device driver using GPIO and pinctrl subsystems");
MODULE_LICENSE("GPL");
在这个示例中,驱动程序通过设备树获取 LED 对应的 GPIO 引脚,并将其设置为输出模式。然后创建了一个字符设备,用户可以通过写入文件来控制 LED 的亮灭。