Linux学习笔记--GPIO子系统和PinCtrl子系统

1. 设备树节点

1.1 PinCtrl

作用:模拟硬件引脚控制器,管理引脚复用和配置

复制代码
virtual_pincontroller {
    compatible = "100ask,virtual_pinctrl";  // 匹配虚拟pinctrl驱动
    myled_pin: myled_pin {                  // 引脚配置节点,标签为myled_pin
        functions = "gpio";                 // 引脚功能:GPIO模式
        groups = "pin0";                    // 引脚组:pin0
        configs = <0x11223344>;             // 电气特性配置值
    };
};

对应驱动 :需要实现 100ask,virtual_pinctrl 兼容的pinctrl驱动

1.2 GPIO控制器

复制代码
gpio_virt: virtual_gpiocontroller {
    compatible = "100ask,virtual_gpio";  // 匹配虚拟GPIO驱动
    gpio-controller;                     // 声明这是GPIO控制器
    #gpio-cells = <2>;                   // GPIO说明符有2个cell
    ngpios = <4>;                        // 提供4个GPIO
};

作用:提供GPIO操作接口

关键属性

  • #gpio-cells = <2>:引用此GPIO时需要2个参数

    • 参数1:GPIO编号(0-3)

    • 参数2:GPIO标志(如GPIO_ACTIVE_LOW)

对应驱动 :需要实现 100ask,virtual_gpio 兼容的GPIO驱动

1.3 LED设备

复制代码
myled {
    compatible = "100ask,leddrv";           // 匹配LED驱动
    led-gpios = <&gpio_virt 0 GPIO_ACTIVE_LOW>;  // 引用GPIO控制器
    pinctrl-names = "default";              // 引脚状态名称
    pinctrl-0 = <&myled_pin>;               // 引用引脚配置
};

作用:具体的LED设备,使用前面定义的资源

关键引用

  • &gpio_virt:引用GPIO控制器标签

  • &myled_pin:引用引脚配置标签

2. 节点关系图

复制代码
virtual_pincontroller          gpio_virt
     ↓                              ↓
  myled_pin                    GPIO控制器
     ↓                              ↓
  pinctrl-0 ────────┐               ↓
                    ↓               ↓
                  myled (LED设备) ──┘
                    ↓
               led-gpios引用

4. 交互流程

4.1 系统启动和驱动加载顺序

复制代码
1. 虚拟pinctrl驱动加载 → 注册pinctrl设备
2. 虚拟GPIO驱动加载 → 注册GPIO控制器  
3. LED驱动加载 → 设备匹配和初始化

4.2 LED驱动probe函数执行流程

复制代码
static int led_probe(struct platform_device *pdev)
{
    // 1. 内核自动应用pinctrl配置
    //    在probe调用前,内核会自动调用pinctrl_select_state()
    //    将myled_pin配置应用到硬件
    
    // 2. 获取GPIO描述符
    struct gpio_desc *led_gpio;
    led_gpio = gpiod_get(&pdev->dev, "led", GPIOD_OUT_LOW);
    // 这会解析: led-gpios = <&gpio_virt 0 GPIO_ACTIVE_LOW>
    
    // 3. 配置GPIO方向(如果需要)
    gpiod_direction_output(led_gpio, 0);
    
    // 4. 使用GPIO控制LED
    gpiod_set_value(led_gpio, 1);  // 根据GPIO_ACTIVE_LOW,实际输出低电平
}

4.3 具体的引用解析过程

设备树引用解析

复制代码
led-gpios = <&gpio_virt 0 GPIO_ACTIVE_LOW>;

解析为:

  • &gpio_virt → 找到 gpio_virt: virtual_gpiocontroller 节点

  • 0 → GPIO编号0

  • GPIO_ACTIVE_LOW → 低电平有效标志

5. 对应的驱动实现框架

5.1 pinctrl驱动

复制代码
static const struct of_device_id virtual_pinctrl_of_match[] = {
    { .compatible = "100ask,virtual_pinctrl" },
    {}
};

static struct platform_driver virtual_pinctrl_driver = {
    .driver = {
        .name = "virtual_pinctrl",
        .of_match_table = virtual_pinctrl_of_match,
    },
    .probe = virtual_pinctrl_probe,
};

5.2 虚拟GPIO驱动

复制代码
static const struct of_device_id virtual_gpio_of_match[] = {
    { .compatible = "100ask,virtual_gpio" },
    {}
};

static struct platform_driver virtual_gpio_driver = {
    .driver = {
        .name = "virtual_gpio",
        .of_match_table = virtual_gpio_of_match,
    },
    .probe = virtual_gpio_probe,
};

5.3 LED驱动

复制代码
static const struct of_device_id led_drv_of_match[] = {
    { .compatible = "100ask,leddrv" },
    {}
};

static struct platform_driver led_driver = {
    .driver = {
        .name = "led_drv",
        .of_match_table = led_drv_of_match,
    },
    .probe = led_probe,
};

6. 在系统中的体现

6.1 加载后的sysfs结构

复制代码
/sys/class/gpio/
└── gpiochipXXX/          # 虚拟GPIO控制器
    ├── base → 480
    ├── label → "virtual_gpiocontroller"
    └── ngpio → 4

/sys/class/leds/
└── myled/               # LED设备

/sys/kernel/debug/pinctrl/
└── virtual_pincontroller/  # 虚拟引脚控制器

6.2 用户空间使用

复制代码
# 导出GPIO
echo 480 > /sys/class/gpio/export      # GPIO 480 (虚拟GPIO0)
echo out > /sys/class/gpio/gpio480/direction
echo 1 > /sys/class/gpio/gpio480/value  # 控制LED

7. 完整的工作流程

步骤1:设备树解析

  • 内核解析设备树,建立设备节点关系

  • 识别 virtual_pincontrollergpio_virtmyled 节点

步骤2:驱动匹配和加载

复制代码
// pinctrl驱动匹配virtual_pincontroller
// GPIO驱动匹配gpio_virt  
// LED驱动匹配myled

步骤3:LED设备初始化

复制代码
// 1. 内核自动调用pinctrl,应用myled_pin配置
// 2. LED驱动probe函数执行
// 3. 获取GPIO描述符,控制LED

步骤4:正常运行

  • 用户空间或内核其他部分可以通过GPIO接口控制LED

  • 所有硬件抽象层正常工作

gpio-ranges属性

基本语法:

复制代码
gpio-ranges = <&pinctrl_phandle gpio_base pin_base count>;

参数解释:

  • &pinctrl_phandle:指向Pinctrl控制器的句柄

  • gpio_base:GPIO控制器中的起始GPIO编号

  • pin_base:Pinctrl控制器中的起始引脚编号

  • count:映射的引脚数量

完整的设备树

传统方式(需要显式pinctrl配置)

复制代码
// Pinctrl控制器
pinctrlA: pinctrl@40020000 {
    compatible = "vendor,pinctrl";
    reg = <0x40020000 0x1000>;
    
    // 引脚配置
    uart1_pins: uart1-pins {
        pins = "PIOA0", "PIOA1";
        function = "uart1";
    };
    
    gpio_pins: gpio-pins {
        pins = "PIOA2", "PIOA3";
        function = "gpio";
    };
};

// GPIO控制器
gpioA: gpio@40021000 {
    compatible = "vendor,gpio";
    reg = <0x40021000 0x1000>;
    gpio-controller;
    #gpio-cells = <2>;
    ngpios = <16>;
    // 没有gpio-ranges,需要显式配置pinctrl
};

// 使用GPIO的设备
led {
    compatible = "vendor,led";
    led-gpios = <&gpioA 2 GPIO_ACTIVE_HIGH>;
    pinctrl-names = "default";
    pinctrl-0 = <&gpio_pins>;  // 必须显式配置
};

使用gpio-ranges的方式

复制代码
// Pinctrl控制器
pinctrlA: pinctrl@40020000 {
    compatible = "vendor,pinctrl";
    reg = <0x40020000 0x1000>;
    
    // 注意:这里不需要定义gpio-pins了
};

// GPIO控制器
gpioA: gpio@40021000 {
    compatible = "vendor,gpio";
    reg = <0x40021000 0x1000>;
    gpio-controller;
    #gpio-cells = <2>;
    ngpios = <16>;
    
    // 建立GPIO和Pinctrl的映射关系
    gpio-ranges = <&pinctrlA 0 128 12>;  // GPIO0-11 → Pinctrl引脚128-139
};

// 使用GPIO的设备
led {
    compatible = "vendor,led";
    led-gpios = <&gpioA 2 GPIO_ACTIVE_HIGH>;
    // 不需要pinctrl-0,GPIO子系统自动处理
};

映射关系的内部实现

内核中的映射处理

当GPIO控制器注册时:

复制代码
int gpiochip_add(struct gpio_chip *chip)
{
    // 解析gpio-ranges属性
    ret = gpiochip_add_pin_range(chip, dev_name(&pdev->dev),
                                0, 0, chip->ngpio);
    
    // 建立映射关系
    pinctrl_add_gpio_range(chip->pinctrl, &range);
}

GPIO请求时的自动处理

复制代码
struct gpio_desc *gpiod_get(struct device *dev, const char *con_id,
                           enum gpiod_flags flags)
{
    // 1. 获取GPIO描述符
    desc = of_get_named_gpiod_flags(dev->of_node, con_id, 0, &flags);
    
    // 2. 自动通过pinctrl配置引脚为GPIO功能
    ret = pinctrl_request_gpio(desc);
    
    // 3. 配置GPIO方向
    gpiod_direction_output(desc, flags & GPIOD_OUT_HIGH ? 1 : 0);
    
    return desc;
}

复杂的映射场景

多个不连续的映射范围

复制代码
gpioA: gpio@40021000 {
    compatible = "vendor,gpio";
    gpio-controller;
    #gpio-cells = <2>;
    ngpios = <32>;
    
    // 多个映射范围
    gpio-ranges =
        <&pinctrlA 0  128 8>,   // GPIO0-7   → 引脚128-135
        <&pinctrlA 8  200 4>,   // GPIO8-11  → 引脚200-203  
        <&pinctrlA 12 240 16>;  // GPIO12-27 → 引脚240-255
};

多个Pinctrl控制器的映射

复制代码
gpioA: gpio@40021000 {
    compatible = "vendor,gpio";
    gpio-controller;
    #gpio-cells = <2>;
    ngpios = <32>;
    
    // 映射到不同的Pinctrl控制器
    gpio-ranges =
        <&pinctrlA 0 128 16>,   // GPIO0-15  → PinctrlA的128-143
        <&pinctrlB 16 64 16>;   // GPIO16-31 → PinctrlB的64-79
};

驱动中的实际效果

使用gpio-ranges前的代码

复制代码
static int led_probe(struct platform_device *pdev)
{
    struct gpio_desc *led;
    
    // 需要确保设备树中有pinctrl-0配置
    led = gpiod_get(&pdev->dev, "led", GPIOD_OUT_LOW);
    if (IS_ERR(led)) {
        // 可能因为pinctrl配置失败
        return PTR_ERR(led);
    }
    
    return 0;
}

使用gpio-ranges后的代码

复制代码
static int led_probe(struct platform_device *pdev)
{
    struct gpio_desc *led;
    
    // 直接获取GPIO,不需要关心pinctrl配置
    led = gpiod_get(&pdev->dev, "led", GPIOD_OUT_LOW);
    // GPIO子系统自动处理引脚复用
    
    return 0;
}

验证映射关系

在sysfs中查看映射

复制代码
# 查看GPIO范围
cat /sys/kernel/debug/gpio

# 查看pinctrl映射
cat /sys/kernel/debug/pinctrl/pinctrl-handles

# 查看具体的引脚状态
cat /sys/kernel/debug/pinctrl/<pinctrl>/pinmux-pins

在驱动中调试

复制代码
// 在GPIO驱动probe函数中添加调试信息
static int gpio_probe(struct platform_device *pdev)
{
    struct gpio_chip *chip;
    
    // 解析gpio-ranges
    ret = gpiochip_add_pin_range(chip, dev_name(&pdev->dev), 0, 0, chip->ngpio);
    if (ret) {
        dev_info(&pdev->dev, "Added pin range: GPIO%d-%d -> Pin%d-%d\n",
                 range.base, range.base + range.npins - 1,
                 range.pin_base, range.pin_base + range.npins - 1);
    }
}

1. 功能定位对比

第一段代码:显式引脚配置

复制代码
uart1_pins: uart1-pins {
    pins = "PIOA0", "PIOA1";
    function = "uart1";
};

gpio_pins: gpio-pins {
    pins = "PIOA2", "PIOA3";
    function = "gpio";
};

作用 :为特定功能显式定义引脚配置

第二段代码:GPIO-Pinctrl映射

复制代码
gpio-ranges = <&pinctrlA 0 128 12>;

2. 使用方式的根本区别

方式A:功能驱动配置(第一段代码)

复制代码
// 设备树
my_uart_device {
    compatible = "vendor,uart";
    pinctrl-names = "default";
    pinctrl-0 = <&uart1_pins>;  // 显式选择UART功能
};

my_led_device {
    compatible = "vendor,led";  
    pinctrl-names = "default";
    pinctrl-0 = <&gpio_pins>;   // 显式选择GPIO功能
    led-gpios = <&gpioA 2 GPIO_ACTIVE_HIGH>;
};

方式B:自动GPIO配置(第二段代码)

复制代码
// 设备树  
my_led_device {
    compatible = "vendor,led";
    // 不需要pinctrl-0配置!
    led-gpios = <&gpioA 2 GPIO_ACTIVE_HIGH>;  // 自动配置为GPIO
};

3. 底层机制对比

方式A的底层流程:

复制代码
设备probe
    ↓
pinctrl-0应用 → 配置为指定功能
    ↓
GPIO操作(如果配置的是GPIO功能)

方式B的底层流程:

复制代码
设备probe
    ↓
GPIO获取请求
    ↓
通过gpio-ranges自动映射 → 自动配置为GPIO功能
    ↓
GPIO操作

4. 具体关联分析

4.1 它们是互斥的设计选择

在实际项目中,你通常选择其中一种方式

选择方式A(显式配置)时:

复制代码
// 需要定义详细的功能配置
pinctrlA: pinctrl {
    uart1_pins: uart1-pins { ... };
    gpio_pins: gpio-pins { ... };
    i2c1_pins: i2c1-pins { ... };
};

gpioA: gpio {
    // 不需要gpio-ranges
    // 或者有gpio-ranges但不依赖它
};

选择方式B(自动映射)时:

复制代码
// Pinctrl中可能不定义GPIO功能配置
pinctrlA: pinctrl {
    uart1_pins: uart1-pins { ... };  // 只有非GPIO功能
    i2c1_pins: i2c1-pins { ... };    // 只有非GPIO功能
    // 没有gpio-pins!
};

gpioA: gpio {
    gpio-ranges = <&pinctrlA 0 128 12>;  // 关键映射
};

4.2 混合使用的情况

有些系统可能同时使用两种方式:

复制代码
pinctrlA: pinctrl {
    // 为复杂功能定义显式配置
    uart1_pins: uart1-pins {
        pins = "PIOA0", "PIOA1";
        function = "uart1";
        bias-pull-up;  // 需要特殊电气特性
    };
    
    // 为GPIO定义特殊配置
    gpio_strong_pins: gpio-strong {
        pins = "PIOA2", "PIOA3"; 
        function = "gpio";
        drive-strength = <20>;  // 强驱动强度
    };
};

gpioA: gpio {
    gpio-ranges = <&pinctrlA 0 128 12>;  // 通用映射
    
    // 特殊GPIO可以引用显式配置
    special-gpio {
        gpio-ranges = <&pinctrlA 4 132 2>;  // 特定范围的映射
    };
};

5. 实际芯片中的差异

芯片类型1:需要显式配置(如某些老款芯片)

复制代码
// 必须为每个GPIO使用显式pinctrl
pinctrl {
    led1_pins: led1-pins {
        pins = "GPIO0_5";
        function = "gpio";
    };
    
    led2_pins: led2-pins {
        pins = "GPIO0_6"; 
        function = "gpio";
    };
};

leds {
    led1 {
        pinctrl-0 = <&led1_pins>;
        gpios = <&gpio0 5 GPIO_ACTIVE_HIGH>;
    };
    
    led2 {
        pinctrl-0 = <&led2_pins>;
        gpios = <&gpio0 6 GPIO_ACTIVE_HIGH>;
    };
};

芯片类型2:支持自动映射(如STM32)

复制代码
// 通过gpio-ranges自动处理
pinctrl {
    // 可能只有非GPIO功能的配置
    uart1_pins: uart1-pins {
        pins = "PA9", "PA10";
        function = "uart1";
    };
};

gpioA: gpio@50000000 {
    gpio-ranges = <&pinctrl 0 0 16>;  // GPIOA0-15 -> 引脚0-15
};

leds {
    led1 {
        // 不需要pinctrl-0!
        gpios = <&gpioA 5 GPIO_ACTIVE_HIGH>;  // 自动配置为GPIO
    };
};

7. 在驱动代码中的体现

使用显式配置的驱动:

复制代码
static int device_probe(struct platform_device *pdev)
{
    // 内核会在probe前自动应用pinctrl-0
    // 驱动假设引脚已正确配置
    
    struct gpio_desc *gpio;
    gpio = gpiod_get(&pdev->dev, "signal", GPIOD_OUT_LOW);
    // 这里只是使用已配置好的GPIO
}

使用自动映射的驱动:

复制代码
static int device_probe(struct platform_device *pdev)  
{
    // 驱动不关心引脚如何配置
    
    struct gpio_desc *gpio;
    gpio = gpiod_get(&pdev->dev, "signal", GPIOD_OUT_LOW);
    // GPIO获取过程中自动配置引脚功能
}
相关推荐
黄昏晓x3 小时前
Linux----权限
linux·运维·服务器
小白不想白a4 小时前
【shell】每日shell练习(系统服务状态监控/系统性能瓶颈分析)
linux·运维·服务器
lingggggaaaa4 小时前
小迪安全v2023学习笔记(一百三十四讲)—— Windows权限提升篇&数据库篇&MySQL&MSSQL&Oracle&自动化项目
java·数据库·windows·笔记·学习·安全·网络安全
一匹电信狗4 小时前
【MySQL】数据库的相关操作
linux·运维·服务器·数据库·mysql·ubuntu·小程序
bugtraq20214 小时前
为什么.NET的System.IO.Compression无法解压zlib流
linux·运维·服务器
咖啡教室4 小时前
每日一个计算机小知识:Linux
linux·后端
StarPrayers.5 小时前
损失函数(Loss Function)、反向传播(Backward Propagation)和优化器(Optimizer)学习笔记
人工智能·笔记·深度学习·学习
江公望6 小时前
Qt的QT_QPA_EGLFS_INTEGRATION环境变量浅解
linux·qt·qml
明月5666 小时前
github开源笔记应用程序项目推荐-Joplin
笔记·开源·joplin·跨平台笔记应用