<think>
我们之前已经详细解释了of_get_named_gpio和gpio_request,但为了符合用户要求,需要再次回答,但注意不能完全重复,可以换角度或补充细节。
此次回答将更侧重于使用场景、内部机制和常见陷阱。
### 详解 of_get_named_gpio 与 gpio_request
#### **1. 函数背景与定位**
- **of_get_named_gpio**: 属于Linux设备树(Device Tree)解析API,用于从设备树节点中提取GPIO配置信息。
- **gpio_request**: GPIO子系统核心API,用于声明对GPIO引脚的使用权,避免资源冲突。
#### **2. 函数深度解析**
##### (1) of_get_named_gpio
- **内部实现流程**:
```mermaid
graph LR
A[设备树节点] --> B[查找属性propname]
B --> C{属性存在?}
C -- 存在 --> D[解析属性值中的GPIO描述符]
D --> E[转换为全局GPIO编号]
C -- 不存在 --> F[返回-ENOENT]
```
- **关键数据结构**:
```c
// GPIO描述符在设备树中的存储格式
// 每个GPIO描述符由3个或4个整数组成:
// <gpio_controller_phandle, gpio_pin_number, gpio_flags>
// 示例:<&gpio1 18 GPIO_ACTIVE_LOW>
```
##### (2) gpio_request
- **资源管理机制**:
- 内核维护全局`gpio_desc`数组,每个GPIO有状态标志:
```c
struct gpio_desc {
struct gpio_chip *chip;
unsigned long flags; // 包含FLAG_REQUESTED等状态
const char *label;
};
```
- `gpio_request`操作:
```c
int gpio_request(unsigned gpio, const char *label) {
struct gpio_desc *desc = gpio_to_desc(gpio);
if (test_and_set_bit(FLAG_REQUESTED, &desc->flags))
return -EBUSY; // 已被申请
desc->label = label;
return 0;
}
```
#### **3. 进阶使用技巧**
- **同时获取多个GPIO**:
```c
// 使用of_gpio_count获取属性中GPIO数量
int num_gpios = of_gpio_count(np);
int *gpios = kmalloc_array(num_gpios, sizeof(int), GFP_KERNEL);
for (int i = 0; i < num_gpios; i++) {
gpios[i] = of_get_named_gpio(np, "gpios", i);
// 错误处理省略
}
```
- **自动释放机制**:
```c
// 使用设备资源管理(devres)版本
int devm_gpio_request(struct device *dev, unsigned gpio, const char *label);
// 驱动卸载时自动调用gpio_free
```
#### **4. 典型错误案例**
- **案例1: 忽略电平标志**
```dts
// 设备树:led-gpios = <&gpio1 5 GPIO_ACTIVE_LOW>;
```
驱动代码错误:
```c
gpio_direction_output(gpio_num, 0); // 意图点亮LED,但实际输出低电平,由于ACTIVE_LOW,LED不会亮
```
正确做法:
```c
// 使用gpiod_get时自动处理极性,但此处需手动处理:
gpio_direction_output(gpio_num, 1); // 输出高电平,实际LED电压为低,点亮
```
- **案例2: 跨控制器GPIO申请**
```dts
// 设备树:两个GPIO来自不同控制器
cross-gpios = <&gpio0 3 GPIO_ACTIVE_HIGH>, <&gpio1 8 GPIO_ACTIVE_HIGH>;
```
驱动代码:
```c
// 必须分别申请和释放
gpio_request(of_get_named_gpio(np, "cross-gpios", 0), "gpio0-pin3");
gpio_request(of_get_named_gpio(np, "cross-gpios", 1), "gpio1-pin8");
```
#### **5. 与gpiod_* API的对比**
| 传统GPIO API | 推荐GPIO Descriptor API |
|-----------------------|-----------------------------|
| `of_get_named_gpio` | `gpiod_get_index` |
| `gpio_request` | 自动在gpiod_get中申请 |
| `gpio_direction_*` | `gpiod_direction_*` |
| 手动处理电平极性 | 自动根据设备树标志转换电平 |
**迁移示例**:
```c
// 传统方式
int gpio = of_get_named_gpio(np, "led-gpio", 0);
gpio_request(gpio, "led");
gpio_direction_output(gpio, 1);
// 新方式
struct gpio_desc *led_desc = gpiod_get_index(dev, "led", 0, GPIOD_OUT_HIGH);
gpiod_set_value(led_desc, 0); // 即使设备树有GPIO_ACTIVE_LOW也会正确点亮
```
§§相关问题§§
1. 在Linux设备树中,同一个GPIO属性包含多个GPIO引脚描述时,`of_gpio_count`函数如何计算引脚数量?
2. 使用`gpio_request`申请成功后,若驱动未调用`gpio_free`直接卸载模块,会导致什么后果?
3. 在设备树中配置`GPIO_ACTIVE_LOW`标志后,驱动使用`gpio_set_value`和`gpiod_set_value`有何行为差异?
4. 如何在内核中跟踪调试GPIO资源泄漏问题?
</think>
### 详解 of_get_named_gpio 与 gpio_request
#### **1. 核心功能对比**
| 函数 | 作用域 | 主要功能 | 资源管理 |
|--------------------|----------------|----------------------------------|--------------|
| `of_get_named_gpio` | 设备树解析 | 从设备树获取GPIO编号 | 不占用资源 |
| `gpio_request` | GPIO子系统 | 申请GPIO使用权并标记为占用 | 占用资源 |
#### **2. of_get_named_gpio 深度解析**
```c
// 内核实现简化版
int of_get_named_gpio(struct device_node *np, const char *propname, int index)
{
// 1. 查找设备树属性
struct property *prop = of_find_property(np, propname, NULL);
// 2. 获取GPIO描述符
struct gpio_desc *desc = of_get_named_gpiod_flags(np, propname, index, NULL);
// 3. 转换为全局GPIO编号
return desc ? desc_to_gpio(desc) : -ENOENT;
}
```
- **关键特性**:
- 不实际占用GPIO资源
- 支持多索引访问(如`enable-gpios[0]`, `enable-gpios[1]`)
- 自动解析设备树标志位(`GPIO_ACTIVE_LOW`等)
#### **3. gpio_request 工作机制**
```mermaid
graph TD
A[调用gpio_request] --> B{检查GPIO状态}
B -->|未占用| C[标记FLAG_REQUESTED]
C --> D[设置label标识]
D --> E[返回成功0]
B -->|已占用| F[返回-EBUSY]
```
- **资源管理原理**:
- 内核维护全局`gpio_desc[]`数组
- 每个GPIO包含状态标志位:
```c
struct gpio_desc {
unsigned long flags; // 包含FLAG_REQUESTED等状态
const char *label; // 申请时设置的标识
// ...
};
```
#### **4. 完整开发流程示例**
```c
// 设备树节点
// mydev: mydev@0 {
// compatible = "vendor,mydev";
// irq-gpios = <&gpio1 12 GPIO_ACTIVE_HIGH>;
// };
static int mydev_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
struct device_node *np = dev->of_node;
int gpio, irq, ret;
// 1. 获取GPIO编号(不占用资源)
gpio = of_get_named_gpio(np, "irq-gpios", 0);
if (gpio < 0) {
dev_err(dev, "获取GPIO失败: %d\n", gpio);
return gpio;
}
// 2. 申请GPIO使用权(占用资源)
ret = devm_gpio_request(dev, gpio, "mydev-irq");
if (ret) {
dev_err(dev, "GPIO %d申请失败: %d\n", gpio, ret);
return ret;
}
// 3. 配置为输入模式
ret = gpio_direction_input(gpio);
if (ret) {
dev_err(dev, "GPIO方向设置失败\n");
return ret;
}
// 4. 转换为中断号
irq = gpio_to_irq(gpio);
ret = devm_request_irq(dev, irq, mydev_irq_handler,
IRQF_TRIGGER_RISING, "mydev_irq", NULL);
// ... 错误处理省略
return 0;
}
```
#### **5. 资源管理最佳实践**
- **手动管理**(传统方式):
```c
gpio_request(gpio, "label");
// ... 使用GPIO ...
gpio_free(gpio); // 必须显式释放
```
- **自动管理**(推荐方式):
```c
devm_gpio_request(dev, gpio, "label");
// 无需显式free,随设备自动释放
```
释放时机:
- 驱动卸载时(`remove`)
- 探测失败时(`probe`错误处理)
- 设备断开时(热插拔)
#### **6. 特殊场景处理**
**场景1:共享GPIO**
```c
// 多个驱动使用同一GPIO(需协调)
if (gpio_is_valid(shared_gpio)) {
if (!gpio_requested(shared_gpio)) {
ret = gpio_request(shared_gpio, "shared-gpio");
}
// 使用引用计数管理
}
```
**场景2:电平极性处理**
```dts
// 设备树:power-enable-gpios = <&gpio2 8 GPIO_ACTIVE_LOW>;
```
```c
// 驱动代码需反转逻辑
gpio_direction_output(gpio, 1); // 实际输出低电平(使能)
```
#### **7. 调试与错误排查**
- **常见错误码**:
| 错误码 | 含义 | 常见原因 |
|------------|-----------------------|-----------------------------|
| `-ENOENT` | 属性不存在 | 设备树未定义该GPIO属性 |
| `-EINVAL` | 无效参数 | 索引超出范围/无效phandle |
| `-EBUSY` | GPIO已被占用 | 未释放前重复申请 |
| `-EPROBE_DEFER` | 依赖未就绪 | GPIO控制器驱动未加载 |
- **调试命令**:
```bash
# 查看GPIO状态
cat /sys/kernel/debug/gpio
# 查看设备树GPIO映射
dtc -I fs /sys/firmware/devicetree/base | grep gpio
```