Pinctrl和GPIO子系统

目录

  1. 1. Pinctrl子系统
    1. 1.1. 示例代码
    2. 1.2. 1. 主要功能
      1. 1.2.1. Pin Controller 概念
      2. 1.2.2. Client Device 概念
    3. 1.3. 2. 关键组件
    4. 1.4. 3. 使用示例
    5. 1.5. 4. API 接口
    6. 1.6. 5. 适用场景
    7. 1.7. 6. 总结
  2. 2. GPIO子系统
    1. 2.1. GPIO 控制器在设备树中的定义
    2. 2.2. 在设备节点中引用 GPIO 引脚
    3. 2.3. 在驱动代码中调用 GPIO 子系统
    4. 2.4. 在sysfs中访问GPIO的方式

Pinctrl 子系统负责管理引脚的复用与配置它允许将引脚配置为不同的功能如 GPIOI2CUART 等并设置引脚的电气特性如上拉下拉等
GPIO 子系统 主要负责对通用输入输出引脚的管理它提供 API 来配置引脚为输入或输出并控制引脚的电平状态
层次结构 Pinctrl 子系统位于 GPIO 子系统之上Pinctrl 负责引脚的初步配置和复用而 GPIO 子系统则利用这些配置来实现输入输出操作两者有密切的关系先配置Pinctrl在配置GPIO

  • 引脚配置首先通过 Pinctrl 子系统配置引脚的功能例如将某个引脚设置为 GPIO 功能
  • 状态管理然后GPIO 子系统使用这些配置来管理引脚的电平状态读取或设置引脚电平

Pinctrl子系统

pinctrl 子系统是 Linux 内核中用于管理 GPIO通用输入输出引脚和其他引脚控制功能的一个重要组件它的主要作用是提供一个统一的接口来配置和控制各种硬件引脚的功能例如输入输出复用等以下是对 pinctrl 子系统的详细介绍

在有pinctrl之前一般直接操作硬件寄存器以配置 GPIO 引脚的方式通常涉及到访问特定的内存地址在Imx6u平台上展示如何在 Linux 驱动中直接访问寄存器来设置 GPIO 引脚的模式和状态

示例代码

假设我们有一个简单的硬件平台其 GPIO 寄存器映射如下

  • 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 引脚并在模块卸载时清除引脚状态并解除映射

导致问题

  • 硬件依赖性直接寄存器访问依赖于特定硬件平台的寄存器映射具有很强的硬件依赖性不易移植
  • 安全性直接操作硬件寄存器时必须确保不会引起冲突或不安全的状态
  • 错误处理实际代码中应包含更多的错误处理逻辑以确保健壮性

1. 主要功能

  • 引脚配置允许用户设置每个引脚的模式如输入输出复用功能等
  • 引脚状态管理可以读写引脚的电平状态
  • 引脚复用支持将同一引脚配置为不同的功能以适应不同的硬件需求

在设备树Device TreePin Controller 节点的格式并没有统一的标准这意味着不同的芯片制造商可能会采用不同的方式来定义这些节点尽管所有芯片都具备类似的功能概念如引脚复用和配置但具体的实现和描述方式各不相同

Pin Controller 概念

Pin Controller 是一个软件抽象概念主要用于管理和配置芯片上的引脚功能它并不是在芯片手册中可以直接找到的硬件模块而是通过软件来实现引脚的复用和配置

  • 引脚复用类似于 IOMUXPin Controller 允许将同一引脚配置为多种功能例如 GPIOI2CUART 等
  • 引脚配置除了复用功能外Pin Controller 还可以配置引脚的电气特性例如上下拉电阻驱动强度等

Client Device 概念

Client Device客户设备是指在 pinctrl 系统中使用 Pin Controller 控制引脚的外设或模块简而言之Client Device 是依赖于 Pin Controller 提供的接口和功能的设备Client Device 是使用 pinctrl 系统的设备它通过 Pin Controller 配置和管理所需的引脚功能以实现与主控芯片或其他设备的通信

在设备树Device TreeClient Device 通常被定义为一个节点该节点包含以下信息

my_device: my_device@0 {
    compatible = "my_vendor,my_device";
    pinctrl-names = "default";
    pinctrl = <&pinctrl_0>;  // 引用 Pin Controller 配置
    // 其他设备属性
};

在设备树中<&pinctrl_0>; 是对 Pin Controller 配置的引用它通常在设备树的某个部分定义具体来说pinctrl_0 是一个节点标签label通常定义在 pinctrl 子系统部分描述相关引脚的配置和功能

&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 引脚

驱动结构每个硬件平台通常实现一个 pinctrl 驱动负责解析设备树并配置引脚引脚控制逻辑驱动通过访问特定的寄存器来实现引脚的配置和状态管理

由BSP工程师完成驱动程序使用特定的输入/输出操作函数如 readl 和 writel来访问硬件寄存器这些寄存器通常位于系统级芯片SoC的地址空间中负责控制引脚的功能和状态驱动程序通过写入相应的寄存器值配置引脚为特定的功能例如将引脚设置为 GPIOI2C 或 UART 等驱动程序可以设置引脚为输入或输出模式在输出模式下驱动程序控制引脚的电平状态在输入模式下驱动程序读取引脚的电平状态

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 PiBeagleBone 等用户需要对 GPIO 进行灵活控制
  • 自定义硬件在需要对硬件引脚进行特定配置的场景中

6. 总结

pinctrl 子系统是 Linux 内核中用于引脚管理的重要组成部分它通过统一的接口简化了 GPIO 和引脚控制的复杂性了解和使用 pinctrl 可以帮助开发者更有效地管理硬件资源提高开发效率

GPIO子系统

在早期的嵌入式系统中直接通过寄存器操作 GPIO 引脚的方式存在许多问题尤其是在不同硬件平台上的兼容性每个板子的 GPIO 控制代码往往需要完全不同的实现导致代码重复和维护困难

驱动代码可以使用 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 芯片中GPIO 引脚通常被分为多个组每组包含若干个引脚在使用 GPIO 子系统之前必须确定要使用的 GPIO 组和具体引脚以下是如何在设备树中定义和引用 GPIO 控制器的详细说明

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 控制器节点的定义中有两个关键属性需要关注

  1. gpio-controller
  • 意义该属性标记该节点为一个 GPIO 控制器表示它负责管理多个 GPIO 引脚
  • 作用它让内核识别该节点为 GPIO 控制器并允许其他设备节点引用其管理的 GPIO 引脚
  1. #gpio-cells = <2>
  • 意义这个属性指定每个 GPIO 引脚的描述需要使用两个 32 位的数cell
  • 具体含义
    • 第一个值表示引脚的编号index用于指定 GPIO 控制器下的具体引脚
    • 第二个值可以是可选属性例如设置引脚的上下拉电阻或其他特性

在设备节点中引用 GPIO 引脚

gpios 属性并不是将设备定义为 GPIO而是指定该设备使用的 GPIO 引脚具体来说这一行代码指明了该设备将使用由 gpio1 控制器管理的第 0 号引脚

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_ACTIVE_HIGH 指定该 GPIO 引脚在高电平状态下有效这意味着当引脚电平为高时设备的功能将被激活以上dts定义指定了设备所使用的 GPIO 引脚及其状态这是设备树中对设备与 GPIO 之间关系的描述使得内核能够通过驱动程序正确控制该设备

效果

设备功能控制通过此引用驱动程序可以控制该引脚的状态从而影响设备的行为例如如果这个设备是一个 LED设置该引脚为高电平可能会使 LED 亮起

硬件抽象这使得设备树能够抽象出硬件细节驱动程序可以通过统一的接口与硬件进行交互而不必关心具体的硬件实现

在驱动代码中调用 GPIO 子系统

在驱动程序中使用 GPIO 引脚时可以通过 Linux GPIO 子系统提供的 API 来进行管理以下是您提供的代码的详细解释展示如何在驱动程序中使用 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 中的某些文件可能需要特定的权限特别是写入操作
  • 数据格式确保读取和写入的数据格式正确例如整数布尔值等
  • 设备状态在对设备进行操作前了解设备的当前状态以避免不必要的错误