Pinctrl 子系统
GPIO 子系统
层次结构
- 引脚配置
首先: 通过 Pinctrl 子系统配置引脚的功能, 例如。 将某个引脚设置为 GPIO 功能, 。 - 状态管理
然后: GPIO 子系统使用这些配置来管理引脚的电平状态, 读取或设置引脚电平( ) 。
Pinctrl子系统
pinctrl
子系统是 Linux 内核中用于管理 GPIOpinctrl
子系统的详细介绍
在有pinctrl之前
示例代码
假设我们有一个简单的硬件平台
- GPIO 控制寄存器
用于设置引脚模式( ) - GPIO 数据寄存器
用于读写引脚状态( )
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/io.h>
#define GPIO_BASE_ADDR 0x3F200000 // 假设的 GPIO 基址
#define GPIO_SET_OFFSET 0x1C // 设置 GPIO 引脚高电平的偏移量
#define GPIO_CLR_OFFSET 0x28 // 清除 GPIO 引脚高电平的偏移量
#define GPIO_DIR_OFFSET 0x00 // GPIO 方向寄存器偏移量
#define GPIO_PIN 17 // 假设我们操作 GPIO 17
static void __iomem *gpio_base;
static int __init gpio_init(void) {
// 映射 GPIO 控制寄存器
gpio_base = ioremap(GPIO_BASE_ADDR, 0x1000);
if (!gpio_base) {
printk(KERN_ERR "Failed to map GPIO base address\n");
return -ENOMEM;
}
// 设置 GPIO_PIN 为输出
unsigned int *gpio_dir = gpio_base + GPIO_DIR_OFFSET;
*gpio_dir |= (1 << GPIO_PIN); // 将 GPIO_PIN 设置为输出模式
// 设置 GPIO_PIN 为高电平
unsigned int *gpio_set = gpio_base + GPIO_SET_OFFSET;
*gpio_set = (1 << GPIO_PIN);
printk(KERN_INFO "GPIO %d is set to high\n", GPIO_PIN);
return 0;
}
static void __exit gpio_exit(void) {
// 清除 GPIO_PIN 的高电平
unsigned int *gpio_clr = gpio_base + GPIO_CLR_OFFSET;
*gpio_clr = (1 << GPIO_PIN);
// 解除映射
iounmap(gpio_base);
printk(KERN_INFO "GPIO %d is set to low\n", GPIO_PIN);
}
module_init(gpio_init);
module_exit(gpio_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Example Author");
MODULE_DESCRIPTION("Direct GPIO Access Example");
映射寄存器
: - 使用
ioremap()
函数将 GPIO 基地址映射到内核虚拟地址空间。
- 使用
设置 GPIO 方向
: - 通过对 GPIO 方向寄存器进行位操作
将指定的 GPIO 引脚设置为输出模式, 。
- 通过对 GPIO 方向寄存器进行位操作
设置引脚状态
: - 通过操作 GPIO 设置寄存器
将引脚设为高电平, 通过 GPIO 清除寄存器可以将引脚设为低电平; 。
- 通过操作 GPIO 设置寄存器
模块初始化与退出
: - 在模块初始化时配置 GPIO 引脚
并在模块卸载时清除引脚状态并解除映射, 。
- 在模块初始化时配置 GPIO 引脚
导致问题
- 硬件依赖性
直接寄存器访问依赖于特定硬件平台的寄存器映射: 具有很强的硬件依赖性, 不易移植, 。 - 安全性
直接操作硬件寄存器时: 必须确保不会引起冲突或不安全的状态, 。 - 错误处理
实际代码中应包含更多的错误处理逻辑: 以确保健壮性, 。
1. 主要功能
- 引脚配置
允许用户设置每个引脚的模式: 如输入( 输出、 复用功能等、 ) 。 - 引脚状态管理
可以读写引脚的电平状态: 。 - 引脚复用
支持将同一引脚配置为不同的功能: 以适应不同的硬件需求, 。
在设备树
Device Tree ( 中 ) Pin Controller 节点的格式并没有统一的标准 , 这意味着不同的芯片制造商可能会采用不同的方式来定义这些节点 。 尽管所有芯片都具备类似的功能概念 。 如引脚复用和配置 , 但具体的实现和描述方式各不相同 , 。
Pin Controller 概念
Pin Controller 是一个软件抽象概念
- 引脚复用
类似于 IOMUX: Pin Controller 允许将同一引脚配置为多种功能, 例如 GPIO( I2C、 UART 等、 ) 。 - 引脚配置
除了复用功能外: Pin Controller 还可以配置引脚的电气特性, 例如上下拉电阻, 驱动强度等、 。
Client Device 概念
Client Devicepinctrl
系统中
在设备树
my_device: my_device@0 {
compatible = "my_vendor,my_device";
pinctrl-names = "default";
pinctrl = <&pinctrl_0>; // 引用 Pin Controller 配置
// 其他设备属性
};
在设备树中<&pinctrl_0>;
是对 Pin Controller 配置的引用pinctrl_0
是一个节点标签
&pinctrl {
pinctrl_0: pinctrl {
gpio1_pins: gpio1_pins {
fsl,pins = <MX6UL_PAD_GPIO1_IO16__GPIO1_IO16>;
};
gpio2_pins: gpio2_pins {
fsl,pins = <MX6UL_PAD_GPIO1_IO17__GPIO1_IO17>;
};
// 其他引脚配置
};
};
2. 关键组件
- 设备树
: pinctrl
使用设备树 Device Tree( 来描述硬件的引脚功能和配置) 设备树中的节点定义了硬件平台的引脚布局和特性。 。 - pinctrl 驱动
每个硬件平台都有相应的 pinctrl 驱动: 它实现了, pinctrl
子系统的接口 负责具体的硬件控制, 。 - GPIO 线路
: pinctrl
提供的接口通常与 GPIO 线路紧密结合 允许用户在需要时直接控制 GPIO 引脚, 。
驱动结构
由BSP工程师完成
3. 使用示例
在设备树中pinctrl
的定义通常如下
pinctrl {
pinctrl0: pinctrl0 {
pins = "gpio1", "gpio2";
function = "gpio";
};
};
在 Linux 驱动中pinctrl
API 进行引脚配置的一个简单示例
struct pinctrl *pinctrl;
struct pinctrl_state *state;
pinctrl = devm_pinctrl_get(dev);
state = pinctrl_lookup_state(pinctrl, "pinctrl0");
pinctrl_select_state(pinctrl, state);
4. API 接口
pinctrl
提供了一系列的 API
pinctrl_get()
获取 pinctrl 句柄: 。 pinctrl_lookup_state()
查找特定的引脚状态: 。 pinctrl_select_state()
选择并应用指定的引脚状态: 。
5. 适用场景
- 嵌入式系统
广泛应用于嵌入式设备中: 以控制各种外设, 。 - 开发板
如 Raspberry Pi: BeagleBone 等、 用户需要对 GPIO 进行灵活控制, 。 - 自定义硬件
在需要对硬件引脚进行特定配置的场景中: 。
6. 总结
pinctrl
子系统是 Linux 内核中用于引脚管理的重要组成部分pinctrl
可以帮助开发者更有效地管理硬件资源
GPIO子系统
在早期的嵌入式系统中
驱动代码可以使用 GPIO 子系统提供的标准函数来操作 GPIO 引脚
- 获取 GPIO
申请使用特定的 GPIO 引脚: 。 - 设置 GPIO 方向
配置 GPIO 引脚为输入或输出: 。 - 读取/设置 GPIO 值
读取引脚的电平状态或设置引脚的电平: 。
通过这种方式
下面是一个使用 GPIO 子系统的简单示例
#include <linux/gpio.h>
#define LED_GPIO_PIN 17 // 定义 LED 使用的 GPIO 引脚编号
void led_control(int state) {
// 申请 GPIO 引脚
gpio_request(LED_GPIO_PIN, "led_gpio");
// 设置引脚方向为输出
gpio_direction_output(LED_GPIO_PIN, state);
// 控制 LED 状态
gpio_set_value(LED_GPIO_PIN, state);
// 释放 GPIO 引脚
gpio_free(LED_GPIO_PIN);
}
GPIO 控制器在设备树中的定义
在许多 ARM 芯片中
gpio1: gpio@12340000 {
compatible = "vendor,gpio-controller"; // 兼容的设备类型
reg = <0x12340000 0x1000>; // 寄存器基地址及大小
interrupts = <GIC_SPI 32 0>; // 中断配置<span class="bd-box"><h-char class="bd bd-beg"><h-inner>,</h-inner></h-char></span>GIC_SPI 表示中断类型<span class="bd-box"><h-char class="bd bd-beg"><h-inner>,</h-inner></h-char></span>32 是中断号
gpio-cells = <2>; // GPIO 单元数<span class="bd-box"><h-char class="bd bd-beg"><h-inner>:</h-inner></h-char></span>第一个为引脚编号<span class="bd-box"><h-char class="bd bd-beg"><h-inner>,</h-inner></h-char></span>第二个为属性<span class="bd-box"><h-char class="bd bd-end"><h-inner>(</h-inner></h-char></span>如上下拉配置等<span class="bd-box"><h-char class="bd bd-beg"><h-inner>)</h-inner></h-char></span>
gpio-controller; // 指示这是一个 GPIO 控制器
ngpios = <32>; // 可用的 GPIO 引脚数量
// 额外的属性<span class="bd-box"><h-char class="bd bd-beg"><h-inner>,</h-inner></h-char></span>可以根据具体需要添加
vendor = "MyVendor"; // 厂商信息
model = "MyGPIOController"; // 控制器型号
// 可选的引脚配置
pinctrl-names = "default"; // 引脚控制器配置名称
pinctrl-0 = <&pinctrl_0>; // 引用引脚控制器配置
};
在设备树中
- gpio-controller
- 意义
该属性标记该节点为一个 GPIO 控制器: 表示它负责管理多个 GPIO 引脚, 。 - 作用
它让内核识别该节点为 GPIO 控制器: 并允许其他设备节点引用其管理的 GPIO 引脚, 。
- #gpio-cells = <2>
- 意义
这个属性指定每个 GPIO 引脚的描述需要使用两个 32 位的数: cell( ) 。 - 具体含义
: - 第一个值
表示引脚的编号: index( ) 用于指定 GPIO 控制器下的具体引脚, 。 - 第二个值
可以是可选属性: 例如设置引脚的上下拉电阻或其他特性, 。
- 第一个值
在设备节点中引用 GPIO 引脚
gpios
属性并不是将设备定义为 GPIO
my_device: my_device@0 {
compatible = "goodix,gt9xx";
reg = <0x5d>; // 设备寄存器地址
status = "okay"; // 状态
interrupt-parent = <&gpio1>; // 中断父节点
pinctrl-names = "default"; // 默认引脚控制
pinctrl-0 = <&pinctrl_tsc_reset &pinctrl_touchscreen>; // 引脚控制配置
name-gpios = <&gpio1 0 GPIO_ACTIVE_HIGH>; //// 引用 gpio1 控制器的第 0 号引脚<span class="bd-box"><h-char class="bd bd-beg"><h-inner>,</h-inner></h-char></span>高电平有效
reset-gpios = <&gpio2 2 GPIO_ACTIVE_LOW>; // 复位 GPIO 低电平有效
irq-gpios = <&gpio1 5 IRQ_TYPE_EDGE_FALLING>; // 中断 GPIO<span class="bd-box"><h-char class="bd bd-beg"><h-inner>,</h-inner></h-char></span>下降沿触发
};
gpios = <&gpio1 0 GPIO_ACTIVE_HIGH>;
是一个重要的属性 定义了该设备使用的 GPIO 引脚, 。 - 上图中
可以使用 gpios 属性, 也可以使用 name-gpios 属性, 。
pinctrl
负责引脚的配置和初始化gpio
则负责在运行时对这些引脚的实际使用
0 表示该引脚在 gpio1 控制器中的编号
效果
设备功能控制
硬件抽象
在驱动代码中调用 GPIO 子系统
在驱动程序中使用 GPIO 引脚时
#include <linux/gpio.h>
#define GPIO_PIN 0 // 使用 gpio1 控制器中的第 0 号引脚
void control_gpio(void) {
// 申请 GPIO 引脚
gpio_request(GPIO_PIN, "my_gpio");
// 设置引脚方向为输出
gpio_direction_output(GPIO_PIN, 1); // 设置引脚为高电平
// 控制引脚状态
gpio_set_value(GPIO_PIN, 0); // 设置引脚为低电平
// 释放 GPIO 引脚
gpio_free(GPIO_PIN);
}
在sysfs中访问GPIO的方式
在 Linux 的 sysfs
中sysfs
是一个虚拟文件系统sysfs
的一般方法和步骤
查看设备信息
ls /sys/class/ | grep <device_class>
读取属性
cat /sys/class/<device_class>/<device>/attribute_name
写入属性
echo <value> > /sys/class/<device_class>/<device>/attribute_name
在 C 程序中sysfs
文件
#include <stdio.h>
#include <stdlib.h>
void read_sysfs(const char *path) {
FILE *file = fopen(path, "r");
if (file) {
char buffer[256];
fgets(buffer, sizeof(buffer), file);
printf("Value: %s\n", buffer);
fclose(file);
} else {
perror("Failed to open file");
}
}
void write_sysfs(const char *path, const char *value) {
FILE *file = fopen(path, "w");
if (file) {
fprintf(file, "%s\n", value);
fclose(file);
} else {
perror("Failed to open file");
}
}
int main() {
// 示例<span class="bd-box"><h-char class="bd bd-beg"><h-inner>:</h-inner></h-char></span>读取和写入 sysfs 属性
const char *path = "/sys/class/<device_class>/<device>/attribute_name";
read_sysfs(path); // 读取属性
write_sysfs(path, "1"); // 写入属性
return 0;
}
- 权限
访问: sysfs
中的某些文件可能需要特定的权限 特别是写入操作, 。 - 数据格式
确保读取和写入的数据格式正确: 例如整数, 布尔值等、 。 - 设备状态
在对设备进行操作前: 了解设备的当前状态, 以避免不必要的错误, 。